92 lines
3.5 KiB
Python
92 lines
3.5 KiB
Python
#!/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()
|