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
+12 -5
View File
@@ -19,6 +19,7 @@ from app.database import get_db
from app.auth_middleware import require_role, require_auth
from app.models import KPIDefinition, KPIValue, BudgetPlan, StrategicMap, KPIAlert, User, Subject, ActionPlan, OperationLog, ReportHistory
from app.utils.deviation_engine import calc_period_deviation, calc_period_diff, calc_deviation, get_budget_for_kpi
from app.utils.voucher_guard import apply_voucher_guard
from app.deps import get_entity_id, assert_entity_slug_scope, resolve_session_slug
import logging
@@ -565,12 +566,15 @@ def _get_subject_amount(db: Session, code: str, period: str, entity_id: int = 1)
# 2. 从 subjects + voucher_details 取(如果存在)
try:
from app.models import VoucherDetail
result = db.query(
q = db.query(
func.sum(VoucherDetail.debit_amount - VoucherDetail.credit_amount)
).filter(
VoucherDetail.subject_code == code,
VoucherDetail.period == period
).scalar()
VoucherDetail.period == period,
VoucherDetail.entity_id == entity_id, # 账套隔离(2026-09-14 补)
)
# 作废/撤单标记过滤(2026-09-14 收口)
result = apply_voucher_guard(db, q, VoucherDetail).scalar()
if result is not None:
return float(result)
except Exception:
@@ -1465,12 +1469,15 @@ def _get_bs_amount(db: Session, codes: list, period: str, entity_id: int = 1) ->
try:
from app.models import VoucherDetail
for code, sign in codes:
result = db.query(
q = db.query(
func.sum(VoucherDetail.debit_amount - VoucherDetail.credit_amount)
).filter(
VoucherDetail.subject_code == code,
VoucherDetail.period == period,
).scalar()
VoucherDetail.entity_id == entity_id, # 账套隔离(2026-09-14 补)
)
# 作废/撤单标记过滤(2026-09-14 收口)
result = apply_voucher_guard(db, q, VoucherDetail).scalar()
if result is not None:
total += float(result) * sign
has_data = True