1
0
forked from erp-dev/erp
Files
erpnew/docs/CUSTOMER_DEBT_CALCULATION(1).md
2026-05-19 23:41:34 +08:00

7.4 KiB
Raw Blame History

客户欠款统计口径

本文档定义客户欠款的计算方式,已通过 4 个客户的 ERP 导出数据交叉验证,全部精确对上。


1. 核心公式

欠款 = AR - 收款 - 现场收款 - 折扣
     = (sales.JinE + sale_returns.JinE)
       - receipt.FkJinE
       - sales.SfJinE按 BianHaoID 去重)
       - (sales.ZkJinE + sale_returns.ZkJinE + receipt.ZkJinE)

各项说明:

项目 数据来源 筛选条件 取值字段 去重规则
销售额 I_Sale DanType='成品销售单' AND BianHaoID LIKE 'XS%' SUM(JinE) 不去重,每行明细独立
退货抵扣 I_Sale DanType='客户退货单' AND BianHaoID LIKE 'XT%' SUM(JinE) 不去重JinE 本身为负数
收款 F_Skd BianHaoID LIKE 'SK%' SUM(FkJinE) 不去重,每行独立
现场收款 I_Sale DanType='成品销售单' AND SfJinE != 0 SUM(SfJinE) 必须按 BianHaoID 去重
折扣 I_Sale + F_Skd 所有相关行 SUM(ZkJinE) 不去重

2. SfJinE 去重规则(关键)

SfJinE单据头级别字段,被冗余写到同一 BianHaoID 的每一行明细上。

示例(张晓鹏 XS202057885 行明细):

SubID=1  JinE=380      SfJinE=22,287  ← 同一个值
SubID=2  JinE=11,668   SfJinE=22,287  ← 重复
SubID=3  JinE=3,864    SfJinE=22,287  ← 重复
SubID=4  JinE=3,206    SfJinE=22,287  ← 重复
SubID=5  JinE=3,169    SfJinE=22,287  ← 重复

正确做法:每个 BianHaoID 只取一次 SfJinE = 22,287。 错误做法:直接 SUM 所有行 = 22,287 × 5 = 111,435多算 4 倍)。


3. 不参与计算的字段

字段 所在表 原因
I_Sale.YfJinE I_Sale 全部为 0
I_Sale.DingJin I_Sale 订金标记,实际入账已通过 SK/SfJinE 体现
I_Sale.JinET I_Sale 信息字段,不参与欠款
I_Sale.LjQK I_Sale 滚动累欠快照,不是增量
I_Sale.DanJiaCB / JinECB / JinE_SL I_Sale 全部为 0
F_Skd.YfJinE F_Skd XS 行与 I_Sale.JinE 重复XT 行与退货重复
F_Skd.LjJinE F_Skd 累计金额,不参与求和
F_Skd.DjJinE / JyJinE F_Skd 订金/结余,不参与
refund 桶 F_Skd 与 sale_returns.JinE 等值,不重复计入

4. 对应 API 接口

接口 参数
销售单 GET /api/v1/i-sale/by-customer customer_id=KHxxxxx&category=sale
销售退货 GET /api/v1/i-sale/by-customer customer_id=KHxxxxx&category=sale_return
收款单 GET /api/v1/finance/by-customer customer_name_b64=xxx&record_types=receipt

所有接口无分页限制,一次返回全集。

Base URL: http://43.139.183.222:18080 Auth: Authorization: your-fixed-authorization-secret


5. ERP 对账单滚动累计逻辑

ERP 对账单按时间排序,每行的"结欠金额"是滚动累计:

结欠[n] = 结欠[n-1] + 本行应收金额 - 本行已收金额
  • XS 销售单:应收 = JinE已收 = SfJinE现场收款多数为 0
  • SK 收款单:应收 = 0或负数折扣已收 = FkJinE
  • XT 退货单:应收 = JinE负数已收 = 0

6. 折扣与 ERP "应收金额"的关系

ERP 展示的"应收金额"是净应收JinE - ZkJinE我们从 I_Sale.JinE 拿到的是毛额。

ERP 视角:  欠款 = Σ(应收_净) - Σ(已收)
API 视角:  欠款 = Σ(JinE) - Σ(FkJinE) - Σ(SfJinE去重) - Σ(ZkJinE)

两者数学等价。


7. 验证结果(全部精确对上)

客户 KhID API 欠款 ERP 欠款 差额
木棉 KH01078 10,718 10,718 0
腾飞纺织 KH00311 53,521 53,521 0
金庸 KH00290 82,670 82,670 0
张晓鹏 KH00308 753,897 753,897 0

各客户 SfJinE 情况:

客户 SfJinE去重后 笔数 说明
木棉 0 0 无现场收款
腾飞纺织 0 0 无现场收款
金庸 23,394 1 笔 XS20214770 微信收款
张晓鹏 77,132 7 笔 2020-09 期间支付宝/微信收款

8. 伪代码

def calculate_customer_debt(customer_id: str, customer_name_b64: str) -> float:
    # 1. 拉销售单
    sales = api.get("/api/v1/i-sale/by-customer",
                    customer_id=customer_id, category="sale")

    # 2. 拉销售退货
    sale_returns = api.get("/api/v1/i-sale/by-customer",
                           customer_id=customer_id, category="sale_return")

    # 3. 拉收款单
    receipts = api.get("/api/v1/finance/by-customer",
                       customer_name_b64=customer_name_b64, record_types="receipt")

    # 4. 计算 AR每行明细的 JinE 独立求和)
    ar = sum(r.JinE for r in sales.records) + sum(r.JinE for r in sale_returns.records)

    # 5. 收款
    received_sk = sum(r.FkJinE for r in receipts.receipts)

    # 6. 现场收款(按 BianHaoID 去重,每单只取一次)
    seen = set()
    received_inline = 0.0
    for r in sales.records:
        if r.BianHaoID not in seen:
            seen.add(r.BianHaoID)
            received_inline += r.SfJinE

    # 7. 折扣
    discount = (sum(r.ZkJinE for r in sales.records)
                + sum(r.ZkJinE for r in sale_returns.records)
                + sum(r.ZkJinE for r in receipts.receipts))

    # 8. 欠款
    return ar - received_sk - received_inline - discount

9. 按时间窗口计算

def calculate_debt_as_of(customer_id, customer_name_b64, cutoff_date):
    # 拉取同上,然后按 RiQi < cutoff_date 过滤
    sales_f = [r for r in sales if r.RiQi < cutoff_date]
    sr_f = [r for r in sale_returns if r.RiQi < cutoff_date]
    rcp_f = [r for r in receipts if r.RiQi < cutoff_date]

    ar = sum(r.JinE for r in sales_f) + sum(r.JinE for r in sr_f)
    received_sk = sum(r.FkJinE for r in rcp_f)

    seen = set()
    received_inline = 0.0
    for r in sales_f:
        if r.BianHaoID not in seen:
            seen.add(r.BianHaoID)
            received_inline += r.SfJinE

    discount = (sum(r.ZkJinE for r in sales_f)
                + sum(r.ZkJinE for r in sr_f)
                + sum(r.ZkJinE for r in rcp_f))

    return ar - received_sk - received_inline - discount

10. antd-demo 后端待修复的 Bug

Bug 1SfJinE 未去重

当前同步代码对 I_Sale 明细行直接求和 SfJinE导致多行明细的订单被重复计入。

修复:按 BianHaoID 分组,每组只取一次 SfJinE。

Bug 2退货单金额放错列

退货金额被放到"已收金额"negative_amount应该放到"本单应收"positive_amount=0, negative_amount=|JinE|,使得应收为负数)。

Bug 3退货单被冗余写入收款桶

9 条 XT 退货单同时出现在"外部销售退货单"和"收款单"两种类型里,导致收款行数多了 9 条。

修复:同步时 XT 退货只写入退货桶,不写入收款桶。


变更记录

日期 内容
2026-05-18 初版,木棉/腾飞纺织验证通过
2026-05-18 金庸验证发现 SfJinE 必须参与扣减
2026-05-19 最终版:张晓鹏验证发现 SfJinE 必须按 BianHaoID 去重。四个客户全部精确对上 ERP