Merge branch 'massive-email-notification' into master
# Conflicts: # .env.example # apps/api/src/app.module.ts
This commit is contained in:
@@ -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