fix billing and 2fa admin
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
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
This commit is contained in:
+538
@@ -0,0 +1,538 @@
|
||||
-- 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);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_enum e
|
||||
JOIN pg_type t ON t.oid = e.enumtypid
|
||||
WHERE t.typname = 'NotificationType'
|
||||
AND e.enumlabel = 'MANUAL_PAYMENT_EVIDENCE_SUBMITTED'
|
||||
) THEN
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'MANUAL_PAYMENT_EVIDENCE_SUBMITTED';
|
||||
END IF;
|
||||
END $$;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
-- Keep only the newest active manual payment submission per invoice before
|
||||
-- installing the invariant. Historical approved/rejected submissions remain.
|
||||
WITH ranked_active_submissions AS (
|
||||
SELECT
|
||||
"id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "invoiceId"
|
||||
ORDER BY "createdAt" DESC, "id" DESC
|
||||
) AS row_number
|
||||
FROM "manual_payment_submissions"
|
||||
WHERE "status" IN ('DRAFT', 'SUBMITTED', 'UNDER_REVIEW')
|
||||
)
|
||||
UPDATE "manual_payment_submissions" AS submission
|
||||
SET
|
||||
"status" = 'REJECTED',
|
||||
"rejectionReason" = COALESCE(
|
||||
submission."rejectionReason",
|
||||
'Superseded by a newer active manual payment submission.'
|
||||
),
|
||||
"reviewedAt" = COALESCE(submission."reviewedAt", CURRENT_TIMESTAMP)
|
||||
FROM ranked_active_submissions AS ranked
|
||||
WHERE submission."id" = ranked."id"
|
||||
AND ranked.row_number > 1;
|
||||
|
||||
CREATE UNIQUE INDEX "manual_payment_submissions_one_active_per_invoice"
|
||||
ON "manual_payment_submissions" ("invoiceId")
|
||||
WHERE "status" IN ('DRAFT', 'SUBMITTED', 'UNDER_REVIEW');
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
-- Keep a single primary billing account per company. Prefer the account with
|
||||
-- the highest open balance, then the most invoices, then the newest account.
|
||||
WITH account_scores AS (
|
||||
SELECT
|
||||
account."id",
|
||||
account."companyId",
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
WHEN invoice."status" IN ('OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID')
|
||||
THEN invoice."amountDue"
|
||||
ELSE 0
|
||||
END
|
||||
), 0) AS open_balance,
|
||||
COUNT(invoice."id") AS invoice_count,
|
||||
account."createdAt"
|
||||
FROM "billing_accounts" AS account
|
||||
LEFT JOIN "billing_invoices" AS invoice
|
||||
ON invoice."billingAccountId" = account."id"
|
||||
WHERE account."isPrimary" = true
|
||||
GROUP BY account."id", account."companyId", account."createdAt"
|
||||
),
|
||||
ranked_primary_accounts AS (
|
||||
SELECT
|
||||
"id",
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "companyId"
|
||||
ORDER BY open_balance DESC, invoice_count DESC, "createdAt" DESC, "id" DESC
|
||||
) AS row_number
|
||||
FROM account_scores
|
||||
)
|
||||
UPDATE "billing_accounts" AS account
|
||||
SET "isPrimary" = false
|
||||
FROM ranked_primary_accounts AS ranked
|
||||
WHERE account."id" = ranked."id"
|
||||
AND ranked.row_number > 1;
|
||||
|
||||
CREATE UNIQUE INDEX "billing_accounts_one_primary_per_company"
|
||||
ON "billing_accounts" ("companyId")
|
||||
WHERE "isPrimary" = true;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE "platform_billing_settings" (
|
||||
"id" TEXT NOT NULL DEFAULT 'default',
|
||||
"taxRate" DOUBLE PRECISION NOT NULL DEFAULT 20,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"updatedBy" TEXT,
|
||||
|
||||
CONSTRAINT "platform_billing_settings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
INSERT INTO "platform_billing_settings" ("id", "taxRate", "updatedAt")
|
||||
VALUES ('default', 20, NOW())
|
||||
ON CONFLICT ("id") DO NOTHING;
|
||||
@@ -134,6 +134,81 @@ enum BillingPaymentAttemptStatus {
|
||||
PARTIALLY_REFUNDED
|
||||
}
|
||||
|
||||
enum SubscriptionCollectionMethod {
|
||||
STRIPE
|
||||
BANK_TRANSFER
|
||||
CHECK
|
||||
}
|
||||
|
||||
enum BillingPaymentChannel {
|
||||
ONLINE
|
||||
OFFLINE
|
||||
}
|
||||
|
||||
enum ManualPaymentMethod {
|
||||
BANK_TRANSFER
|
||||
CHECK
|
||||
}
|
||||
|
||||
enum ManualPaymentSubmissionStatus {
|
||||
DRAFT
|
||||
SUBMITTED
|
||||
UNDER_REVIEW
|
||||
APPROVED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
enum ManualPaymentDocumentKind {
|
||||
BANK_TRANSFER_RECEIPT
|
||||
CHECK_COPY
|
||||
OTHER_SUPPORTING_EVIDENCE
|
||||
}
|
||||
|
||||
enum ManualPaymentDocumentScanStatus {
|
||||
UPLOADED
|
||||
SCANNING
|
||||
CLEAN
|
||||
QUARANTINED
|
||||
SCAN_FAILED
|
||||
}
|
||||
|
||||
enum CollectionsCaseStatus {
|
||||
SCHEDULED
|
||||
PRE_DUE
|
||||
GRACE_PERIOD
|
||||
RESOLVED
|
||||
SUSPENDED
|
||||
}
|
||||
|
||||
enum CollectionsCallTaskType {
|
||||
PRE_EXPIRY_48H_CALL
|
||||
PAYMENT_PROMISE_FOLLOW_UP
|
||||
}
|
||||
|
||||
enum CollectionsCallTaskStatus {
|
||||
OPEN
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum CollectionsCallOutcome {
|
||||
CONTACTED
|
||||
NO_ANSWER
|
||||
PAYMENT_PROMISED
|
||||
ISSUE_ESCALATED
|
||||
}
|
||||
|
||||
enum CollectionsOverrideType {
|
||||
PAYMENT_DISPUTE
|
||||
MANUAL_EXTENSION
|
||||
}
|
||||
|
||||
enum CollectionsOverrideStatus {
|
||||
ACTIVE
|
||||
REVOKED
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
enum BillingRefundStatus {
|
||||
PENDING
|
||||
SUCCEEDED
|
||||
@@ -441,6 +516,17 @@ enum NotificationType {
|
||||
REFUND_PROCESSED
|
||||
NEW_OFFER_FROM_SAVED_COMPANY
|
||||
REVIEW_REQUEST
|
||||
SUBSCRIPTION_PAYMENT_DUE_14D
|
||||
SUBSCRIPTION_PAYMENT_DUE_7D
|
||||
SUBSCRIPTION_PAYMENT_DUE_48H
|
||||
SUBSCRIPTION_PAYMENT_DUE_24H
|
||||
SUBSCRIPTION_GRACE_DAILY
|
||||
SUBSCRIPTION_GRACE_FINAL
|
||||
COLLECTIONS_CALL_REQUIRED
|
||||
MANUAL_PAYMENT_EVIDENCE_SUBMITTED
|
||||
SUBSCRIPTION_PAYMENT_CONFIRMED
|
||||
MANUAL_PAYMENT_EVIDENCE_REJECTED
|
||||
COLLECTIONS_OVERRIDE_CHANGED
|
||||
}
|
||||
|
||||
enum FeedbackCategory {
|
||||
@@ -492,6 +578,8 @@ enum NotificationStatus {
|
||||
enum NotificationRecipientType {
|
||||
EMPLOYEE
|
||||
RENTER
|
||||
BILLING_CONTACT
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum NotificationDeliveryStatus {
|
||||
@@ -529,27 +617,32 @@ model Company {
|
||||
status CompanyStatus @default(PENDING)
|
||||
subscriptionPaymentRef String?
|
||||
|
||||
subscription Subscription?
|
||||
billingAccounts BillingAccount[]
|
||||
billingInvoices BillingInvoice[]
|
||||
brand BrandSettings?
|
||||
employees Employee[]
|
||||
vehicles Vehicle[]
|
||||
offers Offer[]
|
||||
reservations Reservation[]
|
||||
customers Customer[]
|
||||
rentalPayments RentalPayment[]
|
||||
subscriptionInvoices SubscriptionInvoice[]
|
||||
contractSettings ContractSettings?
|
||||
accountingSettings AccountingSettings?
|
||||
insurancePolicies InsurancePolicy[]
|
||||
pricingRules PricingRule[]
|
||||
notifications Notification[] @relation("CompanyNotifications")
|
||||
notificationEvents NotificationEvent[]
|
||||
notificationDefaults CompanyNotificationPreference[]
|
||||
complaints Complaint[]
|
||||
companyMenuItems CompanyMenuItem[]
|
||||
apiKeys CompanyApiKey[]
|
||||
subscription Subscription?
|
||||
billingAccounts BillingAccount[]
|
||||
billingInvoices BillingInvoice[]
|
||||
brand BrandSettings?
|
||||
employees Employee[]
|
||||
vehicles Vehicle[]
|
||||
offers Offer[]
|
||||
reservations Reservation[]
|
||||
customers Customer[]
|
||||
rentalPayments RentalPayment[]
|
||||
subscriptionInvoices SubscriptionInvoice[]
|
||||
contractSettings ContractSettings?
|
||||
accountingSettings AccountingSettings?
|
||||
insurancePolicies InsurancePolicy[]
|
||||
pricingRules PricingRule[]
|
||||
notifications Notification[] @relation("CompanyNotifications")
|
||||
notificationEvents NotificationEvent[]
|
||||
notificationDefaults CompanyNotificationPreference[]
|
||||
billingContacts BillingContact[]
|
||||
manualPaymentSubmissions ManualPaymentSubmission[]
|
||||
manualPaymentDocuments ManualPaymentDocument[]
|
||||
collectionsCases CollectionsCase[]
|
||||
collectionsEvents CollectionsEvent[]
|
||||
complaints Complaint[]
|
||||
companyMenuItems CompanyMenuItem[]
|
||||
apiKeys CompanyApiKey[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -597,6 +690,7 @@ model Subscription {
|
||||
maxRetryCount Int @default(5)
|
||||
invoices SubscriptionInvoice[]
|
||||
billingInvoices BillingInvoice[]
|
||||
collectionsCases CollectionsCase[]
|
||||
events SubscriptionEvent[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
@@ -623,28 +717,28 @@ model SubscriptionEvent {
|
||||
}
|
||||
|
||||
model SubscriptionInvoice {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String
|
||||
subscription Subscription @relation(fields: [subscriptionId], references: [id])
|
||||
requestedPlan Plan?
|
||||
requestedBillingPeriod BillingPeriod?
|
||||
providerInvoiceId String?
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
status InvoiceStatus
|
||||
amanpayTransactionId String? @unique
|
||||
paypalCaptureId String? @unique
|
||||
stripeCheckoutSessionId String? @unique
|
||||
paymentProvider PaymentProvider @default(AMANPAY)
|
||||
billingInvoiceId String? @unique
|
||||
billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id])
|
||||
dueAt DateTime?
|
||||
paidAt DateTime?
|
||||
failedAt DateTime?
|
||||
voidedAt DateTime?
|
||||
attempts PaymentAttempt[]
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String
|
||||
subscription Subscription @relation(fields: [subscriptionId], references: [id])
|
||||
requestedPlan Plan?
|
||||
requestedBillingPeriod BillingPeriod?
|
||||
providerInvoiceId String?
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
status InvoiceStatus
|
||||
amanpayTransactionId String? @unique
|
||||
paypalCaptureId String? @unique
|
||||
stripeCheckoutSessionId String? @unique
|
||||
paymentProvider PaymentProvider @default(AMANPAY)
|
||||
billingInvoiceId String? @unique
|
||||
billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id])
|
||||
dueAt DateTime?
|
||||
paidAt DateTime?
|
||||
failedAt DateTime?
|
||||
voidedAt DateTime?
|
||||
attempts PaymentAttempt[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -653,35 +747,44 @@ model SubscriptionInvoice {
|
||||
}
|
||||
|
||||
model BillingAccount {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
isPrimary Boolean @default(true)
|
||||
legalName String
|
||||
billingEmail String
|
||||
preferredLanguage String @default("en")
|
||||
billingAddress Json?
|
||||
taxId String?
|
||||
taxExempt Boolean @default(false)
|
||||
defaultCurrency String @default("MAD")
|
||||
defaultPaymentMethodId String?
|
||||
defaultPaymentMethod BillingPaymentMethod? @relation("BillingAccountDefaultPaymentMethod", fields: [defaultPaymentMethodId], references: [id])
|
||||
invoiceTerms BillingInvoiceTerms @default(DUE_ON_RECEIPT)
|
||||
netTermsDays Int @default(0)
|
||||
providerCustomerId String?
|
||||
dunningPaused Boolean @default(false)
|
||||
dunningPausedAt DateTime?
|
||||
dunningPausedBy String?
|
||||
metadata Json @default("{}")
|
||||
paymentMethods BillingPaymentMethod[] @relation("BillingAccountPaymentMethods")
|
||||
invoices BillingInvoice[]
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
creditBalances BillingCreditBalance[]
|
||||
creditLedgerEntries BillingCreditLedgerEntry[]
|
||||
creditNotes BillingCreditNote[]
|
||||
refunds BillingRefund[]
|
||||
events BillingEvent[]
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
isPrimary Boolean @default(true)
|
||||
legalName String
|
||||
billingEmail String
|
||||
preferredLanguage String @default("en")
|
||||
timezone String @default("Africa/Casablanca")
|
||||
reminderLocalTime String @default("09:00")
|
||||
enabledCommunicationLocales String[] @default(["en"])
|
||||
defaultCommunicationLocale String @default("en")
|
||||
collectionsOwnerAdminId String?
|
||||
collectionsOwnerAdmin AdminUser? @relation("BillingAccountCollectionsOwner", fields: [collectionsOwnerAdminId], references: [id], onDelete: SetNull)
|
||||
billingAddress Json?
|
||||
taxId String?
|
||||
taxExempt Boolean @default(false)
|
||||
defaultCurrency String @default("MAD")
|
||||
defaultPaymentMethodId String?
|
||||
defaultPaymentMethod BillingPaymentMethod? @relation("BillingAccountDefaultPaymentMethod", fields: [defaultPaymentMethodId], references: [id])
|
||||
invoiceTerms BillingInvoiceTerms @default(DUE_ON_RECEIPT)
|
||||
netTermsDays Int @default(0)
|
||||
providerCustomerId String?
|
||||
dunningPaused Boolean @default(false)
|
||||
dunningPausedAt DateTime?
|
||||
dunningPausedBy String?
|
||||
metadata Json @default("{}")
|
||||
paymentMethods BillingPaymentMethod[] @relation("BillingAccountPaymentMethods")
|
||||
invoices BillingInvoice[]
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
creditBalances BillingCreditBalance[]
|
||||
creditLedgerEntries BillingCreditLedgerEntry[]
|
||||
creditNotes BillingCreditNote[]
|
||||
refunds BillingRefund[]
|
||||
events BillingEvent[]
|
||||
billingContacts BillingContact[]
|
||||
manualPaymentSubmissions ManualPaymentSubmission[]
|
||||
collectionsCases CollectionsCase[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -691,6 +794,31 @@ model BillingAccount {
|
||||
@@map("billing_accounts")
|
||||
}
|
||||
|
||||
model BillingContact {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
employeeId String?
|
||||
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
|
||||
email String
|
||||
locale String?
|
||||
isPrimary Boolean @default(false)
|
||||
receivePaymentNotices Boolean @default(true)
|
||||
isActive Boolean @default(true)
|
||||
verifiedAt DateTime?
|
||||
notificationRecipients NotificationRecipient[]
|
||||
callTasks CollectionsCallTask[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([billingAccountId, email])
|
||||
@@index([companyId, isActive])
|
||||
@@index([employeeId])
|
||||
@@map("billing_contacts")
|
||||
}
|
||||
|
||||
model BillingPaymentMethod {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
@@ -717,26 +845,26 @@ model BillingPaymentMethod {
|
||||
}
|
||||
|
||||
model BillingInvoice {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String?
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
legacySubscriptionInvoice SubscriptionInvoice?
|
||||
invoiceNumber String? @unique
|
||||
invoiceSequence Int? @unique
|
||||
invoiceNumber String? @unique
|
||||
invoiceSequence Int? @unique
|
||||
invoiceType BillingInvoiceType
|
||||
status BillingInvoiceStatus @default(DRAFT)
|
||||
currency String @default("MAD")
|
||||
subtotalAmount Int @default(0)
|
||||
discountAmount Int @default(0)
|
||||
creditAmount Int @default(0)
|
||||
taxAmount Int @default(0)
|
||||
totalAmount Int @default(0)
|
||||
amountPaid Int @default(0)
|
||||
amountDue Int @default(0)
|
||||
status BillingInvoiceStatus @default(DRAFT)
|
||||
currency String @default("MAD")
|
||||
subtotalAmount Int @default(0)
|
||||
discountAmount Int @default(0)
|
||||
creditAmount Int @default(0)
|
||||
taxAmount Int @default(0)
|
||||
totalAmount Int @default(0)
|
||||
amountPaid Int @default(0)
|
||||
amountDue Int @default(0)
|
||||
invoiceDate DateTime?
|
||||
dueAt DateTime?
|
||||
finalizedAt DateTime?
|
||||
@@ -748,9 +876,14 @@ model BillingInvoice {
|
||||
billingAddress Json?
|
||||
providerInvoiceId String?
|
||||
paymentProvider PaymentProvider?
|
||||
isSubscriptionBlocking Boolean @default(false)
|
||||
collectionMethod SubscriptionCollectionMethod @default(STRIPE)
|
||||
requestedPlan Plan?
|
||||
requestedBillingPeriod BillingPeriod?
|
||||
checkoutIdempotencyKey String?
|
||||
renewalKey String? @unique
|
||||
isSubscriptionBlocking Boolean @default(false)
|
||||
adminReason String?
|
||||
metadata Json @default("{}")
|
||||
metadata Json @default("{}")
|
||||
createdByAdminId String?
|
||||
lineItems BillingInvoiceLineItem[]
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
@@ -759,14 +892,18 @@ model BillingInvoice {
|
||||
creditNotes BillingCreditNote[]
|
||||
refunds BillingRefund[]
|
||||
events BillingEvent[]
|
||||
manualPaymentSubmissions ManualPaymentSubmission[]
|
||||
collectionsCase CollectionsCase?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
BillingCreditLedgerEntry BillingCreditLedgerEntry[]
|
||||
|
||||
@@unique([billingAccountId, checkoutIdempotencyKey])
|
||||
@@index([billingAccountId])
|
||||
@@index([companyId])
|
||||
@@index([subscriptionId])
|
||||
@@index([collectionMethod, status, dueAt])
|
||||
@@map("billing_invoices")
|
||||
}
|
||||
|
||||
@@ -816,32 +953,217 @@ model BillingPaymentIntent {
|
||||
}
|
||||
|
||||
model BillingPaymentAttempt {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
paymentIntentId String?
|
||||
paymentIntent BillingPaymentIntent? @relation(fields: [paymentIntentId], references: [id])
|
||||
paymentMethodId String?
|
||||
paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id])
|
||||
providerPaymentId String?
|
||||
status BillingPaymentAttemptStatus
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
failureCode String?
|
||||
failureMessage String?
|
||||
attemptedAt DateTime
|
||||
metadata Json @default("{}")
|
||||
refunds BillingRefund[]
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
paymentIntentId String?
|
||||
paymentIntent BillingPaymentIntent? @relation(fields: [paymentIntentId], references: [id])
|
||||
paymentMethodId String?
|
||||
paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id])
|
||||
providerPaymentId String?
|
||||
channel BillingPaymentChannel @default(ONLINE)
|
||||
manualMethod ManualPaymentMethod?
|
||||
externalReference String?
|
||||
normalizedExternalReference String?
|
||||
receivedAt DateTime?
|
||||
confirmedAt DateTime?
|
||||
confirmedByAdminId String?
|
||||
confirmedByAdmin AdminUser? @relation("ManualPaymentConfirmingAdmin", fields: [confirmedByAdminId], references: [id], onDelete: SetNull)
|
||||
idempotencyKey String?
|
||||
note String?
|
||||
status BillingPaymentAttemptStatus
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
failureCode String?
|
||||
failureMessage String?
|
||||
attemptedAt DateTime
|
||||
metadata Json @default("{}")
|
||||
refunds BillingRefund[]
|
||||
manualPaymentSubmission ManualPaymentSubmission?
|
||||
resolvedCollectionsCase CollectionsCase? @relation("CollectionsResolutionPayment")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([billingAccountId, idempotencyKey])
|
||||
@@unique([billingAccountId, manualMethod, normalizedExternalReference])
|
||||
@@index([invoiceId])
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_payment_attempts")
|
||||
}
|
||||
|
||||
model ManualPaymentSubmission {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
method ManualPaymentMethod
|
||||
submittedReference String
|
||||
normalizedSubmittedReference String
|
||||
status ManualPaymentSubmissionStatus @default(DRAFT)
|
||||
submittedByEmployeeId String
|
||||
submittedByEmployee Employee @relation(fields: [submittedByEmployeeId], references: [id])
|
||||
submittedAt DateTime?
|
||||
reviewedByAdminId String?
|
||||
reviewedByAdmin AdminUser? @relation("ManualPaymentReviewingAdmin", fields: [reviewedByAdminId], references: [id], onDelete: SetNull)
|
||||
reviewedAt DateTime?
|
||||
rejectionReason String?
|
||||
idempotencyKey String
|
||||
paymentAttemptId String? @unique
|
||||
paymentAttempt BillingPaymentAttempt? @relation(fields: [paymentAttemptId], references: [id], onDelete: SetNull)
|
||||
documents ManualPaymentDocument[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([billingAccountId, idempotencyKey])
|
||||
@@index([invoiceId, status])
|
||||
@@index([status, submittedAt])
|
||||
@@map("manual_payment_submissions")
|
||||
}
|
||||
|
||||
model ManualPaymentDocument {
|
||||
id String @id @default(cuid())
|
||||
submissionId String
|
||||
submission ManualPaymentSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
|
||||
invoiceId String
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
kind ManualPaymentDocumentKind
|
||||
storageKey String @unique
|
||||
originalFilename String
|
||||
detectedMimeType String
|
||||
detectedExtension String
|
||||
byteSize Int
|
||||
sha256 String
|
||||
scanStatus ManualPaymentDocumentScanStatus @default(UPLOADED)
|
||||
scannerResultCode String?
|
||||
uploadedByEmployeeId String
|
||||
uploadedByEmployee Employee @relation(fields: [uploadedByEmployeeId], references: [id])
|
||||
uploadedAt DateTime @default(now())
|
||||
deletedAt DateTime?
|
||||
|
||||
@@unique([submissionId, sha256])
|
||||
@@index([invoiceId])
|
||||
@@index([companyId])
|
||||
@@index([scanStatus, uploadedAt])
|
||||
@@map("manual_payment_documents")
|
||||
}
|
||||
|
||||
model CollectionsCase {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String @unique
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String
|
||||
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
status CollectionsCaseStatus @default(SCHEDULED)
|
||||
originalExpirationAt DateTime
|
||||
reminder14At DateTime
|
||||
reminder7At DateTime
|
||||
reminder48At DateTime
|
||||
reminder24At DateTime
|
||||
graceStartedAt DateTime?
|
||||
finalSuspensionAt DateTime
|
||||
resolvedAt DateTime?
|
||||
suspendedAt DateTime?
|
||||
nextActionAt DateTime?
|
||||
collectionsOwnerAdminId String?
|
||||
collectionsOwnerAdmin AdminUser? @relation("CollectionsCaseOwner", fields: [collectionsOwnerAdminId], references: [id], onDelete: SetNull)
|
||||
resolutionPaymentAttemptId String? @unique
|
||||
resolutionPaymentAttempt BillingPaymentAttempt? @relation("CollectionsResolutionPayment", fields: [resolutionPaymentAttemptId], references: [id], onDelete: SetNull)
|
||||
processingLeaseUntil DateTime?
|
||||
processingBy String?
|
||||
version Int @default(0)
|
||||
tasks CollectionsCallTask[]
|
||||
overrides CollectionsOverride[]
|
||||
events CollectionsEvent[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([subscriptionId, originalExpirationAt])
|
||||
@@index([status, nextActionAt])
|
||||
@@map("collections_cases")
|
||||
}
|
||||
|
||||
model CollectionsCallTask {
|
||||
id String @id @default(cuid())
|
||||
collectionsCaseId String
|
||||
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
|
||||
taskType CollectionsCallTaskType
|
||||
assignedAdminId String
|
||||
assignedAdmin AdminUser @relation("CollectionsTaskAssignee", fields: [assignedAdminId], references: [id])
|
||||
billingContactId String?
|
||||
billingContact BillingContact? @relation(fields: [billingContactId], references: [id], onDelete: SetNull)
|
||||
dueAt DateTime
|
||||
status CollectionsCallTaskStatus @default(OPEN)
|
||||
outcome CollectionsCallOutcome?
|
||||
note String?
|
||||
promisedPaymentAt DateTime?
|
||||
nextFollowUpAt DateTime?
|
||||
completedByAdminId String?
|
||||
completedByAdmin AdminUser? @relation("CollectionsTaskCompleter", fields: [completedByAdminId], references: [id], onDelete: SetNull)
|
||||
completedAt DateTime?
|
||||
cancellationReason String?
|
||||
companyDefaultLocale String
|
||||
contactLocale String
|
||||
customerScript String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([collectionsCaseId, taskType])
|
||||
@@index([assignedAdminId, status, dueAt])
|
||||
@@map("collections_call_tasks")
|
||||
}
|
||||
|
||||
model CollectionsOverride {
|
||||
id String @id @default(cuid())
|
||||
collectionsCaseId String
|
||||
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
|
||||
type CollectionsOverrideType
|
||||
status CollectionsOverrideStatus @default(ACTIVE)
|
||||
reason String
|
||||
expiresAt DateTime
|
||||
revisedSuspensionAt DateTime?
|
||||
pauseSuspension Boolean @default(true)
|
||||
pauseNotifications Boolean @default(false)
|
||||
createdByAdminId String
|
||||
createdByAdmin AdminUser @relation("CollectionsOverrideCreator", fields: [createdByAdminId], references: [id])
|
||||
revokedByAdminId String?
|
||||
revokedByAdmin AdminUser? @relation("CollectionsOverrideRevoker", fields: [revokedByAdminId], references: [id], onDelete: SetNull)
|
||||
revokedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([collectionsCaseId, status, expiresAt])
|
||||
@@map("collections_overrides")
|
||||
}
|
||||
|
||||
model CollectionsEvent {
|
||||
id String @id @default(cuid())
|
||||
collectionsCaseId String
|
||||
collectionsCase CollectionsCase @relation(fields: [collectionsCaseId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
eventType String
|
||||
idempotencyKey String
|
||||
scheduledFor DateTime?
|
||||
occurredAt DateTime @default(now())
|
||||
actorType String @default("system")
|
||||
actorId String?
|
||||
payload Json @default("{}")
|
||||
|
||||
@@unique([collectionsCaseId, idempotencyKey])
|
||||
@@index([companyId, occurredAt])
|
||||
@@map("collections_events")
|
||||
}
|
||||
|
||||
model BillingCreditBalance {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
@@ -1008,8 +1330,8 @@ model BrandSettings {
|
||||
paypalEmail String?
|
||||
paypalMerchantId String?
|
||||
paymentMethodsEnabled PaymentProvider[]
|
||||
isListedOnCarplace Boolean @default(true)
|
||||
carplaceRating Float?
|
||||
isListedOnCarplace Boolean @default(true)
|
||||
carplaceRating Float?
|
||||
homePageConfig Json? @map("home_page_config")
|
||||
menuConfig Json? @map("menu_config")
|
||||
|
||||
@@ -1036,10 +1358,13 @@ model Employee {
|
||||
preferredLanguage String @default("en")
|
||||
isActive Boolean @default(true)
|
||||
|
||||
notifications Notification[] @relation("EmployeeNotifications")
|
||||
notificationRecipients NotificationRecipient[]
|
||||
notificationPreferences NotificationPreference[]
|
||||
recordedRentalPayments RentalPayment[] @relation("RecordedRentalPayments")
|
||||
notifications Notification[] @relation("EmployeeNotifications")
|
||||
notificationRecipients NotificationRecipient[]
|
||||
notificationPreferences NotificationPreference[]
|
||||
recordedRentalPayments RentalPayment[] @relation("RecordedRentalPayments")
|
||||
billingContacts BillingContact[]
|
||||
manualPaymentSubmissions ManualPaymentSubmission[]
|
||||
manualPaymentDocuments ManualPaymentDocument[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1073,10 +1398,10 @@ model Vehicle {
|
||||
allowDifferentDropoff Boolean @default(false)
|
||||
dropoffLocations String[] @default([])
|
||||
|
||||
reservations Reservation[]
|
||||
maintenance MaintenanceLog[]
|
||||
offerVehicles OfferVehicle[]
|
||||
calendarBlocks VehicleCalendarBlock[]
|
||||
reservations Reservation[]
|
||||
maintenance MaintenanceLog[]
|
||||
offerVehicles OfferVehicle[]
|
||||
calendarBlocks VehicleCalendarBlock[]
|
||||
pricingConfiguration VehiclePricingConfiguration?
|
||||
priceHistory VehiclePriceHistory[]
|
||||
|
||||
@@ -1116,11 +1441,11 @@ model VehiclePricingConfiguration {
|
||||
}
|
||||
|
||||
model VehiclePricingRule {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
configurationId String
|
||||
configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
ruleType VehiclePricingRuleType @default(DATE_RANGE)
|
||||
ruleType VehiclePricingRuleType @default(DATE_RANGE)
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
dailyRate Int?
|
||||
@@ -1131,8 +1456,8 @@ model VehiclePricingRule {
|
||||
maxDailyRate Int?
|
||||
automaticAdjustmentPct Int?
|
||||
priceAdjustment Int?
|
||||
isActive Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1143,12 +1468,12 @@ model VehiclePricingRule {
|
||||
}
|
||||
|
||||
model VehiclePriceHistory {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
configurationId String
|
||||
configuration VehiclePricingConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade)
|
||||
vehicleId String
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
source VehiclePriceChangeSource @default(CONFIG_UPDATE)
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
source VehiclePriceChangeSource @default(CONFIG_UPDATE)
|
||||
changedByEmployeeId String?
|
||||
previousDailyRate Int?
|
||||
nextDailyRate Int?
|
||||
@@ -1156,7 +1481,7 @@ model VehiclePriceHistory {
|
||||
nextWeeklyRate Int?
|
||||
note String?
|
||||
effectiveFrom DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([vehicleId, createdAt])
|
||||
@@index([configurationId, createdAt])
|
||||
@@ -1282,9 +1607,9 @@ model Customer {
|
||||
licenseApprovedAt DateTime?
|
||||
licenseApprovalNote String?
|
||||
|
||||
reservations Reservation[]
|
||||
complaints Complaint[]
|
||||
reviewOptOut Boolean @default(false)
|
||||
reservations Reservation[]
|
||||
complaints Complaint[]
|
||||
reviewOptOut Boolean @default(false)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1309,7 +1634,7 @@ model Reservation {
|
||||
promoCodeUsed String?
|
||||
vehicleCategory VehicleCategory?
|
||||
source BookingSource @default(DASHBOARD)
|
||||
carplaceRef String?
|
||||
carplaceRef String?
|
||||
status ReservationStatus @default(DRAFT)
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
@@ -1342,11 +1667,11 @@ model Reservation {
|
||||
reviewPaused Boolean @default(false)
|
||||
|
||||
insurances ReservationInsurance[]
|
||||
insuranceTotal Int @default(0)
|
||||
insuranceTotal Int @default(0)
|
||||
additionalDrivers AdditionalDriver[]
|
||||
additionalDriverTotal Int @default(0)
|
||||
additionalDriverTotal Int @default(0)
|
||||
pricingRulesApplied Json?
|
||||
pricingRulesTotal Int @default(0)
|
||||
pricingRulesTotal Int @default(0)
|
||||
damageReports DamageReport[]
|
||||
rentalPayments RentalPayment[]
|
||||
inspections DamageInspection[]
|
||||
@@ -1402,7 +1727,6 @@ model CarplaceFunnelEvent {
|
||||
@@map("carplace_funnel_events")
|
||||
}
|
||||
|
||||
|
||||
model WebhookEvent {
|
||||
id String @id @default(cuid())
|
||||
provider String
|
||||
@@ -1420,50 +1744,50 @@ model WebhookEvent {
|
||||
}
|
||||
|
||||
model RentalPayment {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
reservationId String
|
||||
reservation Reservation @relation(fields: [reservationId], references: [id])
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
status PaymentStatus @default(PENDING)
|
||||
type PaymentType @default(CHARGE)
|
||||
paymentProvider PaymentProvider
|
||||
amanpayTransactionId String? @unique
|
||||
paypalCaptureId String? @unique
|
||||
stripeCheckoutSessionId String? @unique
|
||||
stripePaymentIntentId String? @unique
|
||||
paymentMethod String?
|
||||
reference String?
|
||||
note String?
|
||||
receivedAt DateTime?
|
||||
recordedByEmployeeId String?
|
||||
recordedByEmployee Employee? @relation("RecordedRentalPayments", fields: [recordedByEmployeeId], references: [id])
|
||||
idempotencyKey String?
|
||||
paidAt DateTime?
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
reservationId String
|
||||
reservation Reservation @relation(fields: [reservationId], references: [id])
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
status PaymentStatus @default(PENDING)
|
||||
type PaymentType @default(CHARGE)
|
||||
paymentProvider PaymentProvider
|
||||
amanpayTransactionId String? @unique
|
||||
paypalCaptureId String? @unique
|
||||
stripeCheckoutSessionId String? @unique
|
||||
stripePaymentIntentId String? @unique
|
||||
paymentMethod String?
|
||||
reference String?
|
||||
note String?
|
||||
receivedAt DateTime?
|
||||
recordedByEmployeeId String?
|
||||
recordedByEmployee Employee? @relation("RecordedRentalPayments", fields: [recordedByEmployeeId], references: [id])
|
||||
idempotencyKey String?
|
||||
paidAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([companyId, idempotencyKey])
|
||||
@@index([companyId])
|
||||
@@index([reservationId])
|
||||
@@unique([companyId, idempotencyKey])
|
||||
@@map("rental_payments")
|
||||
}
|
||||
|
||||
model Review {
|
||||
id String @id @default(cuid())
|
||||
reservationId String @unique
|
||||
reservation Reservation @relation(fields: [reservationId], references: [id])
|
||||
id String @id @default(cuid())
|
||||
reservationId String @unique
|
||||
reservation Reservation @relation(fields: [reservationId], references: [id])
|
||||
renterId String?
|
||||
renter Renter? @relation(fields: [renterId], references: [id])
|
||||
renter Renter? @relation(fields: [renterId], references: [id])
|
||||
companyId String
|
||||
overallRating Int
|
||||
vehicleRating Int?
|
||||
serviceRating Int?
|
||||
comment String?
|
||||
isPublished Boolean @default(true)
|
||||
isPublished Boolean @default(true)
|
||||
companyReply String?
|
||||
companyRepliedAt DateTime?
|
||||
category FeedbackCategory?
|
||||
@@ -1476,29 +1800,29 @@ model Review {
|
||||
}
|
||||
|
||||
model Complaint {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
reservationId String?
|
||||
reservation Reservation? @relation(fields: [reservationId], references: [id])
|
||||
reservation Reservation? @relation(fields: [reservationId], references: [id])
|
||||
reviewId String?
|
||||
review Review? @relation(fields: [reviewId], references: [id])
|
||||
review Review? @relation(fields: [reviewId], references: [id])
|
||||
customerId String?
|
||||
customer Customer? @relation(fields: [customerId], references: [id])
|
||||
customer Customer? @relation(fields: [customerId], references: [id])
|
||||
|
||||
severity ComplaintSeverity @default(LEVEL_1)
|
||||
status ComplaintStatus @default(OPEN)
|
||||
category FeedbackCategory
|
||||
subject String
|
||||
description String?
|
||||
resolution String?
|
||||
notes String?
|
||||
assignedTo String?
|
||||
resolvedAt DateTime?
|
||||
resolvedBy String?
|
||||
severity ComplaintSeverity @default(LEVEL_1)
|
||||
status ComplaintStatus @default(OPEN)
|
||||
category FeedbackCategory
|
||||
subject String
|
||||
description String?
|
||||
resolution String?
|
||||
notes String?
|
||||
assignedTo String?
|
||||
resolvedAt DateTime?
|
||||
resolvedBy String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([companyId])
|
||||
@@index([reservationId])
|
||||
@@ -1538,12 +1862,12 @@ model Notification {
|
||||
}
|
||||
|
||||
model NotificationEvent {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
type NotificationType
|
||||
templateKey String?
|
||||
locale String @default("en")
|
||||
locale String @default("en")
|
||||
title String
|
||||
body String
|
||||
data Json?
|
||||
@@ -1552,7 +1876,7 @@ model NotificationEvent {
|
||||
idempotencyKey String
|
||||
recipients NotificationRecipient[]
|
||||
outboxEntries NotificationOutbox[]
|
||||
createdAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([companyId, idempotencyKey])
|
||||
@@index([companyId, type, createdAt])
|
||||
@@ -1568,6 +1892,10 @@ model NotificationRecipient {
|
||||
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
renterId String?
|
||||
renter Renter? @relation(fields: [renterId], references: [id], onDelete: Cascade)
|
||||
billingContactId String?
|
||||
billingContact BillingContact? @relation(fields: [billingContactId], references: [id], onDelete: Cascade)
|
||||
adminUserId String?
|
||||
adminUser AdminUser? @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
|
||||
readAt DateTime?
|
||||
archivedAt DateTime?
|
||||
deliveries NotificationDelivery[]
|
||||
@@ -1575,6 +1903,8 @@ model NotificationRecipient {
|
||||
|
||||
@@index([employeeId, readAt, createdAt])
|
||||
@@index([renterId, readAt, createdAt])
|
||||
@@index([billingContactId, readAt, createdAt])
|
||||
@@index([adminUserId, readAt, createdAt])
|
||||
@@index([notificationEventId])
|
||||
@@map("notification_recipients")
|
||||
}
|
||||
@@ -1953,9 +2283,19 @@ model AdminUser {
|
||||
passwordResetToken String? @unique
|
||||
passwordResetExpiresAt DateTime?
|
||||
|
||||
auditLogs AuditLog[]
|
||||
permissions AdminPermission[]
|
||||
recoveryCodes AdminRecoveryCode[]
|
||||
auditLogs AuditLog[]
|
||||
permissions AdminPermission[]
|
||||
recoveryCodes AdminRecoveryCode[]
|
||||
preferredLocale String @default("en")
|
||||
ownedBillingAccounts BillingAccount[] @relation("BillingAccountCollectionsOwner")
|
||||
confirmedManualPayments BillingPaymentAttempt[] @relation("ManualPaymentConfirmingAdmin")
|
||||
reviewedManualSubmissions ManualPaymentSubmission[] @relation("ManualPaymentReviewingAdmin")
|
||||
ownedCollectionsCases CollectionsCase[] @relation("CollectionsCaseOwner")
|
||||
assignedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskAssignee")
|
||||
completedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskCompleter")
|
||||
createdCollectionsOverrides CollectionsOverride[] @relation("CollectionsOverrideCreator")
|
||||
revokedCollectionsOverrides CollectionsOverride[] @relation("CollectionsOverrideRevoker")
|
||||
notificationRecipients NotificationRecipient[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1974,7 +2314,6 @@ model AdminPermission {
|
||||
@@map("admin_permissions")
|
||||
}
|
||||
|
||||
|
||||
model AdminRecoveryCode {
|
||||
id String @id @default(cuid())
|
||||
adminUserId String
|
||||
@@ -2022,6 +2361,15 @@ model PricingConfig {
|
||||
@@map("pricing_configs")
|
||||
}
|
||||
|
||||
model PlatformBillingSettings {
|
||||
id String @id @default("default")
|
||||
taxRate Float @default(20)
|
||||
updatedAt DateTime @updatedAt
|
||||
updatedBy String?
|
||||
|
||||
@@map("platform_billing_settings")
|
||||
}
|
||||
|
||||
model PlanFeature {
|
||||
id String @id @default(cuid())
|
||||
plan Plan
|
||||
|
||||
Reference in New Issue
Block a user