from __future__ import annotations import argparse from typing import Any from sqlalchemy import text from app.db import engine from app.opportunity_action_task_materializer import MATERIALIZED_ACTIONS, ensure_pending_task_for_next_action from app.opportunity_next_action_service import get_opportunity_next_action def _candidate_opportunities(limit: int) -> list[dict[str, Any]]: sql = text(""" SELECT DISTINCT o.id::text, o.title, o.customer_name, o.updated_at FROM opportunities o WHERE COALESCE(o.status, 'open') = 'open' AND COALESCE(o.stage, '') NOT IN ('WON','LOST','NO_INTEREST') ORDER BY o.updated_at DESC NULLS LAST LIMIT :limit """) with engine.begin() as conn: return [dict(r) for r in conn.execute(sql, {"limit": limit}).mappings().all()] def _next_action_code(decision: dict[str, Any]) -> str: return str(decision.get("action_code") or (decision.get("next_action") or {}).get("code") or "").upper() def main() -> None: parser = argparse.ArgumentParser(description="Cria tasks pendentes para próximas ações humanas materializáveis.") parser.add_argument("--apply", action="store_true", help="grava alterações; sem isto apenas mostra dry-run") parser.add_argument("--limit", type=int, default=300) parser.add_argument("--action", choices=sorted(MATERIALIZED_ACTIONS), help="filtrar por action_code") args = parser.parse_args() candidates = _candidate_opportunities(args.limit) print(f"apply={args.apply} candidates={len(candidates)} materialized_actions={sorted(MATERIALIZED_ACTIONS)}") to_create: list[tuple[dict[str, Any], dict[str, Any], str]] = [] skipped = 0 errors = 0 for opp in candidates: try: decision = get_opportunity_next_action(opp["id"]) code = _next_action_code(decision) if code in MATERIALIZED_ACTIONS and (not args.action or code == args.action): to_create.append((opp, decision, code)) else: skipped += 1 except Exception as exc: errors += 1 print(f"ERROR opp={opp.get('id')} title={opp.get('title')} error={exc}") print(f"TO_CREATE_OR_EXISTING={len(to_create)} skipped={skipped} errors={errors}") for opp, decision, code in to_create: doc = decision.get("document_number") or (decision.get("next_action") or {}).get("document_number") or "" print(f"- action={code} opp={opp['id']} doc={doc} title={opp.get('title')} customer={opp.get('customer_name')}") if args.apply: result = ensure_pending_task_for_next_action( opp["id"], decision, source="repair_missing_materialized_next_action_tasks", actor="repair-script", ) print(f" result={result}") print("SUMMARY") print(f"created_or_existing={len(to_create) if args.apply else 0}") print(f"dry_run={not args.apply}") if __name__ == "__main__": main()