fix(bep-caliber): expense and profit sums use net (debit minus credit) under carry_forward=0
ci/woodpecker/push/main Pipeline failed

ASK-20261002-06 fix04u: admin(5602%) and depreciation(560213) take net amount in both the dashboard card helper and the BEP deriver; sales (5001) keeps credit_amount. Verbatim per proposal section 2. No DB writes, no kpi_values backfill.
This commit is contained in:
Hermes CI Fix
2026-10-02 11:22:38 +08:00
parent 6c1ea85d71
commit 6be903fbc9
3 changed files with 121 additions and 12 deletions
@@ -0,0 +1,97 @@
"""口径4「费用取净额」单测 — ASK-20261002-06(2026-10-02)
口径依据: /srv/hermes/wisdom/_decisions/cma-expense-profit-net-caliber-20261002.md
「任何费用/利润取数一律净额(借−贷)」
覆盖两条实现面取数助手(改前均只认 debit/credit 单边字段):
· app/api/dashboard.py::_bep_sum_field(卡片现算面)
· scripts/kpi_bep_deriver.py::_sum_field(派生落库写入面)
断言要点(含负对照,退回 debit_amount 口径时本文件必红):
1. field="net" = 借−贷(行域上存在贷方冲销行 ⇒ 净额 ≠ 借方合计);
2. carry_forward==0 过滤仍在位(结转腿行不参与);
3. 收入侧 sales 仍取 credit_amount,未改为净额(改净额会变负)。
4. `5602%` LIKE 前缀天然包含 `560213`(账面口径含折旧),故 admin 净额 = 两科目之和。
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import datetime # noqa: E402
from app.models import VoucherDetail # noqa: E402
from app.api.dashboard import _bep_sum_field # noqa: E402
from scripts import kpi_bep_deriver as D # noqa: E402
D_DATE = datetime.datetime(2026, 8, 31)
PERIOD = "2026-08"
def _row(db, no, code, debit=0.0, credit=0.0, cf=0):
db.add(VoucherDetail(entity_id=1, voucher_no=str(no), voucher_date=D_DATE,
subject_code=code, subject_name="单测科目",
debit_amount=debit, credit_amount=credit,
summary="口径4 单测", carry_forward=cf, period=PERIOD))
db.flush()
def _fixture(db):
"""借贷双侧行域 + 一条结转腿(carry_forward=1)行。
560202 : 借 1000 / 贷 200 → 净额 800(借方合计 1000)
560213 : 借 500 / 贷 100 → 净额 400(借方合计 500)
5001 : 贷 8000 / 借 0 → 收入仍取贷方 8000(取净额 = -8000)
560202 cf=1 : 借 9999 → 结转腿,任何口径下都不得计入
LIKE '5602%' 命中 560202 + 560213(账面口径含折旧)⇒ admin 净额 = 1200。
"""
_row(db, 91001, "560202", debit=1000.0)
_row(db, 91002, "560202", credit=200.0)
_row(db, 91003, "560213", debit=500.0)
_row(db, 91004, "560213", credit=100.0)
_row(db, 91005, "5001", credit=8000.0)
_row(db, 91006, "560202", debit=9999.0, cf=1)
def test_dashboard_net_caliber_admin_and_dep(db):
"""改后:dashboard._bep_sum_field 支持 field='net'=借−贷;借方口径仍可用(负对照)。"""
_fixture(db)
net_admin = _bep_sum_field(db, 1, PERIOD, PERIOD, "net", ("like", "5602"))
debit_admin = _bep_sum_field(db, 1, PERIOD, PERIOD, "debit_amount", ("like", "5602"))
net_dep = _bep_sum_field(db, 1, PERIOD, PERIOD, "net", ("eq", "560213"))
debit_dep = _bep_sum_field(db, 1, PERIOD, PERIOD, "debit_amount", ("eq", "560213"))
assert abs(net_admin - 1200.0) < 1e-6, net_admin # (1000-200) + (500-100)
assert abs(net_dep - 400.0) < 1e-6, net_dep # 500 - 100
# 负对照:退回 debit_amount 口径时上面的 net 断言必红(1500/500 ≠ 1200/400)
assert abs(debit_admin - 1500.0) < 1e-6, debit_admin
assert abs(debit_dep - 500.0) < 1e-6, debit_dep
def test_dashboard_net_excludes_carry_forward_rows(db):
"""carry_forward==0 过滤在位:结转腿行(cf=1,借 9999)不参与。"""
_fixture(db)
net_admin = _bep_sum_field(db, 1, PERIOD, PERIOD, "net", ("like", "5602"))
assert abs(net_admin - 1200.0) < 1e-6, net_admin # 含 cf=1 行则会是 1200+9999
def test_dashboard_sales_still_credit_amount(db):
"""收入侧未改:5001 仍取 credit_amount(若改净额 → 负数)。"""
_fixture(db)
sales_credit = _bep_sum_field(db, 1, PERIOD, PERIOD, "credit_amount", ("eq", "5001"))
sales_net = _bep_sum_field(db, 1, PERIOD, PERIOD, "net", ("eq", "5001"))
assert abs(sales_credit - 8000.0) < 1e-6, sales_credit
assert sales_net < 0, sales_net # 这正是「收入不得取净额」的反证
def test_deriver_net_caliber_parity(db):
"""派生器 _sum_field 与卡片助手同口径(两文件同批改的目的:避免同页两口径)。"""
_fixture(db)
d_net_admin = D._sum_field(db, 1, PERIOD, PERIOD, "net", ("like", "5602"))
d_net_dep = D._sum_field(db, 1, PERIOD, PERIOD, "net", ("eq", "560213"))
d_net_rebate = D._sum_field(db, 1, PERIOD, PERIOD, "net", ("eq", "571103"))
assert abs(d_net_admin - 1200.0) < 1e-6, d_net_admin
assert abs(d_net_dep - 400.0) < 1e-6, d_net_dep
assert abs(d_net_rebate - 0.0) < 1e-6, d_net_rebate # 本夹具无 571103 行
assert abs(d_net_admin - _bep_sum_field(db, 1, PERIOD, PERIOD, "net", ("like", "5602"))) < 1e-6