unis_manager/scripts/migrate_custom.py

171 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""把「定制开发」从通用项目体系拆出,独立成定制交付项目。
数据来源:
1. 《项目明细表》台账(区域 / 金额 / 类别 / 状态说明)
2. 原「定制开发」项目下的子任务(子项目名,部分自带金额)与各周期执行记录
产出:custom_projects + custom_updates 两张独立表;
原 projects / tasks / updates 中的「定制开发」整体移除,常规看板只留平台产品类项目。
用法:python scripts/migrate_custom.py [--dry-run]
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import argparse # noqa: E402
from app.ai_parser import _guess_progress # noqa: E402
from app.constants import ( # noqa: E402
CUSTOM_STAGE_CODES,
guess_custom_stage,
infer_region,
normalize_custom_name,
)
from app.db import SessionLocal, engine # noqa: E402
from app.models import ( # noqa: E402
Base,
CustomProject,
CustomUpdate,
LedgerItem,
Period,
Project,
)
CONTAINER = "定制开发"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
Base.metadata.create_all(bind=engine)
db = SessionLocal()
# ---------------------------------------------------------------- 台账
by_name: dict[str, CustomProject] = {}
created_from_ledger = 0
for it in db.query(LedgerItem).order_by(LedgerItem.id).all():
clean, amt = normalize_custom_name(it.name)
if not clean:
continue
key = clean
cp = by_name.get(key)
if cp is None:
cp = CustomProject(
name=clean,
region=it.region,
amount=it.amount if it.amount else amt,
stage=guess_custom_stage(it.status_note, it.category),
progress=0,
remark=it.status_note,
period_id=it.period_id,
is_key=(it.category == "重点项目"),
source="import",
)
db.add(cp)
db.flush()
by_name[key] = cp
created_from_ledger += 1
else:
if not cp.amount and (it.amount or amt):
cp.amount = it.amount or amt
if not cp.region and it.region:
cp.region = it.region
# ---------------------------------------------------------------- 原「定制开发」子任务
container = db.query(Project).filter(Project.name == CONTAINER).one_or_none()
created_from_task = 0
moved_updates = 0
if container:
for t in sorted(container.tasks, key=lambda x: (x.sort_order, x.id)):
clean, amt = normalize_custom_name(t.name)
if not clean:
continue
cp = by_name.get(clean) or by_name.get(t.name)
if cp is None:
cp = CustomProject(
name=clean,
amount=amt,
stage="signed",
progress=0,
source="import",
)
db.add(cp)
db.flush()
by_name[clean] = cp
created_from_task += 1
elif not cp.amount and amt:
cp.amount = amt
# 该子项目的各周期执行记录 → custom_updates
for u in t.updates:
stage = guess_custom_stage(u.content)
cu = CustomUpdate(
custom_project_id=cp.id,
period_id=u.period_id,
stage=stage,
content=u.content,
summary=u.summary,
progress=u.progress,
risk=u.risk,
next_step=u.next_step,
owner=u.owner,
source="import",
)
db.add(cu)
moved_updates += 1
# 用最新记录回写项目状态
if cp.period_id is None or u.period_id >= cp.period_id:
cp.period_id = u.period_id
cp.stage = stage
cp.remark = (u.content or "")[:500]
if u.progress is not None:
cp.progress = u.progress
# ------------------------------------------------------------ 清理常规表
if not args.dry_run:
db.delete(container)
db.query(LedgerItem).delete()
# ---------------------------------------------------------------- 收尾
for cp in by_name.values():
if cp.stage not in CUSTOM_STAGE_CODES:
cp.stage = "signed"
if cp.stage in ("delivered", "accepted", "revenue", "closed") and not cp.progress:
cp.progress = 100
elif cp.stage == "developing" and not cp.progress:
cp.progress = 30
if not cp.region:
cp.region = infer_region(cp.name)
if cp.remark and not cp.progress:
p = _guess_progress(cp.remark)
if p is not None:
cp.progress = p
total = len(by_name)
if args.dry_run:
db.rollback()
print(f"[dry-run] 将生成定制项目 {total} 个(台账 {created_from_ledger} + 子任务 {created_from_task}),迁移执行记录 {moved_updates} 条")
return
db.commit()
amount_sum = sum(c.amount or 0 for c in by_name.values())
print(f"[ok] 定制项目 {total} 个(台账 {created_from_ledger} / 子任务 {created_from_task})")
print(f"[ok] 迁移执行记录 {moved_updates} 条 → custom_updates")
print(f"[ok] 合同金额合计 ¥{amount_sum:,.2f}")
from collections import Counter
print("[ok] 阶段分布:", dict(Counter(c.stage for c in by_name.values())))
print(f"[ok] 常规项目剩余 {db.query(Project).count()} 个(已移除「{CONTAINER}」)")
db.close()
if __name__ == "__main__":
main()