@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.
@@ -1,203 +0,0 @@
1
- // =============================================================================
2
- // SaaS platform Prisma fragment: Bundle + BusinessType (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
- // Naming note: GESCHAEFTSTYP_SPEC §3.1 partly speaks of `BundleDefinition`
20
- // (with suffix) and `BusinessType` (without) — that is asymmetric. SPEC_V2
21
- // unified this to a consistent `Bundle` / `BusinessType` (each without the
22
- // `Definition` suffix). This file follows the V2 convention.
23
-
24
- // -----------------------------------------------------------------------------
25
- // Bundle — reusable component of features + quotas + pricing.
26
- //
27
- // Tenants do **not** book bundles directly; they are a SuperAdmin maintenance
28
- // shell that is referenced in BusinessType versions (m:n via
29
- // BusinessTypeBundle). Root entity without content — the purchasable fields
30
- // (features, quotas, pricing) live on BundleVersion.
31
- // -----------------------------------------------------------------------------
32
-
33
- model Bundle {
34
- id String @id @default(uuid())
35
- projectKey String // e.g. 'clubapp' | 'demoapp' | …
36
- bundleKey String // SCREAMING_SNAKE_CASE, e.g. 'SPORT', 'ACCOUNTING', 'EVERSEND_PEPPOL'
37
- label String
38
- description String?
39
- icon String?
40
- sortOrder Int @default(0)
41
-
42
- // Locale translations { "en": { label, description }, … }. SPEC_V2 §6.4.
43
- i18n Json @default("{}")
44
-
45
- createdAt DateTime @default(now())
46
- updatedAt DateTime @updatedAt
47
- deletedAt DateTime?
48
-
49
- versions BundleVersion[]
50
-
51
- @@unique([projectKey, bundleKey])
52
- @@index([projectKey, deletedAt])
53
- @@map("bundles")
54
- }
55
-
56
- // -----------------------------------------------------------------------------
57
- // BundleVersion — versioned composition (features, quotas, pricing).
58
- // `features` is FeatureKey[] (references Discovery / FeatureCatalogEntry).
59
- // `quotas` is Record<QuotaKey, number>; -1 = unlimited; missing key = 0.
60
- // `compatibility` is BundleCompatibility (`businessTypeKeys?` /
61
- // `planIds?` whitelists). Both empty = usable everywhere.
62
- // `pricingOverrides` is Array<BundlePricingOverride> for context-dependent
63
- // prices (see GESCHAEFTSTYP_SPEC §6.1, "most-specific wins").
64
- // -----------------------------------------------------------------------------
65
-
66
- model BundleVersion {
67
- id String @id @default(uuid())
68
- bundleId String
69
- version Int
70
- baseVersionId String? // predecessor; null for v1
71
-
72
- features Json // FeatureKey[]
73
- quotas Json @default("{}") // Record<QuotaKey, number>
74
- compatibility Json @default("{}") // BundleCompatibility
75
- pricingOverrides Json @default("[]") // BundlePricingOverride[]
76
-
77
- monthlyNet Decimal? @db.Decimal(10, 2) // default price; null = only via override
78
- yearlyNet Decimal? @db.Decimal(10, 2)
79
- marketed Boolean @default(true)
80
-
81
- publishedAt DateTime?
82
- supersededAt DateTime?
83
- publishedChanges Json? // VersionChange[] — diff to predecessor version
84
- changeNote String @default("")
85
- nonRegressive Boolean @default(true)
86
-
87
- createdByUserId String?
88
- publishedByUserId String?
89
-
90
- createdAt DateTime @default(now())
91
- updatedAt DateTime @updatedAt
92
-
93
- bundle Bundle @relation(fields: [bundleId], references: [id], onDelete: Cascade)
94
- baseVersion BundleVersion? @relation("BundleVersionLineage", fields: [baseVersionId], references: [id])
95
- derivedVersions BundleVersion[] @relation("BundleVersionLineage")
96
- businessTypeBundles BusinessTypeBundle[]
97
-
98
- @@unique([bundleId, version])
99
- @@index([bundleId, supersededAt])
100
- @@index([bundleId, publishedAt])
101
- @@map("bundle_versions")
102
- }
103
-
104
- // -----------------------------------------------------------------------------
105
- // BusinessType — business vertical (club type, industry variant).
106
- //
107
- // Examples for a club app: SPORT_VEREIN, MOSCHEE_GEMEINDE,
108
- // KIRCHEN_GEMEINDE, SOZIAL_TRAEGER. Example for a commerce app: AUTO_HANDEL.
109
- // At **most one** BusinessType is referenced per subscription (via the
110
- // associated BusinessTypeVersion). Multiple bundles at once = a dedicated
111
- // BusinessType that composes the bundles internally.
112
- // -----------------------------------------------------------------------------
113
-
114
- model BusinessType {
115
- id String @id @default(uuid())
116
- projectKey String
117
- businessTypeKey String // SCREAMING_SNAKE_CASE
118
- label String
119
- description String?
120
- icon String?
121
- sortOrder Int @default(0)
122
-
123
- createdAt DateTime @default(now())
124
- updatedAt DateTime @updatedAt
125
- deletedAt DateTime?
126
-
127
- versions BusinessTypeVersion[]
128
-
129
- @@unique([projectKey, businessTypeKey])
130
- @@index([projectKey, deletedAt])
131
- @@map("business_types")
132
- }
133
-
134
- // -----------------------------------------------------------------------------
135
- // BusinessTypeVersion — versioned composition of referenced bundles.
136
- // `quotaOverrides` is Partial<Record<QuotaKey, number>>; missing key →
137
- // Σ(bundle quotas), set key → replaces the sum (-1 = unlimited).
138
- // `monthlyNet` null → effective price = Σ(bundle prices). Set → override.
139
- // -----------------------------------------------------------------------------
140
-
141
- model BusinessTypeVersion {
142
- id String @id @default(uuid())
143
- businessTypeId String
144
- version Int
145
- baseVersionId String?
146
-
147
- quotaOverrides Json @default("{}") // Partial<Record<QuotaKey, number>>
148
-
149
- monthlyNet Decimal? @db.Decimal(10, 2) // null = Σ(bundle prices)
150
- yearlyNet Decimal? @db.Decimal(10, 2)
151
- marketed Boolean @default(true)
152
-
153
- publishedAt DateTime?
154
- supersededAt DateTime?
155
- publishedChanges Json?
156
- changeNote String @default("")
157
- nonRegressive Boolean @default(true)
158
-
159
- createdByUserId String?
160
- publishedByUserId String?
161
-
162
- createdAt DateTime @default(now())
163
- updatedAt DateTime @updatedAt
164
-
165
- businessType BusinessType @relation(fields: [businessTypeId], references: [id], onDelete: Cascade)
166
- baseVersion BusinessTypeVersion? @relation("BusinessTypeVersionLineage", fields: [baseVersionId], references: [id])
167
- derivedVersions BusinessTypeVersion[] @relation("BusinessTypeVersionLineage")
168
- bundles BusinessTypeBundle[]
169
-
170
- // Opposite side of Subscription.businessTypeVersion (01-subscription.prisma,
171
- // M5). Subscription carries `businessTypeVersionId String?` plus the CHECK
172
- // constraint "at least one of planVersionId / businessTypeVersionId set"
173
- // (sql/constraints.postgres.sql).
174
- subscriptionsCurrent Subscription[] @relation("SubscriptionBusinessTypeVersion")
175
-
176
- @@unique([businessTypeId, version])
177
- @@index([businessTypeId, supersededAt])
178
- @@index([businessTypeId, publishedAt])
179
- @@map("business_type_versions")
180
- }
181
-
182
- // -----------------------------------------------------------------------------
183
- // BusinessTypeBundle — m:n junction between BusinessTypeVersion and
184
- // BundleVersion. Stores the *concrete* BundleVersion (not just the root), so
185
- // that a published BusinessType stays deterministic — even when the bundle
186
- // later gets a newer version.
187
- // onDelete: Restrict for bundleVersion prevents deleting a BundleVersion that
188
- // is still referenced in an active BusinessTypeVersion (additionally a hard
189
- // block in the publish strict check).
190
- // -----------------------------------------------------------------------------
191
-
192
- model BusinessTypeBundle {
193
- businessTypeVersionId String
194
- bundleVersionId String
195
- sortOrder Int @default(0)
196
-
197
- businessTypeVersion BusinessTypeVersion @relation(fields: [businessTypeVersionId], references: [id], onDelete: Cascade)
198
- bundleVersion BundleVersion @relation(fields: [bundleVersionId], references: [id], onDelete: Restrict)
199
-
200
- @@id([businessTypeVersionId, bundleVersionId])
201
- @@index([bundleVersionId])
202
- @@map("business_type_bundles")
203
- }