docs(пилот): 26.05 ночь UTC — supplier-webhook Phase 1+2+3 deployed + cleanup 26 dups (refund 11350 RUB tenant client1)
Three independent fixes deployed to liderra.ru in 3 incremental phase
deploys (13 commits b92d9b3b..48eaffec on main):
Phase 1: webhook always returns JSON 422 on ValidationException
(was 302 redirect for non-JSON Accept clients — 76 lost/day)
Phase 2: merge webhook-after-CSV-recovered into existing deal,
no double-charge (closed 37 duplicate pairs/day pattern)
Phase 3: accept non-B-prefix projects as platform=DIRECT end-to-end
(controller + 4 services + migration v8.36→v8.37)
Schema bump: platform VARCHAR(4)→VARCHAR(8), CHECK enum extended to
include DIRECT, seed suppliers.code='direct' added.
Cleanup (А) 26 dup pairs: soft-delete + reverse balance_transactions
(audit-friendly), refund 11 350 RUB to tenant client1 balance.
(Б) 82 lost leads recovered automatically by CsvReconcileJob after
Phase 3 deploy (entry id=209 recovered_count=58, remaining via webhook
retries).
Lessons: migrate --force упал — manual psql спас; redeploy.sh не
делает git pull (scp нужен); background ssh с heredoc обрывается —
nohup решает; fail2ban whitelist + keepalive (ControlMaster broken
on Windows OpenSSH).
Spec: docs/superpowers/specs/2026-05-25-supplier-webhook-reliability-design.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
BEGIN;
|
||||
CREATE TEMP TABLE dups AS
|
||||
SELECT d.id AS deal_id, lc.id AS charge_id, lc.price_per_lead_kopecks
|
||||
FROM deals d
|
||||
JOIN lead_charges lc ON lc.deal_id = d.id
|
||||
WHERE d.tenant_id=2
|
||||
AND d.created_at::date = DATE '2026-05-25'
|
||||
AND d.source_crm_id IS NULL
|
||||
AND d.deleted_at IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM deals d2
|
||||
WHERE d2.tenant_id=d.tenant_id
|
||||
AND d2.phone=d.phone
|
||||
AND d2.project_id=d.project_id
|
||||
AND d2.source_crm_id IS NOT NULL
|
||||
AND d2.created_at::date = DATE '2026-05-25'
|
||||
AND d2.deleted_at IS NULL
|
||||
);
|
||||
|
||||
\echo === dups to clean ===
|
||||
SELECT COUNT(*) AS dup_count, (SUM(price_per_lead_kopecks)/100.0)::numeric(12,2) AS refund_rub FROM dups;
|
||||
|
||||
\echo === refund balance ===
|
||||
UPDATE tenants
|
||||
SET balance_rub = balance_rub + (SELECT (SUM(price_per_lead_kopecks)/100.0)::numeric(14,2) FROM dups),
|
||||
delivered_in_month = GREATEST(0, delivered_in_month - (SELECT COUNT(*)::int FROM dups))
|
||||
WHERE id = 2
|
||||
RETURNING id, balance_rub, delivered_in_month;
|
||||
|
||||
\echo === insert refund txns ===
|
||||
WITH ins AS (
|
||||
INSERT INTO balance_transactions(tenant_id, type, amount_leads, amount_rub, balance_leads_after, balance_rub_after, related_type, related_id, created_at)
|
||||
SELECT 2, 'refund', NULL, (price_per_lead_kopecks/100.0)::numeric(14,2), NULL,
|
||||
(SELECT balance_rub FROM tenants WHERE id=2),
|
||||
'App\Models\Deal', deal_id, NOW()
|
||||
FROM dups
|
||||
RETURNING id
|
||||
)
|
||||
SELECT COUNT(*) AS refund_txns_inserted FROM ins;
|
||||
|
||||
\echo === soft delete deals ===
|
||||
WITH upd AS (
|
||||
UPDATE deals SET deleted_at = NOW(), updated_at = NOW()
|
||||
WHERE id IN (SELECT deal_id FROM dups)
|
||||
RETURNING id
|
||||
)
|
||||
SELECT COUNT(*) AS deals_soft_deleted FROM upd;
|
||||
|
||||
COMMIT;
|
||||
|
||||
\echo === verify ===
|
||||
SELECT id, balance_rub, delivered_in_month FROM tenants WHERE id=2;
|
||||
SELECT COUNT(*) AS refund_txns FROM balance_transactions WHERE tenant_id=2 AND type='refund' AND created_at > NOW() - interval '5 minutes';
|
||||
SELECT COUNT(*) AS remaining_active_dup_pairs FROM (SELECT phone, project_id FROM deals WHERE tenant_id=2 AND created_at::date = DATE '2026-05-25' AND deleted_at IS NULL GROUP BY phone, project_id HAVING COUNT(*) > 1) t;
|
||||
Reference in New Issue
Block a user