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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user