"""大批直通良率专用回刷脚本(gxerp) 只解析检验批的大批归属(BatchSt.zt_batch)并重算各大批的 直通_* 统计, 不重建批次的其他统计数据(那个用 correct_batchst.py)。 用法: python scripts/correct_zt_gxerp.py [起始日期] [--fresh] 起始日期默认 2025-06-17; --fresh 清除大批已缓存的白料数, 强制重新回溯 """ import os import sys import django CUR_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(CUR_DIR) sys.path.insert(0, BASE_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") django.setup() from datetime import datetime from django.db.models import Q from apps.wpm.models import BatchSt from apps.wpm.scripts.batch_gxerp import resolve_zt_big, cal_zt_big import json from apps.utils.tools import MyJSONEncoder def main(since: str = "2025-06-17", fresh: bool = False): cond = Q(data__has_key="尺寸检验_日期") | Q(data__has_key="外观检验_日期") if fresh: # 纠错模式需覆盖曾被(错误)锁定/计算过的批 cond = cond | Q(zt_batch__isnull=False) | Q(data__has_key="直通_良率") qs = BatchSt.objects.filter( create_time__gte=datetime.strptime(since, "%Y-%m-%d"), version=1 ).filter(cond).order_by("create_time") total = qs.count() print(f"检验批 {total} 个, 开始解析大批归属 (fresh={fresh})", flush=True) bigs = [] # 保序去重 seen = set() for ind, bs in enumerate(qs.iterator()): try: # fresh 模式忽略旧锁, 从谱系重新解析(纠正多级拆批错锁) big = resolve_zt_big(bs, bs.data or {}, use_lock=not fresh) if big and bs.zt_batch != big: bs.zt_batch = big bs.save(update_fields=["zt_batch", "update_time"]) except Exception as e: print(f"[{ind + 1}/{total}] {bs.batch} 归属解析失败: {e}", flush=True) continue if big and big not in seen: seen.add(big) bigs.append(big) if (ind + 1) % 1000 == 0: print(f"归属解析 {ind + 1}/{total}", flush=True) if fresh: # 清理: 曾被错当成大批计算过直通字段、如今无任何子批指向的批, # 剥离其直通键并解除自指锁定, 交由后续大批计算重新归属 cleaned = 0 for bs in BatchSt.objects.filter( create_time__gte=datetime.strptime(since, "%Y-%m-%d"), version=1, data__has_key="直通_良率", ).iterator(): if bs.batch in seen: continue if BatchSt.objects.filter(zt_batch=bs.batch, version=1).exclude(id=bs.id).exists(): continue data = bs.data for k in ["直通_子批次", "直通_总合格数", "直通_白料数", "直通_良率", "直通_口径"]: data.pop(k, None) bs.data = json.loads(json.dumps(data, cls=MyJSONEncoder)) if bs.zt_batch == bs.batch: bs.zt_batch = None bs.save(update_fields=["data", "zt_batch", "update_time"]) else: bs.save(update_fields=["data", "update_time"]) cleaned += 1 print(f"清理错误大批标记: {bs.batch}", flush=True) print(f"清理 {cleaned} 个", flush=True) print(f"共 {len(bigs)} 个大批, 开始计算直通良率", flush=True) for ind, big in enumerate(bigs): try: if fresh: big_bs = BatchSt.objects.filter(batch=big, version=1).first() if big_bs and "直通_白料数" in (big_bs.data or {}): data = big_bs.data data.pop("直通_白料数", None) big_bs.data = json.loads(json.dumps(data, cls=MyJSONEncoder)) big_bs.save(update_fields=["data", "update_time"]) res = cal_zt_big(big) or {} print(f"[{ind + 1}/{len(bigs)}] {big} 良率={res.get('良率')} 白料={res.get('白料数')} " f"合格={res.get('总合格数')} 子批={res.get('子批数')} 口径={res.get('口径')}", flush=True) except Exception as e: print(f"[{ind + 1}/{len(bigs)}] {big} 计算失败: {e}", flush=True) print("done") if __name__ == "__main__": args = [a for a in sys.argv[1:] if a != "--fresh"] main(since=args[0] if args else "2025-06-17", fresh="--fresh" in sys.argv)