Files
order_site/docs/打手工单深挖-抢单拼单好友赠送结算-2026-10-23.md
T
yml2213 31a8b6fc65 Merge branch 'dev1023' into dev
解决 9 个冲突文件,并修复合并后遗留问题:

- 大厅候选集:采用 FIFO 窗口 + 全部置顶单 UNION(列表与抢单/拼单复核同源)
- 拼单份额:采用多次参与模型(每次参与 INSERT 新份额、requestId 幂等),
  取消后可重新参与;移除 HEAD 已废弃的单行 rejoin 逻辑与对应测试
- 工单迁移与成本中心 103–108 编号撞车:合并重写为
  109_work_order_share_participation / 110_work_order_hall_and_shop_indexes;
  临时库全量执行与重跑幂等验证通过(10 列、唯一键已删、7 个索引)
- 实时补丁:保留个性化字段、置顶重排、分页总数同步;补回 orderTimelineVisible 类型
- 打手大厅:保留 memo 组件化,移植「再拼一次」多份额按钮逻辑
- 全项目表格统一 PercentTable:WorkerOrderShareTable 改造

验证:backend typecheck/lint/check:sql/test(833 pass)、
frontend typecheck/lint/test(172 pass)/build 全绿。
2026-10-26 12:10:53 +08:00

26 KiB
Raw Blame History

打手工单深挖:抢单 / 拼单 / 好友赠送 / 结算(2026-10-23)

状态:梳理稿(待评审,未登记索引) 落笔日期:2026-10-23 代码基线:工作区当前 HEAD(迁移 001–110) 范围:work_orders 从「可抢」到「已验收 + 资金结算」的完整闭环,含拼单份额、好友赠送、超时/问题单/退单申请、售后与欠款 不在范围:寄卖提报与匹配算法、订单进单、领取页(见 项目全流程与数据库设计梳理-2026-10-23.md)


一、一页结论

  1. 一条工单只有两个资金入口:接单时冻结押金(deposit_freeze),验收时结算报酬 + 释放押金(reward_settlement / deposit_release,或延迟解冻)。
  2. 押金不是「按单」而是「按接单关系的未结押金」:getOutstandingDepositAmountWithClient(workerId, workOrderId) 是唯一口径,同一工单多次接取/退回也只处理当前仍冻结的部分。
  3. 拼单的免押额度按「该打手在本工单的累计份额」抵扣一次,且已冻结金额不会因为追加份数变少而自动回退(Math.max(当前冻结, 新要求))。
  4. 好友赠送是「三态叠加」:work_orders.status(生命周期)+ gift_phase(赠送阶段)+ gift_available_at(冷却到点)。gift_ready 不落库,由到点实时计算。
  5. 确认加好友是一道资金分水岭:friend_added_at 一旦写入,任何「释放押金」的处置都会被强制改成「扣除押金」,寄卖卖家的发布押金同口径一并没收。
  6. 超时、问题单、退单申请三条处置路径共用一套押金策略词表:reopen / cancel_release / cancel_deduct(问题单另有 return_to_worker,不碰押金)。
  7. 验收有两条免审通道:内部打手(worker_type='internal')与 VIP(autoAcceptWithoutEvidence,可无图提交,但留 24 小时补证据窗口);好友赠送单一律人工验收。
  8. 退单是唯一会掉等级/封号的常规路径:VIP1 累计 5 次通过 → 直接禁用账号并吊销会话;其他等级每 10 次通过 → 降一级并把 level_penalty_accepted_count 抬高,变相提高升级门槛。
  9. 资金全部落在 worker_wallet_ledgers(只追加),但余额没有「由流水重算」的校验,也没有会计期间;它仍然是业务钱包,不是总账。
  10. 锁序不统一:抢单先锁钱包再更新工单,指派先锁工单再锁钱包,拼单先锁工单再锁钱包——grabWorkOrder 是唯一的「钱包优先」路径。

二、单据与资金模型

flowchart LR
  subgraph 单据
    WO["work_orders<br/>生命周期状态"]
    WS["work_order_shares<br/>拼单份额"]
    EV["work_order_events<br/>只追加时间线"]
  end
  subgraph 资金
    W["worker_wallets<br/>available / frozen_deposit<br/>pending_unfreeze / frozen_withdraw"]
    L["worker_wallet_ledgers<br/>只追加流水"]
    U["worker_deposit_unfreezes<br/>延迟解冻队列"]
  end
  WO -->|"整单接取"| D1["deposit_freeze"]
  WS -->|"按份额接取"| D1
  WO -->|"验收"| D2["reward_settlement / deposit_release"]
  WS -->|"份额验收"| D2
  D2 -->|"unfreezeDays > 0"| U
  U -->|"到期任务"| D3["deposit_unfreeze"]
  WO -->|"超时/问题单/退单"| D4["deposit_release 或 deposit_deduction"]
  D1 --> L
  D2 --> L
  D3 --> L
  D4 --> L
  L --> W

唯一的余额写入方式(除提现外)都是「SELECT … FOR UPDATE 读钱包 → 计算 next → UPDATE → INSERT ledger」,没有任何一处只更新余额而不写流水。


三、抢单

3.1 入口与校验顺序

POST /api/v1/worker/hall/orders/:workOrderId/grab → grabWorkerHallOrder

sequenceDiagram
  participant W as 打手
  participant S as hall-action-service
  participant DB as PostgreSQL
  W->>S: grab(workOrderId)
  S->>S: 会话有效 → 读打手 + 等级权限 + 大厅配置
  S->>S: visibleAfterIso = now - visibleDelaySeconds(等级可见延迟)
  S->>DB: 读工单
  alt workOrder.sharing_enabled
    S->>DB: joinWorkOrderShare(takeRemaining=true)
  else 整单抢单
    S->>DB: countWorkerActiveOrders
    S->>S: 校验 maxActiveOrders / 可用余额 ≥ 冻结额
    S->>DB: grabWorkOrder(...)(单事务)
    S->>DB: work_order_events grabbed + 实时事件
  end

整单抢单在事务内再次做全部校验(防并发),并条件更新:

UPDATE work_orders
SET status='in_progress', assigned_worker_id=$1, last_assigned_worker_id=$1,
    assigned_at=$2, deadline_at=$3,
    gift_phase = CASE WHEN acceptance_mode='friend_gift' THEN 'material_required' ELSE '' END,
    booster_material_json='{}', friend_added_at=NULL, gift_available_at=NULL,
    <CLEAR_DELIVERY_RESIDUE_SQL>, updated_at=$2
WHERE id=$4 AND status='open' AND assigned_worker_id IS NULL

CLEAR_DELIVERY_RESIDUE_SQL 清掉上一手的 submitted_at / accepted_at / acceptance_json / draft_acceptance_json / problem_note / worker_note,但不动买家资料(material.collect)和寄卖留痕(consignmentHistory)。

3.2 冻结押金怎么算

整单:freezeAmount = max(0, work_order.required_deposit_amount - depositFreeAmount)
拼单:nextRequired = round(required_deposit_amount × nextQuantity / totalQuantity)
      requiredShareDeposit = max(0, nextRequired - depositFreeAmount)
      share_deposit = max(已冻结的 share_deposit, requiredShareDeposit)   // 不回退
      本次冻结 = share_deposit - 已冻结

depositFreeAmount = 等级 permission_json.depositFreeAmount + worker_users.personal_deposit_free_amount(两者叠加,个人额度是等级之上的追加额度)。

注意:deposit_threshold_amount(押金门槛)目前只读写/展示,不参与冻结计算;自动生成的工单把 required_deposit_amount 与 deposit_threshold_amount 都写成 0(见风险 W1)。

3.3 大厅可见性

规则 实现
等级可见延迟 permission_json.visibleDelaySeconds;未配置时按等级键默认:vip1 20s / vip2 15s / vip3 10s / vip4 5s / vip5 0s
大厅总量 worker_hall.maxVisibleOrders(默认 50,上限 500)
分类上限 worker_hall.categoryMaxVisibleOrders(分类 ID → 上限)
拼满隐藏 excludeFilledSharing

可见延迟不是拦截,而是「只把发布时间早于 now - delay 的单放进候选集」,抢单时在事务内再校验一次容量,防止延迟期内被绕过。


四、拼单

4.1 数据模型

work_order_shares:UNIQUE(work_order_id, worker_id),一份行代表一个打手在该工单的全部份额。

列 含义
quantity 该打手累计份数(同一工单可追加)
unit_reward / share_reward 单份报酬 / 累计报酬
share_deposit 该打手是否该单的累计冻结额(不是本次增量)
status joined → submitted → accepted / cancelled
acceptance_json / draft_acceptance_json 验收资料 / 草稿

4.2 参与规则

  • 普通参与:指定 quantity(默认 1),不得超过剩余份数 sharing_total_quantity - Σ(非 cancelled 份额)。
  • 「立即抢单」:takeRemaining=true,在事务锁内承接全部剩余份数;如果已有自己的 joined 份额,则在此基础上追加,否则报「数量已满」。
  • 整单接取者不能再拼单(work_order_owner_conflict)。
  • 重复参与被唯一键挡住;同一打手多次参与只能走「追加份数」路径。
  • 活跃单上限只在新建份额时校验(追加不重复计入)。

4.3 母单状态收敛

份额 joined      → 母单保持 open(可继续参与)
份额 submitted   → 仍 open;若「份数已全部分配」且「没有 joined 份额」→ 母单转 pending_acceptance
单份额验收       → 该份额 accepted;若这是最后一个未结算份额 → 母单 accepted 并补跑寄卖钩子
全部取消         → 母单回 open

submitWorkOrderShareAcceptance 里那句 WHERE id=$2 AND status='open' 是关键:只有母单还在 open 时才允许被自动推进,避免与后台手动验收并发覆盖。

4.4 两种验收姿势

方式 入口 行为
单独验收某份额 POST /admin/worker-platform/orders/:id/shares/:shareId/accept 只结算该份额:份额报酬 + 份额押金;已结算份额不会在统一验收时重复结算
统一验收母单 POST /admin/worker-platform/orders/:id/accept 先检查所有份额都已 submitted(有任一 joined 直接 409 sharing_pending_submissions),再逐个结算未结算份额,最后结算整单打手(如有)

设计上的一个细节:统一验收时 settledShareCount 用「status !== 'accepted'」统计,已经单独验收过的份额会被跳过——这是避免重复发钱的唯一保护,没有幂等键。

4.5 已提交份额不能绕过

问题单的 reopen / cancel_* 与超时处置都会先检查 getActiveWorkOrderShareCountWithClient;有活跃份额就拒绝整单处置(work_order_has_active_shares)。这是为了防止「母单被整单带走,份额验收入口全部锁死」。


五、好友赠送(friend_gift)

5.1 三态叠加

stateDiagram-v2
  [*] --> material_required: 建单时 acceptance_mode=friend_gift
  material_required --> material_submitted: 打手提交本人资料(昵称/ID/区服/系统 + 主页截图)
  material_submitted --> friend_countdown: 后台确认已加好友(记 friend_added_at,算 gift_available_at)
  friend_countdown --> gift_ready: 到点(不落库,实时计算)
  gift_ready --> pending_acceptance: 打手提交赠送凭证
  pending_acceptance --> accepted: 后台人工验收(必审)
  material_submitted --> material_required: 后台重置资料
  friend_countdown --> material_required: 后台重置资料
  • work_orders.status 在整条链路上保持 in_progress(或 problem),不因赠送阶段改变;前端展示用 resolveWorkOrderGiftPhase()。
  • 冷却时长 = gift_cooldown_hours 小时 + gift_cooldown_minutes 分钟;两者都为 0 时按默认 72 小时 兜底(DEFAULT_GIFT_COOLDOWN_HOURS),上限 30 天。
  • 确认加好友时,deadline_at 被顺延为「冷却结束 + timeout_minutes」,即冷却期不计入打手超时。
  • 提交验收时如果 gift_phase !== friend_countdown 或未到点,直接 409 work_order_gift_cooldown_active。

5.2 押金分水岭

friend_added_at IS NULL   → 取消/退回/超时:按策略正常退还
friend_added_at 有值      → 取消/退回/超时:释放类一律转为扣除(deposit_deduction)
                            寄卖卖家的发布押金同口径没收

实现集中在 isWorkOrderGiftDepositForfeitable(),被超时处置、问题单处置、退单审核三处共用。语义是:好友已经加了,赠送动作由打手/卖家完成,此时脱离订单属于套利。

5.3 内部现货变体(寄卖)

寄卖库存绑定工单走 internal_stock 时没有接单打手,流程被压缩:

assigned_worker_id = null,material.consignment.fulfillmentType = internal_stock
  → parkWorkOrderWaitingFriendAdd:status=in_progress,gift_phase=material_required(语义变成「待加好友」)
  → 客服线下通知买家加好友 → 手动确认 → 进入冷却
  → 冷却结束:后台可直接验收(无需打手交图)

hasInternalConsignmentFulfillment() 是识别标志:assigned_worker_id=0 + material.consignment.inventoryId + 非 worker_deposit + 无 consignment.workerId。


六、验收

6.1 打手提交(整单)

规则 细节
归属 必须是 assigned_worker_id 本人(寄卖卖家走 asConsignmentSupplier 例外)
状态 in_progress / problem
超时前置 已过期则先执行超时处置再抛 409(接口惰性判定)
图片 默认必须至少 1 张(work_order_acceptance_image_required);VIP/内部打手可不交图
赠送单 必须 friend_countdown 且已到点;一律人工验收,VIP/内部免审不适用
草稿 可先存 draft_acceptance_json,提交时优先取「本次上传 + 草稿里已有的图」并去重

免审条件:worker_type === 'internal' 或 等级 permission_json.autoAcceptWithoutEvidence === true(且不是拼单份额参与,canWorkerAutoAcceptWholeOrder 明确排除拼单)。

免审且无图时,验收资料写入:

{ "reviewMode": "vip_auto", "evidenceStatus": "pending", "evidenceDueAt": "<now+24h>" }

补证据走 supplementWorkerAcceptedOrderEvidence(验收后仍可补)。

6.2 催促

  • 提交满 10 分钟 才能催;
  • 每 30 分钟 一次,最多 3 次;
  • 只支持整单(拼单份额不能催);
  • 催的是 admin_notifications 的同一个待办(reminder_count 累加),不会刷屏。

6.3 后台验收

  • 只接受 pending_acceptance(capabilities.canAccept)。
  • 历史拼单母单如果还是 open,验收前自动推成 pending_acceptance。
  • 内部现货好友赠送在 gift_ready 且 in_progress 时,后台可直接验收(自动补 submitted_at)。
  • 有未提交份额 → 409 work_order_sharing_pending_submissions。

七、结算(核心)

7.1 一个事务里做完 8 件事

acceptWorkOrderAndSettle(整单)与 acceptWorkOrderShareAndSettle(份额)结构一致:

flowchart TD
  A["锁工单(pending_acceptance)+ 锁全部非取消份额"] --> B{"还有 joined 份额?"}
  B -->|是| X["409 sharing_pending_submissions"]
  B -->|否| C["工单置 accepted / 份额置 accepted"]
  C --> D["释放寄卖发布押金(仅整单,按 material.consignment.sourceSubmissionId)"]
  D --> E["逐份额结算:售后欠款抵扣 → 报酬 → 押金"]
  E --> F["整单打手结算:押金 → 报酬 → 欠款抵扣"]
  F --> G["寄卖供货货款(internal_stock 时补给卖家)"]
  G --> H["等级提升判定"]
  H --> I["写 work_order_events accepted"]

7.2 资金等式(单次结算)

releaseAmount = min(该工单未结押金, 钱包 frozen_deposit_amount)
debtOffset    = min(售后未结欠款, 本次报酬)          // FIFO,按欠款创建时间
shouldDelay   = rewardUnfreezeEnabled && unfreezeDays > 0
pendingDelta  = (shouldDelay ? 报酬 : 0) + (unfreezeDays > 0 ? 押金 : 0)
available'    = available + (shouldDelay ? 0 : 报酬) + (unfreezeDays > 0 ? 0 : 押金) − debtOffset
frozen'       = max(0, frozen − releaseAmount)
total_settled += 报酬

默认财务配置(worker_finance 配置键):

项 默认 范围
depositUnfreezeDays 3 天 0–30
rewardUnfreezeEnabled 开启 布尔
consignmentPublishDepositFen 2000 分(¥20) 0–1,000,000
consignmentWhitelistEnabled 开启 布尔

7.3 流水类型对照

场景 ledger_type 金额符号
接单冻结 deposit_freeze −
验收即释放押金 deposit_release +
验收延迟解冻(入队) deposit_pending_unfreeze +(余额不加,进 pending_unfreeze)
到期解冻 deposit_unfreeze +
验收即结算报酬 reward_settlement +
验收延迟解冻报酬 reward_pending_unfreeze +
拼单报酬 sharing_reward +
售后抵扣 after_sales_debt_offset −
售后从可用余额扣 after_sales_balance_deduction −
超时/退单/问题单扣押金 deposit_deduction −
提现冻结 / 支付 / 解冻 withdraw_freeze / withdraw_paid / withdraw_release − / − / +
后台手动加款 manual_credit +

7.4 延迟解冻

  • 入队:worker_deposit_unfreezes(worker_id, work_order_id, amount, fund_type='deposit'|'reward', unfreeze_at, status='pending')。
  • 出队:定时任务 deposit-unfreeze(60s,scanLimit 100)→ settleDueDepositUnfreezes。
  • 到期判定按「当前配置天数」回溯:管理员把 depositUnfreezeDays 调小后,存量队列会提前视为到期(retroactiveDays),不会卡在旧到期时间。
  • 提现前后台会先跑一次 settleDueDepositUnfreezes({workerId}),避免「到期了但还没解冻、余额显示不足」。

7.5 售后抵扣顺序

验收报酬 → 先抵扣 open 欠款(FIFO)→ 抵扣部分写 after_sales_debt_offset
        → 欠款结清时 cases.debt_amount 归零、status 从 recovery_pending 变 closed
        → 报酬不足抵扣的部分继续留在 worker_after_sales_debts.outstanding

7.6 等级提升 / 降级

方向 触发 规则
升 每次验收后 maybeUpgradeWorkerLevelWithClient 有效验收数 = 累计已验收单数 − level_penalty_accepted_count;取 upgradeThreshold ≤ 有效数 的最高等级;is_level_locked 时完全不动;只升不降
降 退单申请审核通过 非 VIP1:每 10 次通过降一级,并把 level_penalty_accepted_count 抬到「实际验收数 − 新等级门槛」,提高重新升级成本
封 退单申请审核通过 VIP1 累计 5 次通过 → status=disabled、session_version+1、吊销全部会话

worker_level_logs.reason 取值:auto / cancel_request_penalty / cancel_request_ban。

7.7 寄卖钩子

钩子 时机 作用
releaseConsignmentPublishDepositBySubmissionWithClient 整单验收 释放卖家发布押金
settleConsignmentSupplierPayoutByWorkOrderWithClient 验收后 内部现货履约时订单没有接单打手,货款补给供货卖家;接单人就是卖家本人时不重复付款
releaseConsignmentInventoryBindingForWorkOrderWithClient 超时/退单/问题单/取消 把占用的现货放回在库池,避免永久 consumed

八、三条处置路径的押金矩阵

处置 入口 目标状态 reopen cancel_release cancel_deduct return_to_worker
超时 定时任务 / 接口惰性 open / cancelled 退押金 退押金 扣押金 —
问题单 后台 problem/resolve open / cancelled / 恢复 退押金 退押金 扣押金 不动押金,恢复到 marked_problem 的 from_status
退单申请 后台审核通过 open(重新上架) — 退押金 — —

补充规则:

  • 三种路径都先检查「是否还有活跃份额」;有则拒绝整单处置。
  • 已确认加好友的赠送单:cancel_release 会被改写成 cancel_deduct,事件类型也按实际资金动作记(timeout_cancel_deduct / problem_cancel_deduct)。
  • 超时与问题单会释放寄卖现货绑定;return_to_worker 不会(单还在进行)。
  • 退单申请:待审核期间订单与押金保持原状;审核通过在同一事务里撤单、退押金、执行等级处罚。
  • 打手每天最多提 5 次退单申请(按 Asia/Shanghai 自然日,pending + approved 计数),同一工单+份额不能有两条 pending。
  • 拼单份额超时只在母单 open 时生效(份额 joined 且 created_at + timeout_minutes 已过),否则跳过。

九、售后与欠款

9.1 生命周期

stateDiagram-v2
  [*] --> awaiting_worker_response: 后台建单(必须已验收)
  awaiting_worker_response --> under_review: 打手提交说明 + 证据
  awaiting_worker_response --> under_review: 后台直接处置
  under_review --> closed: not_upheld(不成立)
  under_review --> closed: remedied(已补救)
  under_review --> recovery_pending: upheld 且追缴 > 报酬/可扣押金 → 产生欠款
  under_review --> closed: upheld 且全额扣到
  recovery_pending --> closed: 欠款结清(验收抵扣 / 充值抵扣 / 后台代还)

9.2 建单约束

约束 说明
工单必须 accepted work_order_not_accepted
指定份额时必须已验收 work_order_share_not_accepted
必须能定位责任打手 worker_missing;内部现货寄卖单没有接单打手时,自动反查供货卖家(getConsignmentSupplierByWorkOrderWithClient)作为责任人
同一「工单 + 打手 + 份额」同时只能有 1 条进行中 case 由部分唯一索引 idx_worker_after_sales_cases_active_target 保证:UNIQUE(work_order_id, worker_id, COALESCE(work_order_share_id,0)) WHERE status IN ('awaiting_worker_response','under_review','recovery_pending'),插入用 ON CONFLICT DO NOTHING,冲突即 active_case_exists
投诉说明必填、≤ 2000 字
问题类型默认 fake_evidence

9.3 追缴顺序

1. 该工单尚未解冻的押金(worker_deposit_unfreezes pending, fund_type='deposit')
   → 走 deductPendingDepositUnfreeze,直接从队列扣,不进可用余额
2. 可用余额(available_amount)
3. 仍不足 → 写入 worker_after_sales_debts(status='open', outstanding_amount)
   → case.status = recovery_pending

后续抵扣:

  • 该打手下一次验收报酬 → 自动 FIFO 抵扣(offsetWorkerAfterSalesDebtsWithClient);
  • 充值入账 → repayWorkerAfterSalesDebtsFromAvailableWithClient;
  • 后台手动代还 → repayAfterSalesCaseDebt。

每一笔抵扣都会写 worker_after_sales_case_events(debt_offset / debt_settled)+ 对应钱包流水,两边都有痕迹。


十、发现的隐患(按资金风险排序)

# 隐患 证据 后果
W1 自动建单硬编码 required_deposit_amount=0、deposit_threshold_amount=0 sync-worker-orders-service.ts 自动单永远无押金约束,模板里的押金配置是死配置
W2 抢单先锁钱包、指派/拼单先锁工单 work-order-grab-repo.ts vs 同文件 assignWorkOrderToWorker / work-order-share-repo.ts 并发抢单+指派可 40P01 死锁;目前只靠客户端重试
W3 统一验收的「已结算份额」判断只看 status !== 'accepted',没有幂等键 acceptWorkOrderAndSettle 极端并发下(两次验收同时进入)可能重复发报酬;FOR UPDATE 锁工单能挡住大部分,但没有业务幂等
W4 发单金额 > 用户付款只在前端提示 publishAdminWorkOrder 只校验 reward_amount > 0 直接调 API 可发亏损单;source_order_amount_fen 只是记录,不参与校验
W5 发布工单时电子凭证核销失败只写事件,不回滚发布 publishAdminWorkOrder 的 voucherConsume.ok=false 分支 券没核销但工单已在大厅;指派路径却会拦截,同一规则两套行为
W6 钱包余额没有「由流水重算」校验,也没有月度关账 worker_wallets 可变字段 任何一次漏写/重复写流水都不会被系统发现,只能人工对账
W7 work_orders.task_id 是历史遗留列(迁移 091 已注释废弃) 仍参与部分 mapper 新人容易误以为「工单必须挂履约任务」

十一、两笔完整流水示例

先记一条硬约束:好友赠送与拼单互斥。建单时 sharingEnabled = isFriendGiftAcceptanceMode(rule.acceptance_mode) ? false : ...,所以不存在「好友赠送 + 拼单份额」的单子。

11.1 拼单单

设定:sharing_total_quantity=3、unit_reward=3000 分、required_deposit_amount=6000 分;打手 A 等级免押 1000 分、活跃上限 3;depositUnfreezeDays=3、rewardUnfreezeEnabled=true。

步骤 动作 钱包变化 关键字段/流水
1 A 参与 2 份 nextRequired = 6000×2/3 = 4000;requiredShare = 4000−1000 = 3000;冻结 3000 work_order_shares(quantity=2, share_deposit=3000),deposit_freeze −3000
2 A 追加 1 份 nextRequired = 6000;requiredShare = 5000;本次冻结 2000 quantity=3, share_deposit=5000,deposit_freeze −2000
3 提交验收 份数已满且无 joined 份额 → 母单 open → pending_acceptance share.status=submitted
4 验收该份额 报酬 9000 延迟解冻;押金 5000 延迟解冻 pending_unfreeze +14000,reward_pending_unfreeze +9000,deposit_pending_unfreeze +5000
5 3 天后任务 到期释放 deposit_unfreeze +14000,pending_unfreeze −14000

11.2 好友赠送单

设定:acceptance_mode=friend_gift、required_deposit_amount=5000、免押 0、冷却 72h、timeout_minutes=120。

步骤 动作 结果
1 打手抢单 冻结 5000(deposit_freeze);gift_phase=material_required;deadline_at=now+2h
2 提交本人资料 gift_phase=material_submitted;friend_added_at=NULL
3 客服确认加好友 friend_added_at=now;gift_available_at=now+72h;deadline_at=now+72h+2h(冷却不计超时)
4 冷却中尝试提交 409 work_order_gift_cooldown_active
5 到点后提交赠送凭证 status=pending_acceptance;不触发免审(赠送单强制人工)
6 客服验收 押金 5000 → 延迟解冻;报酬按 reward_amount 入账(同样按 rewardUnfreezeEnabled 决定是否延迟)
7′ 若第 3 步之后打手退单 押金从「应释放」改为 deposit_deduction −5000;寄卖发布押金同口径没收

十二、待确认问题(等你回答后我再补口径)

  1. 押金策略的业务意图:required_deposit_amount 现在是「按单金额」还是「按 SKU 单价 × 份数」?自动单为什么固定为 0——是刻意豁免,还是历史遗留?
  2. 延迟解冻的初衷:depositUnfreezeDays=3 + rewardUnfreezeEnabled=true 是为了防售后,还是为了压打手现金流?这决定售后追缴是否应该优先扣「待解冻队列」而不是可用余额。
  3. VIP1 五次退单封号这条规则现在还在执行吗?线上是否有误封申诉流程(代码里只有 review_note)?
  4. 好友赠送冷却 72h 是否要按品类差异化?现在只有模板上的 gift_cooldown_hours/minutes 两个字段。
  5. 拼单是否允许「一人多份 + 全部单人完成」?当前逻辑允许,且验收后一次性结算全部份额——如果业务上要求分散风险,这条需要改。