# 配送异常、重送与返仓结算 实施日期:2026-09-11。适用于平台后台供水配送,不执行支付、退款或外部消息发送。 ## 业务规则 正常路径:待派单 → 已派单 → 配送中 → 已签收 → 路线返仓结算。 异常路径:配送中 → 部分签收/拒收(待退回核对)→ 仓库收回完好及破损货物(已退回)→ 路线返仓结算。已退回任务可为全部未送达桶数创建关联重送任务,重送重新走建单、派单、出库、签收流程。 - 建单冻结权益和仓库可用满桶;出库由锁定转为在途。 - 签收按实际收到桶数扣减权益和在途、增加企业满桶。实收0即拒收,部分/拒收必须填写原因。余量仍在途,权益仍冻结,有效任务键继续占用,防止绕过退货核对重复配送。 - 回收空桶从企业账户扣除,暂记任务 `empty_in_vehicle`。有正式路线时,客户交空桶不代表仓库已入库。 - 仓库核对要求 `good_qty + damaged_qty = quantity - delivered_qty`。完好满桶恢复可用,破损转入 `sku.damaged_qty` 隔离,全部未送达权益解除冻结,不扣除。任务变为 `returned`,释放有效任务键。 - 返仓结算要求路线所有任务均为 completed/returned/cancelled,且逐规格 `实际交空桶 + 差异损失 = 车载待交空桶`。实交进入仓库空桶,损失累计至 `empty_lost_qty`,必须填写核对说明。禁止缺行、多行、重复规格或未解释数量差异。 - 路线未结算时配送员持续占用,不能接新批次、休息、停用或改变容量/片区;结算后结合当日排班恢复可接单。结算不自动把休息或未排班人员改为上班。 - 预警继续按企业现场满桶计算。部分签收可能解除数量预警,但待退回任务仍在调度待办;签收不自动模拟用水,下一轮现场消耗仍需人工换桶登记或未来设备协议。 ## 设计和数据维护 - `WaterFulfillment`:应用层命令服务,负责事务、锁、持久化和审计;统一承接旧 complete 入口的实际签收结算,移除 WaterBusiness 中重复的签收账务。 - `fulfillment/DeliveryState`:显式状态策略;异常处理限定合法来源状态,禁止已出库直接取消。 - `fulfillment/DeliverySettlement`:无数据库副作用的数量计算,分别计算实收及退回的库存、权益变动。 - 继续使用 Eloquent 模型。`WaterDelivery` 提供原任务、重送及履约事件关联。`redelivery_of` 唯一索引保证每次失败尝试最多生成一个关联重送;重送再次失败可延续链条。新任务仍校验企业/规格启用状态、可用权益和可用库存。 - `WaterDeliveryEvent` 由业务入口只追加,保存规范化请求、哈希、操作人、时间及账务快照。相同操作同内容重试成功,内容不同明确拒绝;不覆盖原签收数据。事件键由服务端动作和任务/路线ID组成,不依赖客户端随机值。 - 加锁顺序以SKU → 配送员 → 配送任务 → 路线 → 权益账户为主,与既有派单/优化一致;同规格账务串行,整批返仓任一规格不一致则全量回滚。 - 原库存流水查询/导出增加“部分签收/拒收、退回入库/破损隔离、返仓空桶交接”类型,履约详情同时展示对应路线结算事件。 - 破损数量为隔离库存,不能再次派送;本轮没有自动报废、维修转可用或错误签收冲正入口。账务更正须另走可审计补偿流程,不能直接改旧事件。 ## API 与权限 `POST /core/water/dispatch`: | action | 主要字段 | 权限 | | --- | --- | --- | | receive | id, accepted_qty, empty_returned, receiver, reason | dispatch:write | | return_receive | id, good_qty, damaged_qty, note | dispatch:write + inventory:write | | redeliver | id(原任务), date, note | dispatch:write | | route_settle | route_id, items:[{sku_id,actual,lost}], note | dispatch:write + inventory:write | 以上权限均带 `water:` 前缀,由 WaterController 核对启用账号、角色及菜单权限;不自动扩大代理权限。 - `GET /core/water/dispatch/history?id=...`:dispatch:read,返回任务履约及路线交接记录。 - `GET /core/water/dispatch/route?id=...`:新增 fulfillment(state/can_settle/empty_items)及 settled_at/by/note。 - 看板新增 returning/returned 数量、重送关联、路线结算时间及 `settlement_routes`。待退回计入跨日待办,当天签收/退回的逾期任务继续留在当天列表供查询或重送;任务池桶数按剩余未送达桶数计算。返仓列表展示所选日期及之前全部未结算路线,明确不随任务池关键词/片区过滤,以免漏掉已结束任务的路线。 - 任务详情展示计划与实收数、异常原因、重送父子编号和履约时间线;库存页展示破损隔离及累计空桶损失。 ## 迁移、兼容与运行 在 server 目录执行 `php -d extension=mbstring scripts/migrate_water_fulfillment.php --apply`。默认不带 --apply 只输出计划。 增量增加字段、索引和事件表,不重算历史库存/权益,不伪造历史实收。历史已签收任务没有 received_at 时显示“历史已签收”,原签收重试不会再入账。旧 complete API仍兼容;正式页面使用需填写收货人的 receive。 迁移把具有已登记配送员、尚未关联路线的旧 assigned/transit 任务归档到独立 `LEGACY-FULFILL-<任务ID>` 路线,并保存系统迁移事件(actor_id=0)。本地任务10/11已关联,原状态、桶数、地址和资金/库存/权益不变。缺少已登记配送员时迁移明确中止数据关联事务,需先修复档案。重复执行不重复关联。内部历史服务测试中无路线的直接签收保留旧空桶入仓语义;所有当前实际在途任务已具备正式路线。 部署顺序:迁移 → 加载后端新代码 → 构建前端。当前本地HTTP进程已通过监控加载,8787实测新接口正常;前端4173提供生产构建。回退不能只换旧代码:新 returning/returned、车载空桶和未结算路线需要新逻辑,已产生新履约数据后禁止直接删除字段或回退到旧状态机。 ## 验证与边界 - 59项新增事务回滚检查:部分签收、全拒收、完好/破损退回、权益释放、跨日待办及当天退回保留、重复及冲突提交、单次重送/多次失败链、空桶短少、驾驶员占用、满桶/空桶守恒、多规格返仓失败全量回滚。 - 69项原业务、42项调度、30项流水、30项设备接入、24项策略/模型检查通过;后端事务回归合计254项。 - 19项真实本地HTTP检查跑通出库、部分签收、重复签收、退回、返仓、重送、取消及账务核对;临时专用SKU/企业/司机/任务等已清理,正常后台操作审计保留。 - 本轮仍是后台人员登记实物事实:没有配送员移动端接单、客户电子签字/照片、实时GPS或自动设备核验,也没有支付退款、线上扣款。地图优化仍只按既有批次站序规划配送道路,未声称提供实时返仓导航。 - 前端 vue-tsc、相关 ESLint/Prettier、Vite production build 已通过(3268 modules);道路矩阵/分段等既有前端测试通过。4173静态预览读取本次最终构建。 - 浏览器自动化和备用桌面工具均因本机工具Node运行时路径缺失无法启动,本轮未完成视觉/交互验收。最终只读核对仍为4片区17单103桶、9条未结算正式路线(含旧任务10/11归档的路线35/36),未推进保留的演示任务。