feat: 多租户隔离P2批2 — 预算/成本/费用/预警规则/BI报表加entity_id

- 12表加entity_id列(预算/偏差/规则/成本4表/费用2表/BI2表/驱动预算)
- 模型: BudgetPlan/StandardCost/ActualCost/AbcActivity/AbcAllocation/DriverFactorBudget/BiReport/Template/BudgetDeviationAlert/ExpenseRule/Reimbursement/AlertRule
- API隔离: budget plans / cost standard+actual / expenses rules+reimb / bi_reports list / alert_rules list 按token企业过滤
- 回填: kpi_id关联按KPI归属, 无关联默认酣客(entity=1); 当前数据全归酣客
- 验证: import+全端点200+pytest 451 passed
This commit is contained in:
Hermes CI Fix
2026-08-23 18:20:29 +08:00
parent 27b5b0da47
commit fb9eba38a8
9 changed files with 33 additions and 10 deletions
+5 -2
View File
@@ -11,6 +11,7 @@ import json
import logging
from app.database import get_db, Base
from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import KPIDefinition, KPIValue, KPIAlert, OperationLog
@@ -23,6 +24,7 @@ class AlertRule(Base):
"""预警规则配置"""
__tablename__ = "alert_rules"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, default=1, comment="企业ID (P2多租户隔离 2026-08-23)")
kpi_id = Column(Integer, nullable=False, comment="关联KPI ID")
rule_type = Column(String(30), nullable=False, comment="static/dynamic/trend_up/trend_down")
trigger_on = Column(String(20), default="actual", comment="actual/forecast/both — 实际值/预测值/两者触发")
@@ -64,9 +66,10 @@ def list_alert_rules(
rule_type: Optional[str] = None,
enabled: Optional[int] = None,
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id),
):
"""列出所有预警规则"""
query = db.query(AlertRule)
"""列出所有预警规则(账套隔离: 按token企业, 2026-08-23 P2"""
query = db.query(AlertRule).filter(AlertRule.entity_id == entity_id)
if kpi_id:
query = query.filter(AlertRule.kpi_id == kpi_id)
if rule_type:
+4 -3
View File
@@ -10,6 +10,7 @@ import json
import logging
from app.database import get_db
from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import KPIDefinition, KPIValue, BiReportTemplate, BiReport, OperationLog, KPICausality
@@ -126,9 +127,9 @@ def delete_template(template_id: int, db: Session = Depends(get_db), user=WRITE_
# ============================================================
@router.get("")
def list_reports(db: Session = Depends(get_db)):
"""获取用户保存的报表"""
reports = db.query(BiReport).order_by(BiReport.updated_at.desc()).all()
def list_reports(db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
"""获取用户保存的报表(账套隔离: 按token企业, 2026-08-23 P2"""
reports = db.query(BiReport).filter(BiReport.entity_id == entity_id).order_by(BiReport.updated_at.desc()).all()
result = []
for r in reports:
d = {c.name: getattr(r, c.name) for c in BiReport.__table__.columns}
+3 -1
View File
@@ -8,6 +8,7 @@ from typing import Optional
from datetime import datetime
from app.database import get_db
from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import BudgetPlan, KPIDefinition, OperationLog
@@ -23,11 +24,12 @@ def list_budget_plans(
year: Optional[int] = Query(None),
version: Optional[str] = Query(None),
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id),
):
"""查询预算计划列表"""
query = db.query(BudgetPlan).join(
KPIDefinition, BudgetPlan.kpi_id == KPIDefinition.id
)
).filter(KPIDefinition.entity_id == entity_id)
if kpi_id:
query = query.filter(BudgetPlan.kpi_id == kpi_id)
+7 -4
View File
@@ -5,6 +5,7 @@ from typing import Optional
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.deps import get_entity_id
from app.models import StandardCost, ActualCost, AbcActivity, AbcAllocation
from app.utils.cost_engine import (
calc_product_variance, get_cost_overview, get_cost_breakdown,
@@ -22,9 +23,10 @@ router = APIRouter(prefix="/api/cma/cost", tags=["成本分析"])
@router.get("/standard-costs")
def list_standard_costs(product_code: Optional[str] = Query(None),
cost_type: Optional[str] = Query(None),
db: Session = Depends(get_db)):
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id)):
"""查询标准成本卡片"""
query = db.query(StandardCost).filter(StandardCost.status == "active")
query = db.query(StandardCost).filter(StandardCost.status == "active", StandardCost.entity_id == entity_id)
if product_code:
query = query.filter(StandardCost.product_code == product_code)
if cost_type:
@@ -86,9 +88,10 @@ def delete_standard_cost(cost_id: int, db: Session = Depends(get_db)):
@router.get("/actual-costs")
def list_actual_costs(period: Optional[str] = Query(None),
product_code: Optional[str] = Query(None),
db: Session = Depends(get_db)):
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id)):
"""查询实际成本"""
query = db.query(ActualCost)
query = db.query(ActualCost).filter(ActualCost.entity_id == entity_id)
if period:
query = query.filter(ActualCost.period == period)
if product_code:
+3
View File
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
from sqlalchemy import func
from app.database import get_db
from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import ExpenseRule, ExpenseReimbursement, OperationLog
@@ -120,6 +121,7 @@ def list_rules(
expense_type: str = Query(None),
status: str = Query(None),
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id),
_=Depends(require_auth),
):
"""查询费用规则列表"""
@@ -397,6 +399,7 @@ def list_reimbursements(
applicant: str = Query(None),
keyword: str = Query(None),
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id),
_=Depends(require_auth),
):
"""查询报销单列表(支持状态/类型/申请人/关键字筛选)"""