Files
along_django/docs/资金冻结功能设计方案.md
XingQue 8678f54459 实现提现资金冻结:手动/自动打款共用冻结拆分,驳回仅退 shenqing_jine。
新增公共/用户冻结配置、流水表与后台解冻接口;修复冻结与扣款重复扣余额问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 04:06:08 +08:00

13 KiB
Raw Blame History

资金冻结功能 — 设计方案(待确认)

本文档仅描述思路与边界,确认后再写代码
目标:打手 / 管事 / 商家三类角色的可提现余额,在自动打款申请时按配置冻结一部分资金,进入「冻结资金池」,防止重复提现;后台可配置、可单独覆盖、可解冻。


一、需求理解(我的归纳)

要点 理解
冻结对象 打手佣金余额(user_dashou.yue)、管事余额(user_guanshi.yue)、商家余额(user_shangjia.yue)
冻结资金去向 从「可提现余额」扣出 → 进入「冻结资金池」字段,永远不能通过提现提出
配置层级 公共配置(提现设置页) + 用户单独配置(各角色详情页,开启后覆盖公共)
冻结方式 比例:按当前余额冻结 X%固定金额:冻结池累计须达到 Y 元
生效范围(一期) 仅自动打款申请 POST /yonghu/zddksh;收款 tixiansq 不再二次冻结
驳回退款 仅退回本次实际进入提现流程的金额;本次划入冻结池的金额一并回滚
后台解冻 管理员在角色详情页,可将冻结池部分/全部划回可提现余额

一期不做(除非你确认要加):

  • 组长(leixing=3)、审核官(leixing=4)、打手押金(leixing=5) 的冻结
  • 手动打款 POST /yonghu/tixian 的冻结(可二期复用同一套 freeze_service

二、现有提现流程(自动打款)

用户 POST /yonghu/zddksh  { leixing, jine }
    ↓
create_audit_application()
    ├─ validate_withdraw_eligibility()   # 校验余额 >= jine
    ├─ calc_fee_amounts(jine)              # 手续费、实际到账
    ├─ deduct_balance(leixing, jine)       # 从余额扣 jine申请扣款额
    └─ 写 TixianShenheJilu + Tixianjilu
         shenqing_jine = jine
         jine(记录) = shijidaozhang到账额

审核通过 → 状态 6 待收款
用户 POST /yonghu/tixiansq 收款 → 微信打款(不再动余额)
驳回 → refund_balance(shenqing_jine) 退回余额

改造点:在 deduct_balance 之前插入「冻结拆分」逻辑。


三、表模型设计

3.1 公共冻结配置表 tixian_dongjie_gonggong_peizhi

每个角色一行(leixing 唯一):

字段 类型 说明
leixing SmallInt 1打手佣金 2管事 6商家(唯一)
role_enabled Boolean 该角色是否启用公共冻结(默认 False
dongjie_mode SmallInt 1=按比例 2=固定金额
dongjie_bili Decimal(5,4) 比例,如 0.1000 = 10%
dongjie_guding_jine Decimal(12,2) 固定冻结目标金额
update_time DateTime

3.2 全局总开关(挂在现有 withdraw_config 表)

字段 说明
dongjie_quanju_enabled 全员冻结总开关,默认 False仅当为 True 时,公共配置才可能生效

生效条件(公共):dongjie_quanju_enabled == True 该角色 role_enabled == True

3.3 三个扩展表新增字段(结构相同)

user_dashou / user_guanshi / user_shangjia

字段 类型 说明
dongjie_chi Decimal(12,2) 冻结资金池(累计,默认 0
dandu_dongjie_enabled Boolean 是否启用单独冻结配置(默认 False
dongjie_mode SmallInt 1比例 / 2固定(仅单独配置用)
dongjie_bili Decimal(5,4) 单独比例
dongjie_guding_jine Decimal(12,2) 单独固定金额

配置优先级:
dandu_dongjie_enabled == True → 用扩展表上的 mode/bili/guding
否则 → 用公共表(且需全局+角色开关均开)
否则 → 不冻结

3.4 审核记录补充字段(驳回回滚用)

tixian_shenhe_jilu 新增:

字段 说明
benchi_dongjie_jine 本次申请划入冻结池的金额(驳回时须回滚)

tixianjilu 同步写入(与审核单一致,便于对账)。

3.5 解冻流水表 tixian_dongjie_jilu(建议)

字段 说明
yonghuid, leixing, jine, dongjie_chi_before, dongjie_chi_after
caozuo 1申请冻结 2驳回回滚 3管理员解冻
operator_id, beizhu, create_time

便于审计,避免资金纠纷。


四、配置解析规则

def resolve_freeze_config(profile, leixing) -> FreezeConfig | None:
    """
    返回生效的冻结配置None 表示本笔不冻结。
    """
    if profile.dandu_dongjie_enabled:
        return FreezeConfig(mode=..., bili=..., guding=..., source='user')

    global_on = WithdrawConfig.dongjie_quanju_enabled
    if not global_on:
        return None

    pub = TixianDongjieGonggongPeizhi.objects.get(leixing=leixing)
    if not pub.role_enabled:
        return None

    return FreezeConfig(mode=pub.dongjie_mode, ..., source='public')

前端展示规则(详情页):

  • 选「按比例」→ 只展示比例输入框
  • 选「固定金额」→ 只展示固定金额输入框
  • 始终展示:冻结资金池当前余额是否启用单独配置 开关

五、核心算法(自动打款申请时)

统一入口:compute_freeze_split(balance, pool, config, apply_jine) -> FreezeResult

所有金额 Decimal,保留 2 位,ROUND_HALF_UP

5.1 无配置 / 未开启

freeze_jine = 0
withdraw_jine = apply_jine   # 走原逻辑

5.2 按比例冻结mode=1

语义:按申请时刻的「可提现余额」先切出冻结部分,剩余部分才能进入提现扣款。

设:

  • B = 当前可提现余额(申请前)
  • R = 冻结比例(如 0.10
  • A = 用户申请提现金额 jine

步骤:

  1. freeze_from_balance = quantize(B * R)
  2. max_withdraw = B - freeze_from_balance
  3. A > max_withdraw拒绝最多可提现 {max_withdraw} 元({R*100}% 已冻结)
  4. freeze_from_balance > 0
    • balance -= freeze_from_balance
    • dongjie_chi += freeze_from_balance
  5. 对金额 A 走原流程:deduct_balance(A) → 手续费基于 A 计算

示例10%

余额 B 申请 A 划入冻结池 实际提现扣款 申请后余额 冻结池
100 90 10 90 0 10
100 50 10 50 40 10
100 100 拒绝(最多 90

关键:冻结部分一次性从余额切走,避免「余额还剩 10% 又能再提」的漏洞。

5.3 按固定金额冻结mode=2

语义:冻结池累计须达到目标 T;未满则优先用本次申请金额「填池」。

设:

  • P = 当前 dongjie_chi
  • T = 配置的固定冻结目标
  • A = 用户申请金额
  • B = 当前余额(须 A <= B

步骤:

  1. P >= T不冻结withdraw_jine = A,走原逻辑
  2. P < T
    • gap = T - P
    • 情况 AA + P < T(本次申请填池后仍未达标)
      • 整笔 A 划入冻结池,不创建提现审核单(或创建「仅冻结」记录,状态特殊)
      • balance -= Adongjie_chi += A
      • 返回提示:已冻结 {A} 元,冻结池 {P+A}/{T},满额后可正常提现
    • 情况 BA + P >= T(本次可填满池并有余款提现)
      • fill = gap(填池金额)
      • withdraw_jine = A - fill
      • balance -= A(等价:先扣 A其中 fill 入池、withdraw_jine 进入提现)
      • dongjie_chi += fill
      • withdraw_jine 计算手续费、写审核单

示例T=100

池 P 余额 B 申请 A 结果
30 50 50 50+30<100 → 全部入池,池=80不提现
30 80 80 80+30≥100 → 填池70提现10
100 50 50 池已满 → 正常提现50

5.4 手续费与记录字段(不弄乱)

仅对 withdraw_jine(实际进入提现流程的金额) 计算:

shouxufei = withdraw_jine * feilv
shijidaozhang = withdraw_jine - shouxufei

TixianShenheJilu.shenqing_jine = withdraw_jine      # 申请扣款额
TixianShenheJilu.shijidaozhang = shijidaozhang      # 到账额
TixianShenheJilu.benchi_dongjie_jine = freeze_jine  # 本次入池

收款阶段 tixiansq 只打 shijidaozhang,不再动余额/冻结池。


六、驳回 / 解冻

6.1 审核驳回(自动 + 手动均适用)

在现有 refund_balance(shenqing_jine) 基础上增加:

balance += shenqing_jine           # 退回提现扣款
dongjie_chi -= benchi_dongjie_jine # 回滚本次冻结(若有)
balance += benchi_dongjie_jine     # 冻结部分也回到可提现余额

须在同一 transaction.atomic + select_for_update 内完成。

6.2 管理员解冻

新接口 POST /houtai/.../dongjie_jiedong(三个详情页共用):

{
  "yonghuid": "0000001",
  "leixing": 1,
  "jine": "50.00",
  "beizhu": "人工解冻"
}

校验:

  • 0 < jine <= dongjie_chi
  • balance += jinedongjie_chi -= jine
  • 写解冻流水

七、接口设计

7.1 公共配置(提现设置页)

方法 路径 权限
GET /houtai/hthqtxpz 扩展返回冻结配置 现有提现设置权限
POST /houtai/htxgtxsz 扩展保存冻结配置 同上

返回/保存结构示例:

{
  "dongjie_quanju_enabled": false,
  "dongjie_roles": [
    {
      "leixing": 1,
      "role_name": "打手佣金",
      "role_enabled": false,
      "dongjie_mode": 1,
      "dongjie_bili": "0.1000",
      "dongjie_guding_jine": "0.00"
    },
    { "leixing": 2, "...": "管事" },
    { "leixing": 6, "...": "商家" }
  ]
}

7.2 用户单独配置(三详情页共用)

方法 路径 说明
GET /houtai/hqdjdongjie?yonghuid=&leixing= 查询
POST /houtai/xgdjdongjie 保存单独配置 / 解冻

请求体:

{
  "yonghuid": "0000001",
  "leixing": 1,
  "dandu_dongjie_enabled": true,
  "dongjie_mode": 2,
  "dongjie_guding_jine": "100.00",
  "jiedong_jine": "30.00",
  "beizhu": "可选,有值则执行解冻"
}

dongjie_mode=1 时只读/写 dongjie_bilimode=2 时只读/写 dongjie_guding_jine

7.3 用户端(小程序)

申请前可增加接口 GET /yonghu/tixian_dongjie_info?leixing= 返回:

  • 是否启用冻结、模式、比例/固定额
  • 当前余额、冻结池、最大可申请金额(前端展示用,后端申请时仍强校验)

八、代码改动范围(确认后)

模块 改动
peizhi/models.py WithdrawConfig 增加总开关
yonghu/models.py 公共配置表 + 三扩展表字段 + 审核表 benchi_dongjie_jine + 流水表
yonghu/freeze_service.py 新建 配置解析 + 冻结拆分算法(纯函数,便于单测)
yonghu/tixian_shenhe_services.py create_audit_application 接入冻结;驳回增加回滚
houtai/view.py 提现设置读写、单独配置/解冻接口
kefu/.../Settings.vue 公共冻结配置 UI
打手/管事/商家详情页 单独冻结 + 解冻 UI

不动: tixiansq 收款逻辑(仅打款,不二次冻结)。


九、边界与防错

场景 处理
余额 0 拒绝申请
比例 100% max_withdraw=0,拒绝任何提现申请
固定池已满 与无冻结相同,正常提现
仅冻结不入提现固定模式情况A 不扣手续费、不写微信打款相关记录
并发申请 select_for_update 锁扩展表余额行
驳回 同时回滚 shenqing_jine + benchi_dongjie_jine
金额精度 全程 Decimal,禁止 float

十、待你确认的问题

  1. 一期范围:是否只做 打手/管事/商家 + 自动打款(zddksh)?组长、审核官、押金、手动打款是否二期?
  2. 固定模式「全入池」:申请金额不足以填满冻结池时,是否不创建审核单、仅入池(我按此设计)?
  3. 比例模式:是否按上文「先按余额切冻结比例,再对剩余部分提现」?(避免余额残留可再提)
  4. 驳回:是否同意「提现扣款 + 本次冻结」全部回滚到可提现余额
  5. 公共配置:全局一个总开关 + 每角色独立开关,是否符合你的预期?

十一、实施顺序(确认后)

  1. 迁移 + freeze_service 单元测试(用例覆盖第五节所有表格)
  2. 接入 zddksh / 驳回回滚
  3. 后台 API + 提现设置页 UI
  4. 三角色详情页 UI + 解冻
  5. 小程序申请前提示(可选)

请先看第十节 5 个确认点,回复「对/不对」或补充后,我再按此文档写代码。