init: 管理会计OS初始代码

包含前后端完整代码:
- 前端:Vue3+Vite+ElementPlus
- 后端:FastAPI+SQLAlchemy
- 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动
- 当前版本:v1.0.0
This commit is contained in:
Hermes CI Fix
2026-05-28 17:32:22 +08:00
commit 3dddd36866
142 changed files with 18533 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
"""KPI计算引擎 v4 — 基于会计科目余额和销售报表"""
import httpx, asyncio
from datetime import datetime
from app.database import get_session_local
from app.models import KPIDefinition, KPIValue
ERP_API = "http://127.0.0.1:8300"
ERP_KEY = "erp-gateway-key-bhwl-2026"
async def _get(url: str, params: dict = None):
async with httpx.AsyncClient(timeout=20) as c:
r = await c.get(url, headers={"X-API-Key": ERP_KEY}, params=params)
return r.json()
async def calculate_all():
db = get_session_local()()
try:
now = datetime.now()
period = f"{now.year}-{now.month:02d}"
# 1. 从科目余额表取数据(BalanceInfo
balance_data = await _get(f"{ERP_API}/api/v1/query", {"table": "BalanceInfo", "limit": 200})
balances = balance_data.get("data", [])
# 按科目和期间汇总
revenue = 0 # 营业收入 (Act_ID=4)
cost = 0 # 营业成本 (Act_ID=5)
ar_balance = 0 # 应收账款 (Act_ID=3)
inv_balance = 0 # 库存商品 (Act_ID=2)
for b in balances:
aid = b.get("Act_ID")
tot = float(b.get("Act_Tot", 0) or 0)
hap = float(b.get("Act_Hap", 0) or 0)
if aid == 4: # 营业收入(本期发生额更准确)
revenue += hap if hap > 0 else tot
elif aid == 5: # 营业成本
cost += hap if hap > 0 else tot
elif aid == 3: # 应收账款余额
ar_balance = tot
elif aid == 2: # 存货余额
inv_balance = tot
# 2. 从销售总览取数据
summary = await _get(f"{ERP_API}/api/v1/stats/sales-summary", {"year": now.year})
s = summary.get("summary", {})
total_sales = s.get("total_amount", 0)
total_customers = s.get("customer_count", 0)
# 3. 前5客户集中度
top_customers = await _get(f"{ERP_API}/api/v1/stats/customer-top", {"year": now.year, "limit": 5})
top5_amt = sum(c["amount"] for c in top_customers.get("data", []))
top5_ratio = round(top5_amt / total_sales * 100, 1) if total_sales > 0 else 0
# 4. 计算KPI
gross_margin = round((revenue - cost) / revenue * 100, 2) if revenue > 0 else 0
ar_turnover = round(revenue / ar_balance, 2) if ar_balance > 0 else 0
inv_turnover = round(cost / inv_balance, 2) if inv_balance > 0 else 0
kpi_values = {
"SALES_TOTAL": total_sales,
"CUSTOMER_COUNT": total_customers,
"SALES_PROFIT_RATE": gross_margin,
"RECEIVABLE_TURNOVER": ar_turnover,
"TURNOVER_RATE": inv_turnover,
"TOP5_CUSTOMER_RATIO": top5_ratio,
}
for code, value in kpi_values.items():
kpi = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == code).first()
if kpi and (value > 0 or kpi.kpi_code in ("SALES_PROFIT_RATE","RECEIVABLE_TURNOVER","TURNOVER_RATE")):
existing = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
KPIValue.source_type == "erp",
).first()
if not existing:
kv = KPIValue(kpi_id=kpi.id, period=period, actual_value=round(value, 2), source_type="erp", data_status="verified")
db.add(kv)
db.commit()
print(f"✅ KPI计算完成: {period}")
for code, value in kpi_values.items():
kpi_n = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == code).first()
n = kpi_n.kpi_name if kpi_n else code
print(f" {n}: {round(value,2) if value else '-'}")
except Exception as e:
print(f"❌ KPI计算失败: {e}")
finally:
db.close()
if __name__ == "__main__":
asyncio.run(calculate_all())