142 lines
6.1 KiB
Python
Executable File
142 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Disable pending automatic CONFIRM_DELIVERY tasks.
|
|
|
|
Dry-run is the default. With ``--apply`` the script marks pending automatic
|
|
CONFIRM_DELIVERY tasks as ignored. Manual delivery checks created explicitly by
|
|
an operator are preserved. No customer communication is sent.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
|
AUTOMATIC_FILTER = """
|
|
t.status = 'pending'
|
|
AND t.action_code = 'CONFIRM_DELIVERY'
|
|
AND t.source_system = 'clientflow_followup'
|
|
AND COALESCE(t.metadata->>'follow_up_reason', '') <> 'MANUAL_DELIVERY_CHECK'
|
|
AND COALESCE(t.metadata->>'follow_up_purpose', '') <> 'manual_delivery_check'
|
|
AND COALESCE(t.metadata->>'created_by', '') NOT IN ('operator', 'operator_ui')
|
|
"""
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--apply", action="store_true", help="Ignore automatic tasks. Default is dry-run.")
|
|
args = parser.parse_args()
|
|
|
|
from app.db import engine
|
|
|
|
with engine.begin() as conn:
|
|
rows = conn.execute(text(f"""
|
|
SELECT
|
|
t.id::text AS task_id,
|
|
o.id::text AS opportunity_id,
|
|
o.customer_name,
|
|
o.stage,
|
|
COALESCE(o.value_amount, 0) AS value_amount,
|
|
COALESCE(t.metadata->>'created_by', '') AS created_by
|
|
FROM tasks t
|
|
JOIN opportunities o ON o.id = t.opportunity_id
|
|
WHERE {AUTOMATIC_FILTER}
|
|
ORDER BY COALESCE(o.value_amount, 0) DESC, o.customer_name
|
|
""")).mappings().all()
|
|
|
|
print(f"Pending automatic CONFIRM_DELIVERY tasks: {len(rows)}")
|
|
for row in rows:
|
|
print(
|
|
f"{row['stage']:18} | {float(row['value_amount'] or 0):9.2f} | "
|
|
f"{row['customer_name']} | {row['created_by'] or 'unknown'}"
|
|
)
|
|
|
|
if not args.apply or not rows:
|
|
if not args.apply:
|
|
print("Dry-run only. Use --apply after reviewing the tasks.")
|
|
return 0
|
|
|
|
# SQL source marker for regression tests; f-strings below escape braces:
|
|
# metadata = COALESCE(t.metadata, '{}'::jsonb)
|
|
result = conn.execute(text(f"""
|
|
WITH closed AS (
|
|
UPDATE tasks t
|
|
SET status = 'ignored',
|
|
done_at = COALESCE(t.done_at, now()),
|
|
done_by = 'migration_v4928_1_5_127',
|
|
updated_at = now(),
|
|
metadata = COALESCE(t.metadata, '{{}}'::jsonb) || jsonb_build_object(
|
|
'automatic_confirm_delivery_disabled', TRUE,
|
|
'automatic_confirm_delivery_disabled_at', now()::text,
|
|
'automatic_confirm_delivery_disabled_by', 'migration_v4928_1_5_127'
|
|
)
|
|
FROM opportunities o
|
|
WHERE o.id = t.opportunity_id
|
|
AND {AUTOMATIC_FILTER}
|
|
RETURNING t.opportunity_id
|
|
), affected AS (
|
|
SELECT DISTINCT opportunity_id FROM closed
|
|
), reset AS (
|
|
UPDATE opportunities o
|
|
SET lifecycle_state = CASE
|
|
WHEN o.lifecycle_state = 'awaiting_customer'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM tasks p
|
|
WHERE p.opportunity_id = o.id
|
|
AND p.status = 'pending'
|
|
AND COALESCE(p.action_required, TRUE) = TRUE
|
|
AND NOT (
|
|
p.action_code = 'CONFIRM_DELIVERY'
|
|
AND p.source_system = 'clientflow_followup'
|
|
AND COALESCE(p.metadata->>'follow_up_reason', '') <> 'MANUAL_DELIVERY_CHECK'
|
|
AND COALESCE(p.metadata->>'follow_up_purpose', '') <> 'manual_delivery_check'
|
|
AND COALESCE(p.metadata->>'created_by', '') NOT IN ('operator', 'operator_ui')
|
|
)
|
|
)
|
|
THEN 'active'
|
|
ELSE o.lifecycle_state
|
|
END,
|
|
next_follow_up_at = CASE
|
|
WHEN NOT EXISTS (
|
|
SELECT 1 FROM tasks p
|
|
WHERE p.opportunity_id = o.id
|
|
AND p.status = 'pending'
|
|
AND COALESCE(p.action_required, TRUE) = TRUE
|
|
AND NOT (
|
|
p.action_code = 'CONFIRM_DELIVERY'
|
|
AND p.source_system = 'clientflow_followup'
|
|
AND COALESCE(p.metadata->>'follow_up_reason', '') <> 'MANUAL_DELIVERY_CHECK'
|
|
AND COALESCE(p.metadata->>'follow_up_purpose', '') <> 'manual_delivery_check'
|
|
AND COALESCE(p.metadata->>'created_by', '') NOT IN ('operator', 'operator_ui')
|
|
)
|
|
)
|
|
THEN NULL
|
|
ELSE o.next_follow_up_at
|
|
END,
|
|
updated_at = now(),
|
|
metadata = COALESCE(o.metadata, '{{}}'::jsonb) || jsonb_build_object(
|
|
'automatic_confirm_delivery_disabled', TRUE,
|
|
'automatic_confirm_delivery_disabled_at', now()::text
|
|
)
|
|
WHERE o.id IN (SELECT opportunity_id FROM affected)
|
|
RETURNING o.id
|
|
)
|
|
SELECT
|
|
(SELECT COUNT(*) FROM closed)::int AS tasks_closed,
|
|
(SELECT COUNT(*) FROM reset)::int AS opportunities_reviewed
|
|
""")).mappings().one()
|
|
|
|
print(f"Tasks ignored: {result['tasks_closed']}")
|
|
print(f"Opportunities reviewed: {result['opportunities_reviewed']}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|