"""把全部周期的年份整体平移 N 年(默认 +1)。 场景:历史数据的年份口径需要调整(例如 2024→2025、2025→2026)。 注意实现要点: - Period 同时存在 2024 与 2025,必须**按年份从大到小**更新, 否则 2024→2025 会撞上尚未更新的 2025,造成 sort_key 重复。 - Period.label 存的是『8月第4周』这类不含年份的原始文本,无需改动; 带年份的显示名(full_label)是按 year 动态生成的,会自动跟随。 - 执行前会校验 sort_key 是否出现重复,有重复则整体回滚。 用法: python scripts/shift_year.py --dry-run # 预演 python scripts/shift_year.py # 全部 +1 年 python scripts/shift_year.py --years -1 # 反向:全部 -1 年 """ from __future__ import annotations import argparse import re import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from app.db import SessionLocal # noqa: E402 from app.models import CustomProject, Period # noqa: E402 def shift_date(value: str | None, years: int) -> str | None: """把 '2025-08-14' 这类日期字符串的年份平移。""" if not value: return value m = re.match(r"^(\d{4})(.*)$", str(value).strip()) if not m: return value return f"{int(m.group(1)) + years}{m.group(2)}" def main(): ap = argparse.ArgumentParser() ap.add_argument("--years", type=int, default=1, help="平移年数,默认 +1") ap.add_argument("--dry-run", action="store_true") args = ap.parse_args() if args.years == 0: print("平移年数为 0,无需处理") return db = SessionLocal() try: # 按年份从大到小,避免中间态撞车 periods = db.query(Period).order_by(Period.year.desc(), Period.sort_key.desc()).all() if not periods: print("没有周期数据") return print(f"周期共 {len(periods)} 个,年份平移 {args.years:+d} 年") print(f"{'原周期':>22} → {'新周期':<22}") print("-" * 56) changed = 0 target_keys = [] for p in periods: new_year = p.year + args.years new_key = new_year * 10000 + p.month * 100 + p.week target_keys.append(new_key) old_label = f"{p.year}年{p.month}月第{p.week}周" new_label = f"{new_year}年{p.month}月第{p.week}周" if changed < 6 or changed >= len(periods) - 4: print(f"{old_label:>22} → {new_label:<22}") elif changed == 6: print(f"{'…':>22} {'…'}") p.year = new_year p.sort_key = new_key changed += 1 # 冲突校验 if len(set(target_keys)) != len(target_keys): db.rollback() print("!! 平移后出现重复周期,已回滚。请先清理重复的周期数据。") return # 定制项目的日期字段同步平移 date_changed = 0 for cp in db.query(CustomProject).all(): for field in ("sign_date", "deliver_date", "revenue_date"): old = getattr(cp, field) new = shift_date(old, args.years) if new != old: setattr(cp, field, new) date_changed += 1 if args.dry_run: db.rollback() print("-" * 56) print(f"[dry-run] 将平移 {changed} 个周期、{date_changed} 个日期字段,未写入。") return db.commit() print("-" * 56) print(f"[ok] 已平移 {changed} 个周期({args.years:+d} 年),日期字段 {date_changed} 个") latest = db.query(Period).order_by(Period.sort_key.desc()).first() print(f"[ok] 当前最新周期:{latest.year}年{latest.month}月第{latest.week}周") print(" 提示:如需回滚,执行 python scripts/shift_year.py --years " + f"{-args.years}") finally: db.close() if __name__ == "__main__": main()