Files
cma-management/backend/app/api/data.py
T
Hermes CI Fix da9aceb567 feat: Excel导入支持自定义列映射+智能列名检测
- 上传后自动读取Excel列名,下拉框选择映射
- 智能匹配: 科目/编码→kpi_code, 期间→period, 金额→actual_value
- 支持统一期间(文件无期间列时)
- 后端接受 kpi_col/period_col/value_col/default_period 参数
2026-07-13 15:18:40 +08:00

125 lines
4.9 KiB
Python
Raw 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.
"""数据对接 API"""
import pandas as pd
import io, json, hashlib
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File
from sqlalchemy.orm import Session
from sqlalchemy import func
from typing import Optional
from app.database import get_db
from app.auth_middleware import require_auth, require_role
from app.models import KPIValue, DataSourceConfig, OperationLog
router = APIRouter(prefix="/api/cma/data", tags=["数据对接"],
dependencies=[Depends(require_role("ceo", "finance", "it"))],
)
@router.post("/import-excel")
async def import_excel(file: UploadFile = File(...),
kpi_col: str = Query("kpi_code", description="Excel中KPI编码列名"),
period_col: str = Query("period", description="Excel中期间列名"),
value_col: str = Query("actual_value", description="Excel中实际值列名"),
default_period: str = Query(None, description="如文件无期间列,统一使用此值"),
db: Session = Depends(get_db)):
content = await file.read()
df = pd.read_excel(io.BytesIO(content))
required = [kpi_col, value_col]
if not default_period:
required.append(period_col)
missing = [c for c in required if c not in df.columns]
if missing:
raise HTTPException(400,
f"Excel缺少列: {missing}。当前文件列: {list(df.columns)}")
if len(df) == 0:
raise HTTPException(400, "Excel文件为空,没有数据行")
from app.models import KPIDefinition
kpi_map = {k.kpi_code: k.id for k in db.query(KPIDefinition).all()}
batch = hashlib.md5(str(datetime.now().timestamp()).encode()).hexdigest()[:12]
count = 0
skipped = []
for idx, row in df.iterrows():
kpi_code = str(row.get(kpi_col, "")).strip()
period = str(row.get(period_col, default_period or "")).strip() if period_col in df.columns else (default_period or "").strip()
value = row.get(value_col)
if not kpi_code or not period or pd.isna(value):
skipped.append(f"第{idx+2}行: 缺少必填字段")
continue
kid = kpi_map.get(kpi_code)
if not kid:
skipped.append(f"第{idx+2}行: KPI编码「{kpi_code}」不存在")
continue
db.add(KPIValue(
kpi_id=kid,
period=period,
actual_value=float(value),
source_type="excel",
source_batch=batch,
data_status="verified",
))
count += 1
db.commit()
msg = f"✅ 导入成功 {count} 条数据"
if skipped:
msg += f"{len(skipped)}条跳过:\n" + "\n".join(skipped[:10])
if len(skipped) > 10:
msg += f"\n...还有{len(skipped)-10}条"
return {"message": msg, "batch": batch, "total": count, "skipped": len(skipped)}
@router.get("/sources")
def list_sources(db: Session = Depends(get_db)):
sources = db.query(DataSourceConfig).all()
return {"data": [{c.name: getattr(s, c.name) for c in DataSourceConfig.__table__.columns} for s in sources]}
@router.post("/sources")
def create_source(data: dict, db: Session = Depends(get_db)):
source = DataSourceConfig(
name=data.get("name", ""),
source_type=data.get("source_type", "manual"),
api_endpoint=data.get("api_endpoint"),
api_key=data.get("api_key"),
query_sql=data.get("query_sql"),
sync_type=data.get("sync_type", "manual"),
status="active",
)
db.add(source)
db.commit()
db.refresh(source)
# 操作日志
db.add(OperationLog(action="create_source", target_type="source", detail=source.name))
db.commit()
return {"data": {c.name: getattr(source, c.name) for c in DataSourceConfig.__table__.columns}}
@router.put("/sources/{source_id}")
def update_source(source_id: int, data: dict, db: Session = Depends(get_db)):
source = db.query(DataSourceConfig).filter(DataSourceConfig.id == source_id).first()
if not source:
raise HTTPException(404, "数据源不存在")
for key in ["name", "source_type", "api_endpoint", "api_key", "query_sql", "sync_type", "status"]:
if key in data:
setattr(source, key, data[key])
db.commit()
db.refresh(source)
db.add(OperationLog(action="update_source", target_type="source", detail=source.name))
db.commit()
return {"data": {c.name: getattr(source, c.name) for c in DataSourceConfig.__table__.columns}}
@router.delete("/sources/{source_id}")
def delete_source(source_id: int, db: Session = Depends(get_db)):
source = db.query(DataSourceConfig).filter(DataSourceConfig.id == source_id).first()
if not source:
raise HTTPException(404, "数据源不存在")
db.add(OperationLog(action="delete_source", target_type="source", detail=source.name))
db.delete(source)
db.commit()
return {"message": "删除成功"}