迁移脚本的一些问题修复
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
工作流:
|
||||
1. 读 resources/cards.csv 拿到 iccid 列表
|
||||
2. 连奇成只读查询每张卡的 category / agent_id / 当前生效套餐 / 套餐系列
|
||||
2. 连奇成只读查询每张卡的 account/category / agent_id / 当前生效套餐 / 套餐系列
|
||||
3. 加载已有的 config/mapping.yaml(若没有则创建空的)
|
||||
4. 把扫描到的 legacy_* 增量补到 mapping 里(已有项的 target_* 字段保留,新项的 target_* 为 null)
|
||||
5. 写回 config/mapping.yaml
|
||||
@@ -31,14 +31,17 @@ from lib.mapping_loader import ( # noqa: E402
|
||||
)
|
||||
|
||||
|
||||
def _read_iccid_pairs(cards_csv: Path) -> list[tuple[str, str]]:
|
||||
def _read_iccid_pairs(cards_csv: Path) -> list[tuple[str, str, bool]]:
|
||||
"""从 cards.csv 读 iccid_19 + iccid_20 列(只关心这两列,其他列忽略)。
|
||||
|
||||
返回 (iccid_19, iccid_20) 列表;iccid_20 可为空字符串(CTCC 卡留空)。
|
||||
返回 (iccid_19, iccid_20, allow_iccid_19_lookup) 列表。
|
||||
只填 iccid_20 时,iccid_19 从前 19 位派生,但不用于奇成主表兜底查询。
|
||||
"""
|
||||
if not cards_csv.exists():
|
||||
raise FileNotFoundError(f"找不到 {cards_csv},请按 resources/README.md 准备")
|
||||
pairs: dict[str, str] = {}
|
||||
pairs: dict[str, tuple[str, bool]] = {}
|
||||
first_seen_line: dict[str, int] = {}
|
||||
errors: list[str] = []
|
||||
with cards_csv.open("r", encoding="utf-8-sig", newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
headers = reader.fieldnames or []
|
||||
@@ -46,12 +49,37 @@ def _read_iccid_pairs(cards_csv: Path) -> list[tuple[str, str]]:
|
||||
raise ValueError(f"{cards_csv} 必须包含 iccid_19 列")
|
||||
if "iccid_20" not in headers:
|
||||
raise ValueError(f"{cards_csv} 必须包含 iccid_20 列(CTCC 卡留空)")
|
||||
for row in reader:
|
||||
for line_no, row in enumerate(reader, start=2):
|
||||
i19 = (row.get("iccid_19") or "").strip()
|
||||
i20 = (row.get("iccid_20") or "").strip()
|
||||
if i19:
|
||||
pairs[i19] = i20
|
||||
return sorted(pairs.items())
|
||||
allow_i19_lookup = bool(i19)
|
||||
if not i19 and not i20 and not any((v or "").strip() for v in row.values()):
|
||||
continue
|
||||
if i20 and len(i20) != 20:
|
||||
errors.append(f"第 {line_no} 行 iccid_20 必须是 20 位: {i20!r}")
|
||||
continue
|
||||
if not i19:
|
||||
if not i20:
|
||||
errors.append(f"第 {line_no} 行 iccid_19 和 iccid_20 不能同时为空")
|
||||
continue
|
||||
i19 = i20[:19]
|
||||
if len(i19) != 19:
|
||||
errors.append(f"第 {line_no} 行 iccid_19 必须是 19 位: {i19!r}")
|
||||
continue
|
||||
if i20 and i20[:19] != i19:
|
||||
errors.append(f"第 {line_no} 行 iccid_20 前 19 位必须等于 iccid_19: {i20!r}")
|
||||
continue
|
||||
if i19 in pairs:
|
||||
errors.append(f"第 {line_no} 行 iccid_19 与第 {first_seen_line[i19]} 行重复: {i19!r}")
|
||||
continue
|
||||
first_seen_line[i19] = line_no
|
||||
pairs[i19] = (i20, allow_i19_lookup)
|
||||
if errors:
|
||||
preview = "\n".join(f" - {msg}" for msg in errors[:20])
|
||||
if len(errors) > 20:
|
||||
preview += f"\n - 其余 {len(errors) - 20} 个错误已省略"
|
||||
raise ValueError(f"{cards_csv} 存在无效 ICCID,请先修正后再扫描:\n{preview}")
|
||||
return [(i19, i20, allow_i19_lookup) for i19, (i20, allow_i19_lookup) in sorted(pairs.items())]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -64,9 +92,13 @@ def main() -> int:
|
||||
config_dir = (base / args.config_dir).resolve()
|
||||
resources_dir = (base / args.resources_dir).resolve()
|
||||
|
||||
iccid_pairs = _read_iccid_pairs(resources_dir / "cards.csv")
|
||||
try:
|
||||
iccid_pairs = _read_iccid_pairs(resources_dir / "cards.csv")
|
||||
except (FileNotFoundError, ValueError) as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
return 1
|
||||
if not iccid_pairs:
|
||||
print("cards.csv 中没有 iccid_19,无需扫描", file=sys.stderr)
|
||||
print("cards.csv 中没有有效 ICCID,无需扫描", file=sys.stderr)
|
||||
return 1
|
||||
print(f"读到 {len(iccid_pairs)} 个 ICCID,连接奇成扫描元数据...")
|
||||
|
||||
@@ -107,21 +139,28 @@ def _merge(
|
||||
)
|
||||
|
||||
for meta in card_metas.values():
|
||||
# carrier
|
||||
if meta.category and meta.category not in merged.carriers:
|
||||
merged.carriers[meta.category] = CarrierMapping(
|
||||
# carrier:按开卡公司/运营商账号映射,category 仅作为辅助信息。
|
||||
if meta.account_id and meta.account_id not in merged.carriers:
|
||||
merged.carriers[meta.account_id] = CarrierMapping(
|
||||
legacy_account_id=meta.account_id,
|
||||
legacy_account_name=meta.account_name,
|
||||
legacy_category=meta.category,
|
||||
legacy_category_name=meta.category_name,
|
||||
target_carrier_id=None,
|
||||
target_carrier_type=None,
|
||||
target_carrier_name=None,
|
||||
)
|
||||
elif meta.category in merged.carriers and not merged.carriers[meta.category].legacy_category_name:
|
||||
# 补全 legacy_category_name(如果之前是空的)
|
||||
old = merged.carriers[meta.category]
|
||||
merged.carriers[meta.category] = CarrierMapping(
|
||||
elif meta.account_id in merged.carriers and (
|
||||
not merged.carriers[meta.account_id].legacy_account_name
|
||||
or not merged.carriers[meta.account_id].legacy_category_name
|
||||
):
|
||||
# 补全 legacy_* 快照(如果之前是空的)
|
||||
old = merged.carriers[meta.account_id]
|
||||
merged.carriers[meta.account_id] = CarrierMapping(
|
||||
legacy_account_id=old.legacy_account_id,
|
||||
legacy_account_name=meta.account_name or old.legacy_account_name,
|
||||
legacy_category=old.legacy_category,
|
||||
legacy_category_name=meta.category_name,
|
||||
legacy_category_name=meta.category_name or old.legacy_category_name,
|
||||
target_carrier_id=old.target_carrier_id,
|
||||
target_carrier_type=old.target_carrier_type,
|
||||
target_carrier_name=old.target_carrier_name,
|
||||
@@ -154,9 +193,12 @@ def _merge(
|
||||
def _collect_todo(mapping: Mapping) -> dict[str, list[str]]:
|
||||
"""收集所有待填写的项,按类别分组。"""
|
||||
todo: dict[str, list[str]] = {"carriers": [], "packages": [], "agents": []}
|
||||
for c in sorted(mapping.carriers.values(), key=lambda x: x.legacy_category):
|
||||
for c in sorted(mapping.carriers.values(), key=lambda x: (x.legacy_account_name, x.legacy_account_id)):
|
||||
if not c.target_carrier_id or not c.target_carrier_type or not c.target_carrier_name:
|
||||
todo["carriers"].append(f"category={c.legacy_category} ({c.legacy_category_name})")
|
||||
todo["carriers"].append(
|
||||
f"account_id={c.legacy_account_id} ({c.legacy_account_name}, "
|
||||
f"category={c.legacy_category}/{c.legacy_category_name})"
|
||||
)
|
||||
for p in sorted(mapping.packages.values(), key=lambda x: x.legacy_meal_id):
|
||||
if not p.target_package_id:
|
||||
todo["packages"].append(f"meal_id={p.legacy_meal_id} ({p.legacy_meal_name})")
|
||||
|
||||
Reference in New Issue
Block a user