# 寄售仓库 / 寄卖子系统 深度技术分析 > 分析基线:`ef491660`(含 `9298b31b` 白名单总开关)。全部结论取自仓库真实代码,行号可复核。 ## 一、业务定位与术语表(本仓库口径) | 术语 | 代码实体 | 含义与关键约束 | | --- | --- | --- | | 寄卖 / 寄存 | `consignment_*` 全套 | 打手把自己在《和平精英》的**苏尔南手枪**挂到平台,由平台撮合卖给买家 | | 现货 / 库存 | `consignment_inventories` | 审核通过后落库的一件真实货物,`status ∈ in_stock/consumed/archived` | | 提报 / 上架 | `consignment_submissions`(打手端 `publishWorkerConsignment`) | 打手端唯一入口,发布即冻结发布押金;`SUB…` 单号是全流程唯一锚点(`consignment-worker-service.ts:411`) | | 审核入库 | `approveAdminConsignmentSubmission` | 客服审核通过 → 生成 `INV…` 库存行(`consignment-repo.ts:1170-1217`);驳回退押金 | | 匹配 | `consignment-matching-service.ts` | 按系统/区服/系列/钻级/璀璨做规格比对,`exact/partial/conflict` | | 履约(两条) | `internal_stock` / `worker_deposit` | 内部现货:客服/内部仓代交付(无接单打手);押金履约:指派打手交货并冻结履约押金 | | 待加好友 | `parkWorkOrderWaitingFriendAddWithClient` | 占库后工单停在 `gift_phase=material_required`,等人确认加好友 | | 赠送冷却 | `gift_phase=friend_countdown` | 确认加好友后进入冷却,冷却结束才可上传赠送截图 | | 赔付 / 没收 | `forfeitConsignmentPublishDeposit*`、`settleWorkOrderDepositWithClient` | 卖家发布押金 / 打手履约押金的没收与退还 | | 押金履约押金 | `worker_wallets.frozen_deposit_amount` | 发布押金与工单履约押金共用同一冻结池,靠 `payload_json.kind` 区分口径 | | 白名单 | `worker_users.can_consign` + 总开关 | 详见第八节 | ## 二、端到端流程 ```mermaid flowchart TD A[打手端提报 苏尔南] -->|检查白名单+冻结发布押金 ¥20| B[consignment_submissions pending_review] B -->|客服驳回| B2[rejected + 释放押金] B -->|客服审核通过| C[consignment_inventories in_stock/待安排] C -->|工单发布前候选提醒 / 手动匹配| D{匹配方式} D -->|internal_stock| E1[原子占库 consumed/已安排 + 工单绑 material + 停待加好友] D -->|worker_deposit| E2[条件占库 + 指派打手冻结履约押金 + in_progress] E1 --> F[确认加好友 → 赠送冷却] E2 --> F F -->|冷却结束| G[卖家/打手上传赠送截图 → pending_acceptance] G --> H[客服验收 accepted] H --> I[释放发布押金 + 结算供货货款 cost_price_fen] E1 -.撤销履约 reopen/回库/作废.-> C E2 -.撤单/取消指派/超时.-> C ``` 工单终态解绑出口唯一:`releaseConsignmentInventoryBindingForWorkOrderWithClient`(`consignment-repo.ts:765`),已接入 `work-order-cancel-repo.ts:84,202,325,448,537`、`worker-cancel-request-repo.ts:572`、`work-order-issue-repo.ts:344`。 ## 三、数据模型 | 表 | 关键字段 | 状态枚举 | 索引/约束 | | --- | --- | --- | --- | | `consignment_inventories` (077:3-39, 078:3-7, 079:49-58) | `inventory_no` UNIQUE、`item_name/system/server_zone/series/diamond_level/brilliance_count`、`cost_price_fen`、`credentials_json`(账号密码)、`screenshots_json`、`allow_matching`、`worker_id`(卖家)、`assigned_worker_id`(履约打手)、`deposit_amount_fen`、`bound_order_id/bound_work_order_id/bound_platform_order_id/shop_name` | `status ∈ in_stock/consumed/archived`(077:25-26);`allocation_status ∈ 待安排/已安排`(080:33-35);`source_type ∈ manual/worker_portal/recycle`(079:38-40) | `uniq_consignment_inventories_bound_work_order` 部分唯一(`bound_work_order_id WHERE status='consumed'`, 079:93-95);`idx_..._status_matching`、`_game_category`、`_specs`、`_buyer`、`_shop`、`_batch`、`_worker_id`、`_assigned_worker_id` | | `consignment_submissions` (077:64-91, 078:9-13) | `submission_no` UNIQUE、`result_inventory_id`、`content_json`(characterName/Id/sellerNote/channel)、`asking_price_fen`、`deposit_amount_fen`、`worker_id/name/phone` | `status ∈ pending_review/approved/rejected`(077:83-84) | `idx_..._status_created`;`link_id` 已被 079:11-12 删除 | | `consignment_collect_links` | — | — | **079:15 已整体 DROP** | | `worker_users` (082:4-6) | `can_consign`(默认 FALSE)、`consignment_activated_at` | — | `idx_worker_users_can_consign`(082:12-13) | | `worker_wallet_ledgers` | `ledger_type`、`payload_json.kind=consignment_publish`、`consignmentSubmissionId` | `deposit_freeze/deposit_release/deposit_deduction` | `uniq_worker_wallet_ledgers_consignment_publish_freeze`(079:177-180) | **079 为什么删除收集链接**:H5 定向收集(`/consignment-collect/:token` + 公开 `link/submit/upload`)存在两个致命问题——上传响应层级取错导致截图恒为空(旧 `ConsignmentCollectPage.tsx`,优化方案 P0-1),以及按手机号自动绑定打手可让第三方被动冻结其押金(优化方案 P2-5)。业务改为「只能在接单平台提报」,于是删表删列,`source_type` 的 `collection_link` 归并为 `worker_portal`(079:1-8、17-40)。同一次迁移还补了两件资金/一致性收口:`assigned_worker_id` 拆分(079:47-54,防止占库覆盖卖家导致"我的寄卖"丢件)与冻结流水幂等唯一索引 + 历史重复冻结去重退款(079:97-180)。 ## 四、核心服务清单 | 文件(仓库根相对) | 职责 | 关键导出 | | --- | --- | --- | | `apps/backend/src/services/worker-platform/consignment-worker-service.ts` | 打手端:提报(白名单+规格校验+冻结押金)、我的寄卖聚合、确认加好友、提交赠送截图、建议价 | `publishWorkerConsignment`(:146)、`listWorkerConsignments`(:351)、`confirmWorkerConsignmentFriendAdded`(:551)、`submitWorkerConsignmentGiftEvidence`(:566)、`getWorkerConsignmentPriceSuggestion`(:581) | | `.../consignment-matching-service.ts` | 规格解析/归一、三态比对、候选排序、手动匹配分类、建议价规则 | `extractPeacekeeperSpecs`(:245)、`matchPeacekeeperOrderWithInventory`(:309)、`classifyConsignmentMatch`(:518)、`listConsignmentMatchOptions`(:552)、`findMatchingPeacekeeperInventories`(:569)、`suggestConsignmentPriceFromRules`(:633) | | `.../consignment-fulfillment-service.ts` | 履约:内部现货履约、指派打手履约、补偿回滚、撤销、候选查询、服务端规格复检 | `listWorkOrderInventoryMatchOptions`(:179)、`listInventoryMatchableOrders`(:220)、`checkWorkOrderInventoryMatches`(:305)、`fulfillWorkOrderWithConsignment`(:345)、`assignWorkOrderWithConsignmentWorker`(:603)、`resolveConsignmentAssignRewardFen`(:590)、`revokeWorkOrderConsignmentFulfillment`(:910) | | `.../consignment-admin-service.ts` | 后台:库存 CRUD/开关/作废/回库、提报审核、映射 | `mapConsignmentInventory`(:158)、`fetchAdminConsignmentInventories`(:261)、`createAdminConsignmentInventory`(:319,已 409 停用)、`updateAdminConsignmentInventory`(:326)、`archiveAdminConsignmentInventory`(:414)、`returnAdminConsignmentInventory`(:471)、`approve/reject`(:564/:648) | | `.../repositories/worker-platform/consignment-repo.ts` | 库存/提报全部 SQL、占库/释放/解绑唯一出口 | `claimConsignmentInventoryForWorkerWithClient`(:500)、`releaseConsignmentInventoryClaimWithClient`(:563)、`archiveConsignmentInventoryWithClient`(:673)、`returnConsignmentInventoryToStockWithClient`(:706)、`releaseConsignmentInventoryBindingForWorkOrderWithClient`(:765)、`approveConsignmentSubmissionWithInbound`(:1126) | | `.../repositories/worker-platform/consignment-deposit-repo.ts` | 发布押金冻结/释放/没收、供货货款结算 | `freezeConsignmentPublishDepositWithClient`(:51)、`release...WithClient`(:118)、`getConsignmentDepositLedgerSnapshot`(:186)、`forfeit...BySubmissionWithClient`(:332)、`forfeit...ByWorkOrderWithClient`(:399)、`settleConsignmentSupplierPayoutByWorkOrderWithClient`(:441) | | `.../repositories/worker-platform/consignment-supplier-repo.ts` | 从工单反查供货卖家(货款与售后共用) | `getConsignmentSupplierByWorkOrderWithClient`(:20) | ### 手动匹配(f07e4087)如何工作 1. 两个只读入口:订单侧 `GET /matches/:workOrderId/options` → `listWorkOrderInventoryMatchOptions` 返回**全部**在库现货,按 `exact(0)/partial(1)/conflict(2)` + 成本价 + id 排序(`consignment-fulfillment-service.ts:191-206`);库存侧 `GET /inventories/:id/match-orders` → `listInventoryMatchableOrders` 反查 `unassigned/pending_material/open` 工单(:252-261)。 2. 前端 `ConsignmentManualMatchModal.tsx` 按 `matchType` 决定确认项:`partial` 勾"允许非精确",`conflict` 勾"确认规格不一致"且**必填原因**(`utils/consignment-manual-match.ts:19-53`)。 3. 落地只走 `fulfillWorkOrderWithConsignment`(`ConsignmentManualMatchModal.tsx:178-184`),把 `allowPartial/allowSpecMismatch/matchNote` 传后端。 4. 后端二次复检:`assertConsignmentSpecMatchedOrder`(:73-110) 用宽松模式复检,真冲突默认 409(`consignment_spec_mismatch`),仅 `allowSpecMismatch=true` 且 `matchNote` 非空才放行(`resolveManualMatchNote`:155-167),冲突明细写入工单事件与 `material.consignment.specConflicts`(:489-495)。 ## 五、状态机 **库存项展示阶段** `deriveConsignmentItemStage`(`domain/consignment-item-stage.ts:146-248`)把「提报单 + 库存 + 绑定工单」三套状态推导为唯一阶段: `pending_review` → `in_stock` → (consumed 后按工单) `matched_waiting_friend` → `gift_cooldown` → `gift_ready` → `gift_submitted` → `completed`;标准验收走 `fulfilling`/`pending_acceptance`;异常终态 `rejected`/`archived`/`cancelled`(:20-34、46-78)。 | 迁移 | 触发者 | 代码位置 | 附加效果 | | --- | --- | --- | --- | | `in_stock → consumed`(内部现货) | 客服 `consignment.fulfill` | `consignment-fulfillment-service.ts:424-453` | 锁单 `FOR UPDATE` + 写绑定 + 停在待加好友 | | `in_stock → consumed`(指派打手) | 客服 | `consignment-repo.ts:511-540` | 写 `assigned_worker_id='已安排'`,再调通用指派 | | `consumed → in_stock`(撤销回库) | 客服 | `consignment-fulfillment-service.ts:986-1000` | 带 `AND bound_work_order_id=$4` 仲裁;发布押金 auto=保持冻结 | | `consumed → archived` | 客服/自动 | 同上 :971-984、`consignment-admin-service.ts:414-440` | 已确认加好友**强制**作废不回池(:968-969) | | `in_stock → archived` | 客服 | `consignment-repo.ts:673` | 释放发布押金 | | `consumed → in_stock`(人工回库) | 客服 | `consignment-repo.ts:706-763` | 需 `NO_LIVE_BOUND_WORK_ORDER_SQL`;回库**重新冻结**押金(`ensure...`:262) | | `in_stock → in_stock`(终态解绑) | 系统 | `consignment-repo.ts:765` | 撤单/超时/取消指派自动回池,押金不动 | `allocation_status` 只由系统写:占库写 `已安排`(`consignment-repo.ts:533`、履约服务 :428),回库/撤销写 `待安排`(:990、release :569)。 ## 六、匹配规则 - **品类隔离门禁**:`detectItemCategory` 识别手枪/密钥/机甲/车皮等,双方都能识别且不一致直接 `null`;现货为 `pistol_suernan` 时订单必须含系列或"苏尔南/手枪/枪皮"信号(`consignment-matching-service.ts:319-340`)。 - **规格三态**:`compareSpec`(:292-304) 返回 `match/mismatch/unknown`;通配 `SYSTEM_WILDCARDS=['通用']`、`ZONE_WILDCARDS=['通用','互通']`(:288-290)。 - **档位规则**:钻级/璀璨不得低配充高配(:359-360);`unknown` 项禁止判精确(:363-374)。 - **严格/宽松**:默认严格——系统/区服/系列任一非 `match` 即排除,且无"高配"正信号也排除(:394-418),回退掉了"只写游戏名就命中全库"的旧行为;宽松模式 `allowUnspecifiedSpecs` 仅供人工选定后复检(:394、`consignment-fulfillment-service.ts:88-90`)。 - **排序**:`score DESC → cost_price_fen ASC → inbound_at ASC → id ASC`(:597-611)。 - **白名单/手动匹配**:白名单只管"能否发布",不参与匹配(`domain/consignment-whitelist.ts:7-12`);手动匹配可越过任何规格门禁,代价是必填原因并留痕。 - **建议价**:按系统(含通用)+系列精确、钻级/璀璨向上兜底取后台已配最高档,多命中取最高(:633-710)。 ## 七、资金 / 押金 / 赔付 | 事件 | 资金动作 | 代码 | | --- | --- | --- | | 发布提报 | 按 `consignmentPublishDepositFen`(默认 2000 分 = ¥20,`worker-finance-config-service.ts:73-78,108`)从可用余额冻结,余额不足 409 | `consignment-worker-service.ts:255,297`;`consignment-deposit-repo.ts:51-113` | | 审核驳回 | 全额释放(幂等:`outstanding=0` 即空操作) | `consignment-admin-service.ts:670-674` | | 库存作废 | 释放发布押金 | `consignment-admin-service.ts:420-426` | | 人工回库 | **重新冻结**(错失则 409 `consignment_deposit_already_settled`) | :500-506、:262-314 | | 验收通过 | 释放发布押金 + 补发**供货货款** `cost_price_fen \|\| asking_price_fen` 给卖家 | `work-order-settlement-repo.ts:99-104`、:429、:679;`consignment-deposit-repo.ts:441-...` | | 确认加好友后违约/取消/超时 | 没收发布押金(`deposit_deduction`) | `work-order-cancel-repo.ts:140,261,385`、`work-order-issue-repo.ts:68,254` | | 撤销寄售履约 | 打手履约押金按 `auto/refund/forfeit`;卖家发布押金按 `auto/archive→release/回库→keep_frozen/forfeit` | `consignment-fulfillment-service.ts:1016-1058`;`domain/consignment-deposit-policy.ts:54-77` | | 防双付 | 指派对象≠供货卖家时报酬保持原值,货款走供货货款渠道;=卖家则改报酬为结算价 | `consignment-fulfillment-service.ts:590-601,689-695`;`consignment-deposit-repo.ts:459` | ## 八、权限与白名单(082 + cb8b249e / 9298b31b) - **总开关**:`worker_finance_config.consignmentWhitelistEnabled`(非 DB 列),默认 `true`(`worker-finance-config-service.ts:16-19,79-80,108`);后台在资金配置面板切换。 - **单人权限**:`worker_users.can_consign`,客服在打手管理列表 Switch 实时开关(082:4-6;`admin-worker-user-service.ts:182-205`;路由 `routes/admin/worker-platform/workers.ts:146-158`,权限 `admin/operator`)。空 body 不默认关闭(400 `can_consign_required`)。 - **判定**:`canWorkerPublishConsignment = !whitelistEnabled || workerCanConsign`(`domain/consignment-whitelist.ts:7-12`),后端在提报入口强校验并 403 `consignment_permission_required`(`consignment-worker-service.ts:151-161`);前端同口径(`utils/consignment-whitelist.ts:5-12`)+ 引导卡片(`WorkerConsignmentPage.tsx:597-620`)。 - **平滑过渡**:082:16-24 把历史上出现在 `consignment_submissions` 的 `worker_id` 批量置 `can_consign=TRUE`。语义已从"永久白名单"演进为"总开关可整体放行"(9298b31b 新增),关闭总开关时保留标记以便再次开启。 ## 九、API 端点清单 **打手端**(`routes/worker/consignment.ts`,前缀 `/api/v1/worker`) | 方法/路径 | 门禁 | 服务 | | --- | --- | --- | | `POST /consignment/publish` (:19) | `requireActiveWorker` | `publishWorkerConsignment` | | `GET /consignment/price-suggestion` (:33) | `requireActiveWorker` | 建议价 | | `GET /consignment/my-items` (:51) | 仅会话 | `listWorkerConsignments` | | `POST /consignment/items/:submissionId/confirm-friend` (:65) | `requireActiveWorker` | 复用客服加好友链路 | | `POST /consignment/items/:submissionId/gift-evidence` (:87) | `requireActiveWorker` | 复用 `submitWorkerOrderAcceptance(asConsignmentSupplier)` | **后台**(`routes/admin/worker-platform/consignment.ts`,前缀 `/api/v1/admin`) | 方法与路径 | 权限 | 行号 | | --- | --- | --- | | `GET /consignment/inventories`、`/stats`、`/inventories/:id` | `consignment.view` | :37,:47,:57 | | `POST /consignment/inventories`(已停用 409) | `consignment.manage` | :67 | | `PUT /consignment/inventories/:id` | `consignment.manage` | :80 | | `PATCH /consignment/inventories/:id/matching` | `consignment.manage` | :98 | | `POST /consignment/inventories/:id/archive`、`/return` | `consignment.manage` | :112,:122 | | `GET /consignment/submissions`、`/submissions/:id` | `consignment.view` | :142,:152 | | `POST /consignment/submissions/:id/approve`、`/reject` | `consignment.review` | :162,:180 | | `GET /consignment/matches/:workOrderId`、`/.../options` | `consignment.view` | :200,:211 | | `GET /consignment/inventories/:id/match-orders` | `consignment.view` | :225 | | `POST /consignment/fulfill` | `consignment.fulfill` | :242 | | `POST /consignment/assign-worker` | `consignment.fulfill` | :263(前端已无调用方) | | `POST /consignment/revoke` | `consignment.fulfill` | :285 | | `POST /worker-platform/workers/:workerId/consignment-permission` | `admin/operator` | `workers.ts:146` | ## 十、风险与问题(按严重程度) ### 资金安全 1. **审核改派打手会把货款付给非发货人** — `consignment-repo.ts:1227-1230` 审核可 `worker_id = COALESCE($4, worker_id)` 改派;`consignment-supplier-repo.ts:67` 结算优先取 `inventory.worker_id`。发布押金已按台账归还**原发布人**(`consignment-deposit-repo.ts:186-241`),但 `cost_price_fen` 货款会打给被改派的打手。若改派是刻意玩法可接受,否则是资金错付。**待确认(需业务口径)**。 2. **`assignWorkerWithConsignment` 仍是可达接口但已无前端入口** — `routes/admin/worker-platform/consignment.ts:263-283` + `services/admin/consignment.ts:151` 仍在;直连可绕过 `ConsignmentManualMatchModal.tsx:178` 的"只走内部现货"收敛,回到追踪文档 §四描述的双重扣押金 + 打手被迫重填资料场景。建议下线或加开关。 3. **`mapConsignmentInventory` 全量下发 `credentialsJson`(游戏账号密码)** — `consignment-admin-service.ts:179-182`,仅需 `consignment.view`,列表接口即返回(优化方案 P2-3 仍成立)。 ### 数据一致性 / 并发 4. **指派链路跨事务"部分成功"窗口仍在** — `claimConsignmentInventoryForWorker`(:663) 独立事务提交后,`assignAdminWorkOrderToWorker`(:680)、`updateWorkOrder`(:742)、`createWorkOrderEvent`(:748) 各自独立;失败靠 `compensateFailedConsignmentAssignment`(:802-849) 收敛,且三步补偿全是 `catch {}` 吞错(:821-848)。补偿自身失败时会出现「工单 in_progress + 押金已冻 + 现货已回池」→ 同一件货被第二张单占用。文档 `寄卖系统优化方案.md:159-165` 仍标注"指派事务化未做"。 5. **改派导致卖家端"我的寄卖"错位** — `consignment-worker-service.ts:352-368` 分别按 `submissions.worker_id` 与 `inventories.worker_id` 过滤(repo `:137-140`、`:902-905`)。审核改派后,原卖家有提报无库存 → 永远停在 `in_stock`「正在入库」(`consignment-item-stage.ts:158-161` 兜底);被改派者看到从未提报的货。 6. **提报列表 LEFT JOIN 存在行放大** — `consignment-repo.ts:937-943` 用 `rinv.id = cs.result_inventory_id OR rinv.source_submission_id = cs.id`,两个条件同时命中时同一提报单会出两行。 7. **我的寄卖窗口被硬截断** — `consignment-worker-service.ts:353-354` 固定 `pageSize: 200`,超过 200 条的历史寄卖静默不可见;`:362-367` 同一提报单只保留第一件库存。 8. **`internal_stock` 履约的押金释放依赖验收事务**,撤销/回库路径已覆盖,但"待加好友"无限期挂起时押金与货物同时冻结,无超时策略(文档 `寄卖库存优化现状与进度追踪.md:93,98` 仍标"待业务拍板")。 ### 性能 9. **候选匹配全表入内存** — `consignment-repo.ts:854-887` `listActiveConsignmentInventoriesForMatching` 无分页、无 LIMIT,`checkWorkOrderInventoryMatches`/`listWorkOrderInventoryMatchOptions` 每次调用全量拉取后在 JS 内比对(`consignment-fulfillment-service.ts:190,318`)。 10. **统计接口每次 5 条全表聚合** — `consignment-repo.ts:218-303`(优化方案 P2-2 未处理)。 ### 可维护性 11. **仓库关键字搜索仍不覆盖承诺字段** — `consignment-repo.ts:178-182` 未含 `buyer_name/shop_name/seller_phone/worker_name`,而前端占位符承诺(追踪文档 `:54` 仍标"待优化")。 12. **编辑无法解除打手关联** — `ConsignmentInventoryPanel.tsx:255` 传 `workerId || undefined`,后端按 `!== undefined` 判定(`consignment-admin-service.ts:381-386`),传空即"不修改"。 13. **`remark` 拼接式审计无长度上限**(`:980`、`:996`、release `consignment-repo.ts:582`)。 14. **`listWorkerConsignments` 用 `Promise.all` 两查询 + N 次工单批量查**,卖家量大时内存放大(非阻塞问题)。 ## 十一、文档与代码一致性 | 文档承诺 | 实际 | 结论 | | --- | --- | --- | | 优化方案 P0-1/2/3/4/5 五项 | 全部落地(079 迁移、`assertConsignmentRevocableWorkOrderStatus:864`、绑定唯一索引、`release...BindingForWorkOrderWithClient:765`) | ✅ 一致 | | P1-2「指派事务化」 | 仅做了补偿回滚,未合并事务(见风险 4) | ⚠️ 半做 | | P1-8 前端权限门控 | 已落地(`consignment-permissions.test.ts` 存在) | ✅ | | 批次 E 未做项:报表(节省赏金/出库单数/批次回款)、搜索字段扩展、打手端菜单按账号状态隐藏 | 仍全部未做 | ❌ 未做 | | §六/§七 统计看板 | `getConsignmentInventoryStats` 只有数量/成本 + 4 个 GROUP BY | ❌ 未做 | | 追踪文档 `:260` 写"`if (!worker.can_consign) throw`" | 实际是 `canWorkerPublishConsignment({whitelistEnabled, workerCanConsign})`(`consignment-worker-service.ts:151-155`) | ⚠️ 文档滞后于 9298b31b | | 追踪文档 §四「手动匹配弹窗收敛为内部现货履约」 | 前端已收敛(`ConsignmentManualMatchModal.tsx:178`),但后端 `assign-worker` 接口未下线 | ⚠️ 半做 | | 批次策略"回库保留运营匹配开关" | `releaseConsignmentInventoryClaim`(:563) 与 `returnConsignmentInventoryToStockWithClient`(:706) 均保留 `allow_matching` | ✅ | | 押金策略(待审超期自动驳回、打手自助撤回) | 均未实现,`consignment_submissions.status` 无 `cancelled` | ❌ 未做 | **历史演进(`git log` 摘要)**:`1aa6dba9` 初版(仓库+H5 收集+内部履约)→ `4a6215e8` 接入打手押金履约 → `fd1eb581` 冷静期 + 押金闭环 → `e828e677` 权限/竞态/撤销守卫 → `632dddc4` 下线 H5 + 状态机封闭 → `5d11bf20` 补偿 + 规格复检 → 批次 F–U(押金不退、作废口径、统一状态机、货款结算、手动匹配)→ **`a5727554`「彻底去除非平台内部现货履约」把匹配收敛为指派打手** → 追踪文档 §四发现该路线导致卖家被二次扣押金、被迫重填资料,随后提交把弹窗改回 `fulfillWorkOrderWithConsignment`;**`577e0eec` 反向清理**:从 `WorkOrdersPanel.tsx`/`WorkerOrderDetailDrawer.tsx` 移除已废弃的指派打手路径(a5727554 的方向被回退,指派线降级为遗留接口)→ `cb8b249e`/`9298b31b` 白名单与总开关。 ## 十二、测试覆盖评估 | 测试文件 | 断言重点 | | --- | --- | | `domain/consignment-item-stage.test.ts`(10 例) | 各阶段推导、内部现货 vs 打手线的"确认加好友"按钮差异、冷却到点转待赠送、终态 | | `domain/consignment-deposit-policy.test.ts`(6 例) | auto/显式覆盖的押金处置矩阵、非法值归一 | | `domain/consignment-whitelist.test.ts`(2 例) | 总开关开/关 | | `consignment-matching-service.test.ts`(24 例) | 规格解析、精确/高配充低配、通配互通、空规格不命中、品类隔离、建议价档位兜底 | | `consignment-manual-match.test.ts`(7 例) | 冲突清单、宽松复检、必填原因、两链路留痕、读接口只读 | | `consignment-worker-service.test.ts`(9 例) | 单号聚合、状态机接线、卖家归属、白名单 403、璀璨≤钻级 | | `consignment-payout.test.ts`(10 例) | 单位(分)、双付防护、拼单钩子、079 去重迁移 | | `consignment-fulfillment-price-trace.test.ts`(4 例) | 发单金额改写与撤销恢复原值 | | `consignment-revoke-policy.test.ts`(3 例) | 撤销必结算打手押金、作废关匹配开关 | | `consignment-evidence.test.ts`(8 例) | 凭证取图去重、手工入库停用、链接归一化 | | `consignment-repo.test.ts`(3 例) | SUB 号搜索、join 列前缀、`allocation_status` 落在 080 枚举内 | | `admin-worker-consignment-permission.test.ts`(3 例) | 权限回显、路由注册、空 body 不默认关闭 | | 前端 8 个 consignment 测试 | 冷却倒计时、凭证、手动匹配挂载(断言走内部现货)、权限、规格选项、白名单、供货打手展示 | **缺口**:(a) 无 `approveAdminConsignmentSubmission` 的端到端测试(改派+押金归属+货款归属这条链路无回归保护);(b) 无审核/驳回押金释放的金额断言;(c) 无 `revokeWorkOrderConsignmentFulfillment` 的并发/状态仲裁用例(`assertConsignmentRevocableWorkOrderStatus` 有纯函数测试但事务内 0 行 409 未覆盖);(d) 无 `releaseConsignmentInventoryBindingForWorkOrderWithClient` 的幂等/多工单绑定冲突用例;(e) 白名单仅有总开关与单人布尔两种纯函数用例,无"提报时总开关关闭但 `can_consign=false` 放行"的集成断言;(f) 无前端 `ConsignmentManualMatchModal` 对 `allowSpecMismatch` 必填原因的交互测试。