This commit is contained in:
@@ -142,6 +142,16 @@ class AgentMapping:
|
||||
no_downtime: bool = False # 迁移时免停机:有生效套餐的卡直接以 network_status=1 导入
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PackageLifecycleOverride:
|
||||
"""单卡旧套餐生命周期的人工裁决。"""
|
||||
|
||||
iccid: str
|
||||
active_life_id: str
|
||||
pending_life_ids: tuple[str, ...] = ()
|
||||
skipped_life_ids: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Mapping:
|
||||
"""整体映射(对应单个 mapping.yaml)。"""
|
||||
@@ -155,6 +165,7 @@ class Mapping:
|
||||
packages: dict[str, PackageMapping] = field(default_factory=dict)
|
||||
series: dict[str, SeriesMapping] = field(default_factory=dict)
|
||||
agents: dict[str, AgentMapping] = field(default_factory=dict)
|
||||
package_lifecycle_overrides: dict[str, PackageLifecycleOverride] = field(default_factory=dict)
|
||||
|
||||
def lookup_carrier(self, legacy_account_id: str) -> Optional[CarrierMapping]:
|
||||
return self.carriers.get(str(legacy_account_id or "").strip())
|
||||
@@ -167,6 +178,9 @@ class Mapping:
|
||||
return None
|
||||
return self.series.get(str(legacy_series_id).strip())
|
||||
|
||||
def lookup_package_lifecycle_override(self, iccid: str) -> Optional[PackageLifecycleOverride]:
|
||||
return self.package_lifecycle_overrides.get(str(iccid or "").strip())
|
||||
|
||||
def is_no_downtime_agent(self, legacy_agent_id: str) -> bool:
|
||||
"""判断代理是否配置了免停机迁移。"""
|
||||
agent = self.agents.get(str(legacy_agent_id or "").strip())
|
||||
@@ -261,6 +275,8 @@ def load_mapping(config_dir: Path) -> Mapping:
|
||||
)
|
||||
m.series[s.legacy_series_id] = s
|
||||
|
||||
m.package_lifecycle_overrides = _load_package_lifecycle_overrides(raw.get("package_lifecycle_overrides") or [])
|
||||
|
||||
for item in raw.get("agents") or []:
|
||||
a = AgentMapping(
|
||||
legacy_agent_id=str(item["legacy_agent_id"]).strip(),
|
||||
@@ -323,6 +339,39 @@ def _load_package_rules(raw: dict) -> PackageRules:
|
||||
return PackageRules(migrate_statuses=statuses)
|
||||
|
||||
|
||||
def _load_package_lifecycle_overrides(raw: Any) -> dict[str, PackageLifecycleOverride]:
|
||||
if not isinstance(raw, list):
|
||||
raise ValueError("mapping.yaml.package_lifecycle_overrides 必须是列表")
|
||||
overrides: dict[str, PackageLifecycleOverride] = {}
|
||||
for idx, item in enumerate(raw, start=1):
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError(f"mapping.yaml.package_lifecycle_overrides[{idx}] 必须是 mapping 类型")
|
||||
iccid = _opt_str(item.get("iccid"))
|
||||
active = _opt_str(item.get("active_life_id"))
|
||||
if not iccid or len(iccid) != 20 or not iccid.isdigit():
|
||||
raise ValueError(f"mapping.yaml.package_lifecycle_overrides[{idx}].iccid 必须是 20 位数字")
|
||||
if not active:
|
||||
raise ValueError(f"mapping.yaml.package_lifecycle_overrides[{idx}].active_life_id 不能为空")
|
||||
pending = _life_ids(item.get("pending_life_ids") or [], f"package_lifecycle_overrides[{idx}].pending_life_ids")
|
||||
skipped = _life_ids(item.get("skipped_life_ids") or [], f"package_lifecycle_overrides[{idx}].skipped_life_ids")
|
||||
all_ids = (active,) + pending + skipped
|
||||
if len(all_ids) != len(set(all_ids)):
|
||||
raise ValueError(f"mapping.yaml.package_lifecycle_overrides[{idx}] 生命周期 ID 不可重复或跨状态重复")
|
||||
if iccid in overrides:
|
||||
raise ValueError(f"mapping.yaml.package_lifecycle_overrides iccid 重复: {iccid}")
|
||||
overrides[iccid] = PackageLifecycleOverride(iccid, active, pending, skipped)
|
||||
return overrides
|
||||
|
||||
|
||||
def _life_ids(raw: Any, field_name: str) -> tuple[str, ...]:
|
||||
if not isinstance(raw, list):
|
||||
raise ValueError(f"mapping.yaml.{field_name} 必须是列表")
|
||||
values = tuple(_opt_str(value) or "" for value in raw)
|
||||
if not all(values):
|
||||
raise ValueError(f"mapping.yaml.{field_name} 不可包含空生命周期 ID")
|
||||
return values
|
||||
|
||||
|
||||
def _load_overrides(raw: dict) -> Overrides:
|
||||
"""加载资产覆盖项。"""
|
||||
if not isinstance(raw, dict):
|
||||
@@ -420,6 +469,23 @@ def _render_commented_mapping_yaml(mapping: Mapping) -> str:
|
||||
for status in mapping.package_rules.migrate_statuses:
|
||||
lines.append(f" - {_yaml_scalar(status)}")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"# ============== 套餐生命周期特殊裁决 ==============",
|
||||
"# life_id 来自 tbl_card_life.id;每项必须明确裁决该卡所有可迁移正式套餐。",
|
||||
])
|
||||
if not mapping.package_lifecycle_overrides:
|
||||
lines.append("package_lifecycle_overrides: []")
|
||||
else:
|
||||
lines.append("package_lifecycle_overrides:")
|
||||
for item in sorted(mapping.package_lifecycle_overrides.values(), key=lambda x: x.iccid):
|
||||
lines.extend([
|
||||
f" - iccid: {_yaml_scalar(item.iccid)}",
|
||||
f" active_life_id: {_yaml_scalar(item.active_life_id)}",
|
||||
f" pending_life_ids: {json.dumps(list(item.pending_life_ids), ensure_ascii=False)}",
|
||||
f" skipped_life_ids: {json.dumps(list(item.skipped_life_ids), ensure_ascii=False)}",
|
||||
])
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"# ============== 少量资产级覆盖 ==============",
|
||||
|
||||
Reference in New Issue
Block a user