archetecture security fix
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+2
@@ -0,0 +1,2 @@
|
||||
-- Empty placeholder migration (no schema changes)
|
||||
SELECT 1;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "employees" ADD COLUMN IF NOT EXISTS "passwordHash" TEXT;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
-- AlterTable employees
|
||||
ALTER TABLE "employees" ADD COLUMN IF NOT EXISTS "passwordResetToken" TEXT UNIQUE;
|
||||
ALTER TABLE "employees" ADD COLUMN IF NOT EXISTS "passwordResetExpiresAt" TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable admin_users
|
||||
ALTER TABLE "admin_users" ADD COLUMN IF NOT EXISTS "passwordResetToken" TEXT UNIQUE;
|
||||
ALTER TABLE "admin_users" ADD COLUMN IF NOT EXISTS "passwordResetExpiresAt" TIMESTAMPTZ;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CalendarBlockType" AS ENUM ('MANUAL', 'MAINTENANCE');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "vehicle_calendar_blocks" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vehicleId" TEXT NOT NULL,
|
||||
"startDate" DATE NOT NULL,
|
||||
"endDate" DATE NOT NULL,
|
||||
"reason" TEXT,
|
||||
"type" "CalendarBlockType" NOT NULL DEFAULT 'MANUAL',
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL,
|
||||
|
||||
CONSTRAINT "vehicle_calendar_blocks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "vehicle_calendar_blocks_vehicleId_idx" ON "vehicle_calendar_blocks"("vehicleId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "vehicle_calendar_blocks_vehicleId_startDate_endDate_idx" ON "vehicle_calendar_blocks"("vehicleId", "startDate", "endDate");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "vehicle_calendar_blocks" ADD CONSTRAINT "vehicle_calendar_blocks_vehicleId_fkey" FOREIGN KEY ("vehicleId") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Add reviewToken to reservations for secure one-time review links
|
||||
ALTER TABLE "reservations" ADD COLUMN "reviewToken" TEXT;
|
||||
CREATE UNIQUE INDEX "reservations_reviewToken_key" ON "reservations"("reviewToken");
|
||||
|
||||
-- Make renterId optional on reviews (reviews can come from non-marketplace customers via token link)
|
||||
ALTER TABLE "reviews" ALTER COLUMN "renterId" DROP NOT NULL;
|
||||
ALTER TABLE "reviews" DROP CONSTRAINT IF EXISTS "reviews_renterId_fkey";
|
||||
ALTER TABLE "reviews" ADD CONSTRAINT "reviews_renterId_fkey"
|
||||
FOREIGN KEY ("renterId") REFERENCES "renters"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "reservations" ADD COLUMN "vehicleCategory" "VehicleCategory";
|
||||
+1
@@ -0,0 +1 @@
|
||||
ALTER TABLE "employees" ADD COLUMN "preferredLanguage" TEXT NOT NULL DEFAULT 'en';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "customers"
|
||||
ADD COLUMN "licenseImageUrl" TEXT;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "vehicles"
|
||||
ADD COLUMN "pickupLocations" TEXT[],
|
||||
ADD COLUMN "allowDifferentDropoff" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "dropoffLocations" TEXT[];
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
UPDATE "vehicles"
|
||||
SET
|
||||
"pickupLocations" = COALESCE("pickupLocations", ARRAY[]::TEXT[]),
|
||||
"dropoffLocations" = COALESCE("dropoffLocations", ARRAY[]::TEXT[]);
|
||||
|
||||
ALTER TABLE "vehicles"
|
||||
ALTER COLUMN "pickupLocations" SET DEFAULT ARRAY[]::TEXT[],
|
||||
ALTER COLUMN "pickupLocations" SET NOT NULL,
|
||||
ALTER COLUMN "dropoffLocations" SET DEFAULT ARRAY[]::TEXT[],
|
||||
ALTER COLUMN "dropoffLocations" SET NOT NULL;
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE "pricing_configs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"plan" TEXT NOT NULL,
|
||||
"billingPeriod" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"updatedBy" TEXT,
|
||||
|
||||
CONSTRAINT "pricing_configs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "pricing_configs_plan_billingPeriod_key" ON "pricing_configs"("plan", "billingPeriod");
|
||||
|
||||
-- Seed default prices (amounts in centimes MAD)
|
||||
INSERT INTO "pricing_configs" ("id", "plan", "billingPeriod", "amount", "updatedAt") VALUES
|
||||
('prc_starter_monthly', 'STARTER', 'MONTHLY', 2990, NOW()),
|
||||
('prc_starter_annual', 'STARTER', 'ANNUAL', 29900, NOW()),
|
||||
('prc_growth_monthly', 'GROWTH', 'MONTHLY', 3990, NOW()),
|
||||
('prc_growth_annual', 'GROWTH', 'ANNUAL', 39900, NOW()),
|
||||
('prc_pro_monthly', 'PRO', 'MONTHLY', 99900, NOW()),
|
||||
('prc_pro_annual', 'PRO', 'ANNUAL', 999000, NOW());
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
-- Add new SubscriptionStatus enum values
|
||||
ALTER TYPE "SubscriptionStatus" ADD VALUE IF NOT EXISTS 'PAYMENT_PENDING';
|
||||
ALTER TYPE "SubscriptionStatus" ADD VALUE IF NOT EXISTS 'SUSPENDED';
|
||||
ALTER TYPE "SubscriptionStatus" ADD VALUE IF NOT EXISTS 'EXPIRED';
|
||||
ALTER TYPE "SubscriptionStatus" ADD VALUE IF NOT EXISTS 'PAUSED';
|
||||
|
||||
-- Add new InvoiceStatus enum value
|
||||
ALTER TYPE "InvoiceStatus" ADD VALUE IF NOT EXISTS 'VOIDED';
|
||||
|
||||
-- Extend subscriptions table (camelCase to match Prisma default)
|
||||
ALTER TABLE "subscriptions"
|
||||
ADD COLUMN IF NOT EXISTS "trialUsed" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS "paymentPendingSince" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "paymentDueAt" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "pastDueSince" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "suspendedAt" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "endedAt" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "retryCount" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS "maxRetryCount" INTEGER NOT NULL DEFAULT 5;
|
||||
|
||||
-- Extend subscription_invoices table
|
||||
ALTER TABLE "subscription_invoices"
|
||||
ADD COLUMN IF NOT EXISTS "providerInvoiceId" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "dueAt" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "failedAt" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "voidedAt" TIMESTAMP(3);
|
||||
|
||||
-- Create subscription_events table
|
||||
CREATE TABLE IF NOT EXISTS "subscription_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL DEFAULT 'system',
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"occurredAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "subscription_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "subscription_events_subscriptionId_idx" ON "subscription_events"("subscriptionId");
|
||||
CREATE INDEX IF NOT EXISTS "subscription_events_companyId_idx" ON "subscription_events"("companyId");
|
||||
|
||||
ALTER TABLE "subscription_events"
|
||||
ADD CONSTRAINT "subscription_events_subscriptionId_fkey"
|
||||
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Create payment_attempts table
|
||||
CREATE TABLE IF NOT EXISTS "payment_attempts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT NOT NULL,
|
||||
"providerPaymentId" TEXT,
|
||||
"status" TEXT NOT NULL,
|
||||
"failureCode" TEXT,
|
||||
"failureMessage" TEXT,
|
||||
"attemptedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "payment_attempts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "payment_attempts_invoiceId_idx" ON "payment_attempts"("invoiceId");
|
||||
|
||||
ALTER TABLE "payment_attempts"
|
||||
ADD CONSTRAINT "payment_attempts_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "subscription_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS "pricing_promotions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"discountType" TEXT NOT NULL,
|
||||
"discountValue" INTEGER NOT NULL,
|
||||
"plans" TEXT[] NOT NULL DEFAULT '{}',
|
||||
"periods" TEXT[] NOT NULL DEFAULT '{}',
|
||||
"maxUses" INTEGER,
|
||||
"usedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"validFrom" TIMESTAMP(3) NOT NULL,
|
||||
"validUntil" TIMESTAMP(3),
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdBy" TEXT,
|
||||
CONSTRAINT "pricing_promotions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pricing_promotions_code_key" ON "pricing_promotions"("code");
|
||||
CREATE INDEX IF NOT EXISTS "pricing_promotions_isActive_idx" ON "pricing_promotions"("isActive");
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE "plan_features" (
|
||||
"id" TEXT NOT NULL,
|
||||
"plan" "Plan" NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "plan_features_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "plan_features_plan_sortOrder_idx" ON "plan_features"("plan", "sortOrder");
|
||||
|
||||
INSERT INTO "plan_features" ("id", "plan", "label", "sortOrder") VALUES
|
||||
('plf_starter_1', 'STARTER', 'Up to 10 vehicles', 10),
|
||||
('plf_starter_2', 'STARTER', '1 user account', 20),
|
||||
('plf_starter_3', 'STARTER', 'Basic analytics', 30),
|
||||
('plf_starter_4', 'STARTER', 'Marketplace listing', 40),
|
||||
('plf_growth_1', 'GROWTH', 'Up to 50 vehicles', 10),
|
||||
('plf_growth_2', 'GROWTH', '5 user accounts', 20),
|
||||
('plf_growth_3', 'GROWTH', 'Full analytics', 30),
|
||||
('plf_growth_4', 'GROWTH', 'Priority marketplace placement', 40),
|
||||
('plf_growth_5', 'GROWTH', 'Custom branding', 50),
|
||||
('plf_pro_1', 'PRO', 'Unlimited vehicles', 10),
|
||||
('plf_pro_2', 'PRO', 'Unlimited user accounts', 20),
|
||||
('plf_pro_3', 'PRO', 'Advanced reports', 30),
|
||||
('plf_pro_4', 'PRO', 'API access', 40),
|
||||
('plf_pro_5', 'PRO', 'Dedicated support', 50);
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
CREATE TYPE "BillingInvoiceTerms" AS ENUM (
|
||||
'DUE_ON_RECEIPT',
|
||||
'NET_7',
|
||||
'NET_15',
|
||||
'NET_30',
|
||||
'NET_45',
|
||||
'NET_60'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingInvoiceStatus" AS ENUM (
|
||||
'DRAFT',
|
||||
'OPEN',
|
||||
'PAID',
|
||||
'PARTIALLY_PAID',
|
||||
'PAYMENT_PENDING',
|
||||
'PAST_DUE',
|
||||
'VOID',
|
||||
'UNCOLLECTIBLE',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingInvoiceType" AS ENUM (
|
||||
'SUBSCRIPTION_INITIAL',
|
||||
'SUBSCRIPTION_RENEWAL',
|
||||
'TRIAL_CONVERSION',
|
||||
'SUBSCRIPTION_UPGRADE',
|
||||
'SUBSCRIPTION_DOWNGRADE_CREDIT',
|
||||
'USAGE_BASED',
|
||||
'ONE_TIME',
|
||||
'MANUAL',
|
||||
'CORRECTION',
|
||||
'CANCELLATION_FEE'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingLineItemType" AS ENUM (
|
||||
'SUBSCRIPTION_FEE',
|
||||
'SEAT_FEE',
|
||||
'USAGE_FEE',
|
||||
'SETUP_FEE',
|
||||
'DISCOUNT',
|
||||
'TAX',
|
||||
'CREDIT',
|
||||
'PRORATION',
|
||||
'REFUND_ADJUSTMENT',
|
||||
'MANUAL_ADJUSTMENT',
|
||||
'PROFESSIONAL_SERVICES',
|
||||
'CANCELLATION_FEE'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentMethodType" AS ENUM (
|
||||
'CARD',
|
||||
'ACH_DEBIT',
|
||||
'BANK_TRANSFER',
|
||||
'WIRE_TRANSFER',
|
||||
'MANUAL_INVOICE',
|
||||
'PURCHASE_ORDER'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentIntentStatus" AS ENUM (
|
||||
'REQUIRES_PAYMENT_METHOD',
|
||||
'REQUIRES_CONFIRMATION',
|
||||
'REQUIRES_ACTION',
|
||||
'PROCESSING',
|
||||
'SUCCEEDED',
|
||||
'FAILED',
|
||||
'CANCELED',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentAttemptStatus" AS ENUM (
|
||||
'PENDING',
|
||||
'PROCESSING',
|
||||
'SUCCEEDED',
|
||||
'FAILED',
|
||||
'CANCELED',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingRefundStatus" AS ENUM (
|
||||
'PENDING',
|
||||
'SUCCEEDED',
|
||||
'FAILED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingCreditNoteStatus" AS ENUM (
|
||||
'ISSUED',
|
||||
'APPLIED',
|
||||
'VOID'
|
||||
);
|
||||
|
||||
CREATE SEQUENCE "billing_invoice_sequence_seq" START 1;
|
||||
|
||||
CREATE TABLE "billing_accounts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"isPrimary" BOOLEAN NOT NULL DEFAULT true,
|
||||
"legalName" TEXT NOT NULL,
|
||||
"billingEmail" TEXT NOT NULL,
|
||||
"billingAddress" JSONB,
|
||||
"taxId" TEXT,
|
||||
"taxExempt" BOOLEAN NOT NULL DEFAULT false,
|
||||
"defaultCurrency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"defaultPaymentMethodId" TEXT,
|
||||
"invoiceTerms" "BillingInvoiceTerms" NOT NULL DEFAULT 'DUE_ON_RECEIPT',
|
||||
"netTermsDays" INTEGER NOT NULL DEFAULT 0,
|
||||
"providerCustomerId" TEXT,
|
||||
"dunningPaused" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dunningPausedAt" TIMESTAMP(3),
|
||||
"dunningPausedBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_accounts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_methods" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"type" "BillingPaymentMethodType" NOT NULL,
|
||||
"providerPaymentMethodId" TEXT,
|
||||
"label" TEXT,
|
||||
"brand" TEXT,
|
||||
"last4" TEXT,
|
||||
"expMonth" INTEGER,
|
||||
"expYear" INTEGER,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_methods_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_invoices" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT,
|
||||
"invoiceNumber" TEXT,
|
||||
"invoiceSequence" INTEGER,
|
||||
"invoiceType" "BillingInvoiceType" NOT NULL,
|
||||
"status" "BillingInvoiceStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"subtotalAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"discountAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"creditAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"taxAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"totalAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"amountPaid" INTEGER NOT NULL DEFAULT 0,
|
||||
"amountDue" INTEGER NOT NULL DEFAULT 0,
|
||||
"invoiceDate" TIMESTAMP(3),
|
||||
"dueAt" TIMESTAMP(3),
|
||||
"finalizedAt" TIMESTAMP(3),
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"voidedAt" TIMESTAMP(3),
|
||||
"markedUncollectibleAt" TIMESTAMP(3),
|
||||
"billingName" TEXT,
|
||||
"billingEmail" TEXT,
|
||||
"billingAddress" JSONB,
|
||||
"providerInvoiceId" TEXT,
|
||||
"paymentProvider" "PaymentProvider",
|
||||
"isSubscriptionBlocking" BOOLEAN NOT NULL DEFAULT false,
|
||||
"adminReason" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdByAdminId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_invoices_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_invoice_line_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT,
|
||||
"plan" "Plan",
|
||||
"type" "BillingLineItemType" NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"quantity" INTEGER NOT NULL DEFAULT 1,
|
||||
"unitAmount" INTEGER NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"periodStart" TIMESTAMP(3),
|
||||
"periodEnd" TIMESTAMP(3),
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_invoice_line_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_intents" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"paymentMethodId" TEXT,
|
||||
"providerPaymentIntentId" TEXT,
|
||||
"status" "BillingPaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_PAYMENT_METHOD',
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_intents_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_attempts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"paymentIntentId" TEXT,
|
||||
"paymentMethodId" TEXT,
|
||||
"providerPaymentId" TEXT,
|
||||
"status" "BillingPaymentAttemptStatus" NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"failureCode" TEXT,
|
||||
"failureMessage" TEXT,
|
||||
"attemptedAt" TIMESTAMP(3) NOT NULL,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_attempts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_balances" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"balanceAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_balances_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_ledger_entries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"invoiceId" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_ledger_entries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_notes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"status" "BillingCreditNoteStatus" NOT NULL DEFAULT 'ISSUED',
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_notes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_refunds" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"paymentAttemptId" TEXT,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"status" "BillingRefundStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"providerRefundId" TEXT,
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_refunds_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_tax_records" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"jurisdiction" TEXT,
|
||||
"taxRate" DOUBLE PRECISION,
|
||||
"taxAmount" INTEGER NOT NULL,
|
||||
"taxType" TEXT,
|
||||
"taxExempt" BOOLEAN NOT NULL DEFAULT false,
|
||||
"exemptionReason" TEXT,
|
||||
"providerTaxId" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_tax_records_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT,
|
||||
"invoiceId" TEXT,
|
||||
"subscriptionId" TEXT,
|
||||
"companyId" TEXT,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"occurredAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "subscription_invoices"
|
||||
ADD COLUMN "billingInvoiceId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "subscription_invoices_billingInvoiceId_key" ON "subscription_invoices"("billingInvoiceId");
|
||||
CREATE UNIQUE INDEX "billing_invoices_invoiceNumber_key" ON "billing_invoices"("invoiceNumber");
|
||||
CREATE UNIQUE INDEX "billing_invoices_invoiceSequence_key" ON "billing_invoices"("invoiceSequence");
|
||||
CREATE UNIQUE INDEX "billing_credit_balances_billingAccountId_currency_key" ON "billing_credit_balances"("billingAccountId", "currency");
|
||||
|
||||
CREATE INDEX "billing_accounts_companyId_idx" ON "billing_accounts"("companyId");
|
||||
CREATE INDEX "billing_accounts_companyId_isPrimary_idx" ON "billing_accounts"("companyId", "isPrimary");
|
||||
CREATE INDEX "billing_payment_methods_billingAccountId_idx" ON "billing_payment_methods"("billingAccountId");
|
||||
CREATE INDEX "billing_invoices_billingAccountId_idx" ON "billing_invoices"("billingAccountId");
|
||||
CREATE INDEX "billing_invoices_companyId_idx" ON "billing_invoices"("companyId");
|
||||
CREATE INDEX "billing_invoices_subscriptionId_idx" ON "billing_invoices"("subscriptionId");
|
||||
CREATE INDEX "billing_invoice_line_items_invoiceId_idx" ON "billing_invoice_line_items"("invoiceId");
|
||||
CREATE INDEX "billing_payment_intents_invoiceId_idx" ON "billing_payment_intents"("invoiceId");
|
||||
CREATE INDEX "billing_payment_intents_billingAccountId_idx" ON "billing_payment_intents"("billingAccountId");
|
||||
CREATE INDEX "billing_payment_attempts_invoiceId_idx" ON "billing_payment_attempts"("invoiceId");
|
||||
CREATE INDEX "billing_payment_attempts_billingAccountId_idx" ON "billing_payment_attempts"("billingAccountId");
|
||||
CREATE INDEX "billing_credit_ledger_entries_billingAccountId_idx" ON "billing_credit_ledger_entries"("billingAccountId");
|
||||
CREATE INDEX "billing_credit_notes_invoiceId_idx" ON "billing_credit_notes"("invoiceId");
|
||||
CREATE INDEX "billing_credit_notes_billingAccountId_idx" ON "billing_credit_notes"("billingAccountId");
|
||||
CREATE INDEX "billing_refunds_invoiceId_idx" ON "billing_refunds"("invoiceId");
|
||||
CREATE INDEX "billing_refunds_billingAccountId_idx" ON "billing_refunds"("billingAccountId");
|
||||
CREATE INDEX "billing_tax_records_invoiceId_idx" ON "billing_tax_records"("invoiceId");
|
||||
CREATE INDEX "billing_events_billingAccountId_idx" ON "billing_events"("billingAccountId");
|
||||
CREATE INDEX "billing_events_invoiceId_idx" ON "billing_events"("invoiceId");
|
||||
CREATE INDEX "billing_events_subscriptionId_idx" ON "billing_events"("subscriptionId");
|
||||
CREATE INDEX "billing_events_companyId_idx" ON "billing_events"("companyId");
|
||||
|
||||
ALTER TABLE "billing_accounts"
|
||||
ADD CONSTRAINT "billing_accounts_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_methods"
|
||||
ADD CONSTRAINT "billing_payment_methods_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_accounts"
|
||||
ADD CONSTRAINT "billing_accounts_defaultPaymentMethodId_fkey"
|
||||
FOREIGN KEY ("defaultPaymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_subscriptionId_fkey"
|
||||
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "subscription_invoices"
|
||||
ADD CONSTRAINT "subscription_invoices_billingInvoiceId_fkey"
|
||||
FOREIGN KEY ("billingInvoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoice_line_items"
|
||||
ADD CONSTRAINT "billing_invoice_line_items_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_paymentMethodId_fkey"
|
||||
FOREIGN KEY ("paymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_paymentIntentId_fkey"
|
||||
FOREIGN KEY ("paymentIntentId") REFERENCES "billing_payment_intents"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_paymentMethodId_fkey"
|
||||
FOREIGN KEY ("paymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_balances"
|
||||
ADD CONSTRAINT "billing_credit_balances_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_ledger_entries"
|
||||
ADD CONSTRAINT "billing_credit_ledger_entries_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_ledger_entries"
|
||||
ADD CONSTRAINT "billing_credit_ledger_entries_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_notes"
|
||||
ADD CONSTRAINT "billing_credit_notes_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_notes"
|
||||
ADD CONSTRAINT "billing_credit_notes_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_paymentAttemptId_fkey"
|
||||
FOREIGN KEY ("paymentAttemptId") REFERENCES "billing_payment_attempts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_tax_records"
|
||||
ADD CONSTRAINT "billing_tax_records_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_subscriptionId_fkey"
|
||||
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
INSERT INTO "billing_accounts" (
|
||||
"id",
|
||||
"companyId",
|
||||
"isPrimary",
|
||||
"legalName",
|
||||
"billingEmail",
|
||||
"billingAddress",
|
||||
"taxExempt",
|
||||
"defaultCurrency",
|
||||
"invoiceTerms",
|
||||
"netTermsDays",
|
||||
"metadata"
|
||||
)
|
||||
SELECT
|
||||
'ba_' || c."id",
|
||||
c."id",
|
||||
true,
|
||||
c."name",
|
||||
c."email",
|
||||
c."address",
|
||||
false,
|
||||
COALESCE(a."currency", 'MAD'),
|
||||
'DUE_ON_RECEIPT'::"BillingInvoiceTerms",
|
||||
0,
|
||||
'{}'::jsonb
|
||||
FROM "companies" c
|
||||
LEFT JOIN "accounting_settings" a ON a."companyId" = c."id"
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "billing_accounts" ba
|
||||
WHERE ba."companyId" = c."id" AND ba."isPrimary" = true
|
||||
);
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
ALTER TYPE "NotificationType" ADD VALUE IF NOT EXISTS 'ACCOUNT_CREATED';
|
||||
|
||||
ALTER TABLE "billing_accounts"
|
||||
ADD COLUMN "preferredLanguage" TEXT NOT NULL DEFAULT 'en';
|
||||
|
||||
ALTER TABLE "notifications"
|
||||
ADD COLUMN "templateKey" TEXT,
|
||||
ADD COLUMN "locale" TEXT NOT NULL DEFAULT 'en';
|
||||
|
||||
CREATE TABLE "notification_templates" (
|
||||
"id" TEXT NOT NULL,
|
||||
"templateKey" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"channel" "NotificationChannel" NOT NULL,
|
||||
"locale" TEXT NOT NULL,
|
||||
"subject" TEXT,
|
||||
"body" TEXT NOT NULL,
|
||||
"requiredVariables" JSONB,
|
||||
"optionalVariables" JSONB,
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notification_templates_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "notification_templates_templateKey_channel_locale_version_key"
|
||||
ON "notification_templates"("templateKey", "channel", "locale", "version");
|
||||
|
||||
CREATE INDEX "notification_templates_templateKey_channel_locale_isActive_idx"
|
||||
ON "notification_templates"("templateKey", "channel", "locale", "isActive");
|
||||
|
||||
INSERT INTO "notification_templates" (
|
||||
"id",
|
||||
"templateKey",
|
||||
"category",
|
||||
"channel",
|
||||
"locale",
|
||||
"subject",
|
||||
"body",
|
||||
"requiredVariables",
|
||||
"optionalVariables"
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'notif_tpl_account_created_email_en',
|
||||
'account.created',
|
||||
'account',
|
||||
'EMAIL',
|
||||
'en',
|
||||
'Your workspace is ready - RentalDriveGo',
|
||||
E'Hi {{firstName}},\n\nYour RentalDriveGo workspace for {{companyName}} has been created successfully.\nPlan: {{planName}} ({{billingPeriodLabel}})\nCurrency: {{currency}}\nPrimary payment provider: {{paymentProvider}}\nFree trial ends on {{trialEndDate}}.\n\nYour workspace is ready. Sign in with the email and password you chose during signup.\n\nRentalDriveGo',
|
||||
'["firstName","companyName","planName","billingPeriodLabel","currency","paymentProvider","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_account_created_email_fr',
|
||||
'account.created',
|
||||
'account',
|
||||
'EMAIL',
|
||||
'fr',
|
||||
'Votre espace de travail est pret - RentalDriveGo',
|
||||
E'Bonjour {{firstName}},\n\nVotre espace de travail RentalDriveGo pour {{companyName}} a ete cree avec succes.\nForfait : {{planName}} ({{billingPeriodLabel}})\nDevise : {{currency}}\nFournisseur de paiement principal : {{paymentProvider}}\nLa periode d''essai gratuit se termine le {{trialEndDate}}.\n\nVotre espace de travail est pret. Connectez-vous avec l''e-mail et le mot de passe choisis lors de l''inscription.\n\nRentalDriveGo',
|
||||
'["firstName","companyName","planName","billingPeriodLabel","currency","paymentProvider","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_account_created_email_ar',
|
||||
'account.created',
|
||||
'account',
|
||||
'EMAIL',
|
||||
'ar',
|
||||
'مساحة عملك جاهزة - RentalDriveGo',
|
||||
E'مرحباً {{firstName}}،\n\nتم إنشاء مساحة عمل RentalDriveGo الخاصة بـ {{companyName}} بنجاح.\nالخطة: {{planName}} ({{billingPeriodLabel}})\nالعملة: {{currency}}\nمزود الدفع الرئيسي: {{paymentProvider}}\nتنتهي الفترة التجريبية المجانية في {{trialEndDate}}.\n\nمساحة عملك جاهزة. سجّل الدخول باستخدام البريد الإلكتروني وكلمة المرور التي اخترتهما عند التسجيل.\n\nRentalDriveGo',
|
||||
'["firstName","companyName","planName","billingPeriodLabel","currency","paymentProvider","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_account_created_in_app_en',
|
||||
'account.created',
|
||||
'account',
|
||||
'IN_APP',
|
||||
'en',
|
||||
'Workspace ready',
|
||||
E'Your RentalDriveGo workspace for {{companyName}} is ready. Your free trial ends on {{trialEndDate}}.',
|
||||
'["companyName","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_account_created_in_app_fr',
|
||||
'account.created',
|
||||
'account',
|
||||
'IN_APP',
|
||||
'fr',
|
||||
'Espace pret',
|
||||
E'Votre espace de travail RentalDriveGo pour {{companyName}} est pret. Votre essai gratuit se termine le {{trialEndDate}}.',
|
||||
'["companyName","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_account_created_in_app_ar',
|
||||
'account.created',
|
||||
'account',
|
||||
'IN_APP',
|
||||
'ar',
|
||||
'مساحة العمل جاهزة',
|
||||
E'مساحة عمل RentalDriveGo الخاصة بـ {{companyName}} جاهزة. تنتهي الفترة التجريبية المجانية في {{trialEndDate}}.',
|
||||
'["companyName","trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_email_en',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'EMAIL',
|
||||
'en',
|
||||
'Booking confirmed - {{companyName}}',
|
||||
E'Hi {{firstName}},\n\nYour booking with {{companyName}} has been confirmed.\nPickup: {{startDate}}\nReturn: {{endDate}}\nVehicle: {{vehicleName}}\n\nThank you for choosing RentalDriveGo.',
|
||||
'["firstName","companyName","startDate","endDate","vehicleName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_email_fr',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'EMAIL',
|
||||
'fr',
|
||||
'Reservation confirmee - {{companyName}}',
|
||||
E'Bonjour {{firstName}},\n\nVotre reservation chez {{companyName}} a ete confirmee.\nPrise en charge : {{startDate}}\nRetour : {{endDate}}\nVehicule : {{vehicleName}}\n\nMerci d''avoir choisi RentalDriveGo.',
|
||||
'["firstName","companyName","startDate","endDate","vehicleName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_email_ar',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'EMAIL',
|
||||
'ar',
|
||||
'تم تأكيد الحجز - {{companyName}}',
|
||||
E'مرحباً {{firstName}}،\n\nتم تأكيد حجزك مع {{companyName}}.\nالاستلام: {{startDate}}\nالإرجاع: {{endDate}}\nالمركبة: {{vehicleName}}\n\nشكراً لاختيار RentalDriveGo.',
|
||||
'["firstName","companyName","startDate","endDate","vehicleName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_in_app_en',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'IN_APP',
|
||||
'en',
|
||||
'Booking confirmed',
|
||||
E'Your booking with {{companyName}} has been confirmed.',
|
||||
'["companyName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_in_app_fr',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'IN_APP',
|
||||
'fr',
|
||||
'Reservation confirmee',
|
||||
E'Votre reservation chez {{companyName}} a ete confirmee.',
|
||||
'["companyName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_booking_confirmed_in_app_ar',
|
||||
'booking.confirmed',
|
||||
'reservation',
|
||||
'IN_APP',
|
||||
'ar',
|
||||
'تم تأكيد الحجز',
|
||||
E'تم تأكيد حجزك مع {{companyName}}.',
|
||||
'["companyName"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_subscription_trial_ending_in_app_en',
|
||||
'subscription.trial_ending',
|
||||
'subscription',
|
||||
'IN_APP',
|
||||
'en',
|
||||
'Your trial ends soon',
|
||||
E'Your free trial ends on {{trialEndDate}}. Add a payment method to keep access.',
|
||||
'["trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_subscription_trial_ending_in_app_fr',
|
||||
'subscription.trial_ending',
|
||||
'subscription',
|
||||
'IN_APP',
|
||||
'fr',
|
||||
'Votre essai se termine bientot',
|
||||
E'Votre essai gratuit se termine le {{trialEndDate}}. Ajoutez un moyen de paiement pour conserver l''acces.',
|
||||
'["trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
),
|
||||
(
|
||||
'notif_tpl_subscription_trial_ending_in_app_ar',
|
||||
'subscription.trial_ending',
|
||||
'subscription',
|
||||
'IN_APP',
|
||||
'ar',
|
||||
'ستنتهي الفترة التجريبية قريباً',
|
||||
E'تنتهي فترتك التجريبية المجانية في {{trialEndDate}}. أضف وسيلة دفع للحفاظ على الوصول.',
|
||||
'["trialEndDate"]'::jsonb,
|
||||
'[]'::jsonb
|
||||
);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
UPDATE "notification_templates"
|
||||
SET
|
||||
"body" = E'Hi {{firstName}},\n\nYour booking with {{companyName}} has been confirmed.\nPickup details: {{startDate}}\nPickup address: {{pickupLocation}}\nReturn: {{endDate}}\nVehicle: {{vehicleName}}\n\nThank you for choosing RentalDriveGo.',
|
||||
"requiredVariables" = '["firstName","companyName","startDate","pickupLocation","endDate","vehicleName"]'::jsonb,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "templateKey" = 'booking.confirmed'
|
||||
AND "channel" = 'EMAIL'
|
||||
AND "locale" = 'en';
|
||||
|
||||
UPDATE "notification_templates"
|
||||
SET
|
||||
"body" = E'Bonjour {{firstName}},\n\nVotre reservation chez {{companyName}} a ete confirmee.\nDetails de prise en charge : {{startDate}}\nAdresse de prise en charge : {{pickupLocation}}\nRetour : {{endDate}}\nVehicule : {{vehicleName}}\n\nMerci d''avoir choisi RentalDriveGo.',
|
||||
"requiredVariables" = '["firstName","companyName","startDate","pickupLocation","endDate","vehicleName"]'::jsonb,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "templateKey" = 'booking.confirmed'
|
||||
AND "channel" = 'EMAIL'
|
||||
AND "locale" = 'fr';
|
||||
|
||||
UPDATE "notification_templates"
|
||||
SET
|
||||
"body" = E'مرحباً {{firstName}}،\n\nتم تأكيد حجزك مع {{companyName}}.\nتفاصيل الاستلام: {{startDate}}\nعنوان الاستلام: {{pickupLocation}}\nالإرجاع: {{endDate}}\nالمركبة: {{vehicleName}}\n\nشكراً لاختيار RentalDriveGo.',
|
||||
"requiredVariables" = '["firstName","companyName","startDate","pickupLocation","endDate","vehicleName"]'::jsonb,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "templateKey" = 'booking.confirmed'
|
||||
AND "channel" = 'EMAIL'
|
||||
AND "locale" = 'ar';
|
||||
@@ -0,0 +1,19 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ReservationPhotoType" AS ENUM ('PICKUP', 'DROPOFF');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "reservation_photos" (
|
||||
"id" TEXT NOT NULL,
|
||||
"reservationId" TEXT NOT NULL,
|
||||
"type" "ReservationPhotoType" NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "reservation_photos_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "reservation_photos_reservationId_idx" ON "reservation_photos"("reservationId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "reservation_photos" ADD CONSTRAINT "reservation_photos_reservationId_fkey" FOREIGN KEY ("reservationId") REFERENCES "reservations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- AlterEnum (PostgreSQL enum values must be added outside a transaction)
|
||||
ALTER TYPE "VehicleStatus" ADD VALUE 'RESERVED';
|
||||
ALTER TYPE "VehicleStatus" ADD VALUE 'READY';
|
||||
ALTER TYPE "VehicleStatus" ADD VALUE 'RETURNED';
|
||||
ALTER TYPE "VehicleStatus" ADD VALUE 'NEEDS_CLEANING';
|
||||
ALTER TYPE "VehicleStatus" ADD VALUE 'DAMAGE_REVIEW';
|
||||
@@ -0,0 +1,83 @@
|
||||
-- CreateEnum: FeedbackCategory
|
||||
CREATE TYPE "FeedbackCategory" AS ENUM (
|
||||
'BOOKING',
|
||||
'PICKUP',
|
||||
'VEHICLE_CLEANLINESS',
|
||||
'VEHICLE_CONDITION',
|
||||
'STAFF_SERVICE',
|
||||
'PRICING',
|
||||
'DEPOSIT',
|
||||
'INSURANCE',
|
||||
'DAMAGE_CLAIM',
|
||||
'RETURN_PROCESS',
|
||||
'COMMUNICATION',
|
||||
'ROADSIDE_ASSISTANCE',
|
||||
'BILLING',
|
||||
'OTHER'
|
||||
);
|
||||
|
||||
-- CreateEnum: ComplaintSeverity
|
||||
CREATE TYPE "ComplaintSeverity" AS ENUM ('LEVEL_1', 'LEVEL_2', 'LEVEL_3');
|
||||
|
||||
-- CreateEnum: ComplaintStatus
|
||||
CREATE TYPE "ComplaintStatus" AS ENUM ('OPEN', 'INVESTIGATING', 'RESOLVED', 'CLOSED');
|
||||
|
||||
-- AlterTable: reviews — add category column
|
||||
ALTER TABLE "reviews" ADD COLUMN "category" "FeedbackCategory";
|
||||
|
||||
-- AlterTable: reservations — add review tracking columns
|
||||
ALTER TABLE "reservations"
|
||||
ADD COLUMN "reviewRequestSentAt" TIMESTAMP(3),
|
||||
ADD COLUMN "reviewReminderSentAt" TIMESTAMP(3),
|
||||
ADD COLUMN "reviewFinalReminderSentAt" TIMESTAMP(3),
|
||||
ADD COLUMN "reviewPaused" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable: customers — add reviewOptOut column
|
||||
ALTER TABLE "customers" ADD COLUMN "reviewOptOut" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable: complaints
|
||||
CREATE TABLE "complaints" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"reservationId" TEXT,
|
||||
"reviewId" TEXT,
|
||||
"customerId" TEXT,
|
||||
"severity" "ComplaintSeverity" NOT NULL DEFAULT 'LEVEL_1',
|
||||
"status" "ComplaintStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"category" "FeedbackCategory" NOT NULL,
|
||||
"subject" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"resolution" TEXT,
|
||||
"notes" TEXT,
|
||||
"assignedTo" TEXT,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"resolvedBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "complaints_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "complaints_companyId_idx" ON "complaints"("companyId");
|
||||
CREATE INDEX "complaints_reservationId_idx" ON "complaints"("reservationId");
|
||||
|
||||
-- AddForeignKey: complaints → companies
|
||||
ALTER TABLE "complaints"
|
||||
ADD CONSTRAINT "complaints_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: complaints → reservations
|
||||
ALTER TABLE "complaints"
|
||||
ADD CONSTRAINT "complaints_reservationId_fkey"
|
||||
FOREIGN KEY ("reservationId") REFERENCES "reservations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: complaints → reviews
|
||||
ALTER TABLE "complaints"
|
||||
ADD CONSTRAINT "complaints_reviewId_fkey"
|
||||
FOREIGN KEY ("reviewId") REFERENCES "reviews"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: complaints → customers
|
||||
ALTER TABLE "complaints"
|
||||
ADD CONSTRAINT "complaints_customerId_fkey"
|
||||
FOREIGN KEY ("customerId") REFERENCES "customers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
CREATE TYPE "VehiclePricingMode" AS ENUM ('MANUAL', 'AUTOMATIC');
|
||||
|
||||
CREATE TYPE "VehiclePricingRuleType" AS ENUM (
|
||||
'DATE_RANGE',
|
||||
'SEASONAL',
|
||||
'HOLIDAY',
|
||||
'WEEKEND',
|
||||
'SPECIAL_EVENT',
|
||||
'MANUAL_OVERRIDE'
|
||||
);
|
||||
|
||||
CREATE TYPE "VehiclePriceChangeSource" AS ENUM (
|
||||
'CONFIG_UPDATE',
|
||||
'RULE_CREATED',
|
||||
'RULE_UPDATED',
|
||||
'RULE_DELETED',
|
||||
'MANUAL_OVERRIDE'
|
||||
);
|
||||
|
||||
CREATE TABLE "vehicle_pricing_configurations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vehicleId" TEXT NOT NULL,
|
||||
"pricingMode" "VehiclePricingMode" NOT NULL DEFAULT 'MANUAL',
|
||||
"baseDailyRate" INTEGER,
|
||||
"weeklyRate" INTEGER,
|
||||
"weekendRate" INTEGER,
|
||||
"holidayRate" INTEGER,
|
||||
"monthlyRate" INTEGER,
|
||||
"longTermDailyRate" INTEGER,
|
||||
"minimumDailyRate" INTEGER,
|
||||
"maximumDailyRate" INTEGER,
|
||||
"maxDailyPriceMovementPct" INTEGER NOT NULL DEFAULT 10,
|
||||
"automaticPricingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"approvalRequired" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "vehicle_pricing_configurations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "vehicle_pricing_configurations_vehicleId_key"
|
||||
ON "vehicle_pricing_configurations"("vehicleId");
|
||||
|
||||
CREATE TABLE "vehicle_pricing_rules" (
|
||||
"id" TEXT NOT NULL,
|
||||
"configurationId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"ruleType" "VehiclePricingRuleType" NOT NULL DEFAULT 'DATE_RANGE',
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3) NOT NULL,
|
||||
"dailyRate" INTEGER,
|
||||
"weeklyRate" INTEGER,
|
||||
"weekendRate" INTEGER,
|
||||
"monthlyRate" INTEGER,
|
||||
"minDailyRate" INTEGER,
|
||||
"maxDailyRate" INTEGER,
|
||||
"automaticAdjustmentPct" INTEGER,
|
||||
"priceAdjustment" INTEGER,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "vehicle_pricing_rules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "vehicle_pricing_rules_configurationId_idx"
|
||||
ON "vehicle_pricing_rules"("configurationId");
|
||||
|
||||
CREATE INDEX "vehicle_pricing_rules_configurationId_startDate_endDate_idx"
|
||||
ON "vehicle_pricing_rules"("configurationId", "startDate", "endDate");
|
||||
|
||||
CREATE TABLE "vehicle_price_history" (
|
||||
"id" TEXT NOT NULL,
|
||||
"configurationId" TEXT NOT NULL,
|
||||
"vehicleId" TEXT NOT NULL,
|
||||
"source" "VehiclePriceChangeSource" NOT NULL DEFAULT 'CONFIG_UPDATE',
|
||||
"changedByEmployeeId" TEXT,
|
||||
"previousDailyRate" INTEGER,
|
||||
"nextDailyRate" INTEGER,
|
||||
"previousWeeklyRate" INTEGER,
|
||||
"nextWeeklyRate" INTEGER,
|
||||
"note" TEXT,
|
||||
"effectiveFrom" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "vehicle_price_history_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "vehicle_price_history_vehicleId_createdAt_idx"
|
||||
ON "vehicle_price_history"("vehicleId", "createdAt");
|
||||
|
||||
CREATE INDEX "vehicle_price_history_configurationId_createdAt_idx"
|
||||
ON "vehicle_price_history"("configurationId", "createdAt");
|
||||
|
||||
ALTER TABLE "vehicle_pricing_configurations"
|
||||
ADD CONSTRAINT "vehicle_pricing_configurations_vehicleId_fkey"
|
||||
FOREIGN KEY ("vehicleId") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "vehicle_pricing_rules"
|
||||
ADD CONSTRAINT "vehicle_pricing_rules_configurationId_fkey"
|
||||
FOREIGN KEY ("configurationId") REFERENCES "vehicle_pricing_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "vehicle_price_history"
|
||||
ADD CONSTRAINT "vehicle_price_history_configurationId_fkey"
|
||||
FOREIGN KEY ("configurationId") REFERENCES "vehicle_pricing_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "vehicle_price_history"
|
||||
ADD CONSTRAINT "vehicle_price_history_vehicleId_fkey"
|
||||
FOREIGN KEY ("vehicleId") REFERENCES "vehicles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,129 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MenuItemType" AS ENUM (
|
||||
'INTERNAL_PAGE',
|
||||
'EXTERNAL_LINK',
|
||||
'PARENT_MENU',
|
||||
'SECTION_LABEL',
|
||||
'DIVIDER'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "menu_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"systemKey" TEXT,
|
||||
"label" TEXT NOT NULL,
|
||||
"itemType" "MenuItemType" NOT NULL DEFAULT 'INTERNAL_PAGE',
|
||||
"routeOrUrl" TEXT,
|
||||
"icon" TEXT,
|
||||
"parentId" TEXT,
|
||||
"displayOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"openInNewTab" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isRequired" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdBy" TEXT,
|
||||
"updatedBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "menu_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_menu_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"plan" "Plan" NOT NULL,
|
||||
"menuItemId" TEXT NOT NULL,
|
||||
"displayOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "subscription_menu_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "company_menu_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"menuItemId" TEXT NOT NULL,
|
||||
"displayOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "company_menu_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "menu_item_role_visibility" (
|
||||
"id" TEXT NOT NULL,
|
||||
"menuItemId" TEXT NOT NULL,
|
||||
"role" "EmployeeRole" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "menu_item_role_visibility_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "menu_items_systemKey_key" ON "menu_items"("systemKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "menu_items_parentId_idx" ON "menu_items"("parentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "menu_items_isActive_displayOrder_idx" ON "menu_items"("isActive", "displayOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "subscription_menu_items_plan_menuItemId_key" ON "subscription_menu_items"("plan", "menuItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_menu_items_menuItemId_idx" ON "subscription_menu_items"("menuItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_menu_items_plan_displayOrder_idx" ON "subscription_menu_items"("plan", "displayOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "company_menu_items_companyId_menuItemId_key" ON "company_menu_items"("companyId", "menuItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "company_menu_items_menuItemId_idx" ON "company_menu_items"("menuItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "company_menu_items_companyId_displayOrder_idx" ON "company_menu_items"("companyId", "displayOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "menu_item_role_visibility_menuItemId_role_key" ON "menu_item_role_visibility"("menuItemId", "role");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "menu_item_role_visibility_role_idx" ON "menu_item_role_visibility"("role");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "menu_items"
|
||||
ADD CONSTRAINT "menu_items_parentId_fkey"
|
||||
FOREIGN KEY ("parentId") REFERENCES "menu_items"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "subscription_menu_items"
|
||||
ADD CONSTRAINT "subscription_menu_items_menuItemId_fkey"
|
||||
FOREIGN KEY ("menuItemId") REFERENCES "menu_items"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "company_menu_items"
|
||||
ADD CONSTRAINT "company_menu_items_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "company_menu_items"
|
||||
ADD CONSTRAINT "company_menu_items_menuItemId_fkey"
|
||||
FOREIGN KEY ("menuItemId") REFERENCES "menu_items"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "menu_item_role_visibility"
|
||||
ADD CONSTRAINT "menu_item_role_visibility_menuItemId_fkey"
|
||||
FOREIGN KEY ("menuItemId") REFERENCES "menu_items"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
WITH canonical_menu_items ("systemKey", "label", "itemType", "routeOrUrl", "icon", "displayOrder", "isRequired", "isActive") AS (
|
||||
VALUES
|
||||
('dashboard', 'Dashboard', 'INTERNAL_PAGE'::"MenuItemType", '/', 'LayoutDashboard', 10, true, true),
|
||||
('fleet', 'Fleet', 'INTERNAL_PAGE'::"MenuItemType", '/fleet', 'Car', 20, true, true),
|
||||
('reservations', 'Booking', 'INTERNAL_PAGE'::"MenuItemType", '/reservations', 'Calendar', 30, true, true),
|
||||
('customers', 'Customers', 'INTERNAL_PAGE'::"MenuItemType", '/customers', 'Users', 40, false, true),
|
||||
('subscription', 'Subscription', 'INTERNAL_PAGE'::"MenuItemType", '/subscription', 'CreditCard', 50, true, true),
|
||||
('contracts', 'Contracts', 'INTERNAL_PAGE'::"MenuItemType", '/contracts', 'FileText', 60, false, true),
|
||||
('notifications', 'Notifications', 'INTERNAL_PAGE'::"MenuItemType", '/notifications', 'Bell', 70, false, true),
|
||||
('settings', 'Settings', 'INTERNAL_PAGE'::"MenuItemType", '/settings', 'Settings', 80, false, true),
|
||||
('onlineReservations', 'Online Reservations', 'INTERNAL_PAGE'::"MenuItemType", '/online-reservations', 'Globe', 90, false, true),
|
||||
('team', 'Team', 'INTERNAL_PAGE'::"MenuItemType", '/team', 'UserPlus', 100, false, true),
|
||||
('offers', 'Offers', 'INTERNAL_PAGE'::"MenuItemType", '/offers', 'Tag', 110, false, true),
|
||||
('reports', 'Reports', 'INTERNAL_PAGE'::"MenuItemType", '/reports', 'BarChart2', 120, false, true),
|
||||
('billing', 'Customer Billing', 'INTERNAL_PAGE'::"MenuItemType", '/billing', 'CreditCard', 130, false, true),
|
||||
('reviews', 'Reviews', 'INTERNAL_PAGE'::"MenuItemType", '/reviews', 'Star', 140, false, true),
|
||||
('complaints', 'Complaints', 'INTERNAL_PAGE'::"MenuItemType", '/complaints', 'AlertTriangle', 150, false, true)
|
||||
)
|
||||
INSERT INTO "menu_items" (
|
||||
"id",
|
||||
"systemKey",
|
||||
"label",
|
||||
"itemType",
|
||||
"routeOrUrl",
|
||||
"icon",
|
||||
"displayOrder",
|
||||
"openInNewTab",
|
||||
"isRequired",
|
||||
"isActive",
|
||||
"createdBy",
|
||||
"updatedBy"
|
||||
)
|
||||
SELECT
|
||||
'menu_' || "systemKey",
|
||||
"systemKey",
|
||||
"label",
|
||||
"itemType",
|
||||
"routeOrUrl",
|
||||
"icon",
|
||||
"displayOrder",
|
||||
false,
|
||||
"isRequired",
|
||||
"isActive",
|
||||
'system',
|
||||
'system'
|
||||
FROM canonical_menu_items
|
||||
ON CONFLICT ("systemKey") DO UPDATE
|
||||
SET
|
||||
"label" = EXCLUDED."label",
|
||||
"itemType" = EXCLUDED."itemType",
|
||||
"routeOrUrl" = EXCLUDED."routeOrUrl",
|
||||
"icon" = EXCLUDED."icon",
|
||||
"displayOrder" = EXCLUDED."displayOrder",
|
||||
"isRequired" = EXCLUDED."isRequired",
|
||||
"isActive" = EXCLUDED."isActive",
|
||||
"updatedBy" = 'system',
|
||||
"updatedAt" = CURRENT_TIMESTAMP;
|
||||
|
||||
DELETE FROM "subscription_menu_items"
|
||||
WHERE "menuItemId" IN (
|
||||
SELECT "id"
|
||||
FROM "menu_items"
|
||||
WHERE "systemKey" IN (
|
||||
'dashboard',
|
||||
'fleet',
|
||||
'reservations',
|
||||
'customers',
|
||||
'subscription',
|
||||
'contracts',
|
||||
'notifications',
|
||||
'settings',
|
||||
'onlineReservations',
|
||||
'team',
|
||||
'offers',
|
||||
'reports',
|
||||
'billing',
|
||||
'reviews',
|
||||
'complaints'
|
||||
)
|
||||
);
|
||||
|
||||
WITH canonical_subscription_assignments ("systemKey", "plan", "displayOrder") AS (
|
||||
VALUES
|
||||
('dashboard', 'STARTER'::"Plan", 10),
|
||||
('fleet', 'STARTER'::"Plan", 20),
|
||||
('reservations', 'STARTER'::"Plan", 30),
|
||||
('customers', 'STARTER'::"Plan", 40),
|
||||
('subscription', 'STARTER'::"Plan", 50),
|
||||
('contracts', 'STARTER'::"Plan", 60),
|
||||
('notifications', 'STARTER'::"Plan", 70),
|
||||
('settings', 'STARTER'::"Plan", 80),
|
||||
|
||||
('dashboard', 'GROWTH'::"Plan", 10),
|
||||
('fleet', 'GROWTH'::"Plan", 20),
|
||||
('reservations', 'GROWTH'::"Plan", 30),
|
||||
('customers', 'GROWTH'::"Plan", 40),
|
||||
('subscription', 'GROWTH'::"Plan", 50),
|
||||
('contracts', 'GROWTH'::"Plan", 60),
|
||||
('notifications', 'GROWTH'::"Plan", 70),
|
||||
('settings', 'GROWTH'::"Plan", 80),
|
||||
('onlineReservations', 'GROWTH'::"Plan", 90),
|
||||
('team', 'GROWTH'::"Plan", 100),
|
||||
('offers', 'GROWTH'::"Plan", 110),
|
||||
('reports', 'GROWTH'::"Plan", 120),
|
||||
('billing', 'GROWTH'::"Plan", 130),
|
||||
('reviews', 'GROWTH'::"Plan", 140),
|
||||
('complaints', 'GROWTH'::"Plan", 150),
|
||||
|
||||
('dashboard', 'PRO'::"Plan", 10),
|
||||
('fleet', 'PRO'::"Plan", 20),
|
||||
('reservations', 'PRO'::"Plan", 30),
|
||||
('customers', 'PRO'::"Plan", 40),
|
||||
('subscription', 'PRO'::"Plan", 50),
|
||||
('contracts', 'PRO'::"Plan", 60),
|
||||
('notifications', 'PRO'::"Plan", 70),
|
||||
('settings', 'PRO'::"Plan", 80),
|
||||
('onlineReservations', 'PRO'::"Plan", 90),
|
||||
('team', 'PRO'::"Plan", 100),
|
||||
('offers', 'PRO'::"Plan", 110),
|
||||
('reports', 'PRO'::"Plan", 120),
|
||||
('billing', 'PRO'::"Plan", 130),
|
||||
('reviews', 'PRO'::"Plan", 140),
|
||||
('complaints', 'PRO'::"Plan", 150)
|
||||
)
|
||||
INSERT INTO "subscription_menu_items" (
|
||||
"id",
|
||||
"plan",
|
||||
"menuItemId",
|
||||
"displayOrder",
|
||||
"isActive"
|
||||
)
|
||||
SELECT
|
||||
'menu_sub_' || lower(csa."plan"::text) || '_' || csa."systemKey",
|
||||
csa."plan",
|
||||
mi."id",
|
||||
csa."displayOrder",
|
||||
true
|
||||
FROM canonical_subscription_assignments csa
|
||||
JOIN "menu_items" mi ON mi."systemKey" = csa."systemKey";
|
||||
|
||||
DELETE FROM "menu_item_role_visibility"
|
||||
WHERE "menuItemId" IN (
|
||||
SELECT "id"
|
||||
FROM "menu_items"
|
||||
WHERE "systemKey" IN (
|
||||
'dashboard',
|
||||
'fleet',
|
||||
'reservations',
|
||||
'customers',
|
||||
'subscription',
|
||||
'contracts',
|
||||
'notifications',
|
||||
'settings',
|
||||
'onlineReservations',
|
||||
'team',
|
||||
'offers',
|
||||
'reports',
|
||||
'billing',
|
||||
'reviews',
|
||||
'complaints'
|
||||
)
|
||||
);
|
||||
|
||||
WITH canonical_role_visibilities ("systemKey", "role") AS (
|
||||
VALUES
|
||||
('dashboard', 'OWNER'::"EmployeeRole"),
|
||||
('dashboard', 'MANAGER'::"EmployeeRole"),
|
||||
('dashboard', 'AGENT'::"EmployeeRole"),
|
||||
('fleet', 'OWNER'::"EmployeeRole"),
|
||||
('fleet', 'MANAGER'::"EmployeeRole"),
|
||||
('fleet', 'AGENT'::"EmployeeRole"),
|
||||
('reservations', 'OWNER'::"EmployeeRole"),
|
||||
('reservations', 'MANAGER'::"EmployeeRole"),
|
||||
('reservations', 'AGENT'::"EmployeeRole"),
|
||||
('customers', 'OWNER'::"EmployeeRole"),
|
||||
('customers', 'MANAGER'::"EmployeeRole"),
|
||||
('customers', 'AGENT'::"EmployeeRole"),
|
||||
('subscription', 'OWNER'::"EmployeeRole"),
|
||||
('contracts', 'OWNER'::"EmployeeRole"),
|
||||
('contracts', 'MANAGER'::"EmployeeRole"),
|
||||
('contracts', 'AGENT'::"EmployeeRole"),
|
||||
('notifications', 'OWNER'::"EmployeeRole"),
|
||||
('notifications', 'MANAGER'::"EmployeeRole"),
|
||||
('notifications', 'AGENT'::"EmployeeRole"),
|
||||
('settings', 'OWNER'::"EmployeeRole"),
|
||||
('onlineReservations', 'OWNER'::"EmployeeRole"),
|
||||
('onlineReservations', 'MANAGER'::"EmployeeRole"),
|
||||
('onlineReservations', 'AGENT'::"EmployeeRole"),
|
||||
('team', 'OWNER'::"EmployeeRole"),
|
||||
('team', 'MANAGER'::"EmployeeRole"),
|
||||
('team', 'AGENT'::"EmployeeRole"),
|
||||
('offers', 'OWNER'::"EmployeeRole"),
|
||||
('offers', 'MANAGER'::"EmployeeRole"),
|
||||
('reports', 'OWNER'::"EmployeeRole"),
|
||||
('reports', 'MANAGER'::"EmployeeRole"),
|
||||
('billing', 'OWNER'::"EmployeeRole"),
|
||||
('billing', 'MANAGER'::"EmployeeRole"),
|
||||
('reviews', 'OWNER'::"EmployeeRole"),
|
||||
('reviews', 'MANAGER'::"EmployeeRole"),
|
||||
('reviews', 'AGENT'::"EmployeeRole"),
|
||||
('complaints', 'OWNER'::"EmployeeRole"),
|
||||
('complaints', 'MANAGER'::"EmployeeRole"),
|
||||
('complaints', 'AGENT'::"EmployeeRole")
|
||||
)
|
||||
INSERT INTO "menu_item_role_visibility" (
|
||||
"id",
|
||||
"menuItemId",
|
||||
"role"
|
||||
)
|
||||
SELECT
|
||||
'menu_role_' || lower(crv."role"::text) || '_' || crv."systemKey",
|
||||
mi."id",
|
||||
crv."role"
|
||||
FROM canonical_role_visibilities crv
|
||||
JOIN "menu_items" mi ON mi."systemKey" = crv."systemKey";
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Company API keys are stored hash-only. Raw keys are shown once at creation.
|
||||
CREATE TABLE IF NOT EXISTS "company_api_keys" (
|
||||
"id" TEXT PRIMARY KEY,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"prefix" TEXT NOT NULL UNIQUE,
|
||||
"keyHash" TEXT NOT NULL,
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "company_api_keys_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "company_api_keys_companyId_idx" ON "company_api_keys"("companyId");
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS "reservation_public_access" (
|
||||
"id" TEXT PRIMARY KEY,
|
||||
"reservationId" TEXT NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL UNIQUE,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"usedAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "reservation_public_access_reservationId_fkey"
|
||||
FOREIGN KEY ("reservationId") REFERENCES "reservations"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "reservation_public_access_reservationId_idx" ON "reservation_public_access"("reservationId");
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS "webhook_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerEventId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"payloadHash" TEXT NOT NULL,
|
||||
"errorMessage" TEXT,
|
||||
"receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"processedAt" TIMESTAMP(3),
|
||||
CONSTRAINT "webhook_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "webhook_events_provider_providerEventId_key"
|
||||
ON "webhook_events"("provider", "providerEventId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "webhook_events_provider_status_idx"
|
||||
ON "webhook_events"("provider", "status");
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Remove legacy plaintext company API key storage.
|
||||
-- Company integrations must use company_api_keys, which stores only key hashes and prefixes.
|
||||
DROP INDEX IF EXISTS "companies_apiKey_key";
|
||||
ALTER TABLE "companies" DROP COLUMN IF EXISTS "apiKey";
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "admin_recovery_codes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"adminUserId" TEXT NOT NULL,
|
||||
"codeHash" TEXT NOT NULL,
|
||||
"usedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "admin_recovery_codes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "admin_recovery_codes_adminUserId_usedAt_idx" ON "admin_recovery_codes"("adminUserId", "usedAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "admin_recovery_codes" ADD CONSTRAINT "admin_recovery_codes_adminUserId_fkey" FOREIGN KEY ("adminUserId") REFERENCES "admin_users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
provider = "postgresql"
|
||||
Reference in New Issue
Block a user