# 杜康好客 · 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; ``` 可选:运营通知用户进「地址管理」完善;本版不自动改写历史行。