Merge branch 'massive-email-notification' into master
# Conflicts: # .env.example # apps/api/src/app.module.ts
This commit is contained in:
+65
@@ -0,0 +1,65 @@
|
||||
-- Mass email notifications — modern replacement for the legacy
|
||||
-- `utility_dbo.email_alert_log` + `utility_dbo.send_account_status_history`
|
||||
-- tables, fed by the four PHP scripts under
|
||||
-- `email.notifications/send*.php`. See
|
||||
-- docs/MASS_EMAIL_NOTIFICATIONS.md for the design.
|
||||
--
|
||||
-- The two legacy tables stay on `utility_dbo` untouched: their `NUMid`
|
||||
-- column references a string identifier that no longer exists in the
|
||||
-- unified schema, so a backfill would be destructive, not additive. New
|
||||
-- notifications log here against the unified `customers.id` (uuid) and
|
||||
-- the legacy rows are eventually retired by `utility_dbo` itself once
|
||||
-- the office flips to this codebase as the source of truth.
|
||||
|
||||
-- CreateTable
|
||||
-- ENUM values are declared inline per column (MySQL has no CREATE TYPE)
|
||||
-- and match the Prisma enums `EmailNotificationType`,
|
||||
-- `EmailNotificationServicio`, `EmailNotificationStatus`.
|
||||
CREATE TABLE `email_notification_log` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`sendDate` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`notificationType` ENUM('OUTSTANDING_PAYMENT', 'PAYMENT_CONFIRMATION', 'ACCOUNT_STATUS', 'TRUST_PAYMENT_CONFIRMATION') NOT NULL,
|
||||
`level` INTEGER NULL,
|
||||
`servicio` ENUM('CUSTOMERS', 'TRUST') NOT NULL,
|
||||
`customerId` VARCHAR(191) NULL,
|
||||
`customerName` VARCHAR(191) NOT NULL,
|
||||
`customerEmail` VARCHAR(191) NOT NULL,
|
||||
`subject` VARCHAR(191) NOT NULL,
|
||||
`bodyRequestUrl` TEXT NULL,
|
||||
`bodySnapshot` TEXT NOT NULL,
|
||||
`debug` BOOLEAN NOT NULL DEFAULT false,
|
||||
`providerMessageId` VARCHAR(191) NULL,
|
||||
`providerResponse` VARCHAR(191) NULL,
|
||||
`status` ENUM('SENT', 'FAILED', 'SKIPPED_NO_EMAIL', 'SKIPPED_GATE') NOT NULL,
|
||||
`error` TEXT NULL,
|
||||
|
||||
INDEX `email_notification_log_sendDate_idx`(`sendDate`),
|
||||
INDEX `email_notification_log_notificationType_sendDate_idx`(`notificationType`, `sendDate`),
|
||||
INDEX `email_notification_log_customerId_sendDate_idx`(`customerId`, `sendDate`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `account_status_history` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`sendDate` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`customerId` VARCHAR(191) NOT NULL,
|
||||
`customerName` VARCHAR(191) NOT NULL,
|
||||
`customerEmail` VARCHAR(191) NOT NULL,
|
||||
`tipo` VARCHAR(191) NOT NULL,
|
||||
`tCambio` DECIMAL(10, 4) NULL,
|
||||
`balance` DECIMAL(12, 2) NOT NULL,
|
||||
`solicitado` DECIMAL(12, 2) NOT NULL,
|
||||
`level` INTEGER NOT NULL,
|
||||
|
||||
INDEX `account_status_history_sendDate_idx`(`sendDate`),
|
||||
INDEX `account_status_history_customerId_sendDate_idx`(`customerId`, `sendDate`),
|
||||
INDEX `account_status_history_level_sendDate_idx`(`level`, `sendDate`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `email_notification_log` ADD CONSTRAINT `email_notification_log_customerId_fkey` FOREIGN KEY (`customerId`) REFERENCES `customers`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `account_status_history` ADD CONSTRAINT `account_status_history_customerId_fkey` FOREIGN KEY (`customerId`) REFERENCES `customers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -125,6 +125,9 @@ model Customer {
|
||||
statementDocuments StatementDocument[]
|
||||
policyOcrDocuments PolicyOcrDocument[] @relation("PolicyOcrDocumentCustomer")
|
||||
|
||||
emailNotificationLogs EmailNotificationLog[]
|
||||
accountStatusHistory AccountStatusHistory[]
|
||||
|
||||
@@map("customers")
|
||||
}
|
||||
|
||||
@@ -919,6 +922,146 @@ model EmailLog {
|
||||
@@map("email_log")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mass email notifications — the modern replacement for the legacy
|
||||
// `email_alert_log` + `send_account_status_history` tables on utility_dbo and
|
||||
// the four PHP scripts under `email.notifications/`. See
|
||||
// docs/MASS_EMAIL_NOTIFICATIONS.md for the full design.
|
||||
//
|
||||
// The two legacy tables are not imported into this schema: the unified
|
||||
// `customers` model replaces `datosfreak` (no more NUMid-as-string), so the
|
||||
// rows would no longer carry their meaning. New tables follow the unified
|
||||
// shape (FK to `customers`, signed Decimal balance, proper enums) and the
|
||||
// four notification kinds are one `notificationType` enum rather than four
|
||||
// parallel column families.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Which bulk-notification script produced a row. Mirrors the four PHP
|
||||
/// jobs in `email.notifications/send*.php`:
|
||||
/// - OUTSTANDING_PAYMENT → sendOutstandingPaymentAlerts.php
|
||||
/// - PAYMENT_CONFIRMATION → sendPaymentConfirmation.php (pagosemail)
|
||||
/// - ACCOUNT_STATUS → sendAccountStatus.php (datosfreak, both
|
||||
/// red and yellow; the threshold is in the
|
||||
/// `level` column, 0=yellow / 1=red)
|
||||
/// - TRUST_PAYMENT_CONFIRMATION → sendConfirmTrustPayment.php (TRUSTHFEE)
|
||||
enum EmailNotificationType {
|
||||
OUTSTANDING_PAYMENT
|
||||
PAYMENT_CONFIRMATION
|
||||
ACCOUNT_STATUS
|
||||
TRUST_PAYMENT_CONFIRMATION
|
||||
}
|
||||
|
||||
/// Which "servicio" (line of business) the notification draws its recipients
|
||||
/// from. CUSTOMERS = the unified customers ledger (replaces `datosfreak`);
|
||||
/// TRUST = the trust-fee account table (replaces `TRUSTHFEE`). Keeping the
|
||||
/// two services tagged makes a per-line report trivial.
|
||||
enum EmailNotificationServicio {
|
||||
CUSTOMERS
|
||||
TRUST
|
||||
}
|
||||
|
||||
/// Outcome of a single send attempt. SENT / FAILED are the meaningful ones;
|
||||
/// SKIPPED_NO_EMAIL records the dry-run path and the legacy's
|
||||
/// "EMAIL IS NULL" exclusion, SKIPPED_GATE records the Mon/Wed/Fri day gate
|
||||
/// on the red branch (and the Wed gate on yellow) — so a sweep that ran on
|
||||
/// the wrong day shows up as skipped rows, not as missing rows.
|
||||
enum EmailNotificationStatus {
|
||||
SENT
|
||||
FAILED
|
||||
SKIPPED_NO_EMAIL
|
||||
SKIPPED_GATE
|
||||
}
|
||||
|
||||
/// One row per send attempt. Captures both the deliverable (subject + body
|
||||
/// snapshot + provider message id) and the diagnostic (URL we would have
|
||||
/// fetched in the PHP version, SES response, error string). The body snapshot
|
||||
/// is intentionally kept: the PHP scripts only stored it on the error path;
|
||||
/// we store it always, so a customer reply quoting an old email can be traced
|
||||
/// to the exact letter that was sent.
|
||||
model EmailNotificationLog {
|
||||
id String @id @default(uuid())
|
||||
sendDate DateTime @default(now())
|
||||
notificationType EmailNotificationType
|
||||
/// 0 = yellow ("DEBAJO DEL TIPO"), 1 = red ("EN ROJO"). Only set on
|
||||
/// ACCOUNT_STATUS rows; null on the other three jobs.
|
||||
level Int?
|
||||
/// Which servicio sourced the recipient list. CUSTOMERS for jobs 1/2/3,
|
||||
/// TRUST for job 4. Tagged here so a per-line audit doesn't need to join.
|
||||
servicio EmailNotificationServicio
|
||||
/// FK to the customer that triggered the send. Trust-account notifications
|
||||
/// resolve the owner through `Property.customerId`, so this stays set on
|
||||
/// job 4 too. Null only on skipped rows where the lookup itself failed.
|
||||
customerId String?
|
||||
customer Customer? @relation(fields: [customerId], references: [id])
|
||||
customerName String
|
||||
customerEmail String
|
||||
/// Subject line of the email we attempted to send.
|
||||
subject String
|
||||
/// For PAYMENT_CONFIRMATION: the per-customer URL the PHP code built and
|
||||
/// fetched (kept verbatim so the legacy format is reproducible). Null on
|
||||
/// the other three jobs — the body is built inline.
|
||||
bodyRequestUrl String? @db.Text
|
||||
/// The HTML body that was sent (or that would have been sent, for SKIPPED
|
||||
/// rows). Stored verbatim so audit/customer-service can read the exact
|
||||
/// letter that went out without re-running the render.
|
||||
bodySnapshot String @db.Text
|
||||
/// True when `debug` was passed — the recipient was overridden to the
|
||||
/// admin address and no real customer received the mail. Kept here so a
|
||||
/// "where did all these emails go" investigation finds the answer in one
|
||||
/// place instead of "who ran what with what flags" archaeology.
|
||||
debug Boolean @default(false)
|
||||
/// SES SendEmail MessageId, when we actually got one back. Null on
|
||||
/// failures, skipped rows, and dev/mock transport.
|
||||
providerMessageId String?
|
||||
/// Free-form provider response (or error). Trimmed to 4k chars before
|
||||
/// insert so a verbose SES bounce payload can't blow the column.
|
||||
providerResponse String?
|
||||
status EmailNotificationStatus
|
||||
error String? @db.Text
|
||||
|
||||
@@index([sendDate])
|
||||
@@index([notificationType, sendDate])
|
||||
@@index([customerId, sendDate])
|
||||
@@map("email_notification_log")
|
||||
}
|
||||
|
||||
/// Mirrors the legacy `utility_dbo.send_account_status_history` table — one
|
||||
/// row per ACCOUNT_STATUS send, capturing the inputs the PHP version logged
|
||||
/// for audit ("what balance, what threshold, what category of alert did we
|
||||
/// fire"). Kept separate from `EmailNotificationLog` so the audit query
|
||||
/// ("every red alert we ever sent this customer") doesn't have to filter by
|
||||
/// notificationType; a one-row-per-send history is the whole point of the
|
||||
/// legacy table.
|
||||
model AccountStatusHistory {
|
||||
id String @id @default(uuid())
|
||||
sendDate DateTime @default(now())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
customerName String
|
||||
customerEmail String
|
||||
/// "DEBAJO DEL TIPO" or "EN ROJO" — the legacy literal strings. Kept
|
||||
/// verbatim (not an enum) because the PHP scripts and downstream reports
|
||||
/// filter by them, and "preserve legacy semantics" is the stated goal.
|
||||
tipo String
|
||||
/// Exchange rate at send time, kept for currency conversions downstream.
|
||||
/// Null when the customer has no exchange-rate context (no FX movement).
|
||||
tCambio Decimal? @db.Decimal(10, 4)
|
||||
/// Customer's balance at send time, in the customer's currency. Negative
|
||||
/// for red; 0..min for yellow.
|
||||
balance Decimal @db.Decimal(12, 2)
|
||||
/// Legacy formula: `0 - TIPO - BALANCE` — the amount the customer needs to
|
||||
/// deposit to clear the threshold. Preserved verbatim even though it
|
||||
/// double-subtracts; downstream reports depend on the exact figure.
|
||||
solicitado Decimal @db.Decimal(12, 2)
|
||||
/// 0 = yellow, 1 = red. Mirrors the legacy `level` column.
|
||||
level Int
|
||||
|
||||
@@index([sendDate])
|
||||
@@index([customerId, sendDate])
|
||||
@@index([level, sendDate])
|
||||
@@map("account_status_history")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Admin database operations (Operaciones): backup / restore / re-import / sync.
|
||||
// Each long-running op is one OpsJob row so the web UI can poll status + tail
|
||||
|
||||
Reference in New Issue
Block a user