chore: fork from IRIS track 供 LICA 部门独立运行

- 复制来源: /home/yueli/track @ 192c8ee (feature/ai-audit-update)
- 组织隔离目标: LICA
- 端口规划: 前端 8030 / 后端 8031 / 数据库 8032
- 已排除 deploy.sh、deploy_full.sh、docker-compose.prod.yml(IRIS 生产发布脚本)
- 已排除工作区未提交改动,取干净的 192c8ee 状态
This commit is contained in:
2026-09-21 15:56:52 +08:00
commit 3286a11bc7
212 changed files with 44060 additions and 0 deletions

View File

@ -0,0 +1 @@
"""一次性运维脚本集合按模块方式运行python -m scripts.<name>"""

View File

@ -0,0 +1,84 @@
"""一次性数据清洗 — 修复 Product.status 与 overall_status 脱节的历史存量
背景
----
app/core/lifecycle.py 记录了历史缺陷:早期 receive_task / transfer_task 只改
overall_status、不改 status导致部分设备的 status 残留为 'pending' / 'OUTBOUND'
等旧值。
现状(重要)
----------
所有 overall_status 的写入点**都已补上 sync_product_status()**
task_service.py:387 / :691 / :1007、product_service.py:512、
webhooks.py:84 / :256、product_finalize_service.py:107
因此新数据不会再脱节,本脚本只处理历史存量。
影响面
------
前端列表筛选走的是 macro_status由 overall_status 实时派生,见
product_service._resolve_macro_status所以这批脏数据**基本不影响页面展示**。
但 _mark_after_sales_if_reactivated 等逻辑会读 product.status 判断"是否出库回流"
脏值可能引发误判 —— 故仍需清洗。
用法
----
python -m scripts.fix_product_status # 干跑:只打印待修复清单
python -m scripts.fix_product_status --apply # 确认后真正写库
"""
from __future__ import annotations
import argparse
import asyncio
from sqlalchemy import select
from app.core.database import AsyncSessionLocal
from app.core.lifecycle import overall_to_product_status
from app.models.product import Product
async def main(apply: bool) -> None:
async with AsyncSessionLocal() as db:
products = (
await db.execute(select(Product).order_by(Product.serial_number))
).scalars().all()
fixes: list[tuple[Product, str, str]] = []
for p in products:
expected = overall_to_product_status(p.overall_status)
current = (p.status or "").strip()
# 大小写不敏感比较:历史数据里存在小写 'pending'
if current.upper() == expected:
continue
fixes.append((p, current or "(空)", expected))
print(f"扫描 {len(products)} 台设备,需修复 {len(fixes)}\n")
if fixes:
header = f"{'序列号':<18}{'overall_status':<16}{'当前 status':<14}→ 目标 status"
print(header)
print("-" * len(header) * 2)
for p, cur, exp in fixes:
overall = p.overall_status or "(NULL)"
phase = p.lifecycle_phase or "-"
print(f"{p.serial_number:<18}{overall:<16}{cur:<14}{exp} [{phase}]")
else:
print("所有设备的 status 均已与 overall_status 对齐。")
if not apply:
print("\n[干跑] 未写库。确认清单无误后,加 --apply 执行。")
return
if not fixes:
return
for p, _cur, exp in fixes:
p.status = exp
await db.commit()
print(f"\n[已提交] {len(fixes)} 台设备的 status 已对齐到 overall_status。")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="对齐 Product.status 与 overall_status 的历史脏数据")
parser.add_argument("--apply", action="store_true", help="真正写库(默认仅干跑)")
asyncio.run(main(parser.parse_args().apply))