Files
aura-crm/production/backend/migrations/011_client_notifications.sql
T

73 lines
4.0 KiB
SQL

-- +goose Up
-- Client-facing notification channel — Telegram (client subscribes via a
-- one-time deep-link token) with SMS as a fallback when no chat is linked.
-- Separate from internal/notify (staff-only chat, single fixed chat_id):
-- here every client is a distinct recipient, so delivery needs an outbox
-- (client_notifications) rather than a fire-and-forget Send() call, plus a
-- linking flow (client_notification_links) since a client's Telegram
-- identity isn't known until they message the bot.
ALTER TABLE clients ADD COLUMN phone_normalized TEXT;
ALTER TABLE clients ADD COLUMN tg_chat_id TEXT;
ALTER TABLE clients ADD COLUMN tg_subscribed_at TIMESTAMPTZ;
ALTER TABLE clients ADD COLUMN notify_telegram BOOLEAN NOT NULL DEFAULT TRUE;
-- SMS costs money per message — opt-in, unlike Telegram.
ALTER TABLE clients ADD COLUMN notify_sms BOOLEAN NOT NULL DEFAULT FALSE;
-- 152-ФЗ (RU personal data law) consent timestamp, set when a client
-- explicitly opts in via the lead form or the bot's /start flow.
ALTER TABLE clients ADD COLUMN consent_at TIMESTAMPTZ;
CREATE UNIQUE INDEX clients_tg_chat_id_idx ON clients (tg_chat_id) WHERE tg_chat_id IS NOT NULL;
CREATE INDEX clients_phone_normalized_idx ON clients (phone_normalized) WHERE phone_normalized IS NOT NULL;
-- One-time deep-link tokens (t.me/<bot>?start=<token>) binding a Telegram
-- chat to a specific client once they press Start. Short-lived by design —
-- expires_at enforced in Go, not a CHECK, so "expired" can mean "past
-- expires_at OR already used" without two constraints.
CREATE TABLE client_notification_links (
token TEXT PRIMARY KEY,
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
used_at TIMESTAMPTZ
);
CREATE INDEX client_notification_links_client_id_idx ON client_notification_links (client_id);
-- Outbox + audit trail, append-only like every other ledger in this schema
-- (order_events, cash_transactions). dedupe_key is built from the event
-- that triggered the send (e.g. an order_events.id), not from the
-- client/trigger pair alone, so re-entering the same status twice after
-- leaving it produces a new event and a new notification, while retries of
-- the SAME event never double-send.
CREATE TABLE client_notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
order_id UUID REFERENCES orders(id) ON DELETE SET NULL,
cartridge_batch_id UUID REFERENCES cartridge_batches(id) ON DELETE SET NULL,
trigger TEXT NOT NULL CHECK (trigger IN
('created', 'status_changed', 'ready', 'warranty_expiring',
'loyalty_accrued', 'manual')),
channel TEXT NOT NULL CHECK (channel IN ('telegram', 'sms')),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN
('pending', 'sent', 'failed', 'skipped')),
attempts INT NOT NULL DEFAULT 0,
body TEXT NOT NULL,
dedupe_key TEXT NOT NULL,
provider_message_id TEXT,
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX client_notifications_dedupe_idx ON client_notifications (dedupe_key);
CREATE INDEX client_notifications_client_id_idx ON client_notifications (client_id, created_at DESC);
ALTER TABLE settings ADD COLUMN sms_provider TEXT;
ALTER TABLE settings ADD COLUMN sms_api_id TEXT;
ALTER TABLE settings ADD COLUMN sms_from TEXT;
ALTER TABLE settings ADD COLUMN sms_enabled BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN client_tg_bot_username TEXT;
ALTER TABLE settings ADD COLUMN tg_webhook_secret TEXT;
ALTER TABLE settings ADD COLUMN client_notify_enabled BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN public_tracking_url TEXT;