Release v4928.1.4.2 stable
This commit is contained in:
89
scripts/backfill_opportunity_product_mappings.py
Executable file
89
scripts/backfill_opportunity_product_mappings.py
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/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 <uuid> --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())
|
||||
Reference in New Issue
Block a user