feat(receivables): 催收页只认客户级行 — 月度汇总行不计入卡片/账龄/明细/owners
- 口径实现逐字节取自既有已批准件 475f78b(ASK-20261001-33, wt/hanke-ar-customer-level): _is_customer_level_row(plan) = counterparty 非空且非空白(.strip() 覆盖制表符/全角空格) 用在两处且仅两处:明细循环 + owners 查询 ⇒ 四条消费面同一判据 - 数据零变更:cash_plans 一行不删不改(source='zxt-sync' 的 67 行月度汇总行=正确通道数据,保留) - 不新写第二份实现;不改 frontend/src/api/index.ts 全局拦截器;无 DDL、无数据迁移
This commit is contained in:
+31
-7
@@ -31,6 +31,20 @@ router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
def _is_customer_level_row(plan) -> bool:
|
||||
"""行形态判据(催收页唯一口径,ASK-20261001-33):**客户级行** = 客户列非空且非空白。
|
||||
|
||||
月度汇总行(`counterparty` 为 NULL / 空白,如 `source='zxt-sync'` 的 67 行实收历史)
|
||||
**不是客户级应收**,不得计入催收页的卡片 / 账龄 / 明细 / `owners`——否则求和会得到
|
||||
¥1.86 亿的假「应收总额」(67 行汇总 18,596.71 万 与 18 行客户级 71.68 万混加)。
|
||||
|
||||
判据取**行形态**而非 `source` 白名单:`source` 是数据来源标记(`zxt-sync` = 智享通通道
|
||||
注入标记,来源正确、不得丢弃),不能用来区分行语义。
|
||||
`str.strip()` 覆盖 SQL `TRIM()` 不处理的制表符 / 全角空格等空白变体。
|
||||
"""
|
||||
return bool((plan.counterparty or "").strip())
|
||||
|
||||
|
||||
def _aging_bucket(days_late: int) -> str:
|
||||
"""账龄区间(按逾期天数):未到期 / 0-30天 / 30-60天 / 60-90天 / 90天以上"""
|
||||
if days_late <= 0:
|
||||
@@ -565,7 +579,11 @@ def api_receivables(
|
||||
keyword: str = Query(None, description="客户/说明关键字"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""应收催收视图 — 应收余额 + 账龄(30/60/90) + 逾期状态 + 负责人筛选"""
|
||||
"""应收催收视图 — 应收余额 + 账龄(30/60/90) + 逾期状态 + 负责人筛选
|
||||
|
||||
口径(ASK-20261001-33):**只认客户级行**——月度汇总行(`counterparty` 空/空白)
|
||||
不计入卡片 / 账龄 / 明细 / `owners`。
|
||||
"""
|
||||
query = db.query(CashPlan).filter(
|
||||
CashPlan.entity_id == entity_id,
|
||||
CashPlan.plan_type == "receive",
|
||||
@@ -586,6 +604,8 @@ def api_receivables(
|
||||
completed_amount = 0.0
|
||||
completed_count = 0
|
||||
for p in plans:
|
||||
if not _is_customer_level_row(p):
|
||||
continue # 月度汇总行(客户列为空/空白)不计入催收页
|
||||
d = _plan_dict(p)
|
||||
if status:
|
||||
if status == "overdue":
|
||||
@@ -609,12 +629,16 @@ def api_receivables(
|
||||
completed_amount += d["amount"]
|
||||
completed_count += 1
|
||||
|
||||
owners = [r[0] for r in db.query(CashPlan.owner).filter(
|
||||
CashPlan.entity_id == entity_id,
|
||||
CashPlan.plan_type == "receive",
|
||||
CashPlan.owner.isnot(None),
|
||||
CashPlan.owner != "",
|
||||
).distinct().order_by(CashPlan.owner.asc()).all()]
|
||||
# owners 与明细同一判据:月度汇总行(客户列为空/空白)不得贡献筛选项
|
||||
owners = sorted({
|
||||
row.owner for row in db.query(CashPlan.owner, CashPlan.counterparty).filter(
|
||||
CashPlan.entity_id == entity_id,
|
||||
CashPlan.plan_type == "receive",
|
||||
CashPlan.owner.isnot(None),
|
||||
CashPlan.owner != "",
|
||||
).distinct().all()
|
||||
if _is_customer_level_row(row)
|
||||
})
|
||||
|
||||
return {
|
||||
"entity_id": entity_id,
|
||||
|
||||
Reference in New Issue
Block a user