Saldo baixo WhatsApp: trigger dispara notificação ao cruzar threshold

Fecha o loop do Marco B — tenant não zera mais saldo sem aviso.

Nova função fn_whatsapp_low_balance_notify + trigger BEFORE UPDATE em
whatsapp_credits_balance:
- Dispara quando NEW.balance < NEW.low_balance_threshold e
  NEW.low_balance_alerted_at IS NULL
- Insere system_alert pros stakeholders do tenant (owner do canal
  WhatsApp ativo + clinic_admin + tenant_admin, deduplicado via UNION)
- Deeplink direto pra /configuracoes/creditos-whatsapp
- Seta NEW.low_balance_alerted_at = now() pra anti-spam

Reset do anti-spam já existia: add_whatsapp_credits seta
low_balance_alerted_at=NULL ao creditar (purchase/topup/refund).
Assim o ciclo completo funciona: cai abaixo → alerta → compra recrédita
→ cai de novo futuramente → alerta de novo.

Toast no frontend já é sticky vermelho pra type='system_alert'
(commit anterior). Config de threshold já existia na UI em
/configuracoes/creditos-whatsapp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-04-23 09:25:03 -03:00
parent 881fa16c27
commit e409ba64ef
@@ -0,0 +1,96 @@
-- ==========================================================================
-- Agencia PSI — Migracao: Alerta de saldo baixo WhatsApp
-- ==========================================================================
-- Criado por: Leonardo Nohama
-- Data: 2026-04-23 · Sao Carlos/SP — Brasil
--
-- Fecha o loop do Marco B: tenant compra creditos → usa → saldo zera sem
-- aviso → PIX recusado surpresa. Agora o tenant e admins recebem
-- notificacao quando saldo cai abaixo do threshold configurado.
--
-- Modelo:
-- - whatsapp_credits_balance ja tem:
-- low_balance_threshold (default 20)
-- low_balance_alerted_at (anti-spam; reset via add_whatsapp_credits)
-- - Falta so o trigger que dispara a notificacao.
--
-- Fluxo:
-- 1. UPDATE em whatsapp_credits_balance (via deduct/adjust)
-- 2. Trigger BEFORE UPDATE detecta balance < threshold E alerted_at IS NULL
-- 3. Insere system_alert pros stakeholders (owner do canal WhatsApp
-- ativo + clinic_admin + tenant_admin do tenant, deduplicado)
-- 4. Seta NEW.low_balance_alerted_at = now() pra nao realertar
-- 5. Reset do alerted_at acontece em add_whatsapp_credits / topup
-- (ja existe: "low_balance_alerted_at = NULL" na RPC)
-- ==========================================================================
CREATE OR REPLACE FUNCTION public.fn_whatsapp_low_balance_notify()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_detail TEXT;
BEGIN
-- So alerta na transicao (alerted_at NULL) E se esta abaixo do threshold
IF NEW.balance < NEW.low_balance_threshold
AND NEW.low_balance_alerted_at IS NULL THEN
v_detail := format(
'Saldo atual: %s credito(s). Alerta configurado em %s. '
'Compre mais na loja para nao interromper envios via WhatsApp Oficial.',
NEW.balance,
NEW.low_balance_threshold
);
-- Stakeholders: owner do canal WhatsApp ativo + admins ativos do tenant
INSERT INTO public.notifications
(owner_id, tenant_id, type, ref_id, ref_table, payload)
SELECT
u.user_id,
NEW.tenant_id,
'system_alert',
NEW.tenant_id,
'whatsapp_credits_balance',
jsonb_build_object(
'title', 'Saldo de WhatsApp baixo',
'detail', v_detail,
'severity', 'warn',
'deeplink', '/configuracoes/creditos-whatsapp'
)
FROM (
SELECT owner_id AS user_id
FROM public.notification_channels
WHERE tenant_id = NEW.tenant_id
AND channel = 'whatsapp'
AND is_active = true
AND deleted_at IS NULL
UNION
SELECT user_id
FROM public.tenant_members
WHERE tenant_id = NEW.tenant_id
AND role IN ('clinic_admin', 'tenant_admin')
AND status = 'active'
) u
WHERE u.user_id IS NOT NULL;
-- Anti-spam: so alerta de novo depois que add_whatsapp_credits
-- reseta alerted_at pra NULL (acontece em purchase/topup)
NEW.low_balance_alerted_at := now();
END IF;
RETURN NEW;
END;
$$;
COMMENT ON FUNCTION public.fn_whatsapp_low_balance_notify() IS
'Trigger BEFORE UPDATE em whatsapp_credits_balance. Dispara notificacao system_alert quando saldo cruza low_balance_threshold pra baixo. Anti-spam via low_balance_alerted_at (resetado pelo add_whatsapp_credits).';
DROP TRIGGER IF EXISTS trg_whatsapp_low_balance_notify
ON public.whatsapp_credits_balance;
CREATE TRIGGER trg_whatsapp_low_balance_notify
BEFORE UPDATE ON public.whatsapp_credits_balance
FOR EACH ROW
EXECUTE FUNCTION public.fn_whatsapp_low_balance_notify();