fix(cma): entity isolation batch4 (cash plan writes, budget ownership, read leaks, okr template)
P0-A cash.py: scope api_update_plan / api_delete_plan / api_complete_plan /
api_register_payment to session entity (cross-entity id -> 404, aligning with
the file's existing get_entity_id endpoints).
P0-B budget.py: write entity_id on BudgetPlan creation + require the KPI to
belong to the session entity; scope /comparison, /comparison/kpi/{id} and
/deviation-report reads to the session entity.
P1 notifications.py: explicit field whitelist (name/config/enabled) on
update_channel to close mass-assignment.
P1 read leaks scoped to session entity: bot_kpis list, data_quality check/stats
(KPI calibre only; quality-log table has no entity_id column), kpis causality
chain + associate-map (KPI and map ownership), dashboard my-kpis,
customer_dashboard list, kpi_governance audit, budget_generate candidates,
driver_budget history (+ entity_id on its write path), expenses stats
(+ entity_id on its write path).
P2 okr_templates.py: write entity_id on the StrategicMap created by apply.
tests: tests/test_cross_entity_isolation_batch4_20260921.py (30 cases;
RED before fix = 22 failed, GREEN after = 30 passed; full suite 1302 passed /
1 xfailed / 0 failed).
This commit is contained in:
@@ -343,6 +343,7 @@ def submit_reimbursement(
|
||||
data: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(require_auth),
|
||||
entity_id: int = Depends(get_entity_id),
|
||||
):
|
||||
"""提交报销单 → 自动校验规则(超限自动打回)"""
|
||||
applicant = data.get("applicant") or (current_user.name if hasattr(current_user, "name") else current_user.username)
|
||||
@@ -366,6 +367,7 @@ def submit_reimbursement(
|
||||
expense_date = None
|
||||
|
||||
reimb = ExpenseReimbursement(
|
||||
entity_id=entity_id, # 账套隔离 2026-09-21:写侧先写归属(否则读侧过滤误杀本账套行)
|
||||
reimb_no="BX" + datetime.now().strftime("%Y%m%d%H%M%S") + f"{random.randint(100, 999)}",
|
||||
applicant=applicant,
|
||||
department=data.get("department", ""),
|
||||
@@ -537,6 +539,7 @@ def resubmit_reimbursement(
|
||||
def expense_stats(
|
||||
period: str = Query(None, description="期间 YYYY-MM,默认当前月"),
|
||||
db: Session = Depends(get_db),
|
||||
entity_id: int = Depends(get_entity_id),
|
||||
_=Depends(require_auth),
|
||||
):
|
||||
"""费用审核看板统计: 状态计数 / 费用类型统计 / 超限预警 / 本月总额"""
|
||||
@@ -544,7 +547,9 @@ def expense_stats(
|
||||
period = period or now.strftime("%Y-%m")
|
||||
|
||||
counts = {"pending": 0, "approved": 0, "rejected": 0, "returned": 0, "total": 0}
|
||||
for st, cnt in db.query(ExpenseReimbursement.status, func.count(ExpenseReimbursement.id)).group_by(
|
||||
for st, cnt in db.query(ExpenseReimbursement.status, func.count(ExpenseReimbursement.id)).filter(
|
||||
ExpenseReimbursement.entity_id == entity_id, # 账套隔离 2026-09-21
|
||||
).group_by(
|
||||
ExpenseReimbursement.status
|
||||
).all():
|
||||
if st in counts:
|
||||
@@ -560,6 +565,7 @@ def expense_stats(
|
||||
)
|
||||
.filter(
|
||||
ExpenseReimbursement.status.in_(["pending", "approved"]),
|
||||
ExpenseReimbursement.entity_id == entity_id, # 账套隔离 2026-09-21
|
||||
func.date_format(ExpenseReimbursement.expense_date, "%Y-%m") == period,
|
||||
)
|
||||
.group_by(ExpenseReimbursement.expense_type)
|
||||
@@ -583,6 +589,7 @@ def expense_stats(
|
||||
.filter(
|
||||
ExpenseReimbursement.status == "returned",
|
||||
ExpenseReimbursement.check_result == "fail",
|
||||
ExpenseReimbursement.entity_id == entity_id, # 账套隔离 2026-09-21
|
||||
)
|
||||
.order_by(ExpenseReimbursement.id.desc())
|
||||
.limit(50)
|
||||
@@ -604,7 +611,9 @@ def expense_stats(
|
||||
for r in over_limit_rows
|
||||
]
|
||||
|
||||
recent_rows = db.query(ExpenseReimbursement).order_by(ExpenseReimbursement.id.desc()).limit(10).all()
|
||||
recent_rows = db.query(ExpenseReimbursement).filter(
|
||||
ExpenseReimbursement.entity_id == entity_id, # 账套隔离 2026-09-21
|
||||
).order_by(ExpenseReimbursement.id.desc()).limit(10).all()
|
||||
recent = [_reimb_to_dict(r) for r in recent_rows]
|
||||
|
||||
# 本月预算使用率(招待费15万/月 管理费91万/月)
|
||||
|
||||
Reference in New Issue
Block a user