forked from erp-dev/erp
222 lines
7.4 KiB
Markdown
222 lines
7.4 KiB
Markdown
# 客户欠款统计口径
|
||
|
||
本文档定义客户欠款的计算方式,已通过 4 个客户的 ERP 导出数据交叉验证,全部精确对上。
|
||
|
||
---
|
||
|
||
## 1. 核心公式
|
||
|
||
```text
|
||
欠款 = 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 的每一行明细上。
|
||
|
||
示例(张晓鹏 XS20205788,5 行明细):
|
||
|
||
```text
|
||
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 对账单按时间排序,每行的"结欠金额"是滚动累计:
|
||
|
||
```text
|
||
结欠[n] = 结欠[n-1] + 本行应收金额 - 本行已收金额
|
||
```
|
||
|
||
- XS 销售单:应收 = JinE,已收 = SfJinE(现场收款,多数为 0)
|
||
- SK 收款单:应收 = 0(或负数折扣),已收 = FkJinE
|
||
- XT 退货单:应收 = JinE(负数),已收 = 0
|
||
|
||
---
|
||
|
||
## 6. 折扣与 ERP "应收金额"的关系
|
||
|
||
ERP 展示的"应收金额"是净应收(JinE - ZkJinE),我们从 I_Sale.JinE 拿到的是毛额。
|
||
|
||
```text
|
||
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. 伪代码
|
||
|
||
```python
|
||
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. 按时间窗口计算
|
||
|
||
```python
|
||
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 1:SfJinE 未去重
|
||
|
||
当前同步代码对 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 |
|