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
@@ -305,7 +305,7 @@ export default function ConsignmentInventoryPanel() {
if (!returnItem) return
try {
await returnAdminConsignmentInventory(returnItem.id, { reason: returnReason })
message.success('物品已退单回库,重新进入待匹配池!')
message.success('物品已退单回库,可在列表中查看匹配开关状态')
setReturnItem(null)
setReturnReason('')
await refresh()
@@ -339,9 +339,13 @@ export default function ConsignmentInventoryPanel() {
<Text type="secondary" copyable style={{ fontSize: 12 }}>
{record.inventoryNo}
</Text>
{record.sourceType === 'collection_link' ? (
{record.sourceType === 'worker_portal' ? (
<Tag color="purple" style={{ fontSize: 11, padding: '0 4px' }}>
链接收集
打手端提报
</Tag>
) : record.sourceType === 'recycle' ? (
<Tag color="green" style={{ fontSize: 11, padding: '0 4px' }}>
回收
</Tag>
) : (
<Tag color="blue" style={{ fontSize: 11, padding: '0 4px' }}>
@@ -1249,7 +1253,7 @@ export default function ConsignmentInventoryPanel() {
退回复库吗?
</Text>
<div style={{ marginTop: 8, color: '#8c8c8c', fontSize: 12 }}>
回库后,物品状态将重新变为【在库】,允许匹配开关将自动开启,可重新匹配履约其他工单。
回库后,物品状态将重新变为【在库】。允许匹配开关保持原设置:若该物品此前被手动暂停匹配,回库后需在列表里手动开启才会重新参与匹配。
</div>
</div>
<Form layout="vertical">
@@ -141,7 +141,6 @@ export default function ConsignmentSubmissionsPanel() {
function resolveSubmissionSource(record: ConsignmentSubmission) {
const channel = (record.contentJson as { channel?: string } | undefined)?.channel
if (channel === 'worker_portal' || record.workerId) return '打手端提报'
if (record.linkTitle) return `旧收集链接: ${record.linkTitle}`
return '其他渠道'
}
@@ -167,6 +167,18 @@ function isInternalConsignmentOrder(row: WorkOrder) {
return Boolean(consignment.inventoryId) && !row.worker
}
/**
* 只有「未验收且未取消」的工单可以撤销寄售履约。
* 已验收工单撤销会造成同一件现货二次出货、并重新冻结已退还的押金,后端一律拒绝。
*/
const CONSIGNMENT_REVOCABLE_ORDER_STATUSES = ['in_progress', 'pending_acceptance', 'problem']
function isConsignmentRevocableOrder(row: WorkOrder) {
return (
isInternalConsignmentOrder(row) && CONSIGNMENT_REVOCABLE_ORDER_STATUSES.includes(row.status)
)
}
export default function WorkOrdersPanel() {
const { message, modal } = App.useApp()
const navigate = useNavigate()
@@ -216,6 +228,9 @@ export default function WorkOrdersPanel() {
const canDeductDeposit = sessionPermissions
? sessionPermissions.includes('worker_order.deduct_deposit')
: hasAdminPermission('worker_order.deduct_deposit')
const canFulfillConsignment = sessionPermissions
? sessionPermissions.includes('consignment.fulfill')
: hasAdminPermission('consignment.fulfill')
const canConfigureSharing = sessionPermissions
? sessionPermissions.includes('worker_order.configure_share')
: hasAdminPermission('worker_order.configure_share')
@@ -760,6 +775,10 @@ export default function WorkOrdersPanel() {
async function handleRevokeConsignmentSubmit() {
if (!revokeConsignmentModal.order) return
if (!revokeConsignmentModal.reason.trim()) {
message.warning('请填写撤单原因,便于后续对账与追溯')
return
}
setRevokeConsignmentModal((prev) => ({ ...prev, submitting: true }))
try {
await revokeWorkOrderConsignmentFulfillment({
@@ -1454,7 +1473,7 @@ export default function WorkOrdersPanel() {
扣押金
</Button>
) : null}
{isInternalConsignmentOrder(row) ? (
{isConsignmentRevocableOrder(row) && canFulfillConsignment ? (
<Button
icon={<RollbackOutlined />}
danger
@@ -2997,8 +3016,8 @@ export default function WorkOrdersPanel() {
<Alert
showIcon
type="warning"
message="撤销后,关联寄售物品将退单回库并重新进入待匹配池"
description="适用场景:买家退款、客服误选库存、账号密码错误失效或规格不符等情况。"
message="撤销后,关联寄售物品将退单回库;物品的【允许匹配】开关保持原设置不变"
description="只适用于【进行中 / 待验收 / 问题单】。已验收订单无法撤销(现货已交付、押金已结清),买家退款或纠纷请走售后流程。"
style={{ marginBottom: 16 }}
/>