解决 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 全绿。
26 KiB
打手工单深挖:抢单 / 拼单 / 好友赠送 / 结算(2026-10-23)
状态:梳理稿(待评审,未登记索引) 落笔日期:2026-10-23 代码基线:工作区当前 HEAD(迁移 001–110) 范围:
work_orders从「可抢」到「已验收 + 资金结算」的完整闭环,含拼单份额、好友赠送、超时/问题单/退单申请、售后与欠款 不在范围:寄卖提报与匹配算法、订单进单、领取页(见项目全流程与数据库设计梳理-2026-10-23.md)
一、一页结论
- 一条工单只有两个资金入口:接单时冻结押金(
deposit_freeze),验收时结算报酬 + 释放押金(reward_settlement/deposit_release,或延迟解冻)。 - 押金不是「按单」而是「按接单关系的未结押金」:
getOutstandingDepositAmountWithClient(workerId, workOrderId)是唯一口径,同一工单多次接取/退回也只处理当前仍冻结的部分。 - 拼单的免押额度按「该打手在本工单的累计份额」抵扣一次,且已冻结金额不会因为追加份数变少而自动回退(
Math.max(当前冻结, 新要求))。 - 好友赠送是「三态叠加」:
work_orders.status(生命周期)+gift_phase(赠送阶段)+gift_available_at(冷却到点)。gift_ready不落库,由到点实时计算。 - 确认加好友是一道资金分水岭:
friend_added_at一旦写入,任何「释放押金」的处置都会被强制改成「扣除押金」,寄卖卖家的发布押金同口径一并没收。 - 超时、问题单、退单申请三条处置路径共用一套押金策略词表:
reopen / cancel_release / cancel_deduct(问题单另有return_to_worker,不碰押金)。 - 验收有两条免审通道:内部打手(
worker_type='internal')与 VIP(autoAcceptWithoutEvidence,可无图提交,但留 24 小时补证据窗口);好友赠送单一律人工验收。 - 退单是唯一会掉等级/封号的常规路径:VIP1 累计 5 次通过 → 直接禁用账号并吊销会话;其他等级每 10 次通过 → 降一级并把
level_penalty_accepted_count抬高,变相提高升级门槛。 - 资金全部落在
worker_wallet_ledgers(只追加),但余额没有「由流水重算」的校验,也没有会计期间;它仍然是业务钱包,不是总账。 - 锁序不统一:抢单先锁钱包再更新工单,指派先锁工单再锁钱包,拼单先锁工单再锁钱包——
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或未到点,直接 409work_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;寄卖发布押金同口径没收 |
十二、待确认问题(等你回答后我再补口径)
- 押金策略的业务意图:
required_deposit_amount现在是「按单金额」还是「按 SKU 单价 × 份数」?自动单为什么固定为 0——是刻意豁免,还是历史遗留? - 延迟解冻的初衷:
depositUnfreezeDays=3+rewardUnfreezeEnabled=true是为了防售后,还是为了压打手现金流?这决定售后追缴是否应该优先扣「待解冻队列」而不是可用余额。 - VIP1 五次退单封号这条规则现在还在执行吗?线上是否有误封申诉流程(代码里只有
review_note)? - 好友赠送冷却 72h 是否要按品类差异化?现在只有模板上的
gift_cooldown_hours/minutes两个字段。 - 拼单是否允许「一人多份 + 全部单人完成」?当前逻辑允许,且验收后一次性结算全部份额——如果业务上要求分散风险,这条需要改。