359 lines
14 KiB
Plaintext
359 lines
14 KiB
Plaintext
enum SubscriptionProductKind {
|
|
base_plan
|
|
recurring_add_on
|
|
one_time_setup
|
|
contract_override
|
|
}
|
|
|
|
enum TenantProductAcquisitionStatus {
|
|
pending
|
|
provider_pending
|
|
payment_pending
|
|
active
|
|
failed
|
|
cancelled
|
|
expired
|
|
refunded
|
|
}
|
|
|
|
model SubscriptionProduct {
|
|
id String @id @default(cuid())
|
|
billingProviderId String
|
|
code String?
|
|
lagoPlanCode String?
|
|
kind SubscriptionProductKind @default(base_plan)
|
|
order Int
|
|
title String
|
|
active Boolean
|
|
model Int
|
|
public Boolean
|
|
groupTitle String?
|
|
groupDescription String?
|
|
description String?
|
|
badge String?
|
|
billingAddressCollection String @default("auto")
|
|
hasQuantity Boolean @default(false)
|
|
canBuyAgain Boolean @default(false)
|
|
prices SubscriptionPrice[]
|
|
features SubscriptionFeature[]
|
|
tenantProducts TenantSubscriptionProduct[]
|
|
acquisitions TenantProductAcquisition[]
|
|
usageBasedPrices SubscriptionUsageBasedPrice[]
|
|
@@index([code])
|
|
@@index([lagoPlanCode])
|
|
}
|
|
|
|
model SubscriptionPrice {
|
|
id String @id @default(cuid())
|
|
subscriptionProductId String
|
|
subscriptionProduct SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
|
|
billingProviderId String
|
|
lagoPlanCode String?
|
|
lagoFeeCode String?
|
|
molliePriceReference String?
|
|
billingCadence String @default("monthly")
|
|
contractMonths Int @default(1)
|
|
requiredPaymentMethod String @default("any")
|
|
type Int
|
|
billingPeriod Int
|
|
price Decimal
|
|
currency String
|
|
trialDays Int
|
|
active Boolean
|
|
tenantProductPrices TenantSubscriptionProductPrice[]
|
|
acquisitions TenantProductAcquisition[]
|
|
|
|
@@index([lagoPlanCode])
|
|
@@index([lagoFeeCode])
|
|
@@index([molliePriceReference])
|
|
}
|
|
|
|
model SubscriptionUsageBasedPrice {
|
|
id String @id @default(cuid())
|
|
subscriptionProductId String
|
|
subscriptionProduct SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
|
|
billingProviderId String
|
|
billingPeriod Int
|
|
currency String
|
|
unit String
|
|
unitTitle String
|
|
unitTitlePlural String
|
|
usageType String
|
|
aggregateUsage String
|
|
tiersMode String
|
|
billingScheme String
|
|
tiers SubscriptionUsageBasedTier[]
|
|
tenantProductPrices TenantSubscriptionProductPrice[]
|
|
}
|
|
|
|
model SubscriptionUsageBasedTier {
|
|
id String @id @default(cuid())
|
|
subscriptionUsageBasedPriceId String
|
|
subscriptionUsageBasedPrice SubscriptionUsageBasedPrice @relation(fields: [subscriptionUsageBasedPriceId], references: [id], onDelete: Cascade)
|
|
from Int
|
|
to Int?
|
|
perUnitPrice Decimal?
|
|
flatFeePrice Decimal?
|
|
}
|
|
|
|
model SubscriptionFeature {
|
|
id String @id @default(cuid())
|
|
subscriptionProductId String
|
|
order Int
|
|
title String
|
|
name String
|
|
code String?
|
|
type Int
|
|
value Int
|
|
quotaValue Json?
|
|
href String?
|
|
badge String?
|
|
accumulate Boolean @default(false)
|
|
subscriptionProduct SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([code])
|
|
}
|
|
|
|
model TenantSubscription {
|
|
id String @id @default(cuid())
|
|
tenantId String @unique
|
|
billingProviderCustomerId String?
|
|
lagoCustomerId String?
|
|
mollieCustomerId String?
|
|
planCode String?
|
|
seatQuantity Int @default(1)
|
|
officeSeatQuantity Int @default(0)
|
|
fieldSeatQuantity Int @default(0)
|
|
billingCadence String @default("monthly")
|
|
commitmentEndsAt DateTime?
|
|
storageAddOnCodes String[] @default([])
|
|
lastInvoiceExternalId String?
|
|
lastPaymentExternalId String?
|
|
status String @default("trial")
|
|
trialStartsAt DateTime?
|
|
trialEndsAt DateTime?
|
|
pastDueSince DateTime?
|
|
frozenAt DateTime?
|
|
cancelledAt DateTime?
|
|
dunningAttemptCount Int @default(0)
|
|
lastDunningSentAt DateTime?
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
products TenantSubscriptionProduct[]
|
|
usageRecords TenantSubscriptionUsageRecord[]
|
|
|
|
@@index([status])
|
|
@@index([planCode])
|
|
@@index([lagoCustomerId])
|
|
@@index([mollieCustomerId])
|
|
@@index([trialEndsAt])
|
|
@@index([pastDueSince])
|
|
@@index([frozenAt])
|
|
}
|
|
|
|
model TenantSubscriptionProduct {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
tenantSubscriptionId String
|
|
subscriptionProductId String
|
|
customEntitlementPlanId String?
|
|
cancelledAt DateTime?
|
|
endsAt DateTime?
|
|
billingProviderSubscriptionId String?
|
|
lagoSubscriptionId String?
|
|
planCode String?
|
|
seatQuantity Int?
|
|
officeSeatQuantity Int?
|
|
fieldSeatQuantity Int?
|
|
billingCadence String @default("monthly")
|
|
commitmentEndsAt DateTime?
|
|
storageAddOnCodes String[] @default([])
|
|
quantity Int?
|
|
fromCheckoutSessionId String?
|
|
currentPeriodStart DateTime?
|
|
currentPeriodEnd DateTime?
|
|
tenantSubscription TenantSubscription @relation(fields: [tenantSubscriptionId], references: [id], onDelete: Cascade)
|
|
subscriptionProduct SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id])
|
|
prices TenantSubscriptionProductPrice[]
|
|
acquisition TenantProductAcquisition?
|
|
|
|
@@index([lagoSubscriptionId])
|
|
@@index([planCode])
|
|
}
|
|
|
|
/// Idempotent tenant-owned ledger for products acquired outside the one-base-plan
|
|
/// lifecycle. `scopeKey` is the stable business scope (for example `tenant` or a
|
|
/// normalized hostname) and makes provider retries safe without granting the same
|
|
/// entitlement twice. Provider failures remain local to this row and must never
|
|
/// mutate the base TenantSubscription status.
|
|
model TenantProductAcquisition {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
subscriptionProductId String
|
|
subscriptionPriceId String?
|
|
productKind SubscriptionProductKind
|
|
scopeKey String
|
|
requestHash String
|
|
status TenantProductAcquisitionStatus @default(pending)
|
|
version Int @default(1)
|
|
quantity Int @default(1)
|
|
amountCents Int?
|
|
currency String?
|
|
checkoutSessionId String? @unique
|
|
tenantSubscriptionProductId String? @unique
|
|
provider String?
|
|
providerAttempt Int @default(1)
|
|
providerPaymentId String? @unique
|
|
lagoInvoiceIntentReference String?
|
|
providerPaymentExpiresAt DateTime?
|
|
providerPaidAt DateTime?
|
|
invoiceExternalId String?
|
|
lagoSubscriptionId String?
|
|
lagoProviderSubscriptionId String?
|
|
initialLagoInvoiceExternalId String? @unique
|
|
checkoutUrl String?
|
|
sourceReference String?
|
|
failureCode String?
|
|
providerStartedAt DateTime?
|
|
reservationExpiresAt DateTime?
|
|
activatedAt DateTime?
|
|
failedAt DateTime?
|
|
cancelledAt DateTime?
|
|
expiredAt DateTime?
|
|
refundedAt DateTime?
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
subscriptionProduct SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id])
|
|
subscriptionPrice SubscriptionPrice? @relation(fields: [subscriptionPriceId], references: [id])
|
|
checkoutSession CheckoutSessionStatus? @relation(fields: [checkoutSessionId], references: [id], onDelete: SetNull)
|
|
tenantSubscriptionProduct TenantSubscriptionProduct? @relation(fields: [tenantSubscriptionProductId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([tenantId, subscriptionProductId, scopeKey])
|
|
@@index([tenantId, status, updatedAt])
|
|
@@index([tenantId, status, reservationExpiresAt])
|
|
@@index([subscriptionProductId, productKind, status])
|
|
@@index([invoiceExternalId])
|
|
@@index([lagoSubscriptionId])
|
|
@@index([lagoProviderSubscriptionId])
|
|
}
|
|
|
|
model TenantSubscriptionProductPrice {
|
|
id String @id @default(cuid())
|
|
tenantSubscriptionProductId String
|
|
tenantSubscriptionProduct TenantSubscriptionProduct @relation(fields: [tenantSubscriptionProductId], references: [id], onDelete: Cascade)
|
|
subscriptionPriceId String?
|
|
subscriptionPrice SubscriptionPrice? @relation(fields: [subscriptionPriceId], references: [id])
|
|
subscriptionUsageBasedPriceId String?
|
|
subscriptionUsageBasedPrice SubscriptionUsageBasedPrice? @relation(fields: [subscriptionUsageBasedPriceId], references: [id])
|
|
quantity Int @default(1)
|
|
isMetered Boolean @default(false)
|
|
}
|
|
|
|
model TenantSubscriptionUsageRecord {
|
|
id String @id @default(cuid())
|
|
tenantId String
|
|
tenantSubscriptionId String
|
|
tenantSubscriptionProductPriceId String
|
|
tenantSubscription TenantSubscription @relation(fields: [tenantSubscriptionId], references: [id], onDelete: Cascade)
|
|
metricCode String @default("legacy")
|
|
timestamp Int
|
|
quantity Int
|
|
idempotencyKey String?
|
|
billingProviderSubscriptionItemId String?
|
|
|
|
@@unique([tenantId, metricCode, idempotencyKey])
|
|
@@index([tenantId, metricCode, timestamp])
|
|
}
|
|
|
|
model TenantProductConfiguration {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String @unique
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
blueprintId String @default("crm-saas")
|
|
notes String?
|
|
signupSubscriptionPriceId String?
|
|
signupOfficeSeatQuantity Int?
|
|
signupFieldSeatQuantity Int?
|
|
}
|
|
|
|
model TenantFeatureOverride {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
featureKey String
|
|
override String
|
|
reason String?
|
|
|
|
@@unique([tenantId, featureKey])
|
|
@@index([featureKey, override])
|
|
}
|
|
|
|
model CheckoutSessionStatus {
|
|
id String @id
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
pending Boolean @default(true) // has not added products to tenant
|
|
email String
|
|
fromUrl String
|
|
fromUserId String?
|
|
createdUserId String?
|
|
createdTenantId String?
|
|
provider String?
|
|
providerPaymentId String?
|
|
invoiceExternalId String?
|
|
subscriptionProductId String?
|
|
subscriptionPriceId String?
|
|
lagoSubscriptionId String?
|
|
planCode String?
|
|
productKind SubscriptionProductKind @default(base_plan)
|
|
scopeKey String?
|
|
quantity Int @default(1)
|
|
officeSeatQuantity Int @default(0)
|
|
fieldSeatQuantity Int @default(0)
|
|
billingCadence String @default("monthly")
|
|
commitmentEndsAt DateTime?
|
|
storageAddOnCodes String[] @default([])
|
|
amountCents Int?
|
|
currency String?
|
|
checkoutUrl String?
|
|
status String @default("open")
|
|
productAcquisition TenantProductAcquisition?
|
|
|
|
@@index([providerPaymentId])
|
|
@@index([tenantId, pending])
|
|
}
|
|
|
|
model BillingWebhookLog {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
providerEventId String @unique
|
|
eventType String
|
|
processedAt DateTime?
|
|
relatedTenantId String?
|
|
error String?
|
|
|
|
@@index([eventType, createdAt])
|
|
@@index([relatedTenantId, createdAt])
|
|
}
|
|
|
|
model Credit {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
tenantId String
|
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
userId String?
|
|
user User? @relation(fields: [userId], references: [id])
|
|
amount Int
|
|
type String
|
|
objectId String?
|
|
|
|
@@index([tenantId, userId])
|
|
@@index([tenantId, createdAt])
|
|
}
|