feat(kpi): route all voucher_details consumers through single void-mark guard

Single source: app/utils/voucher_guard.py (NOT EXISTS on voucher_void_marks,

key = entity_id+voucher_no+voucher_date and revoked_at IS NULL; same caliber as

the DB view cma.v_voucher_details_active). No hardcoded voucher numbers.

Consumers now guarded: cash.py import linkage (2 fns), reports.py subject/BS

amount (2 sites, also entity_id isolation added), dashboard.py _bep_sum_field,

alert_attribution.py build_subject_attribution. kpi_value_collector.py now

delegates to the shared guard (removes the previous duplicate implementation).

Tests: new tests/test_void_marker_consumers_20260914.py (6 cases).
This commit is contained in:
Hermes CI Fix
2026-09-14 19:05:16 +08:00
parent a8a82ff837
commit 8693c8e342
8 changed files with 318 additions and 69 deletions
+8 -2
View File
@@ -775,7 +775,10 @@ def _sync_cash_balance_from_ledger(db: Session, entity_id: int, batch: str) -> f
"""货币资金类科目(1001/1002开头)期末余额 → set_current_cash_balance(万元)"""
from app.models import VoucherDetail
from app.utils.cash_forecast_engine import set_current_cash_balance
rows = db.query(VoucherDetail).filter(VoucherDetail.entity_id == entity_id).all()
from app.utils.voucher_guard import apply_voucher_guard
# 作废/撤单标记过滤(2026-09-14 收口): 已作废凭证不参与货币资金余额
q = db.query(VoucherDetail).filter(VoucherDetail.entity_id == entity_id)
rows = apply_voucher_guard(db, q, VoucherDetail).all()
balance_yuan = round(sum(
(r.debit_amount or 0) - (r.credit_amount or 0)
for r in rows
@@ -795,10 +798,13 @@ def _sync_cash_kpis(db: Session, entity_id: int, batch: str, periods: list) -> l
② F_CASH_SAFETY 现金安全垫(万元 = 货币资金余额 - 短期借款EXT_139,entity_id隔离,不存在则创建)
"""
from app.models import KPIDefinition, KPIValue, VoucherDetail
from app.utils.voucher_guard import apply_voucher_guard
updates = []
period = periods[-1] if periods else datetime.now().strftime("%Y-%m")
rows = db.query(VoucherDetail).filter(VoucherDetail.entity_id == entity_id).all()
# 作废/撤单标记过滤(2026-09-14 收口): 已作废凭证不参与联动取数
q_cash = db.query(VoucherDetail).filter(VoucherDetail.entity_id == entity_id)
rows = apply_voucher_guard(db, q_cash, VoucherDetail).all()
monetary = [
r for r in rows
if r.carry_forward == 0 and r.subject_code