@saasicat/spec 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-api.openapi.yaml +242 -160
- package/package.json +1 -1
- package/prisma-fragments/01-subscription.prisma +5 -18
- package/prisma-fragments/03-plan-versions.prisma +1 -1
- package/prisma-fragments/05-bundle.prisma +96 -0
- package/prisma-fragments/06-catalog-entries.prisma +9 -4
- package/prisma-fragments/11-subscription-bundle.prisma +49 -0
- package/prisma-fragments/README.md +15 -17
- package/schemas/admin-manifest.schema.json +0 -1
- package/sql/constraints.postgres.sql +0 -9
- package/sql/reference-schema.postgres.sql +33 -94
- package/prisma-fragments/05-bundle-business-type.prisma +0 -203
|
@@ -72,13 +72,8 @@ model Subscription {
|
|
|
72
72
|
// same plan; `pendingPlan` the change to a different plan. Both
|
|
73
73
|
// can coexist.
|
|
74
74
|
//
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
// `businessTypeVersionId` (or both) set. The DB CHECK
|
|
78
|
-
// constraint "at least one of the two set" is added by the consumer in
|
|
79
|
-
// the SQL migration (Prisma cannot express this directly) — see
|
|
80
|
-
// sql/constraints.postgres.sql (`subscriptions_plan_or_bt_check`).
|
|
81
|
-
planVersionId String?
|
|
75
|
+
// Every subscription binds a concrete published PlanVersion.
|
|
76
|
+
planVersionId String
|
|
82
77
|
pendingPlanVersionId String?
|
|
83
78
|
pendingPlanVersionEffectiveAt DateTime?
|
|
84
79
|
pendingPlanVersionAccepted Boolean @default(false)
|
|
@@ -87,14 +82,6 @@ model Subscription {
|
|
|
87
82
|
pendingPlanVersionNotifiedAt DateTime?
|
|
88
83
|
pendingPlanVersionReminderSentAt DateTime?
|
|
89
84
|
|
|
90
|
-
// BusinessType composition (SPEC_V2 §11.1 M5). Optional per app: apps
|
|
91
|
-
// with a pure plan model leave the field at null; apps with domain
|
|
92
|
-
// verticals (e.g. club types)
|
|
93
|
-
// set the Subscription to a concrete published BusinessTypeVersion.
|
|
94
|
-
// Aggregation in EntitlementService.computeLimits see
|
|
95
|
-
// GESCHAEFTSTYP_SPEC.md §6.
|
|
96
|
-
businessTypeVersionId String?
|
|
97
|
-
|
|
98
85
|
// Trial / scheduled plan changes
|
|
99
86
|
trialEntitlementPlan String? // Default entitlement during TRIAL
|
|
100
87
|
postTrialPlan String? // Target package after trial
|
|
@@ -125,14 +112,14 @@ model Subscription {
|
|
|
125
112
|
// tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
126
113
|
paymentMethod SubscriptionPaymentMethod?
|
|
127
114
|
promoRedemption PromoCodeRedemption? // see 02-promo-code.prisma
|
|
128
|
-
planVersion PlanVersion
|
|
115
|
+
planVersion PlanVersion @relation("SubscriptionPlanVersion", fields: [planVersionId], references: [id])
|
|
129
116
|
pendingPlanVersion PlanVersion? @relation("SubscriptionPendingPlanVersion", fields: [pendingPlanVersionId], references: [id])
|
|
130
|
-
|
|
117
|
+
// Standalone add-on bookings (see 11-subscription-bundle.prisma).
|
|
118
|
+
bundles SubscriptionBundle[]
|
|
131
119
|
|
|
132
120
|
@@index([tenantId])
|
|
133
121
|
@@index([planVersionId])
|
|
134
122
|
@@index([pendingPlanVersionId])
|
|
135
|
-
@@index([businessTypeVersionId])
|
|
136
123
|
@@index([currentPeriodEnd])
|
|
137
124
|
@@map("subscriptions")
|
|
138
125
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
//
|
|
28
28
|
// Soft-delete via `deletedAt`. A deleted plan root hides its
|
|
29
29
|
// PlanVersions in the UI, but keeps them effective for existing subscriptions
|
|
30
|
-
// (contract protection P1) — analogous to Bundle
|
|
30
|
+
// (contract protection P1) — analogous to Bundle.
|
|
31
31
|
// -----------------------------------------------------------------------------
|
|
32
32
|
|
|
33
33
|
model Plan {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaS platform Prisma fragment: Bundle (plan catalog extension)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — see 03-plan-versions.prisma for versioning conventions.
|
|
6
|
+
//
|
|
7
|
+
// Global catalog tables (no tenantId, no RLS). Write access only via the
|
|
8
|
+
// SUPER_ADMIN guard in the AdminController of the `@saasicat/nest`
|
|
9
|
+
// package. Entries are assigned to the respective consumer app via
|
|
10
|
+
// `projectKey`.
|
|
11
|
+
//
|
|
12
|
+
// Versioning pattern identical to PlanVersion:
|
|
13
|
+
// - at most 1 draft (publishedAt IS NULL) per identity — constraint via a
|
|
14
|
+
// partial unique index in the migration.
|
|
15
|
+
// - multiple published versions, monotonically incremental `version`.
|
|
16
|
+
// - `supersededAt` marks "no longer actively marketed"; existing bookings
|
|
17
|
+
// stay on the committed version (contractual plan guarantee P1).
|
|
18
|
+
//
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
// Bundle — reusable component of features + quotas + pricing.
|
|
21
|
+
//
|
|
22
|
+
// Root entity without content — the purchasable fields (features, quotas,
|
|
23
|
+
// pricing) live on BundleVersion.
|
|
24
|
+
// -----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
model Bundle {
|
|
27
|
+
id String @id @default(uuid())
|
|
28
|
+
projectKey String // e.g. 'clubapp' | 'demoapp' | …
|
|
29
|
+
bundleKey String // SCREAMING_SNAKE_CASE, e.g. 'SPORT', 'ACCOUNTING', 'EVERSEND_PEPPOL'
|
|
30
|
+
label String
|
|
31
|
+
description String?
|
|
32
|
+
icon String?
|
|
33
|
+
sortOrder Int @default(0)
|
|
34
|
+
|
|
35
|
+
// Locale translations { "en": { label, description }, … }. SPEC_V2 §6.4.
|
|
36
|
+
i18n Json @default("{}")
|
|
37
|
+
|
|
38
|
+
createdAt DateTime @default(now())
|
|
39
|
+
updatedAt DateTime @updatedAt
|
|
40
|
+
deletedAt DateTime?
|
|
41
|
+
|
|
42
|
+
versions BundleVersion[]
|
|
43
|
+
|
|
44
|
+
@@unique([projectKey, bundleKey])
|
|
45
|
+
@@index([projectKey, deletedAt])
|
|
46
|
+
@@map("bundles")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// -----------------------------------------------------------------------------
|
|
50
|
+
// BundleVersion — versioned composition (features, quotas, pricing).
|
|
51
|
+
// `features` is FeatureKey[] (references Discovery / FeatureCatalogEntry).
|
|
52
|
+
// `quotas` is Record<QuotaKey, number>; -1 = unlimited; missing key = 0.
|
|
53
|
+
// `compatibility` is BundleCompatibility (`planIds?` whitelist). Empty =
|
|
54
|
+
// usable with every plan. `pricingOverrides` is an array of
|
|
55
|
+
// BundlePricingOverride values for plan-dependent prices.
|
|
56
|
+
// -----------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
model BundleVersion {
|
|
59
|
+
id String @id @default(uuid())
|
|
60
|
+
bundleId String
|
|
61
|
+
version Int
|
|
62
|
+
baseVersionId String? // predecessor; null for v1
|
|
63
|
+
|
|
64
|
+
features Json // FeatureKey[]
|
|
65
|
+
quotas Json @default("{}") // Record<QuotaKey, number>
|
|
66
|
+
compatibility Json @default("{}") // BundleCompatibility
|
|
67
|
+
pricingOverrides Json @default("[]") // BundlePricingOverride[]
|
|
68
|
+
|
|
69
|
+
monthlyNet Decimal? @db.Decimal(10, 2) // default price; null = only via override
|
|
70
|
+
yearlyNet Decimal? @db.Decimal(10, 2)
|
|
71
|
+
marketed Boolean @default(true)
|
|
72
|
+
|
|
73
|
+
publishedAt DateTime?
|
|
74
|
+
supersededAt DateTime?
|
|
75
|
+
publishedChanges Json? // VersionChange[] — diff to predecessor version
|
|
76
|
+
changeNote String @default("")
|
|
77
|
+
nonRegressive Boolean @default(true)
|
|
78
|
+
|
|
79
|
+
createdByUserId String?
|
|
80
|
+
publishedByUserId String?
|
|
81
|
+
|
|
82
|
+
createdAt DateTime @default(now())
|
|
83
|
+
updatedAt DateTime @updatedAt
|
|
84
|
+
|
|
85
|
+
bundle Bundle @relation(fields: [bundleId], references: [id], onDelete: Cascade)
|
|
86
|
+
baseVersion BundleVersion? @relation("BundleVersionLineage", fields: [baseVersionId], references: [id])
|
|
87
|
+
derivedVersions BundleVersion[] @relation("BundleVersionLineage")
|
|
88
|
+
// Standalone add-on bookings pinned to this version (see
|
|
89
|
+
// 11-subscription-bundle.prisma).
|
|
90
|
+
subscriptionBundles SubscriptionBundle[]
|
|
91
|
+
|
|
92
|
+
@@unique([bundleId, version])
|
|
93
|
+
@@index([bundleId, supersededAt])
|
|
94
|
+
@@index([bundleId, publishedAt])
|
|
95
|
+
@@map("bundle_versions")
|
|
96
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// capability (discovery projection)
|
|
10
10
|
// - FeatureCatalogEntry — ditto for features + marketing fields
|
|
11
11
|
// - MarketingProjection — marketing texts/pricing/highlights per
|
|
12
|
-
// plan/bundle
|
|
12
|
+
// plan/bundle version (locale pivot)
|
|
13
13
|
//
|
|
14
14
|
// Approval lifecycle (#20, feature-/quota-centric):
|
|
15
15
|
// pending → approved (↔ revoke) · outdated (drift) · obsolete
|
|
@@ -179,6 +179,11 @@ model QuotaCatalogEntry {
|
|
|
179
179
|
// Approval lifecycle (#20): pending | approved | outdated | obsolete.
|
|
180
180
|
discoveryStatus String @default("pending")
|
|
181
181
|
|
|
182
|
+
// Predecessor quotaKeys this quota replaces + a successor pointer (#39),
|
|
183
|
+
// analogous to FeatureCatalogEntry. The discovery sync overwrites both.
|
|
184
|
+
replaces String[]
|
|
185
|
+
successorKey String?
|
|
186
|
+
|
|
182
187
|
// Approval signature (#20): code-derived quota facts at approval time
|
|
183
188
|
// ('unit|enforcementMode|usageProvider|featureKey'); on drift approved →
|
|
184
189
|
// outdated.
|
|
@@ -202,7 +207,7 @@ model QuotaCatalogEntry {
|
|
|
202
207
|
|
|
203
208
|
// -----------------------------------------------------------------------------
|
|
204
209
|
// MarketingProjection — locale-specific marketing texts per
|
|
205
|
-
// plan/bundle
|
|
210
|
+
// plan/bundle version.
|
|
206
211
|
//
|
|
207
212
|
// Read and projected by the public catalog controller
|
|
208
213
|
// (`GET /public/catalog?locale=de`). Only versions with status `marketed` and
|
|
@@ -222,8 +227,8 @@ model MarketingProjection {
|
|
|
222
227
|
projectKey String
|
|
223
228
|
|
|
224
229
|
// Polymorphic reference to a versioned entity.
|
|
225
|
-
targetType String // 'PLAN' | 'BUNDLE'
|
|
226
|
-
targetVersionId String // PlanVersion.id | BundleVersion.id
|
|
230
|
+
targetType String // 'PLAN' | 'BUNDLE'
|
|
231
|
+
targetVersionId String // PlanVersion.id | BundleVersion.id
|
|
227
232
|
|
|
228
233
|
locale String @default("de") // ISO-639-1, optionally with a region suffix ('de', 'en', 'de-AT')
|
|
229
234
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaS-Platform Prisma fragment: SubscriptionBundle (standalone add-on booking)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — not a standalone schema. Consumers copy the model into
|
|
6
|
+
// their `schema.prisma`.
|
|
7
|
+
//
|
|
8
|
+
// A SubscriptionBundle is a bundle booked ON TOP of a subscription's plan —
|
|
9
|
+
// the tenant "bundle store". It pins a concrete BundleVersion (features/quotas
|
|
10
|
+
// frozen at booking), carries its own minimum term and cancellation window,
|
|
11
|
+
// and is billed independently of the plan.
|
|
12
|
+
//
|
|
13
|
+
// Accompanying fragments:
|
|
14
|
+
// - 01-subscription.prisma (Subscription — the FK target)
|
|
15
|
+
// - 05-bundle.prisma (BundleVersion — the pinned version)
|
|
16
|
+
//
|
|
17
|
+
// Conventions: see 01-subscription.prisma. `@@map` name is canonical — do not
|
|
18
|
+
// rename. The platform's `SubscriptionBundleRepository` adapter and the tenant
|
|
19
|
+
// bundle-store endpoints depend on it.
|
|
20
|
+
|
|
21
|
+
model SubscriptionBundle {
|
|
22
|
+
id String @id @default(uuid())
|
|
23
|
+
subscriptionId String
|
|
24
|
+
bundleVersionId String
|
|
25
|
+
|
|
26
|
+
startedAt DateTime
|
|
27
|
+
|
|
28
|
+
// Minimum contractual term; a cancellation before this date takes effect
|
|
29
|
+
// only at `minimumTermEndsAt`.
|
|
30
|
+
minimumTermEndsAt DateTime?
|
|
31
|
+
|
|
32
|
+
// Cancellation: requested at `canceledAt`, effective at
|
|
33
|
+
// `canceledEffectiveAt` (period/term end). Both null = active booking.
|
|
34
|
+
canceledAt DateTime?
|
|
35
|
+
canceledEffectiveAt DateTime?
|
|
36
|
+
|
|
37
|
+
createdAt DateTime @default(now())
|
|
38
|
+
updatedAt DateTime @updatedAt
|
|
39
|
+
|
|
40
|
+
// The subscription this add-on is booked on. Restrict the BundleVersion so
|
|
41
|
+
// a booked version cannot be hard-deleted from the catalog.
|
|
42
|
+
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
43
|
+
bundleVersion BundleVersion @relation(fields: [bundleVersionId], references: [id], onDelete: Restrict)
|
|
44
|
+
|
|
45
|
+
@@index([subscriptionId])
|
|
46
|
+
@@index([bundleVersionId])
|
|
47
|
+
@@index([canceledEffectiveAt])
|
|
48
|
+
@@map("subscription_bundles")
|
|
49
|
+
}
|
|
@@ -10,7 +10,7 @@ related:
|
|
|
10
10
|
|
|
11
11
|
# Prisma Fragments
|
|
12
12
|
|
|
13
|
-
Prisma-DSL rendering of the
|
|
13
|
+
Prisma-DSL rendering of the SaaSiCat data model, **derived from the normative
|
|
14
14
|
sources**: the logical data model in [`docs/data-model.md`](../../../docs/data-model.md)
|
|
15
15
|
and the PostgreSQL artifacts in [`../sql/`](../sql/)
|
|
16
16
|
(`reference-schema.postgres.sql` is generated FROM these fragments via
|
|
@@ -28,18 +28,18 @@ regenerated after fragment changes (`tests/reference-sql-drift.test.js`).
|
|
|
28
28
|
|
|
29
29
|
## Files
|
|
30
30
|
|
|
31
|
-
| File | Models
|
|
32
|
-
| -------------------------------------------------------------------- |
|
|
33
|
-
| [`01-subscription.prisma`](01-subscription.prisma) | `Subscription`, `SubscriptionPaymentMethod`, `CheckoutOffer` + Enums
|
|
34
|
-
| [`02-promo-code.prisma`](02-promo-code.prisma) | `PromoCode`, `PromoCodeRedemption`, `PromoCodeValidationLog` + Enums
|
|
35
|
-
| [`03-plan-versions.prisma`](03-plan-versions.prisma) | `Plan`, `PlanVersion`
|
|
36
|
-
| [`04-audit-log.prisma`](04-audit-log.prisma) | `AuditLog`
|
|
37
|
-
| [`05-bundle
|
|
38
|
-
| [`06-catalog-entries.prisma`](06-catalog-entries.prisma) | `CapabilityCatalogEntry`, `FeatureCatalogEntry`, `MarketingProjection`
|
|
39
|
-
| [`07-promotion.prisma`](07-promotion.prisma) | `Promotion`
|
|
40
|
-
| [`08-subscription-contract.prisma`](08-subscription-contract.prisma) | `SubscriptionContract`, `ContractLineItem`
|
|
41
|
-
| [`09-pending-registration.prisma`](09-pending-registration.prisma) | `PendingRegistration`, `PaymentEventLog` + `RegistrationStatus`
|
|
42
|
-
| [`10-super-admin.prisma`](10-super-admin.prisma) | `SuperAdminUser`, `SuperAdminMfa`
|
|
31
|
+
| File | Models |
|
|
32
|
+
| -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
33
|
+
| [`01-subscription.prisma`](01-subscription.prisma) | `Subscription`, `SubscriptionPaymentMethod`, `CheckoutOffer` + Enums |
|
|
34
|
+
| [`02-promo-code.prisma`](02-promo-code.prisma) | `PromoCode`, `PromoCodeRedemption`, `PromoCodeValidationLog` + Enums |
|
|
35
|
+
| [`03-plan-versions.prisma`](03-plan-versions.prisma) | `Plan`, `PlanVersion` |
|
|
36
|
+
| [`04-audit-log.prisma`](04-audit-log.prisma) | `AuditLog` |
|
|
37
|
+
| [`05-bundle.prisma`](05-bundle.prisma) | `Bundle`, `BundleVersion` |
|
|
38
|
+
| [`06-catalog-entries.prisma`](06-catalog-entries.prisma) | `CapabilityCatalogEntry`, `FeatureCatalogEntry`, `MarketingProjection` |
|
|
39
|
+
| [`07-promotion.prisma`](07-promotion.prisma) | `Promotion` |
|
|
40
|
+
| [`08-subscription-contract.prisma`](08-subscription-contract.prisma) | `SubscriptionContract`, `ContractLineItem` |
|
|
41
|
+
| [`09-pending-registration.prisma`](09-pending-registration.prisma) | `PendingRegistration`, `PaymentEventLog` + `RegistrationStatus` |
|
|
42
|
+
| [`10-super-admin.prisma`](10-super-admin.prisma) | `SuperAdminUser`, `SuperAdminMfa` |
|
|
43
43
|
|
|
44
44
|
## How the consumer uses the fragments
|
|
45
45
|
|
|
@@ -85,7 +85,6 @@ comment. The consumer enables them using their own `Tenant`/`User` model names.
|
|
|
85
85
|
|
|
86
86
|
`subscriptions`, `plan_versions`, `promo_codes`, `promo_code_redemptions`,
|
|
87
87
|
`promo_code_validation_logs`, `audit_logs`, `bundles`, `bundle_versions`,
|
|
88
|
-
`business_types`, `business_type_versions`, `business_type_bundles`,
|
|
89
88
|
`capability_catalog_entries`, `feature_catalog_entries`,
|
|
90
89
|
`marketing_projections`, `subscription_contracts`, `contract_line_items`.
|
|
91
90
|
Please do **not change** them — otherwise platform migration scripts and the
|
|
@@ -99,9 +98,8 @@ this precision.
|
|
|
99
98
|
|
|
100
99
|
### 5. Constraints Prisma cannot express
|
|
101
100
|
|
|
102
|
-
`PlanVersion
|
|
103
|
-
|
|
104
|
-
must bind a PlanVersion or a BusinessTypeVersion. Both live as SQL in
|
|
101
|
+
`PlanVersion` and `BundleVersion` allow **exactly one** draft per identity key
|
|
102
|
+
(`publishedAt IS NULL`). The partial indexes live as SQL in
|
|
105
103
|
[`../sql/constraints.postgres.sql`](../sql/constraints.postgres.sql) —
|
|
106
104
|
add that file to your migration verbatim. Note that column names are
|
|
107
105
|
**camelCase** (the fragments `@@map` table names only), e.g.:
|
|
@@ -133,7 +133,6 @@
|
|
|
133
133
|
"pilots": { "$ref": "#/$defs/StandardPageDef" },
|
|
134
134
|
"discovery": { "$ref": "#/$defs/StandardPageDef" },
|
|
135
135
|
"bundles": { "$ref": "#/$defs/StandardPageDef" },
|
|
136
|
-
"businessTypes": { "$ref": "#/$defs/StandardPageDef" },
|
|
137
136
|
"marketingCatalog": { "$ref": "#/$defs/StandardPageDef" },
|
|
138
137
|
"platformEmail": { "$ref": "#/$defs/StandardPageDef" },
|
|
139
138
|
"platformEmailHistory": { "$ref": "#/$defs/StandardPageDef" }
|
|
@@ -17,12 +17,3 @@ CREATE UNIQUE INDEX IF NOT EXISTS plan_versions_draft_per_plan
|
|
|
17
17
|
|
|
18
18
|
CREATE UNIQUE INDEX IF NOT EXISTS bundle_versions_draft_per_bundle
|
|
19
19
|
ON bundle_versions ("bundleId") WHERE "publishedAt" IS NULL;
|
|
20
|
-
|
|
21
|
-
CREATE UNIQUE INDEX IF NOT EXISTS business_type_versions_draft_per_business_type
|
|
22
|
-
ON business_type_versions ("businessTypeId") WHERE "publishedAt" IS NULL;
|
|
23
|
-
|
|
24
|
-
-- A subscription binds a PlanVersion, a BusinessTypeVersion, or both —
|
|
25
|
-
-- never neither (SPEC_V2 §11.1 M5).
|
|
26
|
-
ALTER TABLE subscriptions
|
|
27
|
-
ADD CONSTRAINT subscriptions_plan_or_bt_check
|
|
28
|
-
CHECK ("planVersionId" IS NOT NULL OR "businessTypeVersionId" IS NOT NULL);
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
-- prisma-fragments/02-promo-code.prisma
|
|
8
8
|
-- prisma-fragments/03-plan-versions.prisma
|
|
9
9
|
-- prisma-fragments/04-audit-log.prisma
|
|
10
|
-
-- prisma-fragments/05-bundle
|
|
10
|
+
-- prisma-fragments/05-bundle.prisma
|
|
11
11
|
-- prisma-fragments/06-catalog-entries.prisma
|
|
12
12
|
-- prisma-fragments/07-promotion.prisma
|
|
13
13
|
-- prisma-fragments/08-subscription-contract.prisma
|
|
14
14
|
-- prisma-fragments/09-pending-registration.prisma
|
|
15
15
|
-- prisma-fragments/10-super-admin.prisma
|
|
16
|
+
-- prisma-fragments/11-subscription-bundle.prisma
|
|
16
17
|
-- plus the normative constraints from sql/constraints.postgres.sql.
|
|
17
18
|
-- Do not edit by hand — change the fragments/constraints and regenerate.
|
|
18
19
|
|
|
@@ -61,7 +62,7 @@ CREATE TABLE "subscriptions" (
|
|
|
61
62
|
"canceledAt" TIMESTAMP(3),
|
|
62
63
|
"currentPeriodStart" TIMESTAMP(3),
|
|
63
64
|
"currentPeriodEnd" TIMESTAMP(3),
|
|
64
|
-
"planVersionId" TEXT,
|
|
65
|
+
"planVersionId" TEXT NOT NULL,
|
|
65
66
|
"pendingPlanVersionId" TEXT,
|
|
66
67
|
"pendingPlanVersionEffectiveAt" TIMESTAMP(3),
|
|
67
68
|
"pendingPlanVersionAccepted" BOOLEAN NOT NULL DEFAULT false,
|
|
@@ -69,7 +70,6 @@ CREATE TABLE "subscriptions" (
|
|
|
69
70
|
"pendingPlanVersionAcceptedByUserId" TEXT,
|
|
70
71
|
"pendingPlanVersionNotifiedAt" TIMESTAMP(3),
|
|
71
72
|
"pendingPlanVersionReminderSentAt" TIMESTAMP(3),
|
|
72
|
-
"businessTypeVersionId" TEXT,
|
|
73
73
|
"trialEntitlementPlan" TEXT,
|
|
74
74
|
"postTrialPlan" TEXT,
|
|
75
75
|
"pendingPlan" TEXT,
|
|
@@ -294,54 +294,6 @@ CREATE TABLE "bundle_versions" (
|
|
|
294
294
|
CONSTRAINT "bundle_versions_pkey" PRIMARY KEY ("id")
|
|
295
295
|
);
|
|
296
296
|
|
|
297
|
-
-- CreateTable
|
|
298
|
-
CREATE TABLE "business_types" (
|
|
299
|
-
"id" TEXT NOT NULL,
|
|
300
|
-
"projectKey" TEXT NOT NULL,
|
|
301
|
-
"businessTypeKey" TEXT NOT NULL,
|
|
302
|
-
"label" TEXT NOT NULL,
|
|
303
|
-
"description" TEXT,
|
|
304
|
-
"icon" TEXT,
|
|
305
|
-
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
|
306
|
-
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
307
|
-
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
308
|
-
"deletedAt" TIMESTAMP(3),
|
|
309
|
-
|
|
310
|
-
CONSTRAINT "business_types_pkey" PRIMARY KEY ("id")
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
-- CreateTable
|
|
314
|
-
CREATE TABLE "business_type_versions" (
|
|
315
|
-
"id" TEXT NOT NULL,
|
|
316
|
-
"businessTypeId" TEXT NOT NULL,
|
|
317
|
-
"version" INTEGER NOT NULL,
|
|
318
|
-
"baseVersionId" TEXT,
|
|
319
|
-
"quotaOverrides" JSONB NOT NULL DEFAULT '{}',
|
|
320
|
-
"monthlyNet" DECIMAL(10,2),
|
|
321
|
-
"yearlyNet" DECIMAL(10,2),
|
|
322
|
-
"marketed" BOOLEAN NOT NULL DEFAULT true,
|
|
323
|
-
"publishedAt" TIMESTAMP(3),
|
|
324
|
-
"supersededAt" TIMESTAMP(3),
|
|
325
|
-
"publishedChanges" JSONB,
|
|
326
|
-
"changeNote" TEXT NOT NULL DEFAULT '',
|
|
327
|
-
"nonRegressive" BOOLEAN NOT NULL DEFAULT true,
|
|
328
|
-
"createdByUserId" TEXT,
|
|
329
|
-
"publishedByUserId" TEXT,
|
|
330
|
-
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
331
|
-
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
332
|
-
|
|
333
|
-
CONSTRAINT "business_type_versions_pkey" PRIMARY KEY ("id")
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
-- CreateTable
|
|
337
|
-
CREATE TABLE "business_type_bundles" (
|
|
338
|
-
"businessTypeVersionId" TEXT NOT NULL,
|
|
339
|
-
"bundleVersionId" TEXT NOT NULL,
|
|
340
|
-
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
|
341
|
-
|
|
342
|
-
CONSTRAINT "business_type_bundles_pkey" PRIMARY KEY ("businessTypeVersionId","bundleVersionId")
|
|
343
|
-
);
|
|
344
|
-
|
|
345
297
|
-- CreateTable
|
|
346
298
|
CREATE TABLE "capability_catalog_entries" (
|
|
347
299
|
"id" TEXT NOT NULL,
|
|
@@ -408,6 +360,8 @@ CREATE TABLE "quota_catalog_entries" (
|
|
|
408
360
|
"usageProvider" TEXT,
|
|
409
361
|
"enforcementMode" TEXT NOT NULL DEFAULT 'soft',
|
|
410
362
|
"discoveryStatus" TEXT NOT NULL DEFAULT 'pending',
|
|
363
|
+
"replaces" TEXT[],
|
|
364
|
+
"successorKey" TEXT,
|
|
411
365
|
"approvedAt" TIMESTAMP(3),
|
|
412
366
|
"approvedBy" TEXT,
|
|
413
367
|
"approvedSignature" TEXT,
|
|
@@ -596,6 +550,21 @@ CREATE TABLE "super_admin_mfa" (
|
|
|
596
550
|
CONSTRAINT "super_admin_mfa_pkey" PRIMARY KEY ("userId")
|
|
597
551
|
);
|
|
598
552
|
|
|
553
|
+
-- CreateTable
|
|
554
|
+
CREATE TABLE "subscription_bundles" (
|
|
555
|
+
"id" TEXT NOT NULL,
|
|
556
|
+
"subscriptionId" TEXT NOT NULL,
|
|
557
|
+
"bundleVersionId" TEXT NOT NULL,
|
|
558
|
+
"startedAt" TIMESTAMP(3) NOT NULL,
|
|
559
|
+
"minimumTermEndsAt" TIMESTAMP(3),
|
|
560
|
+
"canceledAt" TIMESTAMP(3),
|
|
561
|
+
"canceledEffectiveAt" TIMESTAMP(3),
|
|
562
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
563
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
564
|
+
|
|
565
|
+
CONSTRAINT "subscription_bundles_pkey" PRIMARY KEY ("id")
|
|
566
|
+
);
|
|
567
|
+
|
|
599
568
|
-- CreateIndex
|
|
600
569
|
CREATE UNIQUE INDEX "subscriptions_tenantId_key" ON "subscriptions"("tenantId");
|
|
601
570
|
|
|
@@ -608,9 +577,6 @@ CREATE INDEX "subscriptions_planVersionId_idx" ON "subscriptions"("planVersionId
|
|
|
608
577
|
-- CreateIndex
|
|
609
578
|
CREATE INDEX "subscriptions_pendingPlanVersionId_idx" ON "subscriptions"("pendingPlanVersionId");
|
|
610
579
|
|
|
611
|
-
-- CreateIndex
|
|
612
|
-
CREATE INDEX "subscriptions_businessTypeVersionId_idx" ON "subscriptions"("businessTypeVersionId");
|
|
613
|
-
|
|
614
580
|
-- CreateIndex
|
|
615
581
|
CREATE INDEX "subscriptions_currentPeriodEnd_idx" ON "subscriptions"("currentPeriodEnd");
|
|
616
582
|
|
|
@@ -692,24 +658,6 @@ CREATE INDEX "bundle_versions_bundleId_publishedAt_idx" ON "bundle_versions"("bu
|
|
|
692
658
|
-- CreateIndex
|
|
693
659
|
CREATE UNIQUE INDEX "bundle_versions_bundleId_version_key" ON "bundle_versions"("bundleId", "version");
|
|
694
660
|
|
|
695
|
-
-- CreateIndex
|
|
696
|
-
CREATE INDEX "business_types_projectKey_deletedAt_idx" ON "business_types"("projectKey", "deletedAt");
|
|
697
|
-
|
|
698
|
-
-- CreateIndex
|
|
699
|
-
CREATE UNIQUE INDEX "business_types_projectKey_businessTypeKey_key" ON "business_types"("projectKey", "businessTypeKey");
|
|
700
|
-
|
|
701
|
-
-- CreateIndex
|
|
702
|
-
CREATE INDEX "business_type_versions_businessTypeId_supersededAt_idx" ON "business_type_versions"("businessTypeId", "supersededAt");
|
|
703
|
-
|
|
704
|
-
-- CreateIndex
|
|
705
|
-
CREATE INDEX "business_type_versions_businessTypeId_publishedAt_idx" ON "business_type_versions"("businessTypeId", "publishedAt");
|
|
706
|
-
|
|
707
|
-
-- CreateIndex
|
|
708
|
-
CREATE UNIQUE INDEX "business_type_versions_businessTypeId_version_key" ON "business_type_versions"("businessTypeId", "version");
|
|
709
|
-
|
|
710
|
-
-- CreateIndex
|
|
711
|
-
CREATE INDEX "business_type_bundles_bundleVersionId_idx" ON "business_type_bundles"("bundleVersionId");
|
|
712
|
-
|
|
713
661
|
-- CreateIndex
|
|
714
662
|
CREATE INDEX "capability_catalog_entries_projectKey_codeStatus_idx" ON "capability_catalog_entries"("projectKey", "codeStatus");
|
|
715
663
|
|
|
@@ -785,14 +733,20 @@ CREATE UNIQUE INDEX "super_admin_users_email_key" ON "super_admin_users"("email"
|
|
|
785
733
|
-- CreateIndex
|
|
786
734
|
CREATE INDEX "super_admin_users_isActive_deletedAt_idx" ON "super_admin_users"("isActive", "deletedAt");
|
|
787
735
|
|
|
788
|
-
--
|
|
789
|
-
|
|
736
|
+
-- CreateIndex
|
|
737
|
+
CREATE INDEX "subscription_bundles_subscriptionId_idx" ON "subscription_bundles"("subscriptionId");
|
|
738
|
+
|
|
739
|
+
-- CreateIndex
|
|
740
|
+
CREATE INDEX "subscription_bundles_bundleVersionId_idx" ON "subscription_bundles"("bundleVersionId");
|
|
741
|
+
|
|
742
|
+
-- CreateIndex
|
|
743
|
+
CREATE INDEX "subscription_bundles_canceledEffectiveAt_idx" ON "subscription_bundles"("canceledEffectiveAt");
|
|
790
744
|
|
|
791
745
|
-- AddForeignKey
|
|
792
|
-
ALTER TABLE "subscriptions" ADD CONSTRAINT "
|
|
746
|
+
ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_planVersionId_fkey" FOREIGN KEY ("planVersionId") REFERENCES "plan_versions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
793
747
|
|
|
794
748
|
-- AddForeignKey
|
|
795
|
-
ALTER TABLE "subscriptions" ADD CONSTRAINT "
|
|
749
|
+
ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_pendingPlanVersionId_fkey" FOREIGN KEY ("pendingPlanVersionId") REFERENCES "plan_versions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
796
750
|
|
|
797
751
|
-- AddForeignKey
|
|
798
752
|
ALTER TABLE "subscription_payment_methods" ADD CONSTRAINT "subscription_payment_methods_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -816,19 +770,13 @@ ALTER TABLE "bundle_versions" ADD CONSTRAINT "bundle_versions_bundleId_fkey" FOR
|
|
|
816
770
|
ALTER TABLE "bundle_versions" ADD CONSTRAINT "bundle_versions_baseVersionId_fkey" FOREIGN KEY ("baseVersionId") REFERENCES "bundle_versions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
817
771
|
|
|
818
772
|
-- AddForeignKey
|
|
819
|
-
ALTER TABLE "
|
|
820
|
-
|
|
821
|
-
-- AddForeignKey
|
|
822
|
-
ALTER TABLE "business_type_versions" ADD CONSTRAINT "business_type_versions_baseVersionId_fkey" FOREIGN KEY ("baseVersionId") REFERENCES "business_type_versions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
823
|
-
|
|
824
|
-
-- AddForeignKey
|
|
825
|
-
ALTER TABLE "business_type_bundles" ADD CONSTRAINT "business_type_bundles_businessTypeVersionId_fkey" FOREIGN KEY ("businessTypeVersionId") REFERENCES "business_type_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
773
|
+
ALTER TABLE "contract_line_items" ADD CONSTRAINT "contract_line_items_contractId_fkey" FOREIGN KEY ("contractId") REFERENCES "subscription_contracts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
826
774
|
|
|
827
775
|
-- AddForeignKey
|
|
828
|
-
ALTER TABLE "
|
|
776
|
+
ALTER TABLE "subscription_bundles" ADD CONSTRAINT "subscription_bundles_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
829
777
|
|
|
830
778
|
-- AddForeignKey
|
|
831
|
-
ALTER TABLE "
|
|
779
|
+
ALTER TABLE "subscription_bundles" ADD CONSTRAINT "subscription_bundles_bundleVersionId_fkey" FOREIGN KEY ("bundleVersionId") REFERENCES "bundle_versions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
832
780
|
|
|
833
781
|
-- =============================================================================
|
|
834
782
|
-- SaaSicat — normative PostgreSQL constraints the Prisma DSL cannot express.
|
|
@@ -849,12 +797,3 @@ CREATE UNIQUE INDEX IF NOT EXISTS plan_versions_draft_per_plan
|
|
|
849
797
|
|
|
850
798
|
CREATE UNIQUE INDEX IF NOT EXISTS bundle_versions_draft_per_bundle
|
|
851
799
|
ON bundle_versions ("bundleId") WHERE "publishedAt" IS NULL;
|
|
852
|
-
|
|
853
|
-
CREATE UNIQUE INDEX IF NOT EXISTS business_type_versions_draft_per_business_type
|
|
854
|
-
ON business_type_versions ("businessTypeId") WHERE "publishedAt" IS NULL;
|
|
855
|
-
|
|
856
|
-
-- A subscription binds a PlanVersion, a BusinessTypeVersion, or both —
|
|
857
|
-
-- never neither (SPEC_V2 §11.1 M5).
|
|
858
|
-
ALTER TABLE subscriptions
|
|
859
|
-
ADD CONSTRAINT subscriptions_plan_or_bt_check
|
|
860
|
-
CHECK ("planVersionId" IS NOT NULL OR "businessTypeVersionId" IS NOT NULL);
|