Import ClientFlow production v4928.1.5.132.4
This commit is contained in:
141
scripts/disable_automatic_confirm_delivery.py
Executable file
141
scripts/disable_automatic_confirm_delivery.py
Executable file
@@ -0,0 +1,141 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user