#!/usr/bin/env python3 """One-time sync of post-order packaging balances into backup BOM snapshots.""" import argparse import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) def post_pack_for_item(item, product_qty, bom_usage): """Mirror the value that pending detail displays as 下单后包材.""" pre_pack = item.get('pre_pack_qty') usage = item.get('quantity') if usage in (None, ''): usage = bom_usage.get(str(item.get('material_code') or '')) if pre_pack in (None, '') or usage in (None, ''): stored = item.get('post_pack_qty') return float(stored) if stored not in (None, '') else 0 try: return round(float(pre_pack) + float(item.get('actual_quantity') or 0) - float(product_qty) * float(usage), 2) except (TypeError, ValueError): return 0 def main(): parser = argparse.ArgumentParser() parser.add_argument('--local', action='store_true', help='Use the local test database') parser.add_argument('--dry-run', action='store_true') args = parser.parse_args() if args.local: import test_wsgi # noqa: F401 - installs local DB configuration from routes.purchase import _extract_product_actual_qty from services.db import get_db, query rows = query( "SELECT id, product_code, formal_order_qty, actual_order_qty, bom_items " "FROM pending_orders WHERE status IN ('ordered','rolled_back') ORDER BY id" ) usage_cache = {} updates = [] changed_items = 0 for row in rows: try: items = json.loads(row['bom_items']) if row.get('bom_items') else [] except (TypeError, ValueError): continue if not isinstance(items, list) or not items: continue product_code = str(row.get('product_code') or '') product_qty = _extract_product_actual_qty(items, product_code) if product_qty is None: product_qty = int(row.get('formal_order_qty') or row.get('actual_order_qty') or 0) if product_code not in usage_cache: usage_cache[product_code] = { str(r.get('material_code') or ''): float(r.get('usage_qty') or 0) for r in query("SELECT material_code, quantity AS usage_qty FROM bom WHERE product_code=%s", (product_code,)) } changed = 0 for item in items: if not isinstance(item, dict) or str(item.get('material_code') or '').startswith('成品_'): continue value = post_pack_for_item(item, product_qty, usage_cache[product_code]) if item.get('post_pack_qty') != value: item['post_pack_qty'] = value changed += 1 if changed: updates.append((json.dumps(items, ensure_ascii=False), row['id'])) changed_items += changed if not args.dry_run and updates: conn = get_db() try: with conn.cursor() as cur: cur.executemany("UPDATE pending_orders SET bom_items=%s WHERE id=%s", updates) conn.commit() except Exception: conn.rollback() raise finally: conn.close() print(json.dumps({'records': len(rows), 'updated_records': len(updates), 'updated_items': changed_items, 'dry_run': args.dry_run}, ensure_ascii=False)) if __name__ == '__main__': main()