Import ClientFlow production v4928.1.5.132.4
This commit is contained in:
124
scripts/repair_assigned_picking_shipment_tasks.py
Normal file
124
scripts/repair_assigned_picking_shipment_tasks.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Repair premature shipment tasks created from Odoo picking state assigned.
|
||||
|
||||
Dry-run by default. The script only touches pending CREATE_SHIPMENT tasks when:
|
||||
- the opportunity has an Odoo physical_status link with an outgoing picking assigned;
|
||||
- no physical_validation=validated link exists;
|
||||
- the picking is not done.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from app.db import engine
|
||||
|
||||
|
||||
def _rows(limit: int) -> list[dict]:
|
||||
with engine.begin() as conn:
|
||||
rows = conn.execute(text("""
|
||||
SELECT
|
||||
t.id::text AS task_id,
|
||||
o.id::text AS opportunity_id,
|
||||
o.customer_name,
|
||||
o.stage,
|
||||
t.action_code,
|
||||
t.metadata AS task_metadata,
|
||||
ps.payload AS physical_payload,
|
||||
COALESCE(pv.status, '') AS validation_status
|
||||
FROM tasks t
|
||||
JOIN opportunities o ON o.id = t.opportunity_id
|
||||
JOIN operation_links ps
|
||||
ON ps.opportunity_id = o.id
|
||||
AND ps.system = 'odoo'
|
||||
AND ps.external_type = 'physical_status'
|
||||
LEFT JOIN operation_links pv
|
||||
ON pv.opportunity_id = o.id
|
||||
AND pv.system = 'odoo'
|
||||
AND pv.external_type = 'physical_validation'
|
||||
WHERE t.status = 'pending'
|
||||
AND t.action_code = 'CREATE_SHIPMENT'
|
||||
AND COALESCE(pv.status, '') <> 'validated'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements(
|
||||
COALESCE(ps.payload->'outgoing_pickings', ps.payload->'pickings', '[]'::jsonb)
|
||||
) p
|
||||
WHERE COALESCE(p->>'state', '') = 'assigned'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements(
|
||||
COALESCE(ps.payload->'outgoing_pickings', ps.payload->'pickings', '[]'::jsonb)
|
||||
) p
|
||||
WHERE COALESCE(p->>'state', '') = 'done'
|
||||
)
|
||||
ORDER BY t.created_at
|
||||
LIMIT :limit
|
||||
"""), {"limit": limit}).mappings().all()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--apply', action='store_true')
|
||||
parser.add_argument('--limit', type=int, default=100)
|
||||
args = parser.parse_args()
|
||||
|
||||
rows = _rows(args.limit)
|
||||
print(f"Premature CREATE_SHIPMENT tasks: {len(rows)}")
|
||||
for row in rows:
|
||||
print(f"{row['stage']:18} | {row['customer_name']}")
|
||||
if not args.apply:
|
||||
print('Dry-run only. Use --apply after reviewing the tasks.')
|
||||
return 0
|
||||
|
||||
if not rows:
|
||||
return 0
|
||||
|
||||
with engine.begin() as conn:
|
||||
for row in rows:
|
||||
metadata = dict(row.get('task_metadata') or {})
|
||||
metadata.update({
|
||||
'corrected_by': 'migration_v4928_1_5_130',
|
||||
'previous_action_code': 'CREATE_SHIPMENT',
|
||||
'correction_reason': 'Odoo assigned means stock reserved; physical validation still required',
|
||||
})
|
||||
conn.execute(text("""
|
||||
UPDATE tasks
|
||||
SET action_code = 'VALIDATE_PHYSICAL_ORDER',
|
||||
route = 'logistica',
|
||||
action = 'Validar encomenda física',
|
||||
note = 'O picking está reservado no Odoo. Confirmar produtos, quantidades, embalagem e preparação física antes de criar o envio.',
|
||||
safe_to_post = FALSE,
|
||||
metadata = CAST(:metadata AS JSONB),
|
||||
updated_at = now()
|
||||
WHERE id = CAST(:task_id AS UUID)
|
||||
AND status = 'pending'
|
||||
AND action_code = 'CREATE_SHIPMENT'
|
||||
"""), {'task_id': row['task_id'], 'metadata': json.dumps(metadata, ensure_ascii=False)})
|
||||
conn.execute(text("""
|
||||
UPDATE opportunities
|
||||
SET stage = CASE WHEN stage = 'READY_TO_SHIP' THEN 'ORDER_PREPARATION' ELSE stage END,
|
||||
last_action_code = 'VALIDATE_PHYSICAL_ORDER',
|
||||
updated_at = now(),
|
||||
metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object(
|
||||
'assigned_picking_requires_physical_validation', TRUE,
|
||||
'assigned_picking_gate_version', 'v4928.1.5.130'
|
||||
)
|
||||
WHERE id = CAST(:opportunity_id AS UUID)
|
||||
"""), {'opportunity_id': row['opportunity_id']})
|
||||
print(f"Tasks converted: {len(rows)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user