c6b01def4c
CI / verify (push) Has been cancelled
拦截伪区县写入与下单;推单失败挂 fulfillmentHold,HQ 可见超区/推单失败。 Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
3.8 KiB
Markdown
94 lines
3.8 KiB
Markdown
# 杜康好客 · v4.0.13 开发文档
|
||
|
||
> **2026-09-02** · mini-user / iam / trade / fulfillment / admin-web / h5-partner / domain
|
||
> **主题**:收货地址严格把关(禁「全市」)+ 小飞侠拒单履约可感知
|
||
|
||
需求背景:订单 `DK2026090220803` 自动推小飞侠失败,原因 `超出服务区`;收货区县为门店筛选用伪值「全市」。
|
||
|
||
**不做(本版明确排除)**:仓坐标补全、收件 `chooseLocation` / `toCoordinate` 坐标链路。
|
||
|
||
---
|
||
|
||
## 1. 版本目标
|
||
|
||
| # | 任务 | 类型 | 交付 |
|
||
|---|------|------|------|
|
||
| 1 | 收货选区与门店筛选语义拆分 | 缺陷/体验 | `RegionPicker mode=shipping` 无「全市」 |
|
||
| 2 | 地址保存硬闸 | 缺陷 | 前后端拒伪区县;详细地址最短 8 字 |
|
||
| 3 | 下单/预览拦截脏地址 | 缺陷 | preview `addressOk=false`;确认页不可提交 |
|
||
| 4 | 地址簿存量提示 | 体验 | 「需完善」置顶;结账选址强制去编辑 |
|
||
| 5 | 推单失败可感知 | 缺陷 | `fulfillmentHold` + 原因码;HQ 标签/发货弹窗 |
|
||
|
||
---
|
||
|
||
## 2. 规则
|
||
|
||
### 2.1 收货地址
|
||
|
||
- 伪区县:`全市`、`全部`、空 —— **禁止**写入 `user_address` / 下单快照 / 合伙人代下单。
|
||
- 详细地址:必填且长度 ≥ 8;含「全市」且无街道路牌关键词时前端软提示(不单独硬失败)。
|
||
- 门店列表筛选仍可使用「全市」(`mode=filter`)。
|
||
- 现场取货地址快照(`现场/现场/取货`)不走本校验。
|
||
|
||
### 2.2 履约拦截(推单失败)
|
||
|
||
| `fulfillment_hold_reason` | 含义 | HQ 展示 |
|
||
|---------------------------|------|---------|
|
||
| `LARGE_ORDER_GE_10_BOXES` | 大单≥10箱 | 大单 |
|
||
| `COURIER_OUT_OF_SERVICE` | 小飞侠返回超出服务区 | 超区 |
|
||
| `COURIER_DISPATCH_FAILED` | 其它自动推单失败 | 推单失败 |
|
||
|
||
- 失败后仍写 `MANUAL` 配送记录(待发货),**同时**挂 `fulfillmentHold`,避免静默像「没推过」。
|
||
- HQ 手动推小飞侠或填快递成功后仍清 hold(既有逻辑)。
|
||
|
||
---
|
||
|
||
## 3. 变更面
|
||
|
||
| 层 | 路径 |
|
||
|----|------|
|
||
| domain | `packages/domain/src/shipping-address.ts` |
|
||
| shared-types | `FULFILLMENT_HOLD_REASON_LABELS` + 常量 |
|
||
| API | `UserAddressService`;`TradeService` preview/create/代下单;`FulfillmentService.markCourierDispatchHold` |
|
||
| C 端 | `RegionPicker`、`address-edit`、`addresses`、`order-confirm`、`shipping-address.ts` |
|
||
| 合伙人 | `ProxyOrderPage` 校验 |
|
||
| HQ | `OrdersPage` 拦截筛选文案与标签 |
|
||
|
||
---
|
||
|
||
## 4. API 行为变化
|
||
|
||
| 接口 | 变化 |
|
||
|------|------|
|
||
| `POST/PUT /user/addresses` | 伪区县 / 详情过短 → `400` |
|
||
| `POST /trade/orders/preview` | 脏地址 → `addressOk=false` + message |
|
||
| `POST /trade/orders` | 脏地址 → `400` |
|
||
| 合伙人代下单 | 同上校验 |
|
||
| 支付后自动推单 | 失败写 hold reason(库字段,无新 path) |
|
||
|
||
---
|
||
|
||
## 5. 验收
|
||
|
||
- [ ] 新增/编辑地址:区县列表无「全市」;选真实区县 + 足够详细地址可保存
|
||
- [ ] 保存「全市」或过短详情被拒(前端 toast + 后端 400)
|
||
- [ ] 历史脏地址在地址簿标「需完善」;结账选择时跳转编辑
|
||
- [ ] 确认订单页脏地址不可提交;preview 提示完善区县
|
||
- [ ] 合伙人代下单伪区县/过短详情被拒
|
||
- [ ] 模拟小飞侠「超出服务区」后:订单 `fulfillment_hold=1`、`reason=COURIER_OUT_OF_SERVICE`;HQ 列表「超区」、发货弹窗有说明
|
||
- [ ] 门店列表筛选「全市」行为不变
|
||
|
||
---
|
||
|
||
## 6. 存量建议(运维,非代码)
|
||
|
||
```sql
|
||
-- 生产排查脏地址(只读)
|
||
SELECT id, user_id, province, city, district, detail, updated_at
|
||
FROM user_address
|
||
WHERE district IN ('全市','全部','') OR district IS NULL
|
||
ORDER BY id DESC LIMIT 100;
|
||
```
|
||
|
||
可选:运营通知用户进「地址管理」完善;本版不自动改写历史行。
|