fix: 移除ERP密码和API密钥硬编码,改为环境变量;erp-sync接口加鉴权

This commit is contained in:
Hermes CI Fix
2026-06-05 07:51:20 +08:00
parent 2c92168e0e
commit 7b9ef8ac27
9 changed files with 844 additions and 8 deletions
+145
View File
@@ -0,0 +1,145 @@
#!/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()