fix(consignment): 下线 H5 收集链接渠道并封闭寄卖履约状态机

下线定向收集链接(H5 填报)整条线,提报统一走接单平台打手端:
- 删除前端 ConsignmentCollectPage、consignment-public 服务与 /consignment-collect/:token 路由
- 删除后端公开路由 routes/consignment.ts、consignment-public-service 与管理端 /consignment/links 三接口
- 迁移 079 删表 consignment_collect_links、删列 consignment_submissions.link_id,
  source_type 的 collection_link 归并为 worker_portal 并替换 CHECK

修复手工入库/编辑的凭据与截图静默丢失:后端统一按 credentialsJson ?? credentials、
screenshotsJson ?? screenshots 读取(入库/编辑/审核入库三个入口)。

按"已验收一律禁止撤销"封闭状态机:
- 撤销寄售履约加状态白名单(in_progress|pending_acceptance|problem)+ 事务内 FOR UPDATE
  锁单 + UPDATE 带 status 条件(0 行 409)+ 原因必填 + 清空指派/时限/验收草稿痕迹
- 库存改状态一律带条件:撤销要求 bound_work_order_id 命中本工单;作废/回库要求未被
  进行中工单占用(被挡时提示具体工单号);079 加部分唯一索引并清理历史重复绑定
- 工单终止统一解绑回库:打手撤单/取消指派/退回大厅/撤单/打手退单申请/超时处置接入
  releaseConsignmentInventoryBindingForWorkOrderWithClient(售后不改工单状态,无需接入)
- 回库路径保留运营的 allow_matching 开关设置(不再强制开启),前端文案同步更正

顺带修掉资金与归属的同源缺陷:
- 押金归属与重冻金额改取不可变的冻结核销行,审核改派不再"退给错人/向错人扣款"
- 新增 assigned_worker_id 拆开"卖家"与"被指派履约人",占库不再覆盖卖家
- PUT /inventories/:id 禁止直接改 status;发布接口补 requireActiveWorker
- 押金冻结/释放后补推钱包实时事件;迁移 079 给发布冻结加台账幂等唯一索引

验证:后端 401 测试/398 通过/3 skip(新增 5 例回归),前端 16 通过;
两侧 typecheck、lint:check、format:check 与 check:sql、build 全绿。
迁移 079 未经真实数据库执行(本环境无 Postgres),上库前请先 dry-run。
This commit is contained in:
yml2213
2026-09-16 20:05:49 +08:00
parent e828e677a5
commit 632dddc4c3
25 changed files with 960 additions and 1363 deletions
@@ -0,0 +1,102 @@
-- 079_drop_consignment_collect_links.sql —— 下线「定向收集链接 / H5 填报」渠道
--
-- 背景:寄卖提报统一走接单平台(打手端提报道),对外收集链接这条线整条移除:
-- - 前端:ConsignmentCollectPage、services/consignment-public.ts、/consignment-collect/:token 路由
-- - 后端:routes/consignment.ts(公开 link/submit/upload)、consignment-public-service.ts、
-- 管理端 /consignment/links 三个接口
-- - 数据:consignment_collect_links 表、consignment_submissions.link_id 列
-- 历史数据不做兼容处理(测试阶段),source_type 的 collection_link 归并为 worker_portal。
-- 1) 提报单去掉链接外键列(依赖该列的索引随之删除)
ALTER TABLE consignment_submissions
DROP COLUMN IF EXISTS link_id;
-- 2) 收集链接主表下线
DROP TABLE IF EXISTS consignment_collect_links;
-- 3) 库存来源枚举:collection_link -> worker_portal
-- 顺序很重要:旧 CHECK 只允许 ('manual','collection_link','recycle'),
-- 必须先摘掉旧约束再回填,否则 'worker_portal' 会被旧约束拒绝,整条迁移失败回滚。
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'consignment_inventories_source_type_check'
AND conrelid = 'consignment_inventories'::regclass
) THEN
ALTER TABLE consignment_inventories
DROP CONSTRAINT consignment_inventories_source_type_check;
END IF;
END
$$;
UPDATE consignment_inventories
SET source_type = 'worker_portal', updated_at = NOW()
WHERE source_type = 'collection_link';
ALTER TABLE consignment_inventories
ADD CONSTRAINT consignment_inventories_source_type_check
CHECK (source_type IN ('manual', 'worker_portal', 'recycle'));
COMMENT ON COLUMN consignment_inventories.source_type IS
'入库来源:manual 手工录入 / worker_portal 接单平台提报入库 / recycle 回收';
COMMENT ON TABLE consignment_submissions IS '打手通过接单平台提交的寄卖提报审核单';
-- 4) 区分"卖家"与"被指派的履约打手":worker_id 语义固定为卖家,指派结果单独存列,
-- 避免占库时覆盖卖家导致「我的寄卖」丢件。
ALTER TABLE consignment_inventories
ADD COLUMN IF NOT EXISTS assigned_worker_id BIGINT REFERENCES worker_users(id) ON DELETE SET NULL;
COMMENT ON COLUMN consignment_inventories.worker_id IS '寄卖卖家(提报人)打手ID,入库后不再变更';
COMMENT ON COLUMN consignment_inventories.assigned_worker_id IS
'被指派执行本次履约的打手ID(仅押金履约模式占用,回库/撤单时清空)';
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_assigned_worker_id
ON consignment_inventories(assigned_worker_id)
WHERE assigned_worker_id IS NOT NULL;
-- 一件现货同一时刻最多绑定一张工单:防止同一件现货被多张工单占用。
-- 先清理历史脏数据:同一工单被多件现货同时绑定时,保留工单 material 里记录的那一件,
-- 其余解绑回库(它们是此前"回库后又被另一张单占用"缺陷的产物)。
WITH ranked AS (
SELECT
ci.id,
ci.bound_work_order_id,
ROW_NUMBER() OVER (
PARTITION BY ci.bound_work_order_id
ORDER BY
(ci.id = COALESCE(NULLIF(wo.material_json -> 'consignment' ->> 'inventoryId', '')::bigint, 0)) DESC,
ci.id DESC
) AS rn
FROM consignment_inventories ci
JOIN work_orders wo ON wo.id = ci.bound_work_order_id
WHERE ci.bound_work_order_id IS NOT NULL
AND ci.status = 'consumed'
)
UPDATE consignment_inventories ci
SET
status = 'in_stock',
allocation_status = '待安排',
bound_order_id = NULL,
bound_work_order_id = NULL,
bound_platform_order_id = '',
shop_name = '',
consumed_at = NULL,
remark = remark || ' [重复绑定自动解绑: 工单 ' || ranked.bound_work_order_id || ']',
updated_at = NOW()
FROM ranked
WHERE ci.id = ranked.id
AND ranked.rn > 1;
CREATE UNIQUE INDEX IF NOT EXISTS uniq_consignment_inventories_bound_work_order
ON consignment_inventories(bound_work_order_id)
WHERE bound_work_order_id IS NOT NULL AND status = 'consumed';
-- 5) 寄卖发布押金的冻结核销行幂等键:同一提报单只能有一笔发布冻结,
-- 防止未来任何绕过"先读台账再冻结"约定的调用方重复扣款。
CREATE UNIQUE INDEX IF NOT EXISTS uniq_worker_wallet_ledgers_consignment_publish_freeze
ON worker_wallet_ledgers ((payload_json ->> 'consignmentSubmissionId'))
WHERE ledger_type = 'deposit_freeze'
AND payload_json ->> 'kind' = 'consignment_publish';