开发文档

鲜花交易系统与鲜花交易平台(PHP / ThinkPHP 版)的研发记录:功能上线、架构演进与实施经验,按发布日期持续更新。

下游赊销怎么建模:信用额度、额度占用、账期账单与回款

为什么不能复用"应付结算单"

系统里已经有一张"结算单",用于记录我们欠供应商多少(应付)。有人会想:赊销就是反过来,把金额取负号不就行?

不行,因为方向完全不同:

维度应付结算单账期应账单
主体供应商(我们欠他)下游买方(他欠我们)
生成时机订单完成/发货后按账期周期汇总
核销方式平台付款买方回款
对报表影响成本与现金流应收账款与账龄

把两者塞进一张表,结果是所有涉及"钱"的报表都要加一个正负号判断,而账龄、逾期这些概念在应付侧没有对应物。

四个概念,四张核心表

赊销的本质是四个问题的分离:

  1. 还能欠多少 → 信用账户(额度、已用、账期类型、风险等级)
  2. 这单占了多少 → 额度占用(订单级,可释放)
  3. 这个周期该收多少 → 账期账单(周期汇总,出账后金额不可变)
  4. 收了没有 → 回款(待确认 → 核销账单 → 释放额度)

再加两张辅助表:账单明细(订单粒度快照)与额度变更日志(只增不改,供风控审计)。

关键规则一:出账不释放额度

最容易设计错的一条是"什么时候把已用额度还回去"。

直觉做法是"出账后把占用转成账单,占用释放"。但这会造成一个洞:客户 8 月的账单已经出了,额度回到可用状态,他 9 月继续赊——欠着钱还能继续欠。

正确规则是:已用额度 只在两种情况下减少——回款核销与取消/退款释放。出账只是把占用状态从"占用中"改成"已转账单",金额仍然占着。

关键规则二:扣额度必须原子

下单时"先查额度够不够,再更新已用额度"在并发下必然被刷爆。正确做法是一条带条件的原子更新:

```

UPDATE 信用账户

SET credit_used = credit_used + :amt

WHERE id = :id

AND status = 正常

AND credit_limit - credit_used >= :amt

```

影响行数为 0 即表示额度不足,直接拒绝下单。整个过程在一个事务里,同时写入占用记录与变更日志。

关键规则三:幂等键挡住重复出账

两个唯一键承担了幂等:

  • 一订单只占一次(tenant_id + order_biz_no):重复提交下单不会占用两份额度;
  • 一账户一周期只出一张账单(tenant_id + account + period_start + period_end):定时任务重跑不会给同一个账期出两张账单。

这两个约束比"代码里判重"可靠得多——判重可能因为并发或异常分支失效,唯一键不会。

逾期用标记,不用状态

账单状态只有五个:待确认、待收款、部分收款、已结清、已作废。

逾期不是状态,而是一个日更标记(is_overdue + overdue_days)。原因是:状态用于表达业务流程走到哪一步,逾期只是时间的函数——一张"部分收款"的账单同样可能逾期。把逾期做成状态,会导致状态机出现"部分收款且逾期"这种无法用单值表达的困境。

每日账实校验

赊销最大的风险是账对不上。所以每天跑一次校验:

  • 账户的 已用额度 是否等于「未释放的占用 + 未结清的账单」之和;
  • 账单的 未收金额 是否恒等于「应收 − 已收」;
  • 收入类流水与回款核销是否一一对应。

三条不平就告警,而不是等客户来投诉。

一条边界

这套账期落在业务域(供应链),不进平台资金台账。原因:赊销是租户与买方之间的商业信用,平台不碰这笔钱;一旦写进平台资金账,平台现金流报表会被客户的应收账款污染。

边界清楚,扩展才不打架。

本文属于「开发文档」系列,记录鲜花交易系统(PHP + ThinkPHP 8 + MySQL 8)的实际研发进展。想了解功能清单与报价,可前往系统报价或联系我们。