Files
cma-management/backend/app/main.py
T
wecom-fullstack 0a6525edab
ci/woodpecker/push/main Pipeline was successful
feat(strategy): P0 战略基座 — strategy_charter + GET/PUT /api/cma/charter + P组入口
- 表 strategy_charter(一账套一行,entity_id 唯一):mission/vision/vision_year/vision_measurable/values_text/value_items(JSON) 全 NOT NULL
- 迁移脚本 backend/scripts/migrate_strategy_charter_20261021.py:--check/--apply/--down(回滚先 mysqldump,有数据需 --force),零 INSERT
- API:GET 无行 → filled=false 空态(禁示例数据);PUT 字段白名单 + 账套隔离三道 + vision_year 4位年份 + value_items [{name,meaning}] 结构校验
- 前端:P 组「战略章程」入口 + MyDialog 编辑(ceo/finance 可写,其余只读);战略地图页顶部只读章程条
- 用例 43 条 + auth-surface 58 条 = 101 passed;vite build rc=0
2026-10-21 18:54:09 +08:00

205 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""管理会计OS — 主入口"""
import logging
from typing import Optional
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from dotenv import load_dotenv
from app.database import init_db, get_db
from app.api import auth, kpis, kpi_governance, templates, maps, dashboard, data, alerts, ai_analysis, ai_suggestions, alert_rules, users, thresholds, notifications, permissions, action_plans, alignment, org, objectives, versions, budget, cost, predict, reports, security, knowledge, bot_bridge, bot_bridge_v2, bot_suggestions, lead, tenant, customer_dashboard, deviation_push, budget_generate, knowledge_articles, kpi_causality, data_quality, bi_reports, entities, bsc_layers, okr, okr_templates, subjects, driver_budget, bot_kpis, ontology, bot_iron_law, analysis_results, expenses, cash, tax_compliance, verify, growth_quality, products, data_classification, value_sources, zero_based, derivation_rules, cash_classify, cashflow_structure, cash_vouchers_json, ext_input_ledgers, treasury, doc_center, map_completeness, map_summary, map_dimensions, ar_aging, charter
from app.utils.cache import clear_all as clear_cache, delete as delete_cache
from scripts.erp_sync import run_sync as run_erp_sync
from app.auth_middleware import require_auth, require_auth_401
from app.api.audit_log import bot_audit_middleware
load_dotenv()
# 日志配置
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("cma")
app = FastAPI(title="管理会计OS API", version="1.0.0", docs_url="/docs")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Bot API 操作审计(L1-L4分级标注 + JSON行审计日志,不阻塞业务)
app.middleware("http")(bot_audit_middleware)
app.include_router(auth.router)
app.include_router(kpis.router)
app.include_router(kpi_governance.router)
app.include_router(templates.router)
# ⚠️ 顺序要求:完备度/摘要条路由必须先于 maps/objectives 等「/{map_id}/...」通配路由注册,
# 否则 /maps/completeness/objectives 会被 /{map_id}/objectives 抢先匹配(map_id="completeness" → 422)。
app.include_router(map_completeness.router) # 战略地图数据完备度(口径 CMA-CALIBER-MAP-COMPLETENESS-20261018 v1.0)
app.include_router(map_summary.router) # 战略地图摘要条(收入分线/新式毛利率/管理指标 · 口径 CMA-A1 终稿)
app.include_router(maps.router)
app.include_router(dashboard.router)
app.include_router(data.router)
app.include_router(alerts.router)
app.include_router(ai_analysis.router)
app.include_router(ai_suggestions.router)
app.include_router(bot_suggestions.router)
app.include_router(alert_rules.router)
app.include_router(users.router)
app.include_router(thresholds.router)
app.include_router(notifications.router)
app.include_router(permissions.router)
app.include_router(action_plans.router)
app.include_router(alignment.router)
app.include_router(org.router)
app.include_router(objectives.router)
app.include_router(versions.router)
app.include_router(budget.router)
app.include_router(value_sources.router)
app.include_router(zero_based.router)
app.include_router(derivation_rules.router)
app.include_router(cash_classify.router)
app.include_router(cashflow_structure.router)
app.include_router(cashflow_structure.compat_router)
app.include_router(cost.router)
app.include_router(predict.router)
app.include_router(growth_quality.router)
app.include_router(map_dimensions.router)
app.include_router(products.router)
app.include_router(reports.router)
app.include_router(security.router)
app.include_router(knowledge.router)
app.include_router(bot_bridge.router)
app.include_router(bot_bridge_v2.router)
app.include_router(lead.router)
app.include_router(tenant.router)
app.include_router(customer_dashboard.router)
app.include_router(deviation_push.router)
app.include_router(budget_generate.router)
app.include_router(knowledge_articles.router)
app.include_router(kpi_causality.router)
app.include_router(data_quality.router)
app.include_router(bi_reports.router)
app.include_router(entities.router)
app.include_router(bsc_layers.router)
app.include_router(okr.router)
app.include_router(okr_templates.router)
app.include_router(subjects.router)
app.include_router(driver_budget.router)
app.include_router(bot_kpis.router)
app.include_router(ontology.router)
app.include_router(bot_iron_law.router)
app.include_router(analysis_results.router)
app.include_router(expenses.router)
app.include_router(cash.router)
app.include_router(cash_vouchers_json.router)
app.include_router(data_classification.router)
app.include_router(tax_compliance.router)
app.include_router(verify.router)
app.include_router(ext_input_ledgers.router) # 外部输入台账(M2 准入3 派生,2026-09-15)
app.include_router(treasury.router) # 司库(资金)阶段一:账户池/头寸/内控闸门(2026-09-15)
app.include_router(ar_aging.router) # 应收账龄看板(T2 2026-10-20:逐户·六档·三口径禁合计)
app.include_router(doc_center.router) # 文档中心:md 单一来源(运行期读盘,2026-09-24)
app.include_router(charter.router) # 战略基座:战略章程(使命/愿景/价值观,P0 2026-10-21)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.error(f"未捕获异常: {exc}", exc_info=True)
return JSONResponse(status_code=500, content={"detail": "服务器内部错误"})
@app.on_event("startup")
def startup():
init_db()
logger.info("管理会计OS后端启动完成")
# ERP 直连同步通道停用(2026-08-31 起 erp-gateway DB 凭据删除;真实财务数据走 zxt 智享通注入)
_ERP_CHANNEL_DISABLED_REASON = ("ERP直连同步通道已于2026-08-31停用(erp-gateway DB 凭据已删除,zxbtest 账号停用)。"
"财务原始数据真实通道为 zxt 智享通注入(source_type='zxt-sync'),本端点不再可用。")
# ── admin 端点鉴权(2026-09-13)──────────────────────────────
# 未带 token → 显式 401(缺少认证),校验逻辑仍完全委托 require_auth(无效 token → 401)。
# 依赖实现已上移到 app/auth_middleware.py 的 require_auth_401(用户端点统一入口,
# cma-api-auth-surface-audit-20260914):原局部定义等价迁移,其他端点行为不变。
@app.post("/api/cma/admin/erp-sync")
def admin_erp_sync(user=Depends(require_auth), kpi_codes: str = None):
"""[已停用] 手动触发ERP数据同步 — ERP 直连通道 2026-08-31 停用,返回 410"""
return JSONResponse(status_code=410, content={"detail": _ERP_CHANNEL_DISABLED_REASON})
@app.get("/api/cma/admin/erp-sync/dry-run")
def admin_erp_sync_dry_run(user=Depends(require_auth), kpi_codes: str = None):
"""[已停用] 试运行 — ERP 直连通道 2026-08-31 停用,返回 410"""
return JSONResponse(status_code=410, content={"detail": _ERP_CHANNEL_DISABLED_REASON})
@app.post("/api/cma/admin/alerts/check")
def admin_check_alerts(entity_id: Optional[int] = None, user=Depends(require_auth_401)):
"""手动触发预警检查(含资金管理预警:缺口前3天 + 到期未收款)
- 2026-09-13 安全修复:补鉴权(与同文件 /admin/erp-sync 等 admin 端点一致);
未带 token 显式返回 401(require_auth_401,语义修正:401=未认证 / 403=无权)
- 新增可选 entity_id:只跑指定实体(不传=全部 active 实体,兼容原行为)
"""
from app.database import get_session_local
from scripts.alert_generator import generate_and_push
from app.utils.cash_forecast_engine import check_cash_alerts
from app.models import Entity
db = get_session_local()()
try:
result = generate_and_push(db)
# 资金管理预警 — 指定实体或全部激活企业
cash_result = None
try:
if entity_id is not None:
target = db.query(Entity).filter(
Entity.id == entity_id, Entity.status == "active").first()
if not target:
return JSONResponse(status_code=404,
content={"detail": f"企业不存在或未激活: entity_id={entity_id}"})
entities = [target]
else:
entities = db.query(Entity).filter(Entity.status == "active").all()
cash_list = []
for e in entities:
cash_list.append(check_cash_alerts(db, entity_id=e.id))
cash_result = {"entities": len(entities), "details": cash_list}
except Exception as e:
logger.warning(f"资金预警检查失败: {e}")
return {"message": "预警检查完成", "result": result, "cash": cash_result}
except Exception as e:
return JSONResponse(status_code=500, content={"detail": f"检查失败: {str(e)}"})
finally:
db.close()
@app.post("/api/cma/admin/cache/clear")
def admin_clear_cache(module: str = None, user=Depends(require_auth_401)):
"""清空缓存,指定module则只清该模块
2026-09-14 安全修复(cma-api-auth-surface-audit-20260914):本端点原为无鉴权公开
可写端点(无 token 亦可清空全站缓存 → 可被用于性能打击);补 require_auth_401,
未带 token → 401。
"""
if module:
delete_cache(module)
return {"message": f"缓存已清空: {module}"}
else:
clear_cache()
return {"message": "全部缓存已清空"}
@app.get("/health")
def health():
return {"status": "ok", "version": "1.0.0"}