#!/usr/bin/env python3 """Backfill Jasmin product mappings for Odoo-imported opportunity lines. Maps Odoo line metadata product ids to the ClientFlow catalogue convention: ``product_id=3`` -> ``products.sku='ODOO-3'`` -> ``jasmin_sales_item``. Usage: PYTHONPATH=. python scripts/backfill_opportunity_product_mappings.py PYTHONPATH=. python scripts/backfill_opportunity_product_mappings.py --opportunity-id --apply """ from __future__ import annotations import argparse from sqlalchemy import text from app.db import engine from app.product_service import ensure_product_schema def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--opportunity-id", default="", help="Optional opportunity UUID to restrict the backfill") parser.add_argument("--apply", action="store_true", help="Apply changes. Without this flag it runs as dry-run.") args = parser.parse_args() ensure_product_schema() where_opp = "AND oi.opportunity_id = CAST(:opportunity_id AS UUID)" if args.opportunity_id else "" params = {"opportunity_id": args.opportunity_id} if args.opportunity_id else {} with engine.begin() as conn: rows = conn.execute(text(f""" SELECT oi.id::text AS item_id, oi.opportunity_id::text AS opportunity_id, oi.product_name, oi.sku AS old_sku, oi.jasmin_sales_item AS old_jasmin_sales_item, oi.metadata->>'product_id' AS odoo_product_id, p.id::text AS product_id, p.sku AS new_sku, p.jasmin_sales_item AS new_jasmin_sales_item, p.name AS catalog_name FROM opportunity_items oi JOIN products p ON p.sku = ('ODOO-' || (oi.metadata->>'product_id')) WHERE oi.metadata->>'source_system' = 'odoo' AND COALESCE(oi.metadata->>'product_id','') <> '' AND ( oi.product_id IS DISTINCT FROM p.id OR COALESCE(oi.sku,'') IS DISTINCT FROM COALESCE(p.sku,'') OR COALESCE(oi.jasmin_sales_item,'') IS DISTINCT FROM COALESCE(p.jasmin_sales_item,'') ) {where_opp} ORDER BY oi.created_at DESC """), params).mappings().all() print(f"Candidatos a atualizar: {len(rows)}") for row in rows: print(dict(row)) if args.apply and rows: result = conn.execute(text(f""" UPDATE opportunity_items oi SET product_id = p.id, sku = p.sku, jasmin_sales_item = p.jasmin_sales_item, metadata = COALESCE(oi.metadata, '{{}}'::jsonb) || jsonb_build_object( 'resolved_sku', p.sku, 'resolved_jasmin_sales_item', p.jasmin_sales_item, 'product_mapping_status', CASE WHEN COALESCE(p.jasmin_sales_item,'') <> '' THEN 'mapped' ELSE 'missing_jasmin' END, 'catalog_name', p.name, 'backfilled_at', now()::text ), updated_at = now() FROM products p WHERE p.sku = ('ODOO-' || (oi.metadata->>'product_id')) AND oi.metadata->>'source_system' = 'odoo' AND COALESCE(oi.metadata->>'product_id','') <> '' {where_opp} """), params) print(f"Aplicado: {result.rowcount or 0} linha(s) atualizada(s)") elif not args.apply: print("Dry-run. Usa --apply para aplicar.") return 0 if __name__ == "__main__": raise SystemExit(main())