Files

197 lines
10 KiB
Python
Raw Permalink 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.
"""
CMA 财务示范数据 — 供财务BOT分析使用
运行: (cd /root/cma-management/backend && venv/bin/python3 scripts/seed_finance_data.py)
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.database import get_session_local
from app.models import KPIDefinition, KPIValue, KPIAlert, ActionPlan
from app.models.budget_plan import BudgetPlan
from datetime import datetime
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger("seed_finance")
def seed():
db = get_session_local()()
try:
# ── 1. KPI字典 ──
if db.query(KPIDefinition).count() == 0:
kpis_data = [
# code, name, dim, cat, unit, target, freq, src,
# g_min, y_min, r_min, dept, user
("F_REVENUE", "营业收入", "finance", "revenue_growth", "万元", 5000, "monthly", "erp", ">=4500", ">=4000", "<4000", "销售部", "张经理"),
("F_GROSS_MARGIN","毛利率", "finance", "profitability", "%", 35, "monthly", "erp", ">=35", ">=30", "<30", "财务部", "李会计"),
("F_NET_PROFIT", "净利润", "finance", "profitability", "万元", 800, "monthly", "erp", ">=800", ">=600", "<600", "财务部", ""),
("F_OP_CFLOW", "经营性现金流", "finance", "cash_risk", "万元", 1000, "monthly", "erp", ">=1000", ">=500", "<500", "财务部", ""),
("F_COST_RATIO", "费用率", "finance", "cost_control", "%", 20, "monthly", "erp", "<=20", "<=25", ">25", "财务部", ""),
("F_AR_DAYS", "应收账款周转天数","finance","asset_efficiency", "天", 45, "monthly", "erp", "<=45", "<=60", ">60", "销售部", ""),
("C_SATISFACTION","客户满意度", "customer","customer_satisfaction","分",92, "monthly","manual",">=92",">=85","<85","",""),
("C_NEW_CLIENTS", "新客户数", "customer","customer_scale", "个", 10, "monthly","business",">=10",">=5","<5","销售部",""),
("P_DELIVERY", "交付及时率", "process", "delivery_quality", "%", 95, "monthly", "erp", ">=95", ">=90", "<90", "交付部", ""),
("P_BUG_RATE", "缺陷率", "process", "delivery_quality", "%", 2, "monthly", "erp", "<=2", "<=5", ">5", "", ""),
("L_TRAINING", "培训完成率", "learning","talent_pipeline", "%", 90, "monthly","manual",">=90",">=80","<80","",""),
("L_EMPLOYEE_SAT","员工满意度", "learning","employee_engagement","分", 85, "quarterly","manual",None,None,None,"",""),
]
formula_map = {
"F_REVENUE": "SUM(erp_sales.amount)",
"F_GROSS_MARGIN": "(收入-成本)/收入*100",
"F_NET_PROFIT": "收入-成本-费用-税金",
"F_COST_RATIO": "期间费用/收入*100",
"F_AR_DAYS": "360/应收账款周转率",
"P_BUG_RATE": "缺陷数/总功能点*100",
}
kpis = []
for code, name, dim, cat, unit, target, freq, src, g, y, r, dept, user in kpis_data:
k = KPIDefinition(
kpi_code=code, kpi_name=name, dimension=dim,
category=cat, unit=unit, target_value=target,
frequency=freq, data_source_type=src,
threshold_green=g, threshold_yellow=y, threshold_red=r,
responsible_dept=dept, responsible_user=user,
status="active",
)
if code in formula_map:
k.formula = formula_map[code]
db.add(k)
kpis.append(k)
db.flush()
logger.info(f"✅ KPI字典: {len(kpis)}条")
else:
logger.info("KPI字典已有数据,跳过")
# ── 2. KPI实际值(1-6月趋势)──
if db.query(KPIValue).count() == 0:
kpi_map = {k.kpi_code: k.id for k in db.query(KPIDefinition).all()}
values_data = {
"F_REVENUE": [4200, 4500, 4700, 4900, 5100, 4800],
"F_GROSS_MARGIN":[32, 33, 34, 35, 34, 31],
"F_NET_PROFIT": [650, 700, 750, 800, 820, 700],
"F_OP_CFLOW": [800, 900, 950, 1000, 1050, 850],
"F_COST_RATIO": [22, 21, 20, 19, 20, 23],
"F_AR_DAYS": [50, 48, 46, 45, 44, 52],
"C_SATISFACTION":[88, 89, 90, 91, 92, 90],
"C_NEW_CLIENTS": [7, 8, 9, 10, 11, 8],
"P_DELIVERY": [92, 93, 94, 95, 96, 93],
"P_BUG_RATE": [3.0, 2.5, 2.0, 1.8, 1.5, 2.2],
"L_TRAINING": [85, 87, 88, 90, 91, 86],
}
months = ["2026-01","2026-02","2026-03","2026-04","2026-05","2026-06"]
cnt = 0
for code, vals in values_data.items():
kid = kpi_map.get(code)
if not kid: continue
for m, v in zip(months, vals):
db.add(KPIValue(kpi_id=kid, period=m, actual_value=v,
source_type="manual", data_status="verified"))
cnt += 1
db.flush()
logger.info(f"✅ KPI实际值: {cnt}条")
else:
logger.info("KPI实际值已有数据,跳过")
# ── 3. 预警 ──
if db.query(KPIAlert).count() == 0:
kpi_map = {k.kpi_code: k.id for k in db.query(KPIDefinition).all()}
alerts_data = [
("F_REVENUE", "yellow", "6月营收4800万,低于目标5000万(缺口4%"),
("F_GROSS_MARGIN", "red", "6月毛利率31%,跌破30%预警线"),
("F_NET_PROFIT", "yellow", "6月净利润700万,低于目标800万(-12.5%"),
("F_OP_CFLOW", "yellow", "经营性现金流850万,低于目标1000万"),
("F_COST_RATIO", "yellow", "费用率23%,超目标20%"),
("F_AR_DAYS", "yellow", "应收账款周转52天,超目标45天"),
("C_NEW_CLIENTS", "yellow", "6月新客户仅8家,低于目标10家"),
]
for code, level, msg in alerts_data:
db.add(KPIAlert(
kpi_id=kpi_map.get(code),
alert_level=level,
alert_message=msg,
status="pending",
))
db.flush()
logger.info(f"✅ 预警: {len(alerts_data)}条")
else:
logger.info("预警已有数据,跳过")
# ── 4. 改善行动 ──
if db.query(ActionPlan).count() == 0:
kpi_map = {k.kpi_code: k.id for k in db.query(KPIDefinition).all()}
actions_data = [
(kpi_map.get("F_GROSS_MARGIN"), "成本优化专项 - 降低采购成本5%",
"重新谈判供应商合同,目标毛利率回升至35%", "采购部王经理",
"high", "in_progress", 40, datetime(2026,8,31)),
(kpi_map.get("F_REVENUE"), "Q3客户拓展计划",
"新增3个大客户,目标月度营收5500万", "销售部张经理",
"high", "pending", 0, datetime(2026,9,30)),
(kpi_map.get("F_AR_DAYS"), "应收账款催收行动",
"集中催收超60天应收款,目标周转天降至45天", "财务部李会计",
"medium", "in_progress", 30, datetime(2026,7,31)),
(kpi_map.get("P_DELIVERY"), "交付流程优化",
"优化项目管理流程,交付及时率提升至95%+", "交付部赵主管",
"medium", "pending", 10, datetime(2026,8,15)),
]
for kid, title, desc, assignee, pri, status, prog, due in actions_data:
db.add(ActionPlan(
kpi_id=kid, title=title, description=desc,
assignee=assignee, priority=pri, status=status,
progress=prog, due_date=due,
))
db.flush()
logger.info(f"✅ 改善行动: {len(actions_data)}条")
else:
logger.info("改善行动已有数据,跳过")
# ── 5. 预算数据 ──
if db.query(BudgetPlan).count() == 0:
kpi_map = {k.kpi_code: k.id for k in db.query(KPIDefinition).all()}
# 每个财务KPI全年12个月预算
budget_map = {
"F_REVENUE": [3800,4000,4200,4400,4600,4800,4800,4900,5000,5000,5100,5200],
"F_GROSS_MARGIN":[33]*12,
"F_NET_PROFIT": [600,620,650,680,700,720,720,750,780,800,820,850],
"F_OP_CFLOW": [700,750,800,850,900,950,950,1000,1000,1050,1050,1100],
"F_COST_RATIO": [22]*12,
"F_AR_DAYS": [50,49,48,47,46,45,45,44,44,43,43,42],
}
cnt = 0
for code, vals in budget_map.items():
kid = kpi_map.get(code)
if not kid: continue
for month in range(1, 13):
db.add(BudgetPlan(
kpi_id=kid,
period=f"2026-{month:02d}",
budget_value=vals[month-1],
budget_year=2026,
budget_month=month,
version="v1.0",
status="active",
))
cnt += 1
db.flush()
logger.info(f"✅ 预算数据: {cnt}条")
else:
logger.info("预算数据已有数据,跳过")
db.commit()
logger.info("")
logger.info("🎉 财务示范数据导入完成!")
logger.info(f"📊 KPI指标: {db.query(KPIDefinition).count()}")
logger.info(f"📊 KPI实际值: {db.query(KPIValue).count()}")
logger.info(f"📊 预警: {db.query(KPIAlert).count()}")
logger.info(f"📊 改善行动: {db.query(ActionPlan).count()}")
logger.info(f"📊 预算: {db.query(BudgetPlan).count()}")
except Exception as e:
db.rollback()
logger.error(f"❌ 导入失败: {e}")
import traceback; traceback.print_exc()
raise
finally:
db.close()
if __name__ == "__main__":
seed()