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

This commit is contained in:
root
2026-08-10 22:35:55 -04:00
parent 10ca76fc1e
commit 5f06256271
73 changed files with 8803 additions and 570 deletions
+534 -186
View File
@@ -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