add subscription upgrade plan
Build & Push / Pipeline Tests (push) Failing after 2m0s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 48s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 41s
Test / Dashboard Unit Tests (push) Successful in 43s
Test / API Integration Tests (push) Failing after 1m7s

This commit is contained in:
root
2026-08-10 23:23:58 -04:00
parent 5f06256271
commit 7b8f81336a
15 changed files with 1957 additions and 104 deletions
@@ -0,0 +1,179 @@
CREATE TYPE "SubscriptionUpgradeRequestType" AS ENUM (
'IMMEDIATE_PRORATED',
'AT_RENEWAL',
'TERM_RESET'
);
CREATE TYPE "SubscriptionUpgradeRequestStatus" AS ENUM (
'DRAFT',
'QUOTED',
'PAYMENT_PENDING',
'PAYMENT_REVIEW',
'CORRECTION_REQUIRED',
'APPROVED',
'ACTIVATED',
'SCHEDULED',
'REJECTED',
'EXPIRED',
'CANCELLED',
'ACTIVATION_FAILED',
'SUPERSEDED'
);
CREATE TABLE "subscription_upgrade_requests" (
"id" TEXT NOT NULL,
"companyId" TEXT NOT NULL,
"subscriptionId" TEXT NOT NULL,
"requestType" "SubscriptionUpgradeRequestType" NOT NULL,
"fromPlan" "Plan" NOT NULL,
"fromBillingPeriod" "BillingPeriod" NOT NULL,
"toPlan" "Plan" NOT NULL,
"toBillingPeriod" "BillingPeriod" NOT NULL,
"status" "SubscriptionUpgradeRequestStatus" NOT NULL DEFAULT 'DRAFT',
"requestedByEmployeeId" TEXT NOT NULL,
"requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"effectiveAt" TIMESTAMP(3),
"scheduledFor" TIMESTAMP(3),
"quoteId" TEXT,
"companyLanguageSnapshot" TEXT NOT NULL DEFAULT 'en',
"billingTimezoneSnapshot" TEXT NOT NULL DEFAULT 'Africa/Casablanca',
"acceptedTermsVersion" TEXT,
"acceptedAt" TIMESTAMP(3),
"cancelledAt" TIMESTAMP(3),
"cancelledByEmployeeId" TEXT,
"cancellationReason" TEXT,
"expiresAt" TIMESTAMP(3),
"billingInvoiceId" TEXT,
"approvedPaymentAttemptId" TEXT,
"approvedByAdminId" TEXT,
"approvedAt" TIMESTAMP(3),
"activationKey" TEXT,
"activatedAt" TIMESTAMP(3),
"rejectionReason" TEXT,
"correctionReason" TEXT,
"version" INTEGER NOT NULL DEFAULT 1,
"metadata" JSONB NOT NULL DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "subscription_upgrade_requests_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "subscription_upgrade_quotes" (
"id" TEXT NOT NULL,
"upgradeRequestId" TEXT NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'MAD',
"currentEligibleNetTermPrice" INTEGER NOT NULL,
"targetNetTermPrice" INTEGER NOT NULL,
"termStart" TIMESTAMP(3) NOT NULL,
"renewalDate" TIMESTAMP(3) NOT NULL,
"pricingEffectiveAt" TIMESTAMP(3) NOT NULL,
"termDays" INTEGER NOT NULL,
"remainingDays" INTEGER NOT NULL,
"prorationNumerator" INTEGER NOT NULL,
"prorationDenominator" INTEGER NOT NULL,
"currentCredit" INTEGER NOT NULL,
"targetRemainingValue" INTEGER NOT NULL,
"discountAmount" INTEGER NOT NULL DEFAULT 0,
"taxAmount" INTEGER NOT NULL DEFAULT 0,
"feeAmount" INTEGER NOT NULL DEFAULT 0,
"subtotalAmount" INTEGER NOT NULL,
"totalAmount" INTEGER NOT NULL,
"calculationVersion" TEXT NOT NULL,
"inputSnapshot" JSONB NOT NULL DEFAULT '{}',
"integrityHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"expiredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "subscription_upgrade_quotes_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "subscription_plan_history" (
"id" TEXT NOT NULL,
"subscriptionId" TEXT NOT NULL,
"companyId" TEXT NOT NULL,
"previousPlan" "Plan" NOT NULL,
"previousBillingPeriod" "BillingPeriod" NOT NULL,
"newPlan" "Plan" NOT NULL,
"newBillingPeriod" "BillingPeriod" NOT NULL,
"changeType" TEXT NOT NULL,
"sourceUpgradeRequestId" TEXT,
"approvedPaymentAttemptId" TEXT,
"effectiveAt" TIMESTAMP(3) NOT NULL,
"previousTermStart" TIMESTAMP(3),
"previousTermEnd" TIMESTAMP(3),
"newTermStart" TIMESTAMP(3),
"newTermEnd" TIMESTAMP(3),
"actorType" TEXT NOT NULL,
"actorId" TEXT,
"approvingAdminId" TEXT,
"entitlementSnapshot" JSONB NOT NULL DEFAULT '{}',
"auditCorrelationId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "subscription_plan_history_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "subscription_upgrade_requests_quoteId_key" ON "subscription_upgrade_requests"("quoteId");
CREATE UNIQUE INDEX "subscription_upgrade_requests_billingInvoiceId_key" ON "subscription_upgrade_requests"("billingInvoiceId");
CREATE UNIQUE INDEX "subscription_upgrade_requests_activationKey_key" ON "subscription_upgrade_requests"("activationKey");
CREATE UNIQUE INDEX "subscription_plan_history_sourceUpgradeRequestId_key" ON "subscription_plan_history"("sourceUpgradeRequestId");
CREATE INDEX "subscription_upgrade_requests_companyId_status_idx" ON "subscription_upgrade_requests"("companyId", "status");
CREATE INDEX "subscription_upgrade_requests_subscriptionId_status_idx" ON "subscription_upgrade_requests"("subscriptionId", "status");
CREATE INDEX "subscription_upgrade_requests_expiresAt_idx" ON "subscription_upgrade_requests"("expiresAt");
CREATE INDEX "subscription_upgrade_requests_scheduledFor_idx" ON "subscription_upgrade_requests"("scheduledFor");
CREATE INDEX "subscription_upgrade_quotes_upgradeRequestId_idx" ON "subscription_upgrade_quotes"("upgradeRequestId");
CREATE INDEX "subscription_upgrade_quotes_expiresAt_idx" ON "subscription_upgrade_quotes"("expiresAt");
CREATE INDEX "subscription_plan_history_subscriptionId_idx" ON "subscription_plan_history"("subscriptionId");
CREATE INDEX "subscription_plan_history_companyId_idx" ON "subscription_plan_history"("companyId");
CREATE INDEX "subscription_plan_history_changeType_idx" ON "subscription_plan_history"("changeType");
CREATE UNIQUE INDEX "subscription_upgrade_requests_one_non_terminal_per_subscription"
ON "subscription_upgrade_requests"("subscriptionId")
WHERE "status" IN (
'DRAFT',
'QUOTED',
'PAYMENT_PENDING',
'PAYMENT_REVIEW',
'CORRECTION_REQUIRED',
'APPROVED',
'SCHEDULED',
'ACTIVATION_FAILED'
);
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_companyId_fkey"
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_subscriptionId_fkey"
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_requestedByEmployeeId_fkey"
FOREIGN KEY ("requestedByEmployeeId") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_quoteId_fkey"
FOREIGN KEY ("quoteId") REFERENCES "subscription_upgrade_quotes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_billingInvoiceId_fkey"
FOREIGN KEY ("billingInvoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_requests"
ADD CONSTRAINT "subscription_upgrade_requests_approvedByAdminId_fkey"
FOREIGN KEY ("approvedByAdminId") REFERENCES "admin_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "subscription_upgrade_quotes"
ADD CONSTRAINT "subscription_upgrade_quotes_upgradeRequestId_fkey"
FOREIGN KEY ("upgradeRequestId") REFERENCES "subscription_upgrade_requests"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "subscription_plan_history"
ADD CONSTRAINT "subscription_plan_history_subscriptionId_fkey"
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "subscription_plan_history"
ADD CONSTRAINT "subscription_plan_history_companyId_fkey"
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+145
View File
@@ -45,6 +45,28 @@ enum SubscriptionStatus {
UNPAID
}
enum SubscriptionUpgradeRequestType {
IMMEDIATE_PRORATED
AT_RENEWAL
TERM_RESET
}
enum SubscriptionUpgradeRequestStatus {
DRAFT
QUOTED
PAYMENT_PENDING
PAYMENT_REVIEW
CORRECTION_REQUIRED
APPROVED
ACTIVATED
SCHEDULED
REJECTED
EXPIRED
CANCELLED
ACTIVATION_FAILED
SUPERSEDED
}
enum InvoiceStatus {
PENDING
PAID
@@ -618,6 +640,8 @@ model Company {
subscriptionPaymentRef String?
subscription Subscription?
subscriptionUpgradeRequests SubscriptionUpgradeRequest[]
subscriptionPlanHistory SubscriptionPlanHistory[]
billingAccounts BillingAccount[]
billingInvoices BillingInvoice[]
brand BrandSettings?
@@ -692,6 +716,8 @@ model Subscription {
billingInvoices BillingInvoice[]
collectionsCases CollectionsCase[]
events SubscriptionEvent[]
upgradeRequests SubscriptionUpgradeRequest[]
planHistory SubscriptionPlanHistory[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -853,6 +879,7 @@ model BillingInvoice {
subscriptionId String?
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
legacySubscriptionInvoice SubscriptionInvoice?
subscriptionUpgradeRequest SubscriptionUpgradeRequest?
invoiceNumber String? @unique
invoiceSequence Int? @unique
invoiceType BillingInvoiceType
@@ -907,6 +934,122 @@ model BillingInvoice {
@@map("billing_invoices")
}
model SubscriptionUpgradeRequest {
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], onDelete: Cascade)
requestType SubscriptionUpgradeRequestType
fromPlan Plan
fromBillingPeriod BillingPeriod
toPlan Plan
toBillingPeriod BillingPeriod
status SubscriptionUpgradeRequestStatus @default(DRAFT)
requestedByEmployeeId String
requestedByEmployee Employee @relation("SubscriptionUpgradeRequester", fields: [requestedByEmployeeId], references: [id])
requestedAt DateTime @default(now())
effectiveAt DateTime?
scheduledFor DateTime?
quoteId String? @unique
quote SubscriptionUpgradeQuote? @relation("ActiveSubscriptionUpgradeQuote", fields: [quoteId], references: [id])
quotes SubscriptionUpgradeQuote[] @relation("SubscriptionUpgradeQuotes")
companyLanguageSnapshot String @default("en")
billingTimezoneSnapshot String @default("Africa/Casablanca")
acceptedTermsVersion String?
acceptedAt DateTime?
cancelledAt DateTime?
cancelledByEmployeeId String?
cancellationReason String?
expiresAt DateTime?
billingInvoiceId String? @unique
billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id])
approvedPaymentAttemptId String?
approvedByAdminId String?
approvedByAdmin AdminUser? @relation("SubscriptionUpgradeApprover", fields: [approvedByAdminId], references: [id], onDelete: SetNull)
approvedAt DateTime?
activationKey String? @unique
activatedAt DateTime?
rejectionReason String?
correctionReason String?
version Int @default(1)
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId, status])
@@index([subscriptionId, status])
@@index([expiresAt])
@@index([scheduledFor])
@@map("subscription_upgrade_requests")
}
model SubscriptionUpgradeQuote {
id String @id @default(cuid())
upgradeRequestId String
upgradeRequest SubscriptionUpgradeRequest @relation("SubscriptionUpgradeQuotes", fields: [upgradeRequestId], references: [id], onDelete: Cascade)
activeForUpgradeRequest SubscriptionUpgradeRequest? @relation("ActiveSubscriptionUpgradeQuote")
currency String @default("MAD")
currentEligibleNetTermPrice Int
targetNetTermPrice Int
termStart DateTime
renewalDate DateTime
pricingEffectiveAt DateTime
termDays Int
remainingDays Int
prorationNumerator Int
prorationDenominator Int
currentCredit Int
targetRemainingValue Int
discountAmount Int @default(0)
taxAmount Int @default(0)
feeAmount Int @default(0)
subtotalAmount Int
totalAmount Int
calculationVersion String
inputSnapshot Json @default("{}")
integrityHash String
expiresAt DateTime
expiredAt DateTime?
createdAt DateTime @default(now())
@@index([upgradeRequestId])
@@index([expiresAt])
@@map("subscription_upgrade_quotes")
}
model SubscriptionPlanHistory {
id String @id @default(cuid())
subscriptionId String
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
previousPlan Plan
previousBillingPeriod BillingPeriod
newPlan Plan
newBillingPeriod BillingPeriod
changeType String
sourceUpgradeRequestId String?
approvedPaymentAttemptId String?
effectiveAt DateTime
previousTermStart DateTime?
previousTermEnd DateTime?
newTermStart DateTime?
newTermEnd DateTime?
actorType String
actorId String?
approvingAdminId String?
entitlementSnapshot Json @default("{}")
auditCorrelationId String
createdAt DateTime @default(now())
@@unique([sourceUpgradeRequestId])
@@index([subscriptionId])
@@index([companyId])
@@index([changeType])
@@map("subscription_plan_history")
}
model BillingInvoiceLineItem {
id String @id @default(cuid())
invoiceId String
@@ -1365,6 +1508,7 @@ model Employee {
billingContacts BillingContact[]
manualPaymentSubmissions ManualPaymentSubmission[]
manualPaymentDocuments ManualPaymentDocument[]
subscriptionUpgradeRequests SubscriptionUpgradeRequest[] @relation("SubscriptionUpgradeRequester")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -2290,6 +2434,7 @@ model AdminUser {
ownedBillingAccounts BillingAccount[] @relation("BillingAccountCollectionsOwner")
confirmedManualPayments BillingPaymentAttempt[] @relation("ManualPaymentConfirmingAdmin")
reviewedManualSubmissions ManualPaymentSubmission[] @relation("ManualPaymentReviewingAdmin")
approvedSubscriptionUpgrades SubscriptionUpgradeRequest[] @relation("SubscriptionUpgradeApprover")
ownedCollectionsCases CollectionsCase[] @relation("CollectionsCaseOwner")
assignedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskAssignee")
completedCollectionsTasks CollectionsCallTask[] @relation("CollectionsTaskCompleter")