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
+6 -1
View File
@@ -9,6 +9,7 @@ from app.auth_middleware import require_auth, require_role
from app.deps import get_entity_id
from app.models import KPIDefinition, KPIValue, KPIAlert, User, VoucherDetail
from app.utils.cache import get as cache_get, set as cache_set
from app.utils.voucher_guard import apply_voucher_guard
import json
import logging
@@ -38,7 +39,10 @@ BEP_REBATE_POLICY_PCT = 84.61
def _bep_sum_field(db, entity_id: int, period_from: str, period_to: str,
field: str, subject_filter=None) -> float:
"""聚合 voucher_details 金额(元),subject_filter: ('eq', code) / ('like', prefix)"""
"""聚合 voucher_details 金额(元),subject_filter: ('eq', code) / ('like', prefix)
作废/撤单标记过滤(2026-09-14 收口): 已作废凭证不参与 BEP 均值。
"""
q = db.query(func.sum(getattr(VoucherDetail, field))).filter(
VoucherDetail.entity_id == entity_id,
VoucherDetail.carry_forward == 0,
@@ -51,6 +55,7 @@ def _bep_sum_field(db, entity_id: int, period_from: str, period_to: str,
q = q.filter(VoucherDetail.subject_code == code)
elif kind == "like":
q = q.filter(VoucherDetail.subject_code.like(code + "%"))
q = apply_voucher_guard(db, q, VoucherDetail)
val = q.scalar()
return float(val or 0.0)