diff --git a/backend/app/api/bsc_layers.py b/backend/app/api/bsc_layers.py index 929b27f7..2468eb9e 100644 --- a/backend/app/api/bsc_layers.py +++ b/backend/app/api/bsc_layers.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from typing import List from app.database import get_db +from app.deps import get_entity_id from app.auth_middleware import require_auth from app.models import BscLayerConfig, Entity @@ -13,7 +14,7 @@ router = APIRouter(prefix="/api/cma/bsc-layers", tags=["BSC层配置"], @router.get("") -def list_bsc_layers(entity_id: int = Query(1, description="企业ID"), db: Session = Depends(get_db)): +def list_bsc_layers(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)): """获取某企业的BSC四层权重配置""" # 验证企业存在 entity = db.query(Entity).filter(Entity.id == entity_id).first() diff --git a/backend/app/api/cash.py b/backend/app/api/cash.py index 6dab3ef4..18c7ad7e 100644 --- a/backend/app/api/cash.py +++ b/backend/app/api/cash.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from fastapi import APIRouter, HTTPException, Depends, Query from sqlalchemy.orm import Session from app.database import get_db +from app.deps import get_entity_id from app.auth_middleware import require_role from app.models import CashPlan from app.utils.cash_forecast_engine import ( @@ -45,7 +46,7 @@ def _plan_dict(p: CashPlan) -> dict: @router.get("/gap-forecast") def api_gap_forecast( - entity_id: int = Query(1, description="企业ID"), + entity_id: int = Depends(get_entity_id), days: int = Query(30, ge=1, le=90, description="预测天数"), current_cash: float = Query(None, description="当前现金余额(万元),不传则自动获取"), db: Session = Depends(get_db), @@ -60,7 +61,7 @@ def api_gap_forecast( @router.get("/balance") def api_get_balance( - entity_id: int = Query(1), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), ): """获取当前现金余额(预测基线)""" @@ -89,7 +90,7 @@ def api_set_balance( @router.get("/plans") def api_list_plans( - entity_id: int = Query(1), + entity_id: int = Depends(get_entity_id), plan_type: str = Query(None, description="receive/pay"), status: str = Query(None, description="pending/completed/cancelled"), month: str = Query(None, description="YYYY-MM 按计划月份过滤"), @@ -215,7 +216,7 @@ def api_complete_plan(plan_id: int, db: Session = Depends(get_db)): @router.get("/upcoming") def api_upcoming( - entity_id: int = Query(1), + entity_id: int = Depends(get_entity_id), days: int = Query(7, ge=1, le=30), db: Session = Depends(get_db), ): @@ -243,7 +244,7 @@ def api_upcoming( @router.get("/dashboard") def api_cash_dashboard( - entity_id: int = Query(1), + entity_id: int = Depends(get_entity_id), month: str = Query(None, description="YYYY-MM 默认本月"), days: int = Query(30), current_cash: float = Query(None, description="当前现金余额(万元)"), @@ -303,7 +304,7 @@ def api_cash_dashboard( # ══════════════════════════════════════════════════════════ @router.post("/check-alerts") -def api_check_cash_alerts(entity_id: int = 1, db: Session = Depends(get_db)): +def api_check_cash_alerts(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)): """手动触发资金预警检查(写入预警中心kpi_alerts)""" try: return check_cash_alerts(db, entity_id=entity_id) @@ -313,7 +314,7 @@ def api_check_cash_alerts(entity_id: int = 1, db: Session = Depends(get_db)): @router.get("/alerts/status") -def api_cash_alert_status(entity_id: int = Query(1), db: Session = Depends(get_db)): +def api_cash_alert_status(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)): """资金预警状态概览 — 当前缺口/逾期情况(不写库,只读)""" result = forecast_cash_flow_with_plans(entity_id, db, days=30) return { diff --git a/backend/app/api/data.py b/backend/app/api/data.py index 7c589d6b..e8efdfd7 100644 --- a/backend/app/api/data.py +++ b/backend/app/api/data.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Session from sqlalchemy import func from typing import Optional 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 KPIValue, DataSourceConfig, OperationLog, KPIDefinition @@ -165,7 +166,7 @@ def _smart_detect_statement_type(filename: str) -> str | None: @router.post("/import-excel-smart") async def import_excel_smart( file: UploadFile = File(...), - entity_id: int = Form(1), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), ): """智能导入 — BOT自动识别列名/期间/报表类型,无需手动映射""" diff --git a/backend/app/api/growth_quality.py b/backend/app/api/growth_quality.py index 5a27c684..5c94fdfa 100644 --- a/backend/app/api/growth_quality.py +++ b/backend/app/api/growth_quality.py @@ -11,6 +11,7 @@ from datetime import datetime import re 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, Entity @@ -453,7 +454,7 @@ def _diagnose(db: Session, entity_id: int, period: str, history: List[dict]): @router.get("/periods") -def list_periods(entity_id: int = Query(1), db: Session = Depends(get_db)): +def list_periods(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)): """列出某实体有KPI数据的期间(按月,含数据覆盖度,用于前端默认期间选择)""" rows = (db.query(KPIValue.period, KPIValue.kpi_id) .join(KPIDefinition, KPIDefinition.id == KPIValue.kpi_id) @@ -470,7 +471,7 @@ def list_periods(entity_id: int = Query(1), db: Session = Depends(get_db)): @router.get("/diagnosis") def growth_quality_diagnosis( - entity_id: int = Query(1, ge=1), + entity_id: int = Depends(get_entity_id), period: Optional[str] = Query(None, description="期间 YYYY-MM,默认最近有数据期间"), db: Session = Depends(get_db), ): diff --git a/backend/app/api/kpis.py b/backend/app/api/kpis.py index ace05c5d..0bb6b3ed 100644 --- a/backend/app/api/kpis.py +++ b/backend/app/api/kpis.py @@ -7,6 +7,7 @@ from datetime import datetime import json from app.database import get_db +from app.deps import get_entity_id from app.auth_middleware import require_auth, require_role, filter_kpis_by_role, kpi_visible_dims from app.models import StrategicMap, MapObjective, KPIDefinition, KPIValue, KPIAlert, OperationLog, Entity, KPICausality, KPIHierarchy from app.api.kpi_governance import validate_kpi_payload, kpi_issues_message @@ -27,7 +28,7 @@ def list_kpis( keyword: Optional[str] = None, epic: Optional[str] = None, category: Optional[str] = None, - entity_id: Optional[int] = None, + entity_id: int = Depends(get_entity_id), kpi_level: Optional[str] = None, db: Session = Depends(get_db), current_user = Depends(require_auth), @@ -141,7 +142,7 @@ def _calc_five_tier_score(current_value, target_value, is_reverse=False): @router.get("/score") def get_kpi_score( - entity_id: int = Query(1, ge=1), + entity_id: int = Depends(get_entity_id), period: Optional[str] = None, db: Session = Depends(get_db), current_user = Depends(require_auth), @@ -243,7 +244,7 @@ def get_kpi_score( @router.get("/glossary") def get_kpi_glossary( - entity_id: int = Query(1, ge=1), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), current_user = Depends(require_auth), ): @@ -314,7 +315,7 @@ def get_kpi_glossary( @router.get("/hierarchy") def get_kpi_hierarchy( - entity_id: int = Query(1, ge=1), + entity_id: int = Depends(get_entity_id), kpi_id: Optional[int] = None, db: Session = Depends(get_db), current_user = Depends(require_auth), diff --git a/backend/app/api/predict.py b/backend/app/api/predict.py index 3940b51e..7b130b22 100644 --- a/backend/app/api/predict.py +++ b/backend/app/api/predict.py @@ -10,6 +10,7 @@ from app.utils.cash_forecast_engine import ( calculate_accuracy, generate_scenario_suggestion, ) from app.database import get_db +from app.deps import get_entity_id from sqlalchemy.orm import Session logger = logging.getLogger("cma.predict") @@ -183,7 +184,7 @@ def api_cash_forecast(data: dict, db: Session = Depends(get_db)): @router.get("/cash-forecast/history") def api_cash_forecast_history( - entity_id: int = 1, + entity_id: int = Depends(get_entity_id), days: int = 30, db: Session = Depends(get_db), ): @@ -206,7 +207,7 @@ def api_cash_forecast_history( @router.get("/accuracy") def api_forecast_accuracy( - entity_id: int = 1, + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), ): """预测准确率报表 — 上期预测 vs 本期实际""" diff --git a/backend/app/api/tax_compliance.py b/backend/app/api/tax_compliance.py index e4d9d8a4..94af03cf 100644 --- a/backend/app/api/tax_compliance.py +++ b/backend/app/api/tax_compliance.py @@ -11,6 +11,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session 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 TaxRecord, InvoiceCheck, SocialSecurity, ExpenseReimbursement @@ -140,7 +141,7 @@ def _apply_burden_to_all(db: Session, entity_id: int): def list_tax_records( period: str = Query(None), tax_type: str = Query(None), - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_auth), ): @@ -236,7 +237,7 @@ def delete_tax_record( @router.get("/burden") def burden_analysis( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), tax_type: str = Query(None), db: Session = Depends(get_db), _=Depends(require_auth), @@ -274,7 +275,7 @@ def burden_analysis( @router.post("/check") def run_tax_check( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_role("ceo", "finance")), ): @@ -358,7 +359,7 @@ def _check_invoice(db: Session, inv: InvoiceCheck): def list_invoices( status: str = Query(None), keyword: str = Query(None), - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_auth), ): @@ -457,7 +458,7 @@ def delete_invoice( @router.post("/invoices/check") def batch_check_invoices( invoice_id: int = Query(None), - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_role("ceo", "finance")), ): @@ -481,7 +482,7 @@ def batch_check_invoices( @router.get("/invoices/abnormal") def list_abnormal_invoices( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), limit: int = Query(50), db: Session = Depends(get_db), _=Depends(require_auth), @@ -556,7 +557,7 @@ def list_ss_records( period: str = Query(None), employee: str = Query(None), status: str = Query(None), - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_auth), ): @@ -652,7 +653,7 @@ def delete_ss_record( @router.post("/ss/check") def batch_check_ss( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_role("ceo", "finance")), ): @@ -674,7 +675,7 @@ def batch_check_ss( @router.get("/ss/abnormal") def list_abnormal_ss( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), limit: int = Query(50), db: Session = Depends(get_db), _=Depends(require_auth), @@ -693,7 +694,7 @@ def list_abnormal_ss( @router.get("/dashboard") def tax_dashboard( - entity_id: int = Query(None), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_auth), ): @@ -771,7 +772,7 @@ def tax_dashboard( @router.post("/demo-data") def seed_demo_data( - entity_id: int = Query(1), + entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db), _=Depends(require_role("ceo", "finance")), ): diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index daad802f..bd9b7da4 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -8,12 +8,7 @@ const api = axios.create({ api.interceptors.request.use((config) => { const token = localStorage.getItem('cma_token') if (token) config.headers.Authorization = `Bearer ${token}` - // 多租户实时切换:统一附加当前企业ID(解决29个页面未传entity_id的问题) - const entityId = Number(localStorage.getItem('cma_entity_id') || 1) - config.headers['X-Entity-Id'] = String(entityId) - if (config.method === 'get' || config.method === 'GET') { - config.params = { ...(config.params || {}), entity_id: entityId } - } + // 账套模式:entity_id 由 token 绑定,前端不再自动附加 X-Entity-Id / entity_id return config }) @@ -32,6 +27,9 @@ api.interceptors.response.use( export const authApi = { login: (data: any) => api.post('/auth/login', data), register: (data: any) => api.post('/auth/register', data), + switchEntity: (data: any) => api.post('/auth/switch-entity', data), + myEntities: () => api.get('/auth/my-entities'), + loginEntities: (username: string) => api.get('/auth/login-entities', { params: { username } }), } export const kpiApi = { diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 13dd33ca..3328cec8 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -48,6 +48,7 @@