"""Repair Odoo sale-order references accidentally stored as customer names. v4.9.25.3 fixes the source of the issue. This script repairs rows already created by older v4.9.25.x builds where customers.name became S00xxx although metadata.raw_customer_payload.partner_name contains the real fiscal customer. Dry-run by default. Use --apply to update rows. """ from __future__ import annotations import argparse from typing import Any, Dict, List from sqlalchemy import text from app.db import engine def _partner_name_from_metadata(metadata: Any) -> str: if not isinstance(metadata, dict): return "" raw = metadata.get("raw_customer_payload") if not isinstance(raw, dict): return "" partner_name = str(raw.get("partner_name") or "").strip() if partner_name: return partner_name partner_id = raw.get("partner_id") if isinstance(partner_id, (list, tuple)) and len(partner_id) > 1: return str(partner_id[1] or "").strip() return "" def find_rows() -> List[Dict[str, Any]]: with engine.begin() as conn: rows = conn.execute(text(""" SELECT id::text, name, tax_id, email, metadata FROM customers WHERE name ~ '^S[0-9]{4,}' AND metadata->>'source_system' = 'odoo' ORDER BY updated_at DESC """)).mappings().all() result: List[Dict[str, Any]] = [] for row in rows: partner_name = _partner_name_from_metadata(row.get("metadata")) if partner_name and not partner_name.upper().startswith("S00"): data = dict(row) data["new_name"] = partner_name result.append(data) return result def apply(rows: List[Dict[str, Any]]) -> int: updated = 0 with engine.begin() as conn: for row in rows: conn.execute(text(""" UPDATE customers SET name = :new_name, metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object( 'odoo_sale_order_name_repaired', true, 'previous_customer_name', CAST(:old_name AS TEXT) ), updated_at = now() WHERE id = CAST(:id AS UUID) """), {"id": row["id"], "old_name": row["name"], "new_name": row["new_name"]}) updated += 1 return updated def main() -> None: parser = argparse.ArgumentParser(description="Repair Odoo S00xxx customer names.") parser.add_argument("--apply", action="store_true", help="Apply updates. Default is dry-run.") args = parser.parse_args() rows = find_rows() print(f"Clientes Odoo com nome S00xxx reparáveis: {len(rows)}") for row in rows[:50]: print(f"- {row['name']} -> {row['new_name']} | NIF {row.get('tax_id') or '-'} | email {row.get('email') or '-'}") if len(rows) > 50: print(f"... mais {len(rows)-50}") if not args.apply: print("Dry-run. Para aplicar: repetir com --apply") return print(f"Atualizados: {apply(rows)}") if __name__ == "__main__": main()