fix: 执行分解400错误 — auto-decompose支持批量模式(不传kpi_id分解该年所有预算KPI)
- 原API只支持单KPI(kpi_id+annual_budget),前端UI按整体分解调用 → 400 - 新增批量模式:聚合该年所有KPI年度预算,逐KPI均分/按历史权重分解12个月 - 验证:批量分解4个KPI(收入536万/净利-48万等),v1.0月度48条落库
This commit is contained in:
@@ -10,7 +10,7 @@ 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
|
||||
from app.models import BudgetPlan, KPIDefinition, OperationLog, KPIValue
|
||||
|
||||
router = APIRouter(prefix="/api/cma/budget", tags=["预算管理"],
|
||||
dependencies=[Depends(require_role("ceo", "finance", "it"))],
|
||||
@@ -171,15 +171,106 @@ def auto_decompose_budget(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""自动分解年度预算到月度(均分或按历史权重)"""
|
||||
"""自动分解年度预算到月度(均分或按历史权重)
|
||||
支持两种模式:
|
||||
1. 单KPI:传 kpi_id + annual_budget
|
||||
2. 批量:不传 kpi_id,分解该年所有已有年度预算的KPI
|
||||
"""
|
||||
kpi_id = data.get("kpi_id")
|
||||
year = data.get("year", datetime.now().year)
|
||||
annual_budget = data.get("annual_budget")
|
||||
method = data.get("method", "equal") # equal / weighted
|
||||
version = data.get("version", "v1.0")
|
||||
|
||||
if not kpi_id or annual_budget is None:
|
||||
raise HTTPException(400, "缺少必要参数: kpi_id, annual_budget")
|
||||
# ── 批量模式:不传kpi_id → 分解该年所有有年度预算的KPI ──
|
||||
if not kpi_id:
|
||||
# 找该年已存在的年度预算(period=YYYY-00 或已按月填的KPI汇总)
|
||||
# 优先用 budget_plans 中该年的预算作为年度总额
|
||||
year_budget_rows = db.query(BudgetPlan).filter(
|
||||
BudgetPlan.entity_id == 1,
|
||||
BudgetPlan.budget_year == year,
|
||||
BudgetPlan.status == "active",
|
||||
).all()
|
||||
|
||||
# 按KPI聚合年度预算总额
|
||||
kpi_annual = {}
|
||||
for r in year_budget_rows:
|
||||
kpi_annual[r.kpi_id] = kpi_annual.get(r.kpi_id, 0) + (r.budget_value or 0)
|
||||
|
||||
if not kpi_annual:
|
||||
raise HTTPException(400, "该年度没有可分解的预算,请先在预算执行中录入年度预算")
|
||||
|
||||
results = []
|
||||
created_count = 0
|
||||
for kid, annual in kpi_annual.items():
|
||||
kpi = db.query(KPIDefinition).filter(KPIDefinition.id == kid).first()
|
||||
if not kpi:
|
||||
continue
|
||||
|
||||
# 计算各月权重
|
||||
if method == "weighted":
|
||||
last_year = year - 1
|
||||
values = db.query(KPIValue).filter(
|
||||
KPIValue.kpi_id == kid,
|
||||
KPIValue.period.like(f"{last_year}-%"),
|
||||
KPIValue.actual_value.isnot(None),
|
||||
).order_by(KPIValue.period.asc()).all()
|
||||
total = sum(v.actual_value for v in values)
|
||||
weights = {v.period: v.actual_value / total for v in values} if total > 0 else {}
|
||||
else:
|
||||
weights = {}
|
||||
|
||||
monthly = []
|
||||
for m in range(1, 13):
|
||||
period = f"{year}-{m:02d}"
|
||||
weight = weights.get(period, 1 / 12) if method == "weighted" and weights else 1 / 12
|
||||
monthly_value = round(annual * weight, 2)
|
||||
|
||||
existing = db.query(BudgetPlan).filter(
|
||||
BudgetPlan.kpi_id == kid,
|
||||
BudgetPlan.period == period,
|
||||
BudgetPlan.version == version,
|
||||
BudgetPlan.status == "active",
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.budget_value = monthly_value
|
||||
existing.updated_at = datetime.now()
|
||||
else:
|
||||
db.add(BudgetPlan(
|
||||
entity_id=1,
|
||||
kpi_id=kid,
|
||||
period=period,
|
||||
budget_value=monthly_value,
|
||||
budget_year=year,
|
||||
budget_month=m,
|
||||
version=version,
|
||||
status="active",
|
||||
))
|
||||
created_count += 1
|
||||
monthly.append(monthly_value)
|
||||
|
||||
results.append({
|
||||
"kpi_id": kid,
|
||||
"kpi_code": kpi.kpi_code,
|
||||
"kpi_name": kpi.kpi_name,
|
||||
"annual_budget": round(annual, 2),
|
||||
"method": "equal" if not weights else "weighted",
|
||||
"monthly": monthly,
|
||||
"monthly_count": 12,
|
||||
})
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"message": f"批量分解完成:{len(results)}个KPI",
|
||||
"count": len(results),
|
||||
"results": results,
|
||||
"created": created_count,
|
||||
}
|
||||
|
||||
# ── 单KPI模式(原有逻辑)──
|
||||
if annual_budget is None:
|
||||
raise HTTPException(400, "缺少必要参数: annual_budget")
|
||||
|
||||
kpi = db.query(KPIDefinition).filter(KPIDefinition.id == kpi_id).first()
|
||||
if not kpi:
|
||||
|
||||
Reference in New Issue
Block a user