This commit is contained in:
@@ -171,6 +171,23 @@ def _device_by_virtual_no(devices: list[DeviceRow]) -> dict[str, DeviceRow]:
|
||||
return {d.virtual_no: d for d in devices}
|
||||
|
||||
|
||||
def _device_virtual_no_set(devices: list[DeviceRow]) -> set[str]:
|
||||
return {d.virtual_no for d in devices if d.virtual_no}
|
||||
|
||||
|
||||
def _card_virtual_no_for_import(meta: LegacyCardMeta, device_virtual_nos: set[str]) -> str:
|
||||
"""返回可安全写入卡表的 virtual_no。
|
||||
|
||||
奇成里绑定设备的主卡可能把设备号放在卡 virtual_no 字段。新系统全局标识符要求
|
||||
设备号归属 device,不能再注册成 iot_card,否则资产解析会变成卡视角。
|
||||
"""
|
||||
if not meta.virtual_no:
|
||||
return ""
|
||||
if meta.virtual_no in device_virtual_nos:
|
||||
return ""
|
||||
return meta.virtual_no
|
||||
|
||||
|
||||
def _runtime_asset_for_card(card: CardRow, devices_by_virtual_no: dict[str, DeviceRow]) -> tuple[str, str] | None:
|
||||
"""返回运行态套餐/订单归属:(asset_type, asset_identifier)。
|
||||
|
||||
@@ -218,7 +235,7 @@ def write_step1(
|
||||
|
||||
cards_by_iccid_19 = {c.iccid_19: c for c in valid_cards}
|
||||
|
||||
_write_iot_cards(output_dir / "step1_01_iot_cards.sql", valid_cards, card_metas, mapping, user_id)
|
||||
_write_iot_cards(output_dir / "step1_01_iot_cards.sql", valid_cards, devices, card_metas, mapping, user_id)
|
||||
_write_devices(output_dir / "step1_02_devices.sql", devices, card_metas, mapping, user_id)
|
||||
_write_asset_identifiers(output_dir / "step1_03_asset_identifiers.sql", valid_cards, devices, card_metas)
|
||||
_write_asset_wallets(output_dir / "step1_04_asset_wallets.sql", valid_cards, devices)
|
||||
@@ -292,11 +309,14 @@ def _filter_cards_by_carrier(
|
||||
def _write_iot_cards(
|
||||
path: Path,
|
||||
cards: list[CardRow],
|
||||
devices: list[DeviceRow],
|
||||
card_metas: dict[str, LegacyCardMeta],
|
||||
mapping: Mapping,
|
||||
user_id: int,
|
||||
) -> None:
|
||||
"""写 tb_iot_card。元数据从奇成 card_metas + mapping 推导,iccid_19/iccid_20 用业务方两列。"""
|
||||
device_virtual_nos = _device_virtual_no_set(devices)
|
||||
devices_by_virtual_no = _device_by_virtual_no(devices)
|
||||
with path.open("w", encoding="utf-8") as f:
|
||||
f.write(_file_header("step1_01 卡资产导入 (tb_iot_card)"))
|
||||
for c in cards:
|
||||
@@ -309,7 +329,15 @@ def _write_iot_cards(
|
||||
|
||||
card_category = "industry" if c.is_industry else "normal"
|
||||
shop_code = _resolve_card_shop_code(c, meta, mapping)
|
||||
if c.bound_device_virtual_no:
|
||||
device = devices_by_virtual_no.get(c.bound_device_virtual_no)
|
||||
shop_code = _resolve_device_shop_code(device, card_metas, mapping) if device else ""
|
||||
series_id_expr = _target_series_id_expr_for_iccid_19(c.iccid_19, card_metas, mapping)
|
||||
card_virtual_no = _card_virtual_no_for_import(meta, device_virtual_nos)
|
||||
is_standalone = "FALSE" if c.bound_device_virtual_no else "TRUE"
|
||||
device_virtual_no = c.bound_device_virtual_no or ""
|
||||
card_status = 2 if shop_code else 1
|
||||
shop_id_expr = _shop_id_sub(shop_code)
|
||||
|
||||
f.write(
|
||||
"INSERT INTO tb_iot_card (\n"
|
||||
@@ -326,17 +354,35 @@ def _write_iot_cards(
|
||||
f" {_sql_str(c.iccid_full)}, {_sql_str(c.iccid_19)}, {iccid_20_expr},\n"
|
||||
f" {carrier.target_carrier_id}, {_sql_str(carrier.target_carrier_type)}, {_sql_str(carrier.target_carrier_name)},\n"
|
||||
f" {_sql_str(card_category)}, {_sql_str(mapping.migration_batch_no)},\n"
|
||||
" 1, 0, 0, 0,\n"
|
||||
" 1, 1, TRUE,\n"
|
||||
f" {card_status}, 0, 0, 0,\n"
|
||||
f" 1, 1, {is_standalone},\n"
|
||||
f" 'after_order', {_sql_str(_initial_polling_stop_reason(c))},\n"
|
||||
f" {_sql_str(meta.msisdn) if meta.msisdn else 'NULL'}, "
|
||||
f"{_sql_str(meta.virtual_no) if meta.virtual_no else 'NULL'}, "
|
||||
f"{_shop_id_sub(shop_code)}, {series_id_expr},\n"
|
||||
" '', '',\n"
|
||||
f"{_sql_str(card_virtual_no) if card_virtual_no else 'NULL'}, "
|
||||
f"{shop_id_expr}, {series_id_expr},\n"
|
||||
f" {_sql_str(device_virtual_no)}, '',\n"
|
||||
f" {user_id}, {user_id}, NOW(), NOW()\n"
|
||||
")\n"
|
||||
"ON CONFLICT (iccid) WHERE deleted_at IS NULL DO NOTHING;\n\n"
|
||||
)
|
||||
f.write(
|
||||
"UPDATE tb_iot_card\n"
|
||||
"SET\n"
|
||||
f" status = {card_status},\n"
|
||||
f" shop_id = {shop_id_expr},\n"
|
||||
f" virtual_no = {_sql_str(card_virtual_no) if card_virtual_no else 'NULL'},\n"
|
||||
f" is_standalone = {is_standalone},\n"
|
||||
f" device_virtual_no = {_sql_str(device_virtual_no)},\n"
|
||||
" updated_at = NOW()\n"
|
||||
f"WHERE iccid = {_sql_str(c.iccid_full)} AND deleted_at IS NULL\n"
|
||||
" AND (\n"
|
||||
f" status IS DISTINCT FROM {card_status}\n"
|
||||
f" OR shop_id IS DISTINCT FROM {shop_id_expr}\n"
|
||||
f" OR virtual_no IS DISTINCT FROM {_sql_str(card_virtual_no) if card_virtual_no else 'NULL'}\n"
|
||||
f" OR is_standalone IS DISTINCT FROM {is_standalone}\n"
|
||||
f" OR device_virtual_no IS DISTINCT FROM {_sql_str(device_virtual_no)}\n"
|
||||
" );\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _write_devices(
|
||||
@@ -349,9 +395,9 @@ def _write_devices(
|
||||
"""设备表 INSERT。
|
||||
|
||||
业务方不关心的字段都写默认值:
|
||||
device_name/device_model/manufacturer = NULL(可后台编辑)
|
||||
manufacturer = NULL(可后台编辑)
|
||||
max_sim_slots = 4
|
||||
status / asset_status / online_status = 1 / 1 / 0
|
||||
status = 有店铺归属则已分销,否则在库;asset_status / online_status = 1 / 0
|
||||
enable_polling = TRUE,realname_policy = 'after_order'
|
||||
software_version = '',switch_mode = '0'(自动切卡)
|
||||
"""
|
||||
@@ -359,11 +405,14 @@ def _write_devices(
|
||||
f.write(_file_header("step1_02 设备资产导入 (tb_device)"))
|
||||
for d in devices:
|
||||
shop_code = _resolve_device_shop_code(d, card_metas, mapping)
|
||||
device_status = 2 if shop_code else 1
|
||||
shop_id_expr = _shop_id_sub(shop_code)
|
||||
source_iccid = _device_package_source_iccid(d)
|
||||
series_id_expr = _target_series_id_expr_for_iccid_19(source_iccid, card_metas, mapping)
|
||||
f.write(
|
||||
"INSERT INTO tb_device (\n"
|
||||
" virtual_no, max_sim_slots, imei,\n"
|
||||
" device_name, device_model, device_type,\n"
|
||||
" status, asset_status, generation, online_status,\n"
|
||||
" enable_polling, realname_policy,\n"
|
||||
" software_version, switch_mode,\n"
|
||||
@@ -372,15 +421,53 @@ def _write_devices(
|
||||
") VALUES (\n"
|
||||
f" {_sql_str(d.virtual_no)}, {d.max_sim_slots}, "
|
||||
f"{_sql_str(d.imei) if d.imei else 'NULL'},\n"
|
||||
" 1, 1, 1, 0,\n"
|
||||
f" {_sql_str(d.device_name) if d.device_name else 'NULL'}, "
|
||||
f"{_sql_str(d.device_model) if d.device_model else 'NULL'}, "
|
||||
f"{_sql_str(d.device_type) if d.device_type else 'NULL'},\n"
|
||||
f" {device_status}, 1, 1, 0,\n"
|
||||
" TRUE, 'after_order',\n"
|
||||
" '', '0',\n"
|
||||
f" {_sql_str(mapping.migration_batch_no)}, {_shop_id_sub(shop_code)}, {series_id_expr},\n"
|
||||
f" {_sql_str(mapping.migration_batch_no)}, {shop_id_expr}, {series_id_expr},\n"
|
||||
f" {user_id}, {user_id}, NOW(), NOW()\n"
|
||||
")\n"
|
||||
"ON CONFLICT (virtual_no) WHERE deleted_at IS NULL DO NOTHING;\n\n"
|
||||
)
|
||||
|
||||
metadata_updates = [
|
||||
(column, value)
|
||||
for column, value in (
|
||||
("max_sim_slots", str(d.max_sim_slots)),
|
||||
("status", str(device_status)),
|
||||
("shop_id", shop_id_expr),
|
||||
("device_name", d.device_name),
|
||||
("device_model", d.device_model),
|
||||
("device_type", d.device_type),
|
||||
)
|
||||
if value or column in {"max_sim_slots", "status", "shop_id"}
|
||||
]
|
||||
if metadata_updates:
|
||||
sql_literal_columns = {"max_sim_slots", "status", "shop_id"}
|
||||
set_lines = [
|
||||
f"{column} = {value}" if column in sql_literal_columns else f"{column} = {_sql_str(value)}"
|
||||
for column, value in metadata_updates
|
||||
]
|
||||
diff_checks = [
|
||||
f"{column} IS DISTINCT FROM {value}"
|
||||
if column in sql_literal_columns
|
||||
else f"{column} IS DISTINCT FROM {_sql_str(value)}"
|
||||
for column, value in metadata_updates
|
||||
]
|
||||
set_sql = ",\n ".join(set_lines)
|
||||
diff_sql = " OR ".join(diff_checks)
|
||||
f.write(
|
||||
"UPDATE tb_device\n"
|
||||
"SET\n"
|
||||
f" {set_sql},\n"
|
||||
" updated_at = NOW()\n"
|
||||
f"WHERE virtual_no = {_sql_str(d.virtual_no)} AND deleted_at IS NULL\n"
|
||||
f" AND ({diff_sql});\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _write_asset_identifiers(
|
||||
path: Path,
|
||||
@@ -388,21 +475,23 @@ def _write_asset_identifiers(
|
||||
devices: list[DeviceRow],
|
||||
card_metas: dict[str, LegacyCardMeta],
|
||||
) -> None:
|
||||
"""卡的 iccid + 虚拟号(来自奇成)和设备的 virtual_no 都注册。"""
|
||||
"""卡的 iccid + 安全卡虚拟号和设备 virtual_no 都注册。"""
|
||||
device_virtual_nos = _device_virtual_no_set(devices)
|
||||
with path.open("w", encoding="utf-8") as f:
|
||||
f.write(_file_header("step1_03 资产标识符注册 (tb_asset_identifier)"))
|
||||
for c in cards:
|
||||
meta = card_metas[c.iccid_19]
|
||||
card_virtual_no = _card_virtual_no_for_import(meta, device_virtual_nos)
|
||||
f.write(
|
||||
"INSERT INTO tb_asset_identifier (identifier, asset_type, asset_id)\n"
|
||||
f"SELECT {_sql_str(c.iccid_full)}, 'iot_card', id FROM tb_iot_card\n"
|
||||
f"WHERE iccid = {_sql_str(c.iccid_full)} AND deleted_at IS NULL\n"
|
||||
"ON CONFLICT (identifier) DO NOTHING;\n\n"
|
||||
)
|
||||
if meta.virtual_no:
|
||||
if card_virtual_no:
|
||||
f.write(
|
||||
"INSERT INTO tb_asset_identifier (identifier, asset_type, asset_id)\n"
|
||||
f"SELECT {_sql_str(meta.virtual_no)}, 'iot_card', id FROM tb_iot_card\n"
|
||||
f"SELECT {_sql_str(card_virtual_no)}, 'iot_card', id FROM tb_iot_card\n"
|
||||
f"WHERE iccid = {_sql_str(c.iccid_full)} AND deleted_at IS NULL\n"
|
||||
"ON CONFLICT (identifier) DO NOTHING;\n\n"
|
||||
)
|
||||
@@ -433,6 +522,17 @@ def _write_asset_wallets(path: Path, cards: list[CardRow], devices: list[DeviceR
|
||||
f"WHERE iccid = {_sql_str(c.iccid_full)} AND deleted_at IS NULL\n"
|
||||
"ON CONFLICT (resource_type, resource_id) WHERE deleted_at IS NULL DO NOTHING;\n\n"
|
||||
)
|
||||
f.write(
|
||||
"UPDATE tb_asset_wallet w\n"
|
||||
"SET shop_id_tag = COALESCE(c.shop_id, 0),\n"
|
||||
" updated_at = NOW()\n"
|
||||
"FROM tb_iot_card c\n"
|
||||
"WHERE w.resource_type = 'iot_card'\n"
|
||||
" AND w.resource_id = c.id\n"
|
||||
" AND w.deleted_at IS NULL\n"
|
||||
f" AND c.iccid = {_sql_str(c.iccid_full)} AND c.deleted_at IS NULL\n"
|
||||
" AND w.shop_id_tag IS DISTINCT FROM COALESCE(c.shop_id, 0);\n\n"
|
||||
)
|
||||
for d in devices:
|
||||
f.write(
|
||||
"INSERT INTO tb_asset_wallet (\n"
|
||||
@@ -447,6 +547,17 @@ def _write_asset_wallets(path: Path, cards: list[CardRow], devices: list[DeviceR
|
||||
f"WHERE virtual_no = {_sql_str(d.virtual_no)} AND deleted_at IS NULL\n"
|
||||
"ON CONFLICT (resource_type, resource_id) WHERE deleted_at IS NULL DO NOTHING;\n\n"
|
||||
)
|
||||
f.write(
|
||||
"UPDATE tb_asset_wallet w\n"
|
||||
"SET shop_id_tag = COALESCE(d.shop_id, 0),\n"
|
||||
" updated_at = NOW()\n"
|
||||
"FROM tb_device d\n"
|
||||
"WHERE w.resource_type = 'device'\n"
|
||||
" AND w.resource_id = d.id\n"
|
||||
" AND w.deleted_at IS NULL\n"
|
||||
f" AND d.virtual_no = {_sql_str(d.virtual_no)} AND d.deleted_at IS NULL\n"
|
||||
" AND w.shop_id_tag IS DISTINCT FROM COALESCE(d.shop_id, 0);\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _write_sim_bindings(
|
||||
@@ -475,6 +586,34 @@ def _write_sim_bindings(
|
||||
f" AND c.iccid = {_sql_str(card.iccid_full)} AND c.deleted_at IS NULL\n"
|
||||
"ON CONFLICT (device_id, slot_position) WHERE bind_status = 1 AND deleted_at IS NULL DO NOTHING;\n\n"
|
||||
)
|
||||
f.write(
|
||||
"UPDATE tb_iot_card c\n"
|
||||
"SET is_standalone = FALSE,\n"
|
||||
" device_virtual_no = d.virtual_no,\n"
|
||||
" shop_id = d.shop_id,\n"
|
||||
" status = CASE WHEN d.shop_id IS NULL THEN 1 ELSE 2 END,\n"
|
||||
" updated_at = NOW()\n"
|
||||
"FROM tb_device d\n"
|
||||
f"WHERE d.virtual_no = {_sql_str(d.virtual_no)} AND d.deleted_at IS NULL\n"
|
||||
f" AND c.iccid = {_sql_str(card.iccid_full)} AND c.deleted_at IS NULL\n"
|
||||
" AND (\n"
|
||||
" c.is_standalone IS DISTINCT FROM FALSE\n"
|
||||
" OR c.device_virtual_no IS DISTINCT FROM d.virtual_no\n"
|
||||
" OR c.shop_id IS DISTINCT FROM d.shop_id\n"
|
||||
" OR c.status IS DISTINCT FROM CASE WHEN d.shop_id IS NULL THEN 1 ELSE 2 END\n"
|
||||
" );\n\n"
|
||||
)
|
||||
f.write(
|
||||
"UPDATE tb_asset_wallet w\n"
|
||||
"SET shop_id_tag = COALESCE(c.shop_id, 0),\n"
|
||||
" updated_at = NOW()\n"
|
||||
"FROM tb_iot_card c\n"
|
||||
f"WHERE c.iccid = {_sql_str(card.iccid_full)} AND c.deleted_at IS NULL\n"
|
||||
" AND w.resource_type = 'iot_card'\n"
|
||||
" AND w.resource_id = c.id\n"
|
||||
" AND w.deleted_at IS NULL\n"
|
||||
" AND w.shop_id_tag IS DISTINCT FROM COALESCE(c.shop_id, 0);\n\n"
|
||||
)
|
||||
|
||||
|
||||
def _write_shop_allocations(
|
||||
|
||||
Reference in New Issue
Block a user