5f06256271
Build & Push / Pipeline Tests (push) Failing after 1m58s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 58s
Test / API Unit Tests (push) Successful in 1m9s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 41s
Test / Dashboard Unit Tests (push) Successful in 45s
Test / API Integration Tests (push) Failing after 1m9s
539 lines
24 KiB
SQL
539 lines
24 KiB
SQL
-- CreateEnum
|
|
CREATE TYPE "SubscriptionCollectionMethod" AS ENUM ('STRIPE', 'BANK_TRANSFER', 'CHECK');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "BillingPaymentChannel" AS ENUM ('ONLINE', 'OFFLINE');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "ManualPaymentMethod" AS ENUM ('BANK_TRANSFER', 'CHECK');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "ManualPaymentSubmissionStatus" AS ENUM ('DRAFT', 'SUBMITTED', 'UNDER_REVIEW', 'APPROVED', 'REJECTED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "ManualPaymentDocumentKind" AS ENUM ('BANK_TRANSFER_RECEIPT', 'CHECK_COPY', 'OTHER_SUPPORTING_EVIDENCE');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "ManualPaymentDocumentScanStatus" AS ENUM ('UPLOADED', 'SCANNING', 'CLEAN', 'QUARANTINED', 'SCAN_FAILED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsCaseStatus" AS ENUM ('SCHEDULED', 'PRE_DUE', 'GRACE_PERIOD', 'RESOLVED', 'SUSPENDED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsCallTaskType" AS ENUM ('PRE_EXPIRY_48H_CALL', 'PAYMENT_PROMISE_FOLLOW_UP');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsCallTaskStatus" AS ENUM ('OPEN', 'COMPLETED', 'CANCELLED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsCallOutcome" AS ENUM ('CONTACTED', 'NO_ANSWER', 'PAYMENT_PROMISED', 'ISSUE_ESCALATED');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsOverrideType" AS ENUM ('PAYMENT_DISPUTE', 'MANUAL_EXTENSION');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "CollectionsOverrideStatus" AS ENUM ('ACTIVE', 'REVOKED', 'EXPIRED');
|
|
|
|
-- AlterEnum
|
|
-- This migration adds more than one value to an enum.
|
|
-- With PostgreSQL versions 11 and earlier, this is not possible
|
|
-- in a single migration. This can be worked around by creating
|
|
-- multiple migrations, each migration adding only one value to
|
|
-- the enum.
|
|
|
|
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_PAYMENT_DUE_14D';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_PAYMENT_DUE_7D';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_PAYMENT_DUE_48H';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_PAYMENT_DUE_24H';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_GRACE_DAILY';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_GRACE_FINAL';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'COLLECTIONS_CALL_REQUIRED';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'MANUAL_PAYMENT_EVIDENCE_SUBMITTED';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'SUBSCRIPTION_PAYMENT_CONFIRMED';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'MANUAL_PAYMENT_EVIDENCE_REJECTED';
|
|
ALTER TYPE "NotificationType" ADD VALUE 'COLLECTIONS_OVERRIDE_CHANGED';
|
|
|
|
-- AlterEnum
|
|
-- This migration adds more than one value to an enum.
|
|
-- With PostgreSQL versions 11 and earlier, this is not possible
|
|
-- in a single migration. This can be worked around by creating
|
|
-- multiple migrations, each migration adding only one value to
|
|
-- the enum.
|
|
|
|
|
|
ALTER TYPE "NotificationRecipientType" ADD VALUE 'BILLING_CONTACT';
|
|
ALTER TYPE "NotificationRecipientType" ADD VALUE 'ADMIN';
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "billing_accounts" ADD COLUMN "collectionsOwnerAdminId" TEXT,
|
|
ADD COLUMN "defaultCommunicationLocale" TEXT NOT NULL DEFAULT 'en',
|
|
ADD COLUMN "enabledCommunicationLocales" TEXT[] DEFAULT ARRAY['en']::TEXT[],
|
|
ADD COLUMN "reminderLocalTime" TEXT NOT NULL DEFAULT '09:00',
|
|
ADD COLUMN "timezone" TEXT NOT NULL DEFAULT 'Africa/Casablanca';
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "billing_invoices" ADD COLUMN "checkoutIdempotencyKey" TEXT,
|
|
ADD COLUMN "collectionMethod" "SubscriptionCollectionMethod" NOT NULL DEFAULT 'STRIPE',
|
|
ADD COLUMN "renewalKey" TEXT,
|
|
ADD COLUMN "requestedBillingPeriod" "BillingPeriod",
|
|
ADD COLUMN "requestedPlan" "Plan";
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "billing_payment_attempts" ADD COLUMN "channel" "BillingPaymentChannel" NOT NULL DEFAULT 'ONLINE',
|
|
ADD COLUMN "confirmedAt" TIMESTAMP(3),
|
|
ADD COLUMN "confirmedByAdminId" TEXT,
|
|
ADD COLUMN "externalReference" TEXT,
|
|
ADD COLUMN "idempotencyKey" TEXT,
|
|
ADD COLUMN "manualMethod" "ManualPaymentMethod",
|
|
ADD COLUMN "normalizedExternalReference" TEXT,
|
|
ADD COLUMN "note" TEXT,
|
|
ADD COLUMN "receivedAt" TIMESTAMP(3);
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "notification_recipients" ADD COLUMN "adminUserId" TEXT,
|
|
ADD COLUMN "billingContactId" TEXT;
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "admin_users" ADD COLUMN "preferredLocale" TEXT NOT NULL DEFAULT 'en';
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "billing_contacts" (
|
|
"id" TEXT NOT NULL,
|
|
"billingAccountId" TEXT NOT NULL,
|
|
"companyId" TEXT NOT NULL,
|
|
"employeeId" TEXT,
|
|
"email" TEXT NOT NULL,
|
|
"locale" TEXT,
|
|
"isPrimary" BOOLEAN NOT NULL DEFAULT false,
|
|
"receivePaymentNotices" BOOLEAN NOT NULL DEFAULT true,
|
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
|
"verifiedAt" TIMESTAMP(3),
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "billing_contacts_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "manual_payment_submissions" (
|
|
"id" TEXT NOT NULL,
|
|
"invoiceId" TEXT NOT NULL,
|
|
"billingAccountId" TEXT NOT NULL,
|
|
"companyId" TEXT NOT NULL,
|
|
"method" "ManualPaymentMethod" NOT NULL,
|
|
"submittedReference" TEXT NOT NULL,
|
|
"normalizedSubmittedReference" TEXT NOT NULL,
|
|
"status" "ManualPaymentSubmissionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
"submittedByEmployeeId" TEXT NOT NULL,
|
|
"submittedAt" TIMESTAMP(3),
|
|
"reviewedByAdminId" TEXT,
|
|
"reviewedAt" TIMESTAMP(3),
|
|
"rejectionReason" TEXT,
|
|
"idempotencyKey" TEXT NOT NULL,
|
|
"paymentAttemptId" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "manual_payment_submissions_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "manual_payment_documents" (
|
|
"id" TEXT NOT NULL,
|
|
"submissionId" TEXT NOT NULL,
|
|
"invoiceId" TEXT NOT NULL,
|
|
"companyId" TEXT NOT NULL,
|
|
"kind" "ManualPaymentDocumentKind" NOT NULL,
|
|
"storageKey" TEXT NOT NULL,
|
|
"originalFilename" TEXT NOT NULL,
|
|
"detectedMimeType" TEXT NOT NULL,
|
|
"detectedExtension" TEXT NOT NULL,
|
|
"byteSize" INTEGER NOT NULL,
|
|
"sha256" TEXT NOT NULL,
|
|
"scanStatus" "ManualPaymentDocumentScanStatus" NOT NULL DEFAULT 'UPLOADED',
|
|
"scannerResultCode" TEXT,
|
|
"uploadedByEmployeeId" TEXT NOT NULL,
|
|
"uploadedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"deletedAt" TIMESTAMP(3),
|
|
|
|
CONSTRAINT "manual_payment_documents_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "collections_cases" (
|
|
"id" TEXT NOT NULL,
|
|
"invoiceId" TEXT NOT NULL,
|
|
"subscriptionId" TEXT NOT NULL,
|
|
"billingAccountId" TEXT NOT NULL,
|
|
"companyId" TEXT NOT NULL,
|
|
"status" "CollectionsCaseStatus" NOT NULL DEFAULT 'SCHEDULED',
|
|
"originalExpirationAt" TIMESTAMP(3) NOT NULL,
|
|
"reminder14At" TIMESTAMP(3) NOT NULL,
|
|
"reminder7At" TIMESTAMP(3) NOT NULL,
|
|
"reminder48At" TIMESTAMP(3) NOT NULL,
|
|
"reminder24At" TIMESTAMP(3) NOT NULL,
|
|
"graceStartedAt" TIMESTAMP(3),
|
|
"finalSuspensionAt" TIMESTAMP(3) NOT NULL,
|
|
"resolvedAt" TIMESTAMP(3),
|
|
"suspendedAt" TIMESTAMP(3),
|
|
"nextActionAt" TIMESTAMP(3),
|
|
"collectionsOwnerAdminId" TEXT,
|
|
"resolutionPaymentAttemptId" TEXT,
|
|
"processingLeaseUntil" TIMESTAMP(3),
|
|
"processingBy" TEXT,
|
|
"version" INTEGER NOT NULL DEFAULT 0,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "collections_cases_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "collections_call_tasks" (
|
|
"id" TEXT NOT NULL,
|
|
"collectionsCaseId" TEXT NOT NULL,
|
|
"taskType" "CollectionsCallTaskType" NOT NULL,
|
|
"assignedAdminId" TEXT NOT NULL,
|
|
"billingContactId" TEXT,
|
|
"dueAt" TIMESTAMP(3) NOT NULL,
|
|
"status" "CollectionsCallTaskStatus" NOT NULL DEFAULT 'OPEN',
|
|
"outcome" "CollectionsCallOutcome",
|
|
"note" TEXT,
|
|
"promisedPaymentAt" TIMESTAMP(3),
|
|
"nextFollowUpAt" TIMESTAMP(3),
|
|
"completedByAdminId" TEXT,
|
|
"completedAt" TIMESTAMP(3),
|
|
"cancellationReason" TEXT,
|
|
"companyDefaultLocale" TEXT NOT NULL,
|
|
"contactLocale" TEXT NOT NULL,
|
|
"customerScript" TEXT NOT NULL,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "collections_call_tasks_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "collections_overrides" (
|
|
"id" TEXT NOT NULL,
|
|
"collectionsCaseId" TEXT NOT NULL,
|
|
"type" "CollectionsOverrideType" NOT NULL,
|
|
"status" "CollectionsOverrideStatus" NOT NULL DEFAULT 'ACTIVE',
|
|
"reason" TEXT NOT NULL,
|
|
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
"revisedSuspensionAt" TIMESTAMP(3),
|
|
"pauseSuspension" BOOLEAN NOT NULL DEFAULT true,
|
|
"pauseNotifications" BOOLEAN NOT NULL DEFAULT false,
|
|
"createdByAdminId" TEXT NOT NULL,
|
|
"revokedByAdminId" TEXT,
|
|
"revokedAt" TIMESTAMP(3),
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "collections_overrides_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "collections_events" (
|
|
"id" TEXT NOT NULL,
|
|
"collectionsCaseId" TEXT NOT NULL,
|
|
"companyId" TEXT NOT NULL,
|
|
"eventType" TEXT NOT NULL,
|
|
"idempotencyKey" TEXT NOT NULL,
|
|
"scheduledFor" TIMESTAMP(3),
|
|
"occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"actorType" TEXT NOT NULL DEFAULT 'system',
|
|
"actorId" TEXT,
|
|
"payload" JSONB NOT NULL DEFAULT '{}',
|
|
|
|
CONSTRAINT "collections_events_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "billing_contacts_companyId_isActive_idx" ON "billing_contacts"("companyId", "isActive");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "billing_contacts_employeeId_idx" ON "billing_contacts"("employeeId");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "billing_contacts_billingAccountId_email_key" ON "billing_contacts"("billingAccountId", "email");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "manual_payment_submissions_paymentAttemptId_key" ON "manual_payment_submissions"("paymentAttemptId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "manual_payment_submissions_invoiceId_status_idx" ON "manual_payment_submissions"("invoiceId", "status");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "manual_payment_submissions_status_submittedAt_idx" ON "manual_payment_submissions"("status", "submittedAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "manual_payment_submissions_billingAccountId_idempotencyKey_key" ON "manual_payment_submissions"("billingAccountId", "idempotencyKey");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "manual_payment_documents_storageKey_key" ON "manual_payment_documents"("storageKey");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "manual_payment_documents_invoiceId_idx" ON "manual_payment_documents"("invoiceId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "manual_payment_documents_companyId_idx" ON "manual_payment_documents"("companyId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "manual_payment_documents_scanStatus_uploadedAt_idx" ON "manual_payment_documents"("scanStatus", "uploadedAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "manual_payment_documents_submissionId_sha256_key" ON "manual_payment_documents"("submissionId", "sha256");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "collections_cases_invoiceId_key" ON "collections_cases"("invoiceId");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "collections_cases_resolutionPaymentAttemptId_key" ON "collections_cases"("resolutionPaymentAttemptId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "collections_cases_status_nextActionAt_idx" ON "collections_cases"("status", "nextActionAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "collections_cases_subscriptionId_originalExpirationAt_key" ON "collections_cases"("subscriptionId", "originalExpirationAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "collections_call_tasks_assignedAdminId_status_dueAt_idx" ON "collections_call_tasks"("assignedAdminId", "status", "dueAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "collections_call_tasks_collectionsCaseId_taskType_key" ON "collections_call_tasks"("collectionsCaseId", "taskType");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "collections_overrides_collectionsCaseId_status_expiresAt_idx" ON "collections_overrides"("collectionsCaseId", "status", "expiresAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "collections_events_companyId_occurredAt_idx" ON "collections_events"("companyId", "occurredAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "collections_events_collectionsCaseId_idempotencyKey_key" ON "collections_events"("collectionsCaseId", "idempotencyKey");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "billing_invoices_renewalKey_key" ON "billing_invoices"("renewalKey");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "billing_invoices_collectionMethod_status_dueAt_idx" ON "billing_invoices"("collectionMethod", "status", "dueAt");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "billing_invoices_billingAccountId_checkoutIdempotencyKey_key" ON "billing_invoices"("billingAccountId", "checkoutIdempotencyKey");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "billing_payment_attempts_billingAccountId_idempotencyKey_key" ON "billing_payment_attempts"("billingAccountId", "idempotencyKey");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "billing_payment_attempts_billingAccountId_manualMethod_norm_key" ON "billing_payment_attempts"("billingAccountId", "manualMethod", "normalizedExternalReference");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "notification_recipients_billingContactId_readAt_createdAt_idx" ON "notification_recipients"("billingContactId", "readAt", "createdAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "notification_recipients_adminUserId_readAt_createdAt_idx" ON "notification_recipients"("adminUserId", "readAt", "createdAt");
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "billing_accounts" ADD CONSTRAINT "billing_accounts_collectionsOwnerAdminId_fkey" FOREIGN KEY ("collectionsOwnerAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "billing_contacts" ADD CONSTRAINT "billing_contacts_billingAccountId_fkey" FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "billing_contacts" ADD CONSTRAINT "billing_contacts_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "billing_contacts" ADD CONSTRAINT "billing_contacts_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "billing_payment_attempts" ADD CONSTRAINT "billing_payment_attempts_confirmedByAdminId_fkey" FOREIGN KEY ("confirmedByAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_billingAccountId_fkey" FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_submittedByEmployeeId_fkey" FOREIGN KEY ("submittedByEmployeeId") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_reviewedByAdminId_fkey" FOREIGN KEY ("reviewedByAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_submissions" ADD CONSTRAINT "manual_payment_submissions_paymentAttemptId_fkey" FOREIGN KEY ("paymentAttemptId") REFERENCES "billing_payment_attempts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_documents" ADD CONSTRAINT "manual_payment_documents_submissionId_fkey" FOREIGN KEY ("submissionId") REFERENCES "manual_payment_submissions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_documents" ADD CONSTRAINT "manual_payment_documents_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "manual_payment_documents" ADD CONSTRAINT "manual_payment_documents_uploadedByEmployeeId_fkey" FOREIGN KEY ("uploadedByEmployeeId") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_billingAccountId_fkey" FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_collectionsOwnerAdminId_fkey" FOREIGN KEY ("collectionsOwnerAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_cases" ADD CONSTRAINT "collections_cases_resolutionPaymentAttemptId_fkey" FOREIGN KEY ("resolutionPaymentAttemptId") REFERENCES "billing_payment_attempts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_call_tasks" ADD CONSTRAINT "collections_call_tasks_collectionsCaseId_fkey" FOREIGN KEY ("collectionsCaseId") REFERENCES "collections_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_call_tasks" ADD CONSTRAINT "collections_call_tasks_assignedAdminId_fkey" FOREIGN KEY ("assignedAdminId") REFERENCES "admin_users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_call_tasks" ADD CONSTRAINT "collections_call_tasks_billingContactId_fkey" FOREIGN KEY ("billingContactId") REFERENCES "billing_contacts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_call_tasks" ADD CONSTRAINT "collections_call_tasks_completedByAdminId_fkey" FOREIGN KEY ("completedByAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_overrides" ADD CONSTRAINT "collections_overrides_collectionsCaseId_fkey" FOREIGN KEY ("collectionsCaseId") REFERENCES "collections_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_overrides" ADD CONSTRAINT "collections_overrides_createdByAdminId_fkey" FOREIGN KEY ("createdByAdminId") REFERENCES "admin_users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_overrides" ADD CONSTRAINT "collections_overrides_revokedByAdminId_fkey" FOREIGN KEY ("revokedByAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_events" ADD CONSTRAINT "collections_events_collectionsCaseId_fkey" FOREIGN KEY ("collectionsCaseId") REFERENCES "collections_cases"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "collections_events" ADD CONSTRAINT "collections_events_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "notification_recipients" ADD CONSTRAINT "notification_recipients_billingContactId_fkey" FOREIGN KEY ("billingContactId") REFERENCES "billing_contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "notification_recipients" ADD CONSTRAINT "notification_recipients_adminUserId_fkey" FOREIGN KEY ("adminUserId") REFERENCES "admin_users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- Backfill communication policy from existing supported brand language and
|
|
-- create an explicit primary owner contact. Accounts remain visibly flagged
|
|
-- for owner review because the legacy schema had no verified IANA timezone.
|
|
UPDATE "billing_accounts" AS account
|
|
SET "defaultCommunicationLocale" = CASE
|
|
WHEN brand."defaultLocale" IN ('ar', 'en', 'fr') THEN brand."defaultLocale"
|
|
ELSE 'en'
|
|
END,
|
|
"enabledCommunicationLocales" = ARRAY[CASE
|
|
WHEN brand."defaultLocale" IN ('ar', 'en', 'fr') THEN brand."defaultLocale"
|
|
ELSE 'en'
|
|
END]::TEXT[],
|
|
"metadata" = COALESCE(account."metadata", '{}'::jsonb) || '{"communicationSettingsRequireOwnerReview":true}'::jsonb
|
|
FROM "brand_settings" AS brand
|
|
WHERE brand."companyId" = account."companyId";
|
|
|
|
INSERT INTO "billing_contacts" (
|
|
"id", "billingAccountId", "companyId", "employeeId", "email", "locale",
|
|
"isPrimary", "receivePaymentNotices", "isActive", "verifiedAt", "createdAt", "updatedAt"
|
|
)
|
|
SELECT
|
|
'bc_' || md5(random()::text || clock_timestamp()::text || account."id"),
|
|
account."id", account."companyId", owner."id", owner."email",
|
|
CASE WHEN owner."preferredLanguage" IN ('ar', 'en', 'fr') THEN owner."preferredLanguage" ELSE account."defaultCommunicationLocale" END,
|
|
true, true, true, COALESCE(owner."emailVerified", CURRENT_TIMESTAMP), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
FROM "billing_accounts" AS account
|
|
JOIN LATERAL (
|
|
SELECT employee.*
|
|
FROM "employees" AS employee
|
|
WHERE employee."companyId" = account."companyId"
|
|
AND employee."role" = 'OWNER'
|
|
AND employee."isActive" = true
|
|
ORDER BY employee."createdAt" ASC
|
|
LIMIT 1
|
|
) AS owner ON true
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM "billing_contacts" AS contact
|
|
WHERE contact."billingAccountId" = account."id"
|
|
);
|
|
|
|
UPDATE "billing_accounts" AS account
|
|
SET "collectionsOwnerAdminId" = admin."id"
|
|
FROM LATERAL (
|
|
SELECT "id"
|
|
FROM "admin_users"
|
|
WHERE "isActive" = true AND "role" IN ('FINANCE', 'ADMIN', 'SUPER_ADMIN')
|
|
ORDER BY "createdAt" ASC
|
|
LIMIT 1
|
|
) AS admin
|
|
WHERE account."collectionsOwnerAdminId" IS NULL;
|
|
|
|
-- Database invariants that Prisma cannot express directly.
|
|
ALTER TABLE "billing_accounts"
|
|
ALTER COLUMN "enabledCommunicationLocales" SET NOT NULL,
|
|
ALTER COLUMN "defaultCommunicationLocale" SET NOT NULL,
|
|
ALTER COLUMN "timezone" SET NOT NULL,
|
|
ALTER COLUMN "reminderLocalTime" SET NOT NULL;
|
|
|
|
ALTER TABLE "billing_accounts"
|
|
ADD CONSTRAINT "billing_accounts_supported_locales_check"
|
|
CHECK (
|
|
cardinality("enabledCommunicationLocales") BETWEEN 1 AND 3
|
|
AND "enabledCommunicationLocales" <@ ARRAY['ar', 'en', 'fr']::TEXT[]
|
|
AND "defaultCommunicationLocale" = ANY("enabledCommunicationLocales")
|
|
),
|
|
ADD CONSTRAINT "billing_accounts_reminder_time_check"
|
|
CHECK ("reminderLocalTime" ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$');
|
|
|
|
ALTER TABLE "billing_contacts"
|
|
ADD CONSTRAINT "billing_contacts_supported_locale_check"
|
|
CHECK ("locale" IS NULL OR "locale" IN ('ar', 'en', 'fr'));
|
|
|
|
ALTER TABLE "admin_users"
|
|
ADD CONSTRAINT "admin_users_supported_locale_check"
|
|
CHECK ("preferredLocale" IN ('ar', 'en', 'fr'));
|
|
|
|
ALTER TABLE "billing_payment_attempts"
|
|
ADD CONSTRAINT "billing_payment_attempts_offline_confirmation_check"
|
|
CHECK (
|
|
"channel" <> 'OFFLINE'
|
|
OR "status" <> 'SUCCEEDED'
|
|
OR (
|
|
"manualMethod" IS NOT NULL
|
|
AND length(trim(COALESCE("externalReference", ''))) >= 3
|
|
AND length(trim(COALESCE("normalizedExternalReference", ''))) >= 3
|
|
AND "receivedAt" IS NOT NULL
|
|
AND "confirmedAt" IS NOT NULL
|
|
AND "confirmedByAdminId" IS NOT NULL
|
|
AND "idempotencyKey" IS NOT NULL
|
|
)
|
|
);
|
|
|
|
ALTER TABLE "manual_payment_documents"
|
|
ADD CONSTRAINT "manual_payment_documents_size_check"
|
|
CHECK ("byteSize" > 0 AND "byteSize" <= 10485760),
|
|
ADD CONSTRAINT "manual_payment_documents_mime_check"
|
|
CHECK ("detectedMimeType" IN ('application/pdf', 'image/jpeg', 'image/png'));
|
|
|
|
ALTER TABLE "manual_payment_submissions"
|
|
ADD CONSTRAINT "manual_payment_submissions_reference_check"
|
|
CHECK (length(trim("submittedReference")) >= 3 AND length(trim("normalizedSubmittedReference")) >= 3);
|
|
|
|
ALTER TABLE "collections_overrides"
|
|
ADD CONSTRAINT "collections_overrides_finite_window_check"
|
|
CHECK ("expiresAt" > "createdAt");
|
|
|
|
ALTER TABLE "notification_recipients"
|
|
ADD CONSTRAINT "notification_recipients_exact_actor_check"
|
|
CHECK (num_nonnulls("employeeId", "renterId", "billingContactId", "adminUserId") = 1);
|