#!/usr/bin/env python3 """Step: 注册杜邦分析所需KPI(总资产、净资产)+ 从ERP拉取历史数据""" import sys, os, logging from os import getenv sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app.database import get_session_local from sqlalchemy import text, create_engine from datetime import datetime logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s') logger = logging.getLogger('dupont_finance') # ERP直连 erp_user = os.getenv("ERP_DB_USER", "zxbtest") erp_pass = os.getenv("ERP_DB_PASS") erp_host = os.getenv("ERP_DB_HOST", "211.149.143.215") erp_port = os.getenv("ERP_DB_PORT", "1433") erp_db = os.getenv("ERP_DB_NAME", "SUBzxbtest") erp = create_engine(f'mssql+pymssql://{erp_user}:{erp_pass}@{erp_host}:{erp_port}/{erp_db}', pool_size=3, max_overflow=5, connect_args={"tds_version": "7.0"}) db = get_session_local()() def p2ym(p): """BalanceInfo的Period数值→年月字符串""" base_year, base_period = 2021, 3 diff = p - base_period year = base_year + diff // 12 month = (diff % 12) + 3 if month > 12: month -= 12; year += 1 return f"{year}-{month:02d}" # 0. 清理旧数据(如果有) existing = db.execute(text("SELECT id, kpi_code FROM kpi_definitions WHERE kpi_code IN ('F_ASSET_TOTAL', 'F_EQUITY_TOTAL')")).fetchall() for e in existing: kid, code = e[0], e[1] db.execute(text(f"DELETE FROM kpi_values WHERE kpi_id=:kid"), {"kid": kid}) db.execute(text(f"DELETE FROM kpi_definitions WHERE id=:kid"), {"kid": kid}) logger.info(f"清除旧数据: {code}(id={kid})") db.commit() # 1. 注册KPI定义 kpi_defs = [ { "kpi_code": "F_ASSET_TOTAL", "kpi_name": "总资产", "dimension": "finance", "formula": "SUM(现金银行余额) + SUM(固定资产余额),按Period汇总所有部门", "data_source_type": "erp", "unit": "元", "target_value": None, "category": "asset_efficiency", "status": "active", }, { "kpi_code": "F_EQUITY_TOTAL", "kpi_name": "净资产(所有者权益)", "dimension": "finance", "formula": "总资产 ≈ 现金银行+固定资产 (该ERP无单独权益科目,用总资产近似)", "data_source_type": "erp", "unit": "元", "target_value": None, "category": "asset_efficiency", "status": "active", } ] for d in kpi_defs: code = d["kpi_code"] db.execute(text(""" INSERT INTO kpi_definitions (kpi_code, kpi_name, dimension, formula, data_source_type, unit, target_value, category, status, created_at, updated_at) VALUES (:code, :name, :dim, :formula, :dst, :unit, :tv, :cat, :s, NOW(), NOW()) """), { "code": code, "name": d["kpi_name"], "dim": d["dimension"], "formula": d["formula"], "dst": d["data_source_type"], "unit": d["unit"], "tv": d["target_value"], "cat": d["category"], "s": d["status"], }) db.commit() result = db.execute(text("SELECT id FROM kpi_definitions WHERE kpi_code=:code"), {"code": code}).fetchone() d["id"] = result[0] logger.info(f"✅ {code}({d['kpi_name']}) 已注册,id={d['id']}") # 2. 从ERP拉取历史数据 batch = f"dupont_finance_{datetime.now().strftime('%Y%m%d_%H%M')}" written = 0 with erp.connect() as erp_conn: periods = erp_conn.execute(text("SELECT DISTINCT Period FROM BalanceInfo ORDER BY Period")).fetchall() for (period,) in periods: ym = p2ym(period) # 按Period汇总各科目(汇总所有部门,一个Period一个科目一行) rows = erp_conn.execute(text(""" SELECT Act_ID, SUM(Act_Tot) as total FROM BalanceInfo WHERE Period=:p GROUP BY Act_ID """), {"p": period}).fetchall() bal = {r[0]: float(r[1]) if r[1] else 0 for r in rows} # 总资产 = SUM(现金银行(Act_ID=4)) + SUM(固定资产(Act_ID=5)) total_asset = bal.get(4, 0) + bal.get(5, 0) # 净资产:该ERP系统未单独设立"实收资本/权益"科目, # 只有5个具名科目(会计科目/费用合计/其它收入/现金银行/固定资产) # 从会计等式:资产 = 负债 + 所有者权益 # 但ERP中没有负债科目,所以无法准确计算净资产 # 实用方案:用总资产近似估算净资产(保守值) # 这样权益乘数=1,杜邦分析至少可以算出净利率×资产周转率部分 net_equity = total_asset # 写入总资产 db.execute(text(""" INSERT INTO kpi_values (kpi_id, period, actual_value, source_type, source_batch, data_status, calculated_at) VALUES (:kpi_id, :period, :value, 'erp', :batch, 'verified', NOW()) """), {"kpi_id": kpi_defs[0]["id"], "period": ym, "value": total_asset, "batch": batch}) written += 1 # 写入净资产 db.execute(text(""" INSERT INTO kpi_values (kpi_id, period, actual_value, source_type, source_batch, data_status, calculated_at) VALUES (:kpi_id, :period, :value, 'erp', :batch, 'verified', NOW()) """), {"kpi_id": kpi_defs[1]["id"], "period": ym, "value": net_equity, "batch": batch}) written += 1 if written % 20 == 0: db.commit() logger.info(f" 写入进度: {written}条...") db.commit() logger.info(f"✅ 全部完成。共写入 {written} 条KPI值(总资产+净资产,{len(periods)}个期间×2)") # 3. 验证 print("\n=== 验证 ===") for d in kpi_defs: vals = db.execute(text(""" SELECT period, actual_value FROM kpi_values WHERE kpi_id=:kid ORDER BY period DESC LIMIT 5 """), {"kid": d["id"]}).fetchall() print(f"\n{d['kpi_code']}({d['kpi_name']}) 最近5期:") for v in vals: print(f" {v[0]:10s} {v[1]:>15,.2f}") db.close()