@saasicat/core 1.0.0-rc.0 → 1.0.0-rc.10
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/README.md +21 -0
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +491 -42
- package/dist/index.d.cts +1226 -380
- package/dist/index.d.ts +1226 -380
- package/dist/index.js +469 -41
- package/package.json +49 -48
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
/** Start of day (00:00 UTC) of the moment — for day-inclusive date comparisons. */
|
|
2
2
|
declare function startOfUtcDay(date: Date): Date;
|
|
3
|
+
/**
|
|
4
|
+
* The day before `value` — how a validity window closes when its successor
|
|
5
|
+
* opens (`validUntil = successor.validFrom − 1 day`, the rule this module's
|
|
6
|
+
* header states and `startOfUtcDay` above is the reading half of).
|
|
7
|
+
*
|
|
8
|
+
* It lived as three separate expressions before 2026-08-27: one in each
|
|
9
|
+
* adapter's publish path and one in the bundle repository, all agreeing by
|
|
10
|
+
* coincidence rather than by construction. Day arithmetic rather than
|
|
11
|
+
* `− 24 * 60 * 60 * 1000`: the subtraction is only equivalent while the value
|
|
12
|
+
* is a UTC midnight, and nothing in the type says it is.
|
|
13
|
+
*/
|
|
14
|
+
declare function previousUtcDay(value: Date): Date;
|
|
3
15
|
/** `<=` upper bound for a date (structurally Prisma-compatible). */
|
|
4
16
|
interface DateAtOrBefore {
|
|
5
17
|
lte: Date;
|
|
@@ -73,256 +85,6 @@ type ActiveVersionWhereWithEndsAt = ActivePlanVersionWhereWithEndsAt;
|
|
|
73
85
|
*/
|
|
74
86
|
declare const buildActiveVersionWhere: typeof buildActivePlanVersionWhere;
|
|
75
87
|
|
|
76
|
-
type FeatureKey = string;
|
|
77
|
-
type PlanId = string;
|
|
78
|
-
type QuotaKey = string;
|
|
79
|
-
interface FeatureDef {
|
|
80
|
-
key: FeatureKey;
|
|
81
|
-
label?: string;
|
|
82
|
-
icon?: string;
|
|
83
|
-
/** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
|
|
84
|
-
tier?: string;
|
|
85
|
-
plannedOnly?: boolean;
|
|
86
|
-
}
|
|
87
|
-
interface PlanDef {
|
|
88
|
-
id: PlanId;
|
|
89
|
-
name?: string;
|
|
90
|
-
tagline?: string;
|
|
91
|
-
/** false = not selectable in self-service onboarding. Default: true. */
|
|
92
|
-
marketed?: boolean;
|
|
93
|
-
/** Highlighted card in onboarding (max. 1 per catalog). */
|
|
94
|
-
popular?: boolean;
|
|
95
|
-
/** Net monthly price. null = on request. */
|
|
96
|
-
monthlyNet?: number | null;
|
|
97
|
-
/** Net total amount per year. null = monthly only. */
|
|
98
|
-
yearlyNet?: number | null;
|
|
99
|
-
/** Map quotaKey → max value. -1 = unlimited. */
|
|
100
|
-
quotas: Record<QuotaKey, number>;
|
|
101
|
-
features: FeatureKey[];
|
|
102
|
-
}
|
|
103
|
-
/** App-wide marketing configuration. */
|
|
104
|
-
interface PlanCatalogMarketing {
|
|
105
|
-
/**
|
|
106
|
-
* Allowed language pool that the app may market. First = default
|
|
107
|
-
* locale. From it, the SuperAdmin activates a subset in the marketing
|
|
108
|
-
* catalog (LocaleManager).
|
|
109
|
-
*/
|
|
110
|
-
availableLocales: string[];
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* App identity block for branding + version. Consumed by the `AdminPublicBootController`
|
|
114
|
-
* and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
|
|
115
|
-
* LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
|
|
116
|
-
*
|
|
117
|
-
* `name` = brand display name (e.g. "DemoApp", "ClubApp").
|
|
118
|
-
* `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
|
|
119
|
-
* `version` = app version string (build info).
|
|
120
|
-
* `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
|
|
121
|
-
* `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
|
|
122
|
-
* instead of the initials badge.
|
|
123
|
-
*/
|
|
124
|
-
interface PlanCatalogApp {
|
|
125
|
-
name: string;
|
|
126
|
-
label?: string;
|
|
127
|
-
version?: string;
|
|
128
|
-
icon?: string;
|
|
129
|
-
logoUrl?: string;
|
|
130
|
-
}
|
|
131
|
-
interface PlanCatalog {
|
|
132
|
-
schemaVersion: 1;
|
|
133
|
-
projectKey: string;
|
|
134
|
-
/** App identity (branding + version), see PlanCatalogApp. Optional. */
|
|
135
|
-
app?: PlanCatalogApp;
|
|
136
|
-
/** ISO-4217 currency code. */
|
|
137
|
-
currency: string;
|
|
138
|
-
/** VAT rate in percent. */
|
|
139
|
-
vatRate: number;
|
|
140
|
-
/** App-wide marketing configuration. Optional. */
|
|
141
|
-
marketing?: PlanCatalogMarketing;
|
|
142
|
-
features?: FeatureDef[];
|
|
143
|
-
/**
|
|
144
|
-
* Optional. When omitted, plans come exclusively from the
|
|
145
|
-
* AdminUI / DB table (Plans/PlanVersions lifecycle).
|
|
146
|
-
*/
|
|
147
|
-
plans?: PlanDef[];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Backend capability key, convention: domain.action[.action]. */
|
|
151
|
-
type CapabilityKey = string;
|
|
152
|
-
/** Frontend action-registry key. Same convention as CapabilityKey. */
|
|
153
|
-
type ActionKey = CapabilityKey;
|
|
154
|
-
/** Lookup key in the static extensions: map of the UI build. */
|
|
155
|
-
type ComponentKey = string;
|
|
156
|
-
interface AdminManifest {
|
|
157
|
-
schemaVersion: 1;
|
|
158
|
-
project: {
|
|
159
|
-
key: string;
|
|
160
|
-
displayName: string;
|
|
161
|
-
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
162
|
-
label?: string;
|
|
163
|
-
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
164
|
-
icon?: string;
|
|
165
|
-
logoUrl?: string;
|
|
166
|
-
environment?: 'production' | 'staging' | 'development';
|
|
167
|
-
/**
|
|
168
|
-
* Allowed locale pool from the app config (`saas.yaml`
|
|
169
|
-
* `marketing.availableLocales`). First = default..
|
|
170
|
-
*/
|
|
171
|
-
availableLocales?: string[];
|
|
172
|
-
/** Default locale; equals `availableLocales[0]`. */
|
|
173
|
-
defaultLocale?: string;
|
|
174
|
-
};
|
|
175
|
-
build: {
|
|
176
|
-
platformPackageVersion: string;
|
|
177
|
-
appVersion: string;
|
|
178
|
-
manifestHash: string;
|
|
179
|
-
};
|
|
180
|
-
planCatalogSnapshot: {
|
|
181
|
-
source: string;
|
|
182
|
-
hash: string;
|
|
183
|
-
currency: string;
|
|
184
|
-
vatRate: number;
|
|
185
|
-
features?: FeatureDef[];
|
|
186
|
-
plans: PlanDef[];
|
|
187
|
-
};
|
|
188
|
-
/** Map CapabilityKey → boolean. Manifest is never a security source. */
|
|
189
|
-
capabilities: Record<CapabilityKey, boolean>;
|
|
190
|
-
navigation: {
|
|
191
|
-
standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
192
|
-
projectPages?: ProjectPageDef[];
|
|
193
|
-
};
|
|
194
|
-
dashboard?: {
|
|
195
|
-
kpiCards?: KpiCardDef[];
|
|
196
|
-
};
|
|
197
|
-
tenants?: {
|
|
198
|
-
columns?: TenantColumnDef[];
|
|
199
|
-
actions?: TenantActionDef[];
|
|
200
|
-
};
|
|
201
|
-
audit?: {
|
|
202
|
-
actions?: AuditActionDef[];
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory';
|
|
206
|
-
interface StandardPageDef {
|
|
207
|
-
enabled: boolean;
|
|
208
|
-
requiredCapability?: CapabilityKey;
|
|
209
|
-
}
|
|
210
|
-
interface ProjectPageDef {
|
|
211
|
-
/** `<projectKey>.<area>`, e.g. `demoapp.datev`. */
|
|
212
|
-
id: string;
|
|
213
|
-
label: string;
|
|
214
|
-
icon?: string;
|
|
215
|
-
/** Frontend route, e.g. `/admin/datev`. */
|
|
216
|
-
route: string;
|
|
217
|
-
navSection?: string;
|
|
218
|
-
/** Lookup in the static extensions: map of the shell build. */
|
|
219
|
-
componentKey: ComponentKey;
|
|
220
|
-
requiredCapability?: CapabilityKey;
|
|
221
|
-
prefetchOnIdle?: boolean;
|
|
222
|
-
}
|
|
223
|
-
interface KpiCardDef {
|
|
224
|
-
id: string;
|
|
225
|
-
label: string;
|
|
226
|
-
/** Required path: /api/v1/admin/(extras|dashboard)/... */
|
|
227
|
-
endpoint: string;
|
|
228
|
-
displayHint: KpiDisplayHint;
|
|
229
|
-
/** 0–100; UI sorts descending. */
|
|
230
|
-
slotPriority?: number;
|
|
231
|
-
requiredCapability?: CapabilityKey;
|
|
232
|
-
}
|
|
233
|
-
interface KpiDisplayHint {
|
|
234
|
-
type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
|
|
235
|
-
icon?: string;
|
|
236
|
-
}
|
|
237
|
-
interface TenantColumnDef {
|
|
238
|
-
key: string;
|
|
239
|
-
label: string;
|
|
240
|
-
/** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
|
|
241
|
-
endpoint: string;
|
|
242
|
-
requiredCapability?: CapabilityKey;
|
|
243
|
-
}
|
|
244
|
-
interface TenantActionDef {
|
|
245
|
-
/** `<projectKey>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
|
|
246
|
-
id: string;
|
|
247
|
-
label: string;
|
|
248
|
-
/** Lookup in the static actions: map of the shell build. */
|
|
249
|
-
actionKey: ActionKey;
|
|
250
|
-
requiredCapability?: CapabilityKey;
|
|
251
|
-
requiresMfa?: boolean;
|
|
252
|
-
confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
|
|
253
|
-
}
|
|
254
|
-
interface AuditActionDef {
|
|
255
|
-
/** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
|
|
256
|
-
key: string;
|
|
257
|
-
label: string;
|
|
258
|
-
severity?: 'info' | 'low' | 'medium' | 'high';
|
|
259
|
-
}
|
|
260
|
-
interface ManifestContribution {
|
|
261
|
-
capabilities?: Record<CapabilityKey, boolean>;
|
|
262
|
-
navigation?: {
|
|
263
|
-
standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
264
|
-
projectPages?: ProjectPageDef[];
|
|
265
|
-
};
|
|
266
|
-
dashboard?: {
|
|
267
|
-
kpiCards?: KpiCardDef[];
|
|
268
|
-
};
|
|
269
|
-
tenants?: {
|
|
270
|
-
columns?: TenantColumnDef[];
|
|
271
|
-
actions?: TenantActionDef[];
|
|
272
|
-
};
|
|
273
|
-
audit?: {
|
|
274
|
-
actions?: AuditActionDef[];
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
interface PublicBootResponse {
|
|
278
|
-
project: {
|
|
279
|
-
key: string;
|
|
280
|
-
displayName: string;
|
|
281
|
-
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
282
|
-
label?: string;
|
|
283
|
-
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
284
|
-
icon?: string;
|
|
285
|
-
logoUrl?: string;
|
|
286
|
-
environment?: 'production' | 'staging' | 'development';
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
|
|
291
|
-
type ActorTag = string;
|
|
292
|
-
interface AuditEntry {
|
|
293
|
-
id: string;
|
|
294
|
-
/** null = platform action without tenant context (SUPER_ADMIN). */
|
|
295
|
-
tenantId: string | null;
|
|
296
|
-
/** null = system / cron-triggered. */
|
|
297
|
-
userId: string | null;
|
|
298
|
-
/** Convenience field; backend resolves it from userId. */
|
|
299
|
-
userEmail: string | null;
|
|
300
|
-
/** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
|
|
301
|
-
entity: string;
|
|
302
|
-
entityId: string;
|
|
303
|
-
/** SCREAMING_SNAKE_CASE; past-tense oriented. */
|
|
304
|
-
action: string;
|
|
305
|
-
/** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
|
|
306
|
-
changes: Record<string, unknown> | null;
|
|
307
|
-
actorTag: ActorTag | null;
|
|
308
|
-
ipAddress: string | null;
|
|
309
|
-
userAgent: string | null;
|
|
310
|
-
createdAt: string;
|
|
311
|
-
}
|
|
312
|
-
interface AuditQuery {
|
|
313
|
-
tenantId?: string;
|
|
314
|
-
userId?: string;
|
|
315
|
-
entity?: string;
|
|
316
|
-
entityId?: string;
|
|
317
|
-
action?: string;
|
|
318
|
-
/** Wildcard-capable, e.g. 'cli:*'. */
|
|
319
|
-
actorTag?: string;
|
|
320
|
-
from?: string;
|
|
321
|
-
to?: string;
|
|
322
|
-
page?: number;
|
|
323
|
-
pageSize?: number;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
88
|
/**
|
|
327
89
|
* Approval lifecycle of a feature or a quota. Approval happens per
|
|
328
90
|
* FEATURE/QUOTA — not per capability (#20); only `approved` entries
|
|
@@ -385,7 +147,6 @@ type CatalogEntryI18n = Record<string, CatalogEntryI18nFields>;
|
|
|
385
147
|
*/
|
|
386
148
|
interface CapabilityCatalogEntryRow {
|
|
387
149
|
id: string;
|
|
388
|
-
projectKey: string;
|
|
389
150
|
capabilityKey: string;
|
|
390
151
|
label: string;
|
|
391
152
|
description: string | null;
|
|
@@ -422,7 +183,6 @@ type FeatureTier = 'CORE' | 'ADVANCED' | 'PRO' | 'ENTERPRISE' | string;
|
|
|
422
183
|
*/
|
|
423
184
|
interface FeatureCatalogEntryRow {
|
|
424
185
|
id: string;
|
|
425
|
-
projectKey: string;
|
|
426
186
|
featureKey: string;
|
|
427
187
|
label: string;
|
|
428
188
|
description: string | null;
|
|
@@ -477,7 +237,6 @@ interface FeatureCatalogEntryRow {
|
|
|
477
237
|
*/
|
|
478
238
|
interface QuotaCatalogEntryRow {
|
|
479
239
|
id: string;
|
|
480
|
-
projectKey: string;
|
|
481
240
|
quotaKey: string;
|
|
482
241
|
label: string;
|
|
483
242
|
description: string | null;
|
|
@@ -521,7 +280,6 @@ interface QuotaCatalogEntryRow {
|
|
|
521
280
|
* matching field is relevant.
|
|
522
281
|
*/
|
|
523
282
|
interface CatalogEntryFilter {
|
|
524
|
-
projectKey: string;
|
|
525
283
|
discoveryStatus?: DiscoveryStatus;
|
|
526
284
|
codeStatus?: CapabilityCodeStatus;
|
|
527
285
|
}
|
|
@@ -613,6 +371,18 @@ interface MarketingTopFeature {
|
|
|
613
371
|
label: string;
|
|
614
372
|
strong: string;
|
|
615
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* The range a marketing projection's `priority` may take.
|
|
376
|
+
*
|
|
377
|
+
* Declared here rather than in the DTO because two sides need the same answer:
|
|
378
|
+
* the request pipe rejects anything outside it, and the admin UI computes
|
|
379
|
+
* priorities when an operator drags a plan into a new position. A UI that
|
|
380
|
+
* picked its own bounds would produce a value the pipe refuses — and it did,
|
|
381
|
+
* at the top of the range, where a list of tied plans was lifted past the
|
|
382
|
+
* maximum to pull the ties apart.
|
|
383
|
+
*/
|
|
384
|
+
declare const MARKETING_PRIORITY_MIN = 0;
|
|
385
|
+
declare const MARKETING_PRIORITY_MAX = 10000;
|
|
616
386
|
/**
|
|
617
387
|
* Locale-specific marketing texts per plan/bundle version.
|
|
618
388
|
* Read and projected by the Public-Catalog-Controller
|
|
@@ -623,7 +393,6 @@ interface MarketingTopFeature {
|
|
|
623
393
|
*/
|
|
624
394
|
interface MarketingProjectionRow {
|
|
625
395
|
id: string;
|
|
626
|
-
projectKey: string;
|
|
627
396
|
targetType: MarketingTargetType;
|
|
628
397
|
targetVersionId: string;
|
|
629
398
|
/** ISO-639-1, optionally with region suffix (`de`, `en`, `de-AT`). */
|
|
@@ -668,42 +437,178 @@ interface MarketingProjectionRow {
|
|
|
668
437
|
createdAt: string;
|
|
669
438
|
updatedAt: string;
|
|
670
439
|
}
|
|
671
|
-
/** Filter for `MarketingProjectionRepository.list()`.
|
|
440
|
+
/** Filter for `MarketingProjectionRepository.list()`. */
|
|
672
441
|
interface MarketingProjectionFilter {
|
|
673
|
-
projectKey: string;
|
|
674
442
|
targetType?: MarketingTargetType;
|
|
675
443
|
targetVersionId?: string;
|
|
676
444
|
locale?: string;
|
|
677
445
|
}
|
|
678
|
-
interface CreateMarketingProjectionData {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
446
|
+
interface CreateMarketingProjectionData {
|
|
447
|
+
targetType: MarketingTargetType;
|
|
448
|
+
targetVersionId: string;
|
|
449
|
+
locale?: string;
|
|
450
|
+
displayLabel: string;
|
|
451
|
+
description: string;
|
|
452
|
+
visible?: boolean;
|
|
453
|
+
badge?: string;
|
|
454
|
+
topFeatures?: MarketingTopFeature[];
|
|
455
|
+
trialEnabled?: boolean;
|
|
456
|
+
trialDays?: number;
|
|
457
|
+
priceTag?: string | null;
|
|
458
|
+
ctaLabel?: string | null;
|
|
459
|
+
priority?: number;
|
|
460
|
+
highlight?: boolean;
|
|
461
|
+
}
|
|
462
|
+
interface UpdateMarketingProjectionData {
|
|
463
|
+
displayLabel?: string;
|
|
464
|
+
description?: string;
|
|
465
|
+
visible?: boolean;
|
|
466
|
+
badge?: string;
|
|
467
|
+
topFeatures?: MarketingTopFeature[];
|
|
468
|
+
trialEnabled?: boolean;
|
|
469
|
+
trialDays?: number;
|
|
470
|
+
priceTag?: string | null;
|
|
471
|
+
ctaLabel?: string | null;
|
|
472
|
+
priority?: number;
|
|
473
|
+
highlight?: boolean;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
type FeatureKey = string;
|
|
477
|
+
type PlanId = string;
|
|
478
|
+
type QuotaKey = string;
|
|
479
|
+
interface FeatureDef {
|
|
480
|
+
key: FeatureKey;
|
|
481
|
+
label?: string;
|
|
482
|
+
icon?: string;
|
|
483
|
+
/** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
|
|
484
|
+
tier?: string;
|
|
485
|
+
plannedOnly?: boolean;
|
|
486
|
+
}
|
|
487
|
+
interface PlanDef {
|
|
488
|
+
id: PlanId;
|
|
489
|
+
name?: string;
|
|
490
|
+
tagline?: string;
|
|
491
|
+
/** false = not selectable in self-service onboarding. Default: true. */
|
|
492
|
+
marketed?: boolean;
|
|
493
|
+
/** Highlighted card in onboarding (max. 1 per catalog). */
|
|
494
|
+
popular?: boolean;
|
|
495
|
+
/** Net monthly price. null = on request. */
|
|
496
|
+
monthlyNet?: number | null;
|
|
497
|
+
/** Net total amount per year. null = monthly only. */
|
|
498
|
+
yearlyNet?: number | null;
|
|
499
|
+
/** Map quotaKey → max value. -1 = unlimited. */
|
|
500
|
+
quotas: Record<QuotaKey, number>;
|
|
501
|
+
features: FeatureKey[];
|
|
502
|
+
}
|
|
503
|
+
/** App-wide marketing configuration. */
|
|
504
|
+
interface PlanCatalogMarketing {
|
|
505
|
+
/**
|
|
506
|
+
* Allowed language pool that the app may market. First = default
|
|
507
|
+
* locale. From it, the SuperAdmin activates a subset in the marketing
|
|
508
|
+
* catalog (LocaleManager).
|
|
509
|
+
*/
|
|
510
|
+
availableLocales: string[];
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* App identity block for branding + version. Consumed by the `AdminPublicBootController`
|
|
514
|
+
* and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
|
|
515
|
+
* LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
|
|
516
|
+
*
|
|
517
|
+
* `name` = brand display name (e.g. "DemoApp", "ClubApp").
|
|
518
|
+
* `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
|
|
519
|
+
* `version` = app version string (build info).
|
|
520
|
+
* `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
|
|
521
|
+
* `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
|
|
522
|
+
* instead of the initials badge.
|
|
523
|
+
*/
|
|
524
|
+
interface PlanCatalogApp {
|
|
525
|
+
name: string;
|
|
526
|
+
label?: string;
|
|
527
|
+
version?: string;
|
|
528
|
+
icon?: string;
|
|
529
|
+
logoUrl?: string;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Notice periods, one per rhythm.
|
|
533
|
+
*
|
|
534
|
+
* One number for both was the shape until 2026-08-27, and it could not be right
|
|
535
|
+
* for both: a yearly contract with a fortnight of notice is unusual, and a
|
|
536
|
+
* monthly contract with three months of notice is void against a consumer. The
|
|
537
|
+
* two are configured apart because real contracts set them apart.
|
|
538
|
+
*
|
|
539
|
+
* Both members are required. A missing rhythm would read as zero, and a silent
|
|
540
|
+
* zero is a commercial decision nobody made — the same defect one level below
|
|
541
|
+
* the one that moved these settings into the file.
|
|
542
|
+
*
|
|
543
|
+
* **No ceiling is enforced.** §309 Nr. 9 BGB limits the notice period in German
|
|
544
|
+
* consumer contracts to one month, and an installation serving businesses is
|
|
545
|
+
* not bound by it. The platform cannot know which it is, so the number is the
|
|
546
|
+
* consumer app's to choose and this is the sentence that says what it costs.
|
|
547
|
+
*/
|
|
548
|
+
interface CancellationNoticePeriods {
|
|
549
|
+
/** Days of notice for a monthly subscription. */
|
|
550
|
+
monthly: number;
|
|
551
|
+
/** Days of notice for a yearly subscription. */
|
|
552
|
+
yearly: number;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Plans a tenant may not reach or leave without talking to sales.
|
|
556
|
+
*
|
|
557
|
+
* `asTarget`: may not be selected via self-service — typically ENTERPRISE,
|
|
558
|
+
* which only a special contract activates. `asSource`: may not be left via
|
|
559
|
+
* self-service — typically an active special contract.
|
|
560
|
+
*
|
|
561
|
+
* Both lists are required and may be empty. An empty list says out loud that
|
|
562
|
+
* self-service reaches every plan, which is a decision rather than an omission.
|
|
563
|
+
*/
|
|
564
|
+
interface SelfServiceBlockedPlans {
|
|
565
|
+
asTarget: string[];
|
|
566
|
+
asSource: string[];
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Commercial settings for the tenant-facing self-service routes.
|
|
570
|
+
*
|
|
571
|
+
* They live in `config/saas.yaml` and nowhere else: an operator reading the
|
|
572
|
+
* file has to be reading the values that are running, with no "unless somebody
|
|
573
|
+
* passed it in code" attached. The file is read at boot, so an edit lands on
|
|
574
|
+
* the next restart.
|
|
575
|
+
*/
|
|
576
|
+
interface PlanCatalogTenantBilling {
|
|
577
|
+
cancellationNoticeDays: CancellationNoticePeriods;
|
|
578
|
+
selfServiceBlockedPlans: SelfServiceBlockedPlans;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Who is told when the settings in the file change between two starts.
|
|
582
|
+
*
|
|
583
|
+
* The record inside the application is written whether or not anybody is
|
|
584
|
+
* named here; mail is the addition, never the substitute. Mailed only where an
|
|
585
|
+
* email port is bound — without one the boot log says so once, and the change
|
|
586
|
+
* is recorded in the application only.
|
|
587
|
+
*/
|
|
588
|
+
interface PlanCatalogNotifications {
|
|
589
|
+
/** Addresses mailed when a start finds the applied settings changed. */
|
|
590
|
+
settingsChanged?: string[];
|
|
694
591
|
}
|
|
695
|
-
interface
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
592
|
+
interface PlanCatalog {
|
|
593
|
+
schemaVersion: 1;
|
|
594
|
+
/** App identity (branding + version), see PlanCatalogApp. */
|
|
595
|
+
app: PlanCatalogApp;
|
|
596
|
+
/** ISO-4217 currency code. */
|
|
597
|
+
currency: string;
|
|
598
|
+
/** VAT rate in percent. */
|
|
599
|
+
vatRate: number;
|
|
600
|
+
/** Commercial settings for the tenant self-service routes. */
|
|
601
|
+
tenantBilling: PlanCatalogTenantBilling;
|
|
602
|
+
/** App-wide marketing configuration. Optional. */
|
|
603
|
+
marketing?: PlanCatalogMarketing;
|
|
604
|
+
/** Who is told when the settings change between two starts. Optional. */
|
|
605
|
+
notifications?: PlanCatalogNotifications;
|
|
606
|
+
features?: FeatureDef[];
|
|
607
|
+
/**
|
|
608
|
+
* Optional. When omitted, plans come exclusively from the
|
|
609
|
+
* AdminUI / DB table (Plans/PlanVersions lifecycle).
|
|
610
|
+
*/
|
|
611
|
+
plans?: PlanDef[];
|
|
707
612
|
}
|
|
708
613
|
|
|
709
614
|
type PromoCodeValueType = 'PERCENT' | 'ABSOLUTE';
|
|
@@ -902,7 +807,7 @@ interface VersionedEntityBase {
|
|
|
902
807
|
* own migration).
|
|
903
808
|
* - `startedAt` is the contract start of this booking.
|
|
904
809
|
* - `minimumTermEndsAt` = end of the minimum term; `null` = no minimum term
|
|
905
|
-
* (platform default =
|
|
810
|
+
* (platform default = no commitment, set service-side).
|
|
906
811
|
* - `canceledAt` / `canceledEffectiveAt`: cancellation anchor vs. effective
|
|
907
812
|
* date. Before the minimum term ends, `canceledEffectiveAt =
|
|
908
813
|
* minimumTermEndsAt`, otherwise the subscription's period end.
|
|
@@ -919,6 +824,18 @@ interface SubscriptionBundleRecord {
|
|
|
919
824
|
minimumTermEndsAt: Date | null;
|
|
920
825
|
canceledAt: Date | null;
|
|
921
826
|
canceledEffectiveAt: Date | null;
|
|
827
|
+
/**
|
|
828
|
+
* The rhythm this booking is billed in, and the window it is billed for.
|
|
829
|
+
*
|
|
830
|
+
* A bundle's periods end on the day its plan's do — the first one short,
|
|
831
|
+
* from the booking to the next occurrence of that day, and every one after
|
|
832
|
+
* it anchor to anchor. Null on a booking made before these fields existed,
|
|
833
|
+
* or on one whose plan has no period; readers fall back to the plan's
|
|
834
|
+
* cycle, which is what every booking used before.
|
|
835
|
+
*/
|
|
836
|
+
billingCycle: string | null;
|
|
837
|
+
currentPeriodStart: Date | null;
|
|
838
|
+
currentPeriodEnd: Date | null;
|
|
922
839
|
createdAt: Date;
|
|
923
840
|
updatedAt: Date;
|
|
924
841
|
}
|
|
@@ -932,14 +849,27 @@ interface SubscriptionBundleRecord {
|
|
|
932
849
|
interface SubscriptionBundleView extends SubscriptionBundleRecord {
|
|
933
850
|
bundleKey: string | null;
|
|
934
851
|
label: string | null;
|
|
935
|
-
|
|
852
|
+
/**
|
|
853
|
+
* What this booking is billed at, in the rhythm it was booked in and with
|
|
854
|
+
* the plan's pricing override applied.
|
|
855
|
+
*
|
|
856
|
+
* It was `monthlyNet` until 2026-08-27 and carried the bundle's base
|
|
857
|
+
* monthly price whatever the booking was — so a yearly booking of a bundle
|
|
858
|
+
* priced 10 monthly and 100 yearly reported 10. The name was half the
|
|
859
|
+
* defect: a field called `monthlyNet` on a yearly booking cannot be right.
|
|
860
|
+
*/
|
|
861
|
+
priceNet: number | null;
|
|
936
862
|
}
|
|
937
863
|
interface CreateSubscriptionBundleData {
|
|
938
864
|
subscriptionId: string;
|
|
939
865
|
bundleVersionId: string;
|
|
940
866
|
startedAt: Date;
|
|
941
|
-
/**
|
|
867
|
+
/** Null unless a commitment was configured or asked for. */
|
|
942
868
|
minimumTermEndsAt?: Date | null;
|
|
869
|
+
/** The rhythm and window worked out above this port. */
|
|
870
|
+
billingCycle?: string | null;
|
|
871
|
+
currentPeriodStart?: Date | null;
|
|
872
|
+
currentPeriodEnd?: Date | null;
|
|
943
873
|
}
|
|
944
874
|
interface CancelSubscriptionBundleData {
|
|
945
875
|
canceledAt: Date;
|
|
@@ -988,7 +918,6 @@ interface BundlePricingOverride {
|
|
|
988
918
|
*/
|
|
989
919
|
interface BundleRow {
|
|
990
920
|
id: string;
|
|
991
|
-
projectKey: string;
|
|
992
921
|
bundleKey: string;
|
|
993
922
|
label: string;
|
|
994
923
|
description: string | null;
|
|
@@ -1027,7 +956,6 @@ interface BundleVersionRow extends VersionedEntityBase {
|
|
|
1027
956
|
* first BundleVersion via `CreateBundleVersionDraftData`.
|
|
1028
957
|
*/
|
|
1029
958
|
interface CreateBundleData {
|
|
1030
|
-
projectKey: string;
|
|
1031
959
|
bundleKey: string;
|
|
1032
960
|
label: string;
|
|
1033
961
|
description?: string | null;
|
|
@@ -1036,9 +964,9 @@ interface CreateBundleData {
|
|
|
1036
964
|
i18n?: CatalogEntryI18n;
|
|
1037
965
|
}
|
|
1038
966
|
/**
|
|
1039
|
-
* Fields that may be changed on the bundle master. `bundleKey`
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
967
|
+
* Fields that may be changed on the bundle master. `bundleKey` is
|
|
968
|
+
* intentionally not here — master identity is immutable; whoever wants to
|
|
969
|
+
* change it creates a new bundle and retires the old one.
|
|
1042
970
|
*/
|
|
1043
971
|
interface UpdateBundleData {
|
|
1044
972
|
label?: string;
|
|
@@ -1161,16 +1089,273 @@ interface StrictModeWarning {
|
|
|
1161
1089
|
/** The concrete violating value; optional. */
|
|
1162
1090
|
value?: string;
|
|
1163
1091
|
}
|
|
1164
|
-
/**
|
|
1165
|
-
* Service result for mutating Bundle operations
|
|
1166
|
-
* (createDraft, updateDraft, publish): returns the persisted row plus
|
|
1167
|
-
* a list of strict-mode warnings. In `warn-only` mode the
|
|
1168
|
-
* warnings go into the UI as a banner; in `blocking` mode the service throws
|
|
1169
|
-
* HTTP 422 instead, with the same warning list as the body.
|
|
1170
|
-
*/
|
|
1171
|
-
interface BundleVersionMutationResult {
|
|
1172
|
-
bundleVersion: BundleVersionRow;
|
|
1173
|
-
warnings: StrictModeWarning[];
|
|
1092
|
+
/**
|
|
1093
|
+
* Service result for mutating Bundle operations
|
|
1094
|
+
* (createDraft, updateDraft, publish): returns the persisted row plus
|
|
1095
|
+
* a list of strict-mode warnings. In `warn-only` mode the
|
|
1096
|
+
* warnings go into the UI as a banner; in `blocking` mode the service throws
|
|
1097
|
+
* HTTP 422 instead, with the same warning list as the body.
|
|
1098
|
+
*/
|
|
1099
|
+
interface BundleVersionMutationResult {
|
|
1100
|
+
bundleVersion: BundleVersionRow;
|
|
1101
|
+
warnings: StrictModeWarning[];
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* The column values a new BundleVersion draft starts from.
|
|
1106
|
+
*
|
|
1107
|
+
* Every adapter has to apply the same defaults — an absent quota map is `{}`,
|
|
1108
|
+
* an absent price is null rather than zero, an unstated `marketed` is true —
|
|
1109
|
+
* and two adapters spelling that out separately is the same decision written
|
|
1110
|
+
* twice. It is also the variant jscpd does catch, which is how this came out:
|
|
1111
|
+
* `adapter-drizzle` learning about bundles put a second copy beside
|
|
1112
|
+
* `adapter-prisma`'s.
|
|
1113
|
+
*
|
|
1114
|
+
* Validity windows are deliberately absent. Whether a draft carries
|
|
1115
|
+
* `validFrom`/`validUntil` is an adapter capability rather than a default, and
|
|
1116
|
+
* an adapter that does not maintain those columns must not write them.
|
|
1117
|
+
*/
|
|
1118
|
+
declare function bundleDraftDefaults(data: CreateBundleVersionDraftData): {
|
|
1119
|
+
baseVersionId: string | null;
|
|
1120
|
+
features: string[];
|
|
1121
|
+
quotas: Record<string, number>;
|
|
1122
|
+
compatibility: Record<string, unknown>;
|
|
1123
|
+
pricingOverrides: unknown[];
|
|
1124
|
+
monthlyNet: string | null;
|
|
1125
|
+
yearlyNet: string | null;
|
|
1126
|
+
marketed: boolean;
|
|
1127
|
+
changeNote: string;
|
|
1128
|
+
createdByUserId: string | null;
|
|
1129
|
+
};
|
|
1130
|
+
/**
|
|
1131
|
+
* The column values a new Bundle stem starts from.
|
|
1132
|
+
*
|
|
1133
|
+
* The same defaulting rule as above, one level up: an absent description or
|
|
1134
|
+
* icon is null rather than an empty string, an unstated sort order is 0, an
|
|
1135
|
+
* absent translation map is `{}`. Written out in five places before this — two
|
|
1136
|
+
* adapters and two fakes — which is four opportunities for one of them to
|
|
1137
|
+
* decide differently.
|
|
1138
|
+
*/
|
|
1139
|
+
declare function bundleStemDefaults(data: CreateBundleData): {
|
|
1140
|
+
bundleKey: string;
|
|
1141
|
+
label: string;
|
|
1142
|
+
description: string | null;
|
|
1143
|
+
icon: string | null;
|
|
1144
|
+
sortOrder: number;
|
|
1145
|
+
i18n: CatalogEntryI18n;
|
|
1146
|
+
};
|
|
1147
|
+
/** The stored shape both adapters read a bundle stem back from. */
|
|
1148
|
+
interface StoredBundleStem {
|
|
1149
|
+
id: string;
|
|
1150
|
+
bundleKey: string;
|
|
1151
|
+
label: string;
|
|
1152
|
+
description: string | null;
|
|
1153
|
+
icon: string | null;
|
|
1154
|
+
sortOrder: number;
|
|
1155
|
+
i18n: unknown;
|
|
1156
|
+
createdAt: Date;
|
|
1157
|
+
updatedAt: Date;
|
|
1158
|
+
deletedAt: Date | null;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* A stored bundle stem as the port describes it.
|
|
1162
|
+
*
|
|
1163
|
+
* The two stores spell the columns identically, so the mapping was identical
|
|
1164
|
+
* too — and an identical mapping in two files is one place for a field to be
|
|
1165
|
+
* forgotten when the row grows. `i18n` arrives as JSON of unknown shape from
|
|
1166
|
+
* both, and a non-object becomes `{}` rather than reaching a caller that
|
|
1167
|
+
* expects a map.
|
|
1168
|
+
*/
|
|
1169
|
+
declare function toBundleStemRow(row: StoredBundleStem): BundleRow;
|
|
1170
|
+
/**
|
|
1171
|
+
* The fields a caller actually gave, as a patch.
|
|
1172
|
+
*
|
|
1173
|
+
* The update DTOs in this codebase mean three different things by three
|
|
1174
|
+
* different values: a value changes the column, an explicit `null` clears it,
|
|
1175
|
+
* and an **omitted** field leaves it alone. Only the last one needs care, and
|
|
1176
|
+
* it was written out as `...(data.x !== undefined ? { x: data.x } : {})` more
|
|
1177
|
+
* than fifty times across five repositories — one decision, fifty
|
|
1178
|
+
* opportunities to spell it differently, and the duplication ratchet is what
|
|
1179
|
+
* finally pointed at it.
|
|
1180
|
+
*
|
|
1181
|
+
* `null` is deliberately kept: it is a value a caller chose, not an absence.
|
|
1182
|
+
*/
|
|
1183
|
+
declare function definedFields<T extends object, K extends keyof T>(data: T, keys: readonly K[]): Partial<Pick<T, K>>;
|
|
1184
|
+
|
|
1185
|
+
/** Backend capability key, convention: domain.action[.action]. */
|
|
1186
|
+
type CapabilityKey = string;
|
|
1187
|
+
/** Frontend action-registry key. Same convention as CapabilityKey. */
|
|
1188
|
+
type ActionKey = CapabilityKey;
|
|
1189
|
+
/** Lookup key in the static extensions: map of the UI build. */
|
|
1190
|
+
type ComponentKey = string;
|
|
1191
|
+
interface AdminManifest {
|
|
1192
|
+
schemaVersion: 1;
|
|
1193
|
+
project: {
|
|
1194
|
+
key: string;
|
|
1195
|
+
displayName: string;
|
|
1196
|
+
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
1197
|
+
label?: string;
|
|
1198
|
+
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
1199
|
+
icon?: string;
|
|
1200
|
+
logoUrl?: string;
|
|
1201
|
+
environment?: 'production' | 'staging' | 'development';
|
|
1202
|
+
/**
|
|
1203
|
+
* Allowed locale pool from the app config (`saas.yaml`
|
|
1204
|
+
* `marketing.availableLocales`). First = default..
|
|
1205
|
+
*/
|
|
1206
|
+
availableLocales?: string[];
|
|
1207
|
+
/** Default locale; equals `availableLocales[0]`. */
|
|
1208
|
+
defaultLocale?: string;
|
|
1209
|
+
};
|
|
1210
|
+
build: {
|
|
1211
|
+
platformPackageVersion: string;
|
|
1212
|
+
appVersion: string;
|
|
1213
|
+
manifestHash: string;
|
|
1214
|
+
};
|
|
1215
|
+
planCatalogSnapshot: {
|
|
1216
|
+
source: string;
|
|
1217
|
+
hash: string;
|
|
1218
|
+
currency: string;
|
|
1219
|
+
vatRate: number;
|
|
1220
|
+
features?: FeatureDef[];
|
|
1221
|
+
plans: PlanDef[];
|
|
1222
|
+
};
|
|
1223
|
+
/** Map CapabilityKey → boolean. Manifest is never a security source. */
|
|
1224
|
+
capabilities: Record<CapabilityKey, boolean>;
|
|
1225
|
+
navigation: {
|
|
1226
|
+
standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
1227
|
+
projectPages?: ProjectPageDef[];
|
|
1228
|
+
};
|
|
1229
|
+
dashboard?: {
|
|
1230
|
+
kpiCards?: KpiCardDef[];
|
|
1231
|
+
};
|
|
1232
|
+
tenants?: {
|
|
1233
|
+
columns?: TenantColumnDef[];
|
|
1234
|
+
actions?: TenantActionDef[];
|
|
1235
|
+
};
|
|
1236
|
+
audit?: {
|
|
1237
|
+
actions?: AuditActionDef[];
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory' | 'settings';
|
|
1241
|
+
interface StandardPageDef {
|
|
1242
|
+
enabled: boolean;
|
|
1243
|
+
requiredCapability?: CapabilityKey;
|
|
1244
|
+
}
|
|
1245
|
+
interface ProjectPageDef {
|
|
1246
|
+
/** `<app>.<area>`, e.g. `demoapp.datev`. */
|
|
1247
|
+
id: string;
|
|
1248
|
+
label: string;
|
|
1249
|
+
icon?: string;
|
|
1250
|
+
/** Frontend route, e.g. `/admin/datev`. */
|
|
1251
|
+
route: string;
|
|
1252
|
+
navSection?: string;
|
|
1253
|
+
/** Lookup in the static extensions: map of the shell build. */
|
|
1254
|
+
componentKey: ComponentKey;
|
|
1255
|
+
requiredCapability?: CapabilityKey;
|
|
1256
|
+
prefetchOnIdle?: boolean;
|
|
1257
|
+
}
|
|
1258
|
+
interface KpiCardDef {
|
|
1259
|
+
id: string;
|
|
1260
|
+
label: string;
|
|
1261
|
+
/** Required path: /api/v1/admin/(extras|dashboard)/... */
|
|
1262
|
+
endpoint: string;
|
|
1263
|
+
displayHint: KpiDisplayHint;
|
|
1264
|
+
/** 0–100; UI sorts descending. */
|
|
1265
|
+
slotPriority?: number;
|
|
1266
|
+
requiredCapability?: CapabilityKey;
|
|
1267
|
+
}
|
|
1268
|
+
interface KpiDisplayHint {
|
|
1269
|
+
type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
|
|
1270
|
+
icon?: string;
|
|
1271
|
+
}
|
|
1272
|
+
interface TenantColumnDef {
|
|
1273
|
+
key: string;
|
|
1274
|
+
label: string;
|
|
1275
|
+
/** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
|
|
1276
|
+
endpoint: string;
|
|
1277
|
+
requiredCapability?: CapabilityKey;
|
|
1278
|
+
}
|
|
1279
|
+
interface TenantActionDef {
|
|
1280
|
+
/** `<app>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
|
|
1281
|
+
id: string;
|
|
1282
|
+
label: string;
|
|
1283
|
+
/** Lookup in the static actions: map of the shell build. */
|
|
1284
|
+
actionKey: ActionKey;
|
|
1285
|
+
requiredCapability?: CapabilityKey;
|
|
1286
|
+
requiresMfa?: boolean;
|
|
1287
|
+
confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
|
|
1288
|
+
}
|
|
1289
|
+
interface AuditActionDef {
|
|
1290
|
+
/** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
|
|
1291
|
+
key: string;
|
|
1292
|
+
label: string;
|
|
1293
|
+
severity?: 'info' | 'low' | 'medium' | 'high';
|
|
1294
|
+
}
|
|
1295
|
+
interface ManifestContribution {
|
|
1296
|
+
capabilities?: Record<CapabilityKey, boolean>;
|
|
1297
|
+
navigation?: {
|
|
1298
|
+
standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
|
|
1299
|
+
projectPages?: ProjectPageDef[];
|
|
1300
|
+
};
|
|
1301
|
+
dashboard?: {
|
|
1302
|
+
kpiCards?: KpiCardDef[];
|
|
1303
|
+
};
|
|
1304
|
+
tenants?: {
|
|
1305
|
+
columns?: TenantColumnDef[];
|
|
1306
|
+
actions?: TenantActionDef[];
|
|
1307
|
+
};
|
|
1308
|
+
audit?: {
|
|
1309
|
+
actions?: AuditActionDef[];
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
interface PublicBootResponse {
|
|
1313
|
+
project: {
|
|
1314
|
+
key: string;
|
|
1315
|
+
displayName: string;
|
|
1316
|
+
/** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
|
|
1317
|
+
label?: string;
|
|
1318
|
+
/** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
|
|
1319
|
+
icon?: string;
|
|
1320
|
+
logoUrl?: string;
|
|
1321
|
+
environment?: 'production' | 'staging' | 'development';
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
|
|
1326
|
+
type ActorTag = string;
|
|
1327
|
+
interface AuditEntry {
|
|
1328
|
+
id: string;
|
|
1329
|
+
/** null = platform action without tenant context (SUPER_ADMIN). */
|
|
1330
|
+
tenantId: string | null;
|
|
1331
|
+
/** null = system / cron-triggered. */
|
|
1332
|
+
userId: string | null;
|
|
1333
|
+
/** Convenience field; backend resolves it from userId. */
|
|
1334
|
+
userEmail: string | null;
|
|
1335
|
+
/** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
|
|
1336
|
+
entity: string;
|
|
1337
|
+
entityId: string;
|
|
1338
|
+
/** SCREAMING_SNAKE_CASE; past-tense oriented. */
|
|
1339
|
+
action: string;
|
|
1340
|
+
/** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
|
|
1341
|
+
changes: Record<string, unknown> | null;
|
|
1342
|
+
actorTag: ActorTag | null;
|
|
1343
|
+
ipAddress: string | null;
|
|
1344
|
+
userAgent: string | null;
|
|
1345
|
+
createdAt: string;
|
|
1346
|
+
}
|
|
1347
|
+
interface AuditQuery {
|
|
1348
|
+
tenantId?: string;
|
|
1349
|
+
userId?: string;
|
|
1350
|
+
entity?: string;
|
|
1351
|
+
entityId?: string;
|
|
1352
|
+
action?: string;
|
|
1353
|
+
/** Wildcard-capable, e.g. 'cli:*'. */
|
|
1354
|
+
actorTag?: string;
|
|
1355
|
+
from?: string;
|
|
1356
|
+
to?: string;
|
|
1357
|
+
page?: number;
|
|
1358
|
+
pageSize?: number;
|
|
1174
1359
|
}
|
|
1175
1360
|
|
|
1176
1361
|
/** Promotion type. */
|
|
@@ -1201,7 +1386,6 @@ type PromotionI18n = Record<string, PromotionI18nFields>;
|
|
|
1201
1386
|
/** Wire format of a `promotions` row. */
|
|
1202
1387
|
interface PromotionRow {
|
|
1203
1388
|
id: string;
|
|
1204
|
-
projectKey: string;
|
|
1205
1389
|
/** Internal label (not public). */
|
|
1206
1390
|
internalLabel: string;
|
|
1207
1391
|
type: PromotionType;
|
|
@@ -1227,11 +1411,7 @@ interface PromotionRow {
|
|
|
1227
1411
|
createdAt: string;
|
|
1228
1412
|
updatedAt: string;
|
|
1229
1413
|
}
|
|
1230
|
-
interface PromotionFilter {
|
|
1231
|
-
projectKey: string;
|
|
1232
|
-
}
|
|
1233
1414
|
interface CreatePromotionData {
|
|
1234
|
-
projectKey: string;
|
|
1235
1415
|
internalLabel: string;
|
|
1236
1416
|
type: PromotionType;
|
|
1237
1417
|
value: PromotionValue;
|
|
@@ -1354,7 +1534,6 @@ type CheckoutOfferStatus = 'open' | 'consumed' | 'expired';
|
|
|
1354
1534
|
/** Wire format of a `checkout_offers` row. */
|
|
1355
1535
|
interface CheckoutOfferRow {
|
|
1356
1536
|
id: string;
|
|
1357
|
-
projectKey: string;
|
|
1358
1537
|
/** Plan selected on the website. */
|
|
1359
1538
|
planKey: string;
|
|
1360
1539
|
/** Resolved plan version, if known. */
|
|
@@ -1385,12 +1564,10 @@ interface CheckoutOfferRow {
|
|
|
1385
1564
|
updatedAt: string;
|
|
1386
1565
|
}
|
|
1387
1566
|
interface CheckoutOfferFilter {
|
|
1388
|
-
projectKey: string;
|
|
1389
1567
|
status?: CheckoutOfferStatus;
|
|
1390
1568
|
}
|
|
1391
1569
|
/** Body of `POST /public/checkout-offer` — called from the website. */
|
|
1392
1570
|
interface CreateCheckoutOfferData {
|
|
1393
|
-
projectKey: string;
|
|
1394
1571
|
planKey: string;
|
|
1395
1572
|
planVersionId?: string | null;
|
|
1396
1573
|
billingCycle: 'monthly' | 'yearly';
|
|
@@ -1426,7 +1603,6 @@ interface UpdateCheckoutOfferData {
|
|
|
1426
1603
|
|
|
1427
1604
|
/** Wire format of the `marketing_settings` row. */
|
|
1428
1605
|
interface MarketingSettingsRow {
|
|
1429
|
-
projectKey: string;
|
|
1430
1606
|
/** Runtime-activated subset of the `availableLocales` pool. */
|
|
1431
1607
|
activeLocales: string[];
|
|
1432
1608
|
updatedAt: string;
|
|
@@ -1462,6 +1638,10 @@ interface PublicMarketingPlan {
|
|
|
1462
1638
|
badge: string;
|
|
1463
1639
|
/** Teaser / description text. */
|
|
1464
1640
|
description: string;
|
|
1641
|
+
/**
|
|
1642
|
+
* The recommended plan, and at most one card in a catalogue carries it —
|
|
1643
|
+
* see `keepOneRecommended`, which decides it per language served.
|
|
1644
|
+
*/
|
|
1465
1645
|
highlight: boolean;
|
|
1466
1646
|
/**
|
|
1467
1647
|
* Formatted pricing tag from the MarketingProjection (#47, e.g.
|
|
@@ -1544,7 +1724,6 @@ interface PublicComparisonRow {
|
|
|
1544
1724
|
}
|
|
1545
1725
|
/** Response of `GET /public/marketing-catalog`. */
|
|
1546
1726
|
interface PublicMarketingCatalogResponse {
|
|
1547
|
-
projectKey: string;
|
|
1548
1727
|
locale: string;
|
|
1549
1728
|
currency: string;
|
|
1550
1729
|
/** VAT rate in percent — for the CheckoutOffer price breakdown. */
|
|
@@ -1654,7 +1833,7 @@ interface DiscoverySnapshot {
|
|
|
1654
1833
|
/** ISO timestamp of the boot-time scan. */
|
|
1655
1834
|
scannedAt: string;
|
|
1656
1835
|
app: {
|
|
1657
|
-
/**
|
|
1836
|
+
/** The application's name, from `saas.yaml#app.name`. */
|
|
1658
1837
|
key: string;
|
|
1659
1838
|
/** Backend version, e.g. from package.json. */
|
|
1660
1839
|
version: string;
|
|
@@ -1690,6 +1869,18 @@ interface FeatureUiMeta {
|
|
|
1690
1869
|
/** Map FeatureKey → UI metadata. Consumer apps supply a complete table. */
|
|
1691
1870
|
type FeatureUiRegistry = Record<string, FeatureUiMeta>;
|
|
1692
1871
|
|
|
1872
|
+
/** A quota as a finite number, or `null` where the value cannot be read as one. */
|
|
1873
|
+
declare function readQuotaValue(value: unknown): number | null;
|
|
1874
|
+
/**
|
|
1875
|
+
* Every quota in a JSON column, for a caller that computes with them.
|
|
1876
|
+
*
|
|
1877
|
+
* A key that is there stays there. Dropping an unreadable one made it *absent*,
|
|
1878
|
+
* and absent means undeclared: `enforceLimit` answers an undeclared dimension
|
|
1879
|
+
* with a 500, so every operation on that quota was refused — a fail-closed
|
|
1880
|
+
* answer to somebody else's corrupt row.
|
|
1881
|
+
*/
|
|
1882
|
+
declare function readQuotaRecord(value: unknown): Record<string, number>;
|
|
1883
|
+
|
|
1693
1884
|
/** Alias for historical compatibility — equivalent to VersionChangeDirection. */
|
|
1694
1885
|
type ChangeDirection = VersionChangeDirection;
|
|
1695
1886
|
interface DiffResult {
|
|
@@ -1707,9 +1898,16 @@ type DecimalLike = number | string | {
|
|
|
1707
1898
|
};
|
|
1708
1899
|
interface PlanVersionFields {
|
|
1709
1900
|
features: FeatureKey[];
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1901
|
+
/**
|
|
1902
|
+
* Quotas of the version. -1 = unlimited; missing key = 0.
|
|
1903
|
+
*
|
|
1904
|
+
* Every key either side carries is compared. Which keys exist is the
|
|
1905
|
+
* installation's decision — they come from `@DefinesQuota` — so a fixed
|
|
1906
|
+
* set here would have compared the three the platform happened to know by
|
|
1907
|
+
* name and let every other one be lowered without the confirmation
|
|
1908
|
+
* publishing a regression asks for.
|
|
1909
|
+
*/
|
|
1910
|
+
quotas: Record<QuotaKey, number>;
|
|
1713
1911
|
monthlyNet: DecimalLike;
|
|
1714
1912
|
yearlyNet: DecimalLike;
|
|
1715
1913
|
}
|
|
@@ -1725,8 +1923,7 @@ declare function classifyPlanDiff(oldV: PlanVersionFields, newV: PlanVersionFiel
|
|
|
1725
1923
|
/**
|
|
1726
1924
|
* Classification of a BundleVersion diff for contract protection.
|
|
1727
1925
|
*
|
|
1728
|
-
*
|
|
1729
|
-
* number. Otherwise higher = better. Missing keys are treated as 0.
|
|
1926
|
+
* Quotas are compared exactly as they are for a plan.
|
|
1730
1927
|
*
|
|
1731
1928
|
* Pricing can be `null` (the bundle only has override pricing); a switch
|
|
1732
1929
|
* from value ↔ null is classified as REGRESSION (value dropped) or IMPROVEMENT
|
|
@@ -1824,9 +2021,86 @@ interface OnboardingPromoRedemption {
|
|
|
1824
2021
|
endsAt: string | null;
|
|
1825
2022
|
}
|
|
1826
2023
|
|
|
2024
|
+
/**
|
|
2025
|
+
* The settings subtree of a plan catalogue: every top-level block that is
|
|
2026
|
+
* configuration rather than the catalogue itself. JSON-shaped, because it is
|
|
2027
|
+
* stored as JSON and compared as JSON.
|
|
2028
|
+
*/
|
|
2029
|
+
type AppliedSettingsValues = Record<string, unknown>;
|
|
2030
|
+
/** The one row per installation: what is applied, since when, and from where. */
|
|
2031
|
+
interface AppliedSettingsRecord {
|
|
2032
|
+
/**
|
|
2033
|
+
* `sha256-<hex>` over the canonical JSON of `settings`. Two boots with the
|
|
2034
|
+
* same resolved values produce the same fingerprint however the file was
|
|
2035
|
+
* formatted, and a plan added to the catalogue does not move it.
|
|
2036
|
+
*/
|
|
2037
|
+
fingerprint: string;
|
|
2038
|
+
settings: AppliedSettingsValues;
|
|
2039
|
+
/**
|
|
2040
|
+
* Where the values came from: the absolute path of the file the platform
|
|
2041
|
+
* read, or a phrase saying they were handed to it in code.
|
|
2042
|
+
*/
|
|
2043
|
+
source: string;
|
|
2044
|
+
/** The moment these values became the running configuration. */
|
|
2045
|
+
appliedAt: Date;
|
|
2046
|
+
}
|
|
2047
|
+
/** What a boot noticed had changed since the previous record. */
|
|
2048
|
+
interface SettingsChangeRecord {
|
|
2049
|
+
id: string;
|
|
2050
|
+
/** The boot that noticed the difference and applied the new values. */
|
|
2051
|
+
noticedAt: Date;
|
|
2052
|
+
source: string;
|
|
2053
|
+
previous: AppliedSettingsValues;
|
|
2054
|
+
current: AppliedSettingsValues;
|
|
2055
|
+
/** Set once an operator has seen it; null while it is still owed a look. */
|
|
2056
|
+
acknowledgedAt: Date | null;
|
|
2057
|
+
/** Who acknowledged it — an actor tag, as the audit log writes it. */
|
|
2058
|
+
acknowledgedBy: string | null;
|
|
2059
|
+
}
|
|
2060
|
+
type NewSettingsChange = Pick<SettingsChangeRecord, 'noticedAt' | 'source' | 'previous' | 'current'>;
|
|
2061
|
+
/** One leaf that differs between two settings subtrees. */
|
|
2062
|
+
interface SettingsDifference {
|
|
2063
|
+
/** Dotted path, as the loader names a field: `tenantBilling.cancellationNoticeDays.monthly`. */
|
|
2064
|
+
path: string;
|
|
2065
|
+
/** `undefined` where the leaf did not exist on that side. */
|
|
2066
|
+
before: unknown;
|
|
2067
|
+
after: unknown;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/**
|
|
2071
|
+
* The top-level blocks of `config/saas.yaml` that are the catalogue rather than
|
|
2072
|
+
* the configuration, and the format marker.
|
|
2073
|
+
*
|
|
2074
|
+
* An exclusion list rather than a list of settings, on purpose: a block the
|
|
2075
|
+
* schema gains tomorrow is a setting until somebody says otherwise, so it is
|
|
2076
|
+
* fingerprinted by default. The failure mode of the other list — a new setting
|
|
2077
|
+
* silently left out of the fingerprint, so a change to it is never noticed — is
|
|
2078
|
+
* the one this record exists to prevent. `schemaVersion` is excluded because a
|
|
2079
|
+
* format change is a migration of the file, not a decision an operator took.
|
|
2080
|
+
*
|
|
2081
|
+
* `tests/settings-subtree.test.js` holds this list to the schema in both
|
|
2082
|
+
* directions: every name here is a property the schema declares, and every
|
|
2083
|
+
* property the schema declares lands on one side.
|
|
2084
|
+
*/
|
|
2085
|
+
declare const CATALOGUE_KEYS: ReadonlySet<keyof PlanCatalog>;
|
|
2086
|
+
/** Everything in the catalogue that is configuration, as it was resolved. */
|
|
2087
|
+
declare function settingsSubtreeOf(catalog: PlanCatalog): AppliedSettingsValues;
|
|
2088
|
+
/**
|
|
2089
|
+
* `JSON.stringify` with object keys in sorted order at every depth, so that two
|
|
2090
|
+
* documents saying the same thing in a different order serialise identically.
|
|
2091
|
+
* Array order is kept: a list is what its author wrote, in the order they wrote
|
|
2092
|
+
* it.
|
|
2093
|
+
*/
|
|
2094
|
+
declare function canonicalJson(value: unknown): string;
|
|
2095
|
+
/**
|
|
2096
|
+
* Every leaf that differs between `before` and `after`, in the order the paths
|
|
2097
|
+
* sort. A list counts as one leaf: `asTarget: [] → [ENTERPRISE]` is one thing
|
|
2098
|
+
* that changed, not a change per element.
|
|
2099
|
+
*/
|
|
2100
|
+
declare function diffSettings(before: AppliedSettingsValues, after: AppliedSettingsValues): SettingsDifference[];
|
|
2101
|
+
|
|
1827
2102
|
interface PlanRow {
|
|
1828
2103
|
id: string;
|
|
1829
|
-
projectKey: string;
|
|
1830
2104
|
planKey: string;
|
|
1831
2105
|
label: string;
|
|
1832
2106
|
description: string | null;
|
|
@@ -1843,7 +2117,6 @@ interface PlanRow {
|
|
|
1843
2117
|
* creation (follows in M6 Pack 2).
|
|
1844
2118
|
*/
|
|
1845
2119
|
interface CreatePlanData {
|
|
1846
|
-
projectKey: string;
|
|
1847
2120
|
planKey: string;
|
|
1848
2121
|
label: string;
|
|
1849
2122
|
description?: string | null;
|
|
@@ -1851,9 +2124,9 @@ interface CreatePlanData {
|
|
|
1851
2124
|
sortOrder?: number;
|
|
1852
2125
|
}
|
|
1853
2126
|
/**
|
|
1854
|
-
* Fields that may be changed on the plan stem. `planKey`
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
2127
|
+
* Fields that may be changed on the plan stem. `planKey` is deliberately not
|
|
2128
|
+
* here — stem identity is immutable; whoever wants to change it creates a new
|
|
2129
|
+
* plan and retires the old one.
|
|
1857
2130
|
*/
|
|
1858
2131
|
interface UpdatePlanData {
|
|
1859
2132
|
label?: string;
|
|
@@ -1917,7 +2190,6 @@ interface UpsertResult {
|
|
|
1917
2190
|
skipReason?: string;
|
|
1918
2191
|
}
|
|
1919
2192
|
interface UpsertPlanInput {
|
|
1920
|
-
projectKey: string;
|
|
1921
2193
|
planKey: string;
|
|
1922
2194
|
label: string;
|
|
1923
2195
|
description?: string | null;
|
|
@@ -1937,7 +2209,6 @@ interface UpsertPlanVersionInput {
|
|
|
1937
2209
|
changeNote: string;
|
|
1938
2210
|
}
|
|
1939
2211
|
interface UpsertFeatureCatalogEntryInput {
|
|
1940
|
-
projectKey: string;
|
|
1941
2212
|
featureKey: FeatureKey;
|
|
1942
2213
|
label?: string;
|
|
1943
2214
|
icon?: string;
|
|
@@ -1983,7 +2254,7 @@ interface PlanCatalogReadSnapshot {
|
|
|
1983
2254
|
* implement it against their Prisma tables.
|
|
1984
2255
|
*/
|
|
1985
2256
|
interface PlanCatalogReadSink {
|
|
1986
|
-
loadSnapshot(
|
|
2257
|
+
loadSnapshot(): Promise<PlanCatalogReadSnapshot>;
|
|
1987
2258
|
}
|
|
1988
2259
|
|
|
1989
2260
|
/**
|
|
@@ -2220,6 +2491,26 @@ interface PasswordHasher {
|
|
|
2220
2491
|
hash(plain: string): Promise<string>;
|
|
2221
2492
|
verify(hash: string, plain: string): Promise<boolean>;
|
|
2222
2493
|
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Sends a plain-text mail to an operator.
|
|
2496
|
+
*
|
|
2497
|
+
* The platform composes the text; the adapter delivers it — over whatever the
|
|
2498
|
+
* installation already sends mail with. Deliberately narrow: no templates, no
|
|
2499
|
+
* locale, no HTML. The one thing the platform mails today is a diagnostic for
|
|
2500
|
+
* the operator who runs the installation, and a diagnostic is English and
|
|
2501
|
+
* plain, like the boot log it mirrors. Tenant-facing mail — a verification
|
|
2502
|
+
* code, a resume link — goes through the registration module's own delivery
|
|
2503
|
+
* ports, which carry the locale and the person's name because that mail is
|
|
2504
|
+
* for a customer.
|
|
2505
|
+
*/
|
|
2506
|
+
interface EmailPort {
|
|
2507
|
+
/** Delivers one plain-text mail to one address; rejects when it cannot. */
|
|
2508
|
+
send(message: {
|
|
2509
|
+
to: string;
|
|
2510
|
+
subject: string;
|
|
2511
|
+
text: string;
|
|
2512
|
+
}): Promise<void>;
|
|
2513
|
+
}
|
|
2223
2514
|
/** Adapter for MFA secret persistence. */
|
|
2224
2515
|
interface MfaPort {
|
|
2225
2516
|
/** Returns the stored TOTP secret or null. */
|
|
@@ -2427,6 +2718,26 @@ interface PromoRevenueDeductionAggregator {
|
|
|
2427
2718
|
|
|
2428
2719
|
type ContractLineItemKind = 'plan' | 'bundle' | 'discount';
|
|
2429
2720
|
type SubscriptionContractStatus = 'active' | 'scheduled' | 'terminated' | 'superseded';
|
|
2721
|
+
/**
|
|
2722
|
+
* The statuses a contract is looked up under when asking "what is this tenant
|
|
2723
|
+
* on right now" — `scheduled` included, because a contract that starts today
|
|
2724
|
+
* and has not been switched to `active` yet is still the one in force at its
|
|
2725
|
+
* own `effectiveFrom`.
|
|
2726
|
+
*
|
|
2727
|
+
* One list rather than one per adapter: the two adapters have to answer
|
|
2728
|
+
* `findActiveByTenantId` the same way, and a status added here must not reach
|
|
2729
|
+
* only whichever of them somebody remembered.
|
|
2730
|
+
*/
|
|
2731
|
+
/**
|
|
2732
|
+
* How many bundle versions one price lookup may name.
|
|
2733
|
+
*
|
|
2734
|
+
* One number rather than two: the server validates against it and the client
|
|
2735
|
+
* batches to stay inside it, and a client that learned the cap by receiving a
|
|
2736
|
+
* 400 would fail silently — the lookup answers with an empty map, and every
|
|
2737
|
+
* card falls back to a catalogue price the tenant may not be charged.
|
|
2738
|
+
*/
|
|
2739
|
+
declare const BUNDLE_PRICE_LOOKUP_LIMIT = 200;
|
|
2740
|
+
declare const ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES: readonly SubscriptionContractStatus[];
|
|
2430
2741
|
interface ContractLineItemRecord {
|
|
2431
2742
|
id: string;
|
|
2432
2743
|
contractId: string;
|
|
@@ -2440,6 +2751,26 @@ interface ContractLineItemRecord {
|
|
|
2440
2751
|
priceNet: number;
|
|
2441
2752
|
priceGross: number;
|
|
2442
2753
|
billingCycle: 'monthly' | 'yearly';
|
|
2754
|
+
/**
|
|
2755
|
+
* ISO 4217, as the line was booked in.
|
|
2756
|
+
*
|
|
2757
|
+
* An installation sells in one currency at a time, so this is never a
|
|
2758
|
+
* choice the line makes — it is what keeps the line meaning what it meant
|
|
2759
|
+
* after the configured currency is migrated to another one.
|
|
2760
|
+
*/
|
|
2761
|
+
currency: string;
|
|
2762
|
+
/**
|
|
2763
|
+
* The tax rate in percent that was applied, recorded rather than left in
|
|
2764
|
+
* the ratio between net and gross. That ratio is not the rate: it cannot be
|
|
2765
|
+
* reproduced for a rounded gross, cannot express an exempt or reverse-charge
|
|
2766
|
+
* line, and does not survive a rate change.
|
|
2767
|
+
*/
|
|
2768
|
+
taxRate: number;
|
|
2769
|
+
/**
|
|
2770
|
+
* The tax contained in the line — exactly `priceGross - priceNet`, so the
|
|
2771
|
+
* line cannot disagree with itself. Rounded once, when the line is written.
|
|
2772
|
+
*/
|
|
2773
|
+
taxAmount: number;
|
|
2443
2774
|
minimumTermUntil: Date | null;
|
|
2444
2775
|
featuresSnapshot: string[];
|
|
2445
2776
|
quotaEffectsSnapshot: Record<string, number>;
|
|
@@ -2452,12 +2783,23 @@ interface SubscriptionContractPriceSnapshot {
|
|
|
2452
2783
|
subtotalNet: number;
|
|
2453
2784
|
discountNet: number;
|
|
2454
2785
|
totalNet: number;
|
|
2786
|
+
/**
|
|
2787
|
+
* The rate this contract's total was computed at — in per cent where the
|
|
2788
|
+
* contract was frozen from the catalogue, and as the offer stated it where
|
|
2789
|
+
* it was concluded from one.
|
|
2790
|
+
*
|
|
2791
|
+
* A checkout offer prices its lines as `net * (1 + vatRate)`, so it states
|
|
2792
|
+
* a fraction, and the value is copied here as it stands. The field
|
|
2793
|
+
* therefore carries both units across a history and cannot be compared
|
|
2794
|
+
* across contracts. `ContractLineItemRecord.taxRate` is always per cent and
|
|
2795
|
+
* is the one to read; this is kept as written because it is the record of
|
|
2796
|
+
* what the contract was concluded with.
|
|
2797
|
+
*/
|
|
2455
2798
|
vatRate: number;
|
|
2456
2799
|
totalGross: number;
|
|
2457
2800
|
}
|
|
2458
2801
|
interface SubscriptionContractRecord {
|
|
2459
2802
|
id: string;
|
|
2460
|
-
projectKey: string;
|
|
2461
2803
|
tenantId: string;
|
|
2462
2804
|
status: SubscriptionContractStatus;
|
|
2463
2805
|
effectiveFrom: Date;
|
|
@@ -2476,7 +2818,6 @@ interface SubscriptionContractRecord {
|
|
|
2476
2818
|
}
|
|
2477
2819
|
type NewContractLineItemData = Omit<ContractLineItemRecord, 'id' | 'contractId' | 'createdAt'>;
|
|
2478
2820
|
interface CreateSubscriptionContractData {
|
|
2479
|
-
projectKey: string;
|
|
2480
2821
|
tenantId: string;
|
|
2481
2822
|
status?: SubscriptionContractStatus;
|
|
2482
2823
|
effectiveFrom: Date;
|
|
@@ -2493,10 +2834,22 @@ interface CreateSubscriptionContractData {
|
|
|
2493
2834
|
}
|
|
2494
2835
|
interface TerminateSubscriptionContractData {
|
|
2495
2836
|
effectiveUntil: Date;
|
|
2496
|
-
|
|
2837
|
+
/**
|
|
2838
|
+
* The terminal status, or `null` to end the contract by date alone.
|
|
2839
|
+
*
|
|
2840
|
+
* `findActiveByTenantId` already asks its question as a window —
|
|
2841
|
+
* `effectiveFrom <= asOf` and `effectiveUntil` null or after it — so a
|
|
2842
|
+
* contract given an end in the FUTURE is found until that moment and not
|
|
2843
|
+
* afterwards, with no scheduled job to flip anything.
|
|
2844
|
+
*
|
|
2845
|
+
* Writing a terminal status instead makes the contract disappear from that
|
|
2846
|
+
* lookup at once, which for a cancellation declared months ahead removes an
|
|
2847
|
+
* agreement the customer is still under. Null is how a caller says "it ends
|
|
2848
|
+
* then", and a status is how it says "it is over now".
|
|
2849
|
+
*/
|
|
2850
|
+
status: Extract<SubscriptionContractStatus, 'terminated' | 'superseded'> | null;
|
|
2497
2851
|
}
|
|
2498
2852
|
interface SubscriptionContractFilter {
|
|
2499
|
-
projectKey?: string;
|
|
2500
2853
|
tenantId?: string;
|
|
2501
2854
|
status?: SubscriptionContractStatus;
|
|
2502
2855
|
asOf?: Date;
|
|
@@ -2513,12 +2866,14 @@ interface InvoiceLineItemSnapshot {
|
|
|
2513
2866
|
priceNet: number;
|
|
2514
2867
|
priceGross: number;
|
|
2515
2868
|
billingCycle: 'monthly' | 'yearly';
|
|
2869
|
+
currency: string;
|
|
2870
|
+
taxRate: number;
|
|
2871
|
+
taxAmount: number;
|
|
2516
2872
|
minimumTermUntil: Date | null;
|
|
2517
2873
|
metadata: Record<string, unknown> | null;
|
|
2518
2874
|
}
|
|
2519
2875
|
interface SubscriptionContractInvoiceSnapshot {
|
|
2520
2876
|
contractId: string;
|
|
2521
|
-
projectKey: string;
|
|
2522
2877
|
tenantId: string;
|
|
2523
2878
|
originalOfferId: string | null;
|
|
2524
2879
|
currency: string;
|
|
@@ -2552,6 +2907,24 @@ interface SubscriptionRecord {
|
|
|
2552
2907
|
} | null;
|
|
2553
2908
|
planVersionId: string;
|
|
2554
2909
|
planVersion: PlanVersionRecord;
|
|
2910
|
+
/**
|
|
2911
|
+
* When a cancellation was declared, and when it takes effect.
|
|
2912
|
+
*
|
|
2913
|
+
* Required, and required together, because entitlement resolution ends a
|
|
2914
|
+
* subscription by reading them: without the second date it cannot tell a
|
|
2915
|
+
* subscription that ends next January from one that ended last January, and
|
|
2916
|
+
* it grants the latter everything. Nothing else in the platform would
|
|
2917
|
+
* notice — no repository filters a cancelled subscription out, and stopping
|
|
2918
|
+
* the billing period is a different decision from ending what a tenant may
|
|
2919
|
+
* do.
|
|
2920
|
+
*
|
|
2921
|
+
* `null` on both means no cancellation. On a row written before the two
|
|
2922
|
+
* fields separated, `canceledAt` carries the effective date and
|
|
2923
|
+
* `canceledEffectiveAt` is genuinely null; every reader in the platform
|
|
2924
|
+
* applies `canceledEffectiveAt ?? canceledAt` for that reason.
|
|
2925
|
+
*/
|
|
2926
|
+
canceledAt: Date | null;
|
|
2927
|
+
canceledEffectiveAt: Date | null;
|
|
2555
2928
|
}
|
|
2556
2929
|
/** Snapshot of a `PlanVersion` row. */
|
|
2557
2930
|
interface PlanVersionRecord {
|
|
@@ -2608,11 +2981,10 @@ interface SubscriptionRepository {
|
|
|
2608
2981
|
countByBundleVersionId?(bundleVersionId: string): Promise<number>;
|
|
2609
2982
|
/**
|
|
2610
2983
|
* Counts active subscriptions (status `ACTIVE` or `TRIAL`) per plan key,
|
|
2611
|
-
* platform-wide across
|
|
2612
|
-
*
|
|
2984
|
+
* platform-wide across every tenant — feeds the tenant column of the
|
|
2985
|
+
* SuperAdmin plan list (`GET /admin/catalog/plans/tenant-counts`).
|
|
2613
2986
|
* Cross-version: counts the plan, not a single PlanVersion
|
|
2614
|
-
* (subscriptions on superseded versions are included).
|
|
2615
|
-
* informational for single-project consumers.
|
|
2987
|
+
* (subscriptions on superseded versions are included).
|
|
2616
2988
|
*
|
|
2617
2989
|
* Returns a map `planKey → count`; plans without an active subscription
|
|
2618
2990
|
* are missing (UI defaults to 0). Platform-wide count across all tenants →
|
|
@@ -2620,7 +2992,7 @@ interface SubscriptionRepository {
|
|
|
2620
2992
|
*
|
|
2621
2993
|
* Optional — if not implemented, the tenant column stays 0.
|
|
2622
2994
|
*/
|
|
2623
|
-
countActiveByPlanKey?(
|
|
2995
|
+
countActiveByPlanKey?(): Promise<Record<string, number>>;
|
|
2624
2996
|
}
|
|
2625
2997
|
/**
|
|
2626
2998
|
* Adapter for the `subscription_bundles` junction.
|
|
@@ -2712,6 +3084,40 @@ interface SubscriptionUsageRecord {
|
|
|
2712
3084
|
/** Current period window — for proration and change-effective date. */
|
|
2713
3085
|
currentPeriodStart: Date | null;
|
|
2714
3086
|
currentPeriodEnd: Date | null;
|
|
3087
|
+
/**
|
|
3088
|
+
* End of what was committed to, which the period end need not equal.
|
|
3089
|
+
*
|
|
3090
|
+
* The cancellation rules measure against this: a subscription cancelled
|
|
3091
|
+
* inside its term keeps running until the term ends, not until the period
|
|
3092
|
+
* does. Null on a trial, and on any subscription written before the field
|
|
3093
|
+
* existed — readers treat that as "the period end is the answer".
|
|
3094
|
+
*/
|
|
3095
|
+
minimumTermUntil?: Date | null;
|
|
3096
|
+
/**
|
|
3097
|
+
* The day of the month the subscription is billed on, 1–31.
|
|
3098
|
+
*
|
|
3099
|
+
* Read by the cancellation rules: a declaration after the notice window
|
|
3100
|
+
* lands one period past the term end, and that step has to measure from the
|
|
3101
|
+
* billing day rather than from a term end that may already have been
|
|
3102
|
+
* clamped by a short month.
|
|
3103
|
+
*
|
|
3104
|
+
* Optional, because an adapter that does not store the column keeps today's
|
|
3105
|
+
* behaviour — the step then takes its day from the term end, which is
|
|
3106
|
+
* correct except in the month after a clamp.
|
|
3107
|
+
*/
|
|
3108
|
+
billingAnchorDay?: number | null;
|
|
3109
|
+
/**
|
|
3110
|
+
* When a cancellation was declared, and when it lands.
|
|
3111
|
+
*
|
|
3112
|
+
* Required for the reason the same pair is required on
|
|
3113
|
+
* `SubscriptionRecord`: the tenant billing route reads them to refuse a
|
|
3114
|
+
* plan change on a subscription that has ended, and a record that omits
|
|
3115
|
+
* them answers "not cancelled" — so the change is applied and prorated
|
|
3116
|
+
* while entitlement resolution, which reads a record that does carry them,
|
|
3117
|
+
* grants nothing.
|
|
3118
|
+
*/
|
|
3119
|
+
canceledAt: Date | null;
|
|
3120
|
+
canceledEffectiveAt: Date | null;
|
|
2715
3121
|
pendingPlan: string | null;
|
|
2716
3122
|
pendingBillingCycle: string | null;
|
|
2717
3123
|
pendingEffectiveAt: Date | null;
|
|
@@ -2787,12 +3193,29 @@ interface ImmediatePlanChangeInput {
|
|
|
2787
3193
|
* change, or target package without trial). A `Date` is persisted.
|
|
2788
3194
|
*/
|
|
2789
3195
|
trialEndsAt?: Date | null;
|
|
3196
|
+
/**
|
|
3197
|
+
* `canceledAt` as the caller read it, so the write can claim the row only
|
|
3198
|
+
* while that is still true.
|
|
3199
|
+
*
|
|
3200
|
+
* Three of the plan route's decisions depend on the cancellation — whether
|
|
3201
|
+
* the change is refused at all, whether the billing cycle may move, and
|
|
3202
|
+
* whether a fresh period is opened — and a read and a write are two
|
|
3203
|
+
* moments. A cancellation declared in between made every one of them answer
|
|
3204
|
+
* about a state that no longer existed, and the write went ahead anyway: a
|
|
3205
|
+
* plan term recorded past the date the subscription ends.
|
|
3206
|
+
*
|
|
3207
|
+
* `null` is a value here rather than an absence. It claims a row that has
|
|
3208
|
+
* no cancellation, and loses against one that has acquired one.
|
|
3209
|
+
*/
|
|
3210
|
+
expectedCanceledAt: Date | null;
|
|
2790
3211
|
}
|
|
2791
3212
|
/** Input for `schedulePlanChange` (change at period end). */
|
|
2792
3213
|
interface ScheduledPlanChangeInput {
|
|
2793
3214
|
pendingPlan: string;
|
|
2794
3215
|
pendingBillingCycle: string;
|
|
2795
3216
|
pendingEffectiveAt: Date;
|
|
3217
|
+
/** See `ImmediatePlanChangeInput.expectedCanceledAt`. */
|
|
3218
|
+
expectedCanceledAt: Date | null;
|
|
2796
3219
|
}
|
|
2797
3220
|
/**
|
|
2798
3221
|
* Input for `applyOnboardingSelection`. Plan-change fields that the
|
|
@@ -2801,6 +3224,12 @@ interface ScheduledPlanChangeInput {
|
|
|
2801
3224
|
interface ApplyOnboardingSelectionInput {
|
|
2802
3225
|
planId: string;
|
|
2803
3226
|
cycle: string;
|
|
3227
|
+
/**
|
|
3228
|
+
* See `ImmediatePlanChangeInput.expectedCanceledAt`. The atomic path needs
|
|
3229
|
+
* it for the same reason the sequential one does: without it, the preferred
|
|
3230
|
+
* implementation is the one where the race stays open.
|
|
3231
|
+
*/
|
|
3232
|
+
expectedCanceledAt: Date | null;
|
|
2804
3233
|
/** For TRIAL → null, otherwise period start from `initialPeriodWindow`. */
|
|
2805
3234
|
periodStart: Date | null;
|
|
2806
3235
|
periodEnd: Date | null;
|
|
@@ -2817,6 +3246,12 @@ interface ApplyOnboardingSelectionResult {
|
|
|
2817
3246
|
subscriptionId: string;
|
|
2818
3247
|
/** null if no redeemPromo callback was provided or the callback returned null. */
|
|
2819
3248
|
promoRedemption: PromoCodeRedemptionRecord | null;
|
|
3249
|
+
/**
|
|
3250
|
+
* False when the row's cancellation moved since the caller read it, in
|
|
3251
|
+
* which case nothing was written — including the promo redemption, which
|
|
3252
|
+
* shares the transaction.
|
|
3253
|
+
*/
|
|
3254
|
+
claimed: boolean;
|
|
2820
3255
|
}
|
|
2821
3256
|
/**
|
|
2822
3257
|
* Callback signature for promo-code redemption WITHIN the onboarding
|
|
@@ -2834,14 +3269,46 @@ type RedeemPromoInTransactionCallback = (tx: TransactionContext, subscriptionId:
|
|
|
2834
3269
|
* app-specific. The platform service calls `invalidateTenant` in the
|
|
2835
3270
|
* EntitlementService after a successful adapter call.
|
|
2836
3271
|
*/
|
|
3272
|
+
/** What `cancelSubscription` is told to write. Named so both adapters spell
|
|
3273
|
+
* the same shape once rather than each restating it. */
|
|
3274
|
+
interface CancelSubscriptionInput {
|
|
3275
|
+
canceledAt: Date;
|
|
3276
|
+
effectiveAt: Date;
|
|
3277
|
+
terminateNow: boolean;
|
|
3278
|
+
minimumTermUntil?: Date;
|
|
3279
|
+
}
|
|
3280
|
+
/** What `cancelSubscription` answers with. */
|
|
3281
|
+
interface CancelSubscriptionResult {
|
|
3282
|
+
canceledAt: Date | null;
|
|
3283
|
+
canceledEffectiveAt: Date | null;
|
|
3284
|
+
status: string;
|
|
3285
|
+
/**
|
|
3286
|
+
* True when a cancellation was already recorded and this call changed
|
|
3287
|
+
* nothing — the stored dates are returned instead.
|
|
3288
|
+
*
|
|
3289
|
+
* The caller checks first, but a check and a write are two moments, and two
|
|
3290
|
+
* requests can pass the check before either writes. Straddling a notice
|
|
3291
|
+
* deadline that costs a billing cycle: the first declaration lands on time,
|
|
3292
|
+
* the second recomputes against a later `now`, and an unconditional write
|
|
3293
|
+
* replaces the first answer with one a period further out. An
|
|
3294
|
+
* implementation therefore claims the row only while both cancellation
|
|
3295
|
+
* fields are still empty, and answers `true` here when the claim finds
|
|
3296
|
+
* nothing to claim.
|
|
3297
|
+
*/
|
|
3298
|
+
alreadyCanceled: boolean;
|
|
3299
|
+
}
|
|
2837
3300
|
interface TenantSubscriptionWritePort {
|
|
2838
3301
|
/** Immediate change: set plan + cycle, clear pending fields, optionally reset the period. */
|
|
2839
3302
|
changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
|
|
2840
3303
|
plan: string;
|
|
2841
3304
|
billingCycle: string;
|
|
3305
|
+
/** False when the row's cancellation moved since the caller read it. */
|
|
3306
|
+
claimed: boolean;
|
|
2842
3307
|
}>;
|
|
2843
3308
|
/** Change at period end: set pending fields. */
|
|
2844
|
-
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<
|
|
3309
|
+
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<{
|
|
3310
|
+
claimed: boolean;
|
|
3311
|
+
}>;
|
|
2845
3312
|
/**
|
|
2846
3313
|
* Marks the pending PlanVersion as accepted. Idempotent — a duplicate
|
|
2847
3314
|
* accept is a no-op. Returns `alreadyAccepted: true` if the status was
|
|
@@ -2854,13 +3321,30 @@ interface TenantSubscriptionWritePort {
|
|
|
2854
3321
|
alreadyAccepted: boolean;
|
|
2855
3322
|
}>;
|
|
2856
3323
|
/**
|
|
2857
|
-
*
|
|
2858
|
-
*
|
|
3324
|
+
* Record a cancellation. The dates are decided above this port.
|
|
3325
|
+
*
|
|
3326
|
+
* `canceledAt` is when the customer said it; `effectiveAt` is when it
|
|
3327
|
+
* lands. They differ for every ordinary cancellation, because a
|
|
3328
|
+
* subscription cancelled inside its term keeps running, keeps being billed
|
|
3329
|
+
* and keeps its entitlements until the term ends. An adapter that computed
|
|
3330
|
+
* the second from the first — which this one did, as
|
|
3331
|
+
* `immediate ? now : currentPeriodEnd` — was deciding a commercial
|
|
3332
|
+
* question in a persistence layer, and could not see the minimum term or
|
|
3333
|
+
* the notice period at all.
|
|
3334
|
+
*
|
|
3335
|
+
* `terminateNow` flips the status immediately, and is set when the
|
|
3336
|
+
* cancellation is already effective: an operator ending a contract, or the
|
|
3337
|
+
* rules finding nothing left to run — no period, no term, as on a trial.
|
|
3338
|
+
* It is never a client's request. A tenant may always declare a
|
|
3339
|
+
* cancellation and may never shorten the term they are in; what decides
|
|
3340
|
+
* this flag is the date the rules returned, not the date they asked for.
|
|
3341
|
+
*
|
|
3342
|
+
* `minimumTermUntil` extends the stored commitment, and is set only when
|
|
3343
|
+
* the cancellation itself extends it: a declaration made after the notice
|
|
3344
|
+
* deadline buys the following period. Left unset the stored term end is
|
|
3345
|
+
* unchanged, which is the ordinary case.
|
|
2859
3346
|
*/
|
|
2860
|
-
cancelSubscription(tenantId: string,
|
|
2861
|
-
canceledAt: Date | null;
|
|
2862
|
-
status: string;
|
|
2863
|
-
}>;
|
|
3347
|
+
cancelSubscription(tenantId: string, input: CancelSubscriptionInput): Promise<CancelSubscriptionResult>;
|
|
2864
3348
|
/**
|
|
2865
3349
|
* Atomic onboarding creation: sets plan + cycle + period window
|
|
2866
3350
|
* AND optionally calls a promo-redeem callback — all in a
|
|
@@ -3112,7 +3596,6 @@ interface RlsBypassPort {
|
|
|
3112
3596
|
|
|
3113
3597
|
/** Filter for `PlanRepository.list()`. */
|
|
3114
3598
|
interface PlanListFilter {
|
|
3115
|
-
projectKey: string;
|
|
3116
3599
|
/** Exclude soft-deleted plans — default `true`. */
|
|
3117
3600
|
excludeDeleted?: boolean;
|
|
3118
3601
|
/**
|
|
@@ -3146,7 +3629,7 @@ interface PlanListFilter {
|
|
|
3146
3629
|
interface PlanRepository {
|
|
3147
3630
|
list(filter: PlanListFilter): Promise<PlanRow[]>;
|
|
3148
3631
|
findById(planId: string): Promise<PlanRow | null>;
|
|
3149
|
-
findByKey(
|
|
3632
|
+
findByKey(planKey: string): Promise<PlanRow | null>;
|
|
3150
3633
|
create(data: CreatePlanData): Promise<PlanRow>;
|
|
3151
3634
|
update(planId: string, data: UpdatePlanData): Promise<PlanRow>;
|
|
3152
3635
|
/** Sets `deletedAt` to NOW(); soft-deleted plans are filtered from `list` by default. */
|
|
@@ -3247,10 +3730,24 @@ interface PlanRepository {
|
|
|
3247
3730
|
}
|
|
3248
3731
|
/** Filter for `BundleRepository.list()`. */
|
|
3249
3732
|
interface BundleListFilter {
|
|
3250
|
-
projectKey: string;
|
|
3251
3733
|
/** Exclude soft-deleted bundles — default `true`. */
|
|
3252
3734
|
excludeDeleted?: boolean;
|
|
3253
3735
|
}
|
|
3736
|
+
/**
|
|
3737
|
+
* What publishing a bundle draft records.
|
|
3738
|
+
*
|
|
3739
|
+
* Named because it was written out three times — the port, and each adapter's
|
|
3740
|
+
* implementation of it — and a signature restated is a contract restated: the
|
|
3741
|
+
* copies can drift, and nothing but a reader would notice.
|
|
3742
|
+
*/
|
|
3743
|
+
interface PublishBundleVersionMeta {
|
|
3744
|
+
publishedByUserId: string | null;
|
|
3745
|
+
publishedChanges: VersionChange[];
|
|
3746
|
+
nonRegressive: boolean;
|
|
3747
|
+
/** Required — validated by the service before the repository call. */
|
|
3748
|
+
validFrom: Date;
|
|
3749
|
+
validUntil: Date | null;
|
|
3750
|
+
}
|
|
3254
3751
|
/**
|
|
3255
3752
|
* Adapter for `Bundle` + `BundleVersion` persistence. Consumers implement
|
|
3256
3753
|
* this against their Prisma tables (`bundles` + `bundle_versions`).
|
|
@@ -3269,7 +3766,7 @@ interface BundleListFilter {
|
|
|
3269
3766
|
interface BundleRepository {
|
|
3270
3767
|
list(filter: BundleListFilter): Promise<BundleRow[]>;
|
|
3271
3768
|
findById(bundleId: string): Promise<BundleRow | null>;
|
|
3272
|
-
findByKey(
|
|
3769
|
+
findByKey(bundleKey: string): Promise<BundleRow | null>;
|
|
3273
3770
|
create(data: CreateBundleData): Promise<BundleRow>;
|
|
3274
3771
|
update(bundleId: string, data: UpdateBundleData): Promise<BundleRow>;
|
|
3275
3772
|
/** Sets `deletedAt` to NOW(); soft-deleted bundles are filtered from `list` by default. */
|
|
@@ -3328,14 +3825,7 @@ interface BundleRepository {
|
|
|
3328
3825
|
* if the predecessor carries a `validUntil` — the adapter only
|
|
3329
3826
|
* persists, it does not validate again.
|
|
3330
3827
|
*/
|
|
3331
|
-
publishDraft(versionId: string, publishMeta:
|
|
3332
|
-
publishedByUserId: string | null;
|
|
3333
|
-
publishedChanges: VersionChange[];
|
|
3334
|
-
nonRegressive: boolean;
|
|
3335
|
-
/** Required — validated by the service before the repository call. */
|
|
3336
|
-
validFrom: Date;
|
|
3337
|
-
validUntil: Date | null;
|
|
3338
|
-
}, tx?: TransactionContext): Promise<BundleVersionRow>;
|
|
3828
|
+
publishDraft(versionId: string, publishMeta: PublishBundleVersionMeta, tx?: TransactionContext): Promise<BundleVersionRow>;
|
|
3339
3829
|
/**
|
|
3340
3830
|
* Hard-discards a draft version (`publishedAt === null`) from the DB.
|
|
3341
3831
|
* Throws if the version was already published — published versions
|
|
@@ -3373,7 +3863,6 @@ interface MarketingProjectionRepository {
|
|
|
3373
3863
|
}
|
|
3374
3864
|
/** Upsert input for a capability from the discovery sync. */
|
|
3375
3865
|
interface UpsertCapabilityEntryData {
|
|
3376
|
-
projectKey: string;
|
|
3377
3866
|
capabilityKey: string;
|
|
3378
3867
|
label: string;
|
|
3379
3868
|
description: string | null;
|
|
@@ -3390,7 +3879,6 @@ interface UpsertCapabilityEntryData {
|
|
|
3390
3879
|
}
|
|
3391
3880
|
/** Upsert input for a feature from the discovery sync. */
|
|
3392
3881
|
interface UpsertFeatureEntryData {
|
|
3393
|
-
projectKey: string;
|
|
3394
3882
|
featureKey: string;
|
|
3395
3883
|
label: string;
|
|
3396
3884
|
description: string | null;
|
|
@@ -3404,7 +3892,6 @@ interface UpsertFeatureEntryData {
|
|
|
3404
3892
|
}
|
|
3405
3893
|
/** Upsert input for a quota from the discovery sync. */
|
|
3406
3894
|
interface UpsertQuotaEntryData {
|
|
3407
|
-
projectKey: string;
|
|
3408
3895
|
quotaKey: string;
|
|
3409
3896
|
label: string;
|
|
3410
3897
|
description: string | null;
|
|
@@ -3434,7 +3921,7 @@ interface SetCatalogEntryReviewData {
|
|
|
3434
3921
|
* Prisma tables.
|
|
3435
3922
|
*
|
|
3436
3923
|
* Binding:
|
|
3437
|
-
* - `upsert*` matches on
|
|
3924
|
+
* - `upsert*` matches on `<key>` and leaves `i18n`,
|
|
3438
3925
|
* `sortOrder`, `createdAt` as well as the approval fields (`approvedAt`/
|
|
3439
3926
|
* `approvedBy`/`approvedSignature`) **untouched** on an update —
|
|
3440
3927
|
* only the code-derived fields + the status (resolved by the service)
|
|
@@ -3450,7 +3937,7 @@ interface CatalogEntryRepository {
|
|
|
3450
3937
|
upsertCapability(data: UpsertCapabilityEntryData): Promise<CapabilityCatalogEntryRow>;
|
|
3451
3938
|
upsertFeature(data: UpsertFeatureEntryData): Promise<FeatureCatalogEntryRow>;
|
|
3452
3939
|
upsertQuota(data: UpsertQuotaEntryData): Promise<QuotaCatalogEntryRow>;
|
|
3453
|
-
retireMissing(
|
|
3940
|
+
retireMissing(type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
|
|
3454
3941
|
/**
|
|
3455
3942
|
* Sets or clears the successor pointer of a feature/quota
|
|
3456
3943
|
* (#39). The sync calls this when a key disappears from the snapshot
|
|
@@ -3459,17 +3946,17 @@ interface CatalogEntryRepository {
|
|
|
3459
3946
|
* adapters without a `successor_key` column omit the methods, and the sync
|
|
3460
3947
|
* then skips the pointers with a warn log.
|
|
3461
3948
|
*/
|
|
3462
|
-
setFeatureSuccessor?(
|
|
3463
|
-
setQuotaSuccessor?(
|
|
3464
|
-
findFeature(
|
|
3465
|
-
findQuota(
|
|
3466
|
-
setFeatureReview(
|
|
3467
|
-
setQuotaReview(
|
|
3468
|
-
setFeatureI18n(
|
|
3469
|
-
setQuotaI18n(
|
|
3949
|
+
setFeatureSuccessor?(featureKey: string, successorKey: string | null): Promise<FeatureCatalogEntryRow>;
|
|
3950
|
+
setQuotaSuccessor?(quotaKey: string, successorKey: string | null): Promise<QuotaCatalogEntryRow>;
|
|
3951
|
+
findFeature(featureKey: string): Promise<FeatureCatalogEntryRow | null>;
|
|
3952
|
+
findQuota(quotaKey: string): Promise<QuotaCatalogEntryRow | null>;
|
|
3953
|
+
setFeatureReview(featureKey: string, data: SetCatalogEntryReviewData): Promise<FeatureCatalogEntryRow>;
|
|
3954
|
+
setQuotaReview(quotaKey: string, data: SetCatalogEntryReviewData): Promise<QuotaCatalogEntryRow>;
|
|
3955
|
+
setFeatureI18n(featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
|
|
3956
|
+
setQuotaI18n(quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
|
|
3470
3957
|
/** Sets the editable base fields (default locale `label`/`description`). */
|
|
3471
|
-
setFeatureBase(
|
|
3472
|
-
setQuotaBase(
|
|
3958
|
+
setFeatureBase(featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
|
|
3959
|
+
setQuotaBase(quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
|
|
3473
3960
|
}
|
|
3474
3961
|
/**
|
|
3475
3962
|
* Adapter for `promotions`. **No versioning** — promotions are edited
|
|
@@ -3477,7 +3964,7 @@ interface CatalogEntryRepository {
|
|
|
3477
3964
|
* this against their `promotions` Prisma table.
|
|
3478
3965
|
*/
|
|
3479
3966
|
interface PromotionRepository {
|
|
3480
|
-
list(
|
|
3967
|
+
list(): Promise<PromotionRow[]>;
|
|
3481
3968
|
findById(id: string): Promise<PromotionRow | null>;
|
|
3482
3969
|
create(data: CreatePromotionData): Promise<PromotionRow>;
|
|
3483
3970
|
update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
|
|
@@ -3485,13 +3972,14 @@ interface PromotionRepository {
|
|
|
3485
3972
|
delete(id: string): Promise<void>;
|
|
3486
3973
|
}
|
|
3487
3974
|
/**
|
|
3488
|
-
* Adapter for `marketing_settings` — one row
|
|
3489
|
-
*
|
|
3490
|
-
*
|
|
3975
|
+
* Adapter for `marketing_settings` — at most one row, which a `CHECK` on the
|
|
3976
|
+
* canonical schema holds rather than convention. `get` returns `null` as long
|
|
3977
|
+
* as the SuperAdmin has saved nothing (then the full `availableLocales` pool
|
|
3978
|
+
* counts as active). `upsert` creates the row or replaces it.
|
|
3491
3979
|
*/
|
|
3492
3980
|
interface MarketingSettingsRepository {
|
|
3493
|
-
get(
|
|
3494
|
-
upsert(
|
|
3981
|
+
get(): Promise<MarketingSettingsRow | null>;
|
|
3982
|
+
upsert(data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
|
|
3495
3983
|
}
|
|
3496
3984
|
|
|
3497
3985
|
/**
|
|
@@ -3508,6 +3996,62 @@ interface CheckoutOfferRepository {
|
|
|
3508
3996
|
consume(id: string): Promise<CheckoutOfferRow>;
|
|
3509
3997
|
}
|
|
3510
3998
|
|
|
3999
|
+
/** Which changes to list. */
|
|
4000
|
+
interface SettingsChangeFilter {
|
|
4001
|
+
/** Only changes an operator has, or has not, acknowledged. Omitted: both. */
|
|
4002
|
+
acknowledged?: boolean;
|
|
4003
|
+
/** The most recently recorded ones. Omitted: every matching change. */
|
|
4004
|
+
limit?: number;
|
|
4005
|
+
}
|
|
4006
|
+
/**
|
|
4007
|
+
* Stores what the installation applied and what changed between two boots.
|
|
4008
|
+
*
|
|
4009
|
+
* `applied_settings` holds one row for the installation; `settings_changes`
|
|
4010
|
+
* holds one row per boot that found the fingerprint moved. An adapter
|
|
4011
|
+
* translates: it does not decide what a change is, and it does not read the
|
|
4012
|
+
* row back into anything that runs.
|
|
4013
|
+
*
|
|
4014
|
+
* Both writes are guarded on the fingerprint the caller read. Several replicas
|
|
4015
|
+
* of one installation start together after one edit of the file, each reads
|
|
4016
|
+
* the same record and each finds the same difference; the guard is what makes
|
|
4017
|
+
* one of them the boot that recorded it and the others boots that found it
|
|
4018
|
+
* recorded. Without it every replica would write the change and mail the
|
|
4019
|
+
* addresses, once per replica.
|
|
4020
|
+
*/
|
|
4021
|
+
interface AppliedSettingsPort {
|
|
4022
|
+
/** The record, or null before the first boot that could write one. */
|
|
4023
|
+
readApplied(): Promise<AppliedSettingsRecord | null>;
|
|
4024
|
+
/**
|
|
4025
|
+
* Replaces the installation's record — there is only ever the one row —
|
|
4026
|
+
* provided the stored record still carries `expectedFingerprint`: the
|
|
4027
|
+
* fingerprint the caller read, or `null` where it read no record. Returns
|
|
4028
|
+
* whether it did. `false` means the record moved between the caller's read
|
|
4029
|
+
* and this write, and nothing was written: another boot got there first.
|
|
4030
|
+
*/
|
|
4031
|
+
writeApplied(record: AppliedSettingsRecord, expectedFingerprint: string | null): Promise<boolean>;
|
|
4032
|
+
/**
|
|
4033
|
+
* Appends a change a boot noticed and replaces the record it supersedes, in
|
|
4034
|
+
* one step: both land, or neither does. Guarded like `writeApplied`, on the
|
|
4035
|
+
* fingerprint of the record the change was noticed against. Returns the
|
|
4036
|
+
* change as stored — the id is the adapter's to assign — or `null` where
|
|
4037
|
+
* the record had already moved on: another boot noticed first, and the
|
|
4038
|
+
* change is that boot's to report.
|
|
4039
|
+
*/
|
|
4040
|
+
recordChange(change: NewSettingsChange, record: AppliedSettingsRecord, expectedFingerprint: string): Promise<SettingsChangeRecord | null>;
|
|
4041
|
+
/**
|
|
4042
|
+
* Changes, the most recently recorded first: the order the record went
|
|
4043
|
+
* through them, which the database numbers at each write — not the order
|
|
4044
|
+
* of the moments they carry, which are the recording starts' clocks.
|
|
4045
|
+
*/
|
|
4046
|
+
listChanges(filter?: SettingsChangeFilter): Promise<SettingsChangeRecord[]>;
|
|
4047
|
+
/**
|
|
4048
|
+
* Marks a change as seen. Returns the updated record, or null where no
|
|
4049
|
+
* change has that id. A change already acknowledged keeps its first
|
|
4050
|
+
* acknowledgement — repeating the action changes nothing.
|
|
4051
|
+
*/
|
|
4052
|
+
acknowledgeChange(id: string, acknowledgedBy: string, acknowledgedAt: Date): Promise<SettingsChangeRecord | null>;
|
|
4053
|
+
}
|
|
4054
|
+
|
|
3511
4055
|
/** Class reference usable as a DI token (e.g. the consumer's `PrismaService`). */
|
|
3512
4056
|
type PersistenceClassRef = abstract new (...args: never[]) => unknown;
|
|
3513
4057
|
/** DI token forms a persistence bundle may reference in `inject`. */
|
|
@@ -3565,6 +4109,12 @@ interface SaaSiCatPersistenceCore {
|
|
|
3565
4109
|
auditQuery?: PersistenceProvider<AuditQueryPort>;
|
|
3566
4110
|
/** Aggregation for the admin stats dashboard. */
|
|
3567
4111
|
auditStats?: PersistenceProvider<AuditStatsPort>;
|
|
4112
|
+
/**
|
|
4113
|
+
* The record of the applied configuration (`SettingsModule`). Optional so
|
|
4114
|
+
* an adapter written before it existed keeps working; without it the
|
|
4115
|
+
* platform says once at boot that it is not recording.
|
|
4116
|
+
*/
|
|
4117
|
+
appliedSettings?: PersistenceProvider<AppliedSettingsPort>;
|
|
3568
4118
|
}
|
|
3569
4119
|
/** Repositories for the entitlement/contract loop (`EntitlementModule`). */
|
|
3570
4120
|
interface SaaSiCatPersistenceEntitlement {
|
|
@@ -3703,6 +4253,8 @@ declare const CATALOG_ERROR_CODES: {
|
|
|
3703
4253
|
readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
|
|
3704
4254
|
readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
|
|
3705
4255
|
readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
|
|
4256
|
+
readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
|
|
4257
|
+
readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
|
|
3706
4258
|
readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
|
|
3707
4259
|
readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
|
|
3708
4260
|
readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
|
|
@@ -3730,6 +4282,10 @@ declare const CATALOG_ERROR_CODES: {
|
|
|
3730
4282
|
readonly QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "QUOTA_NOT_IN_DISCOVERY_SNAPSHOT";
|
|
3731
4283
|
readonly DISCOVERY_STATUS_TRANSITION_INVALID: "DISCOVERY_STATUS_TRANSITION_INVALID";
|
|
3732
4284
|
readonly DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED";
|
|
4285
|
+
/** The uploaded document is not a plan catalog — unparseable, or not an object. */
|
|
4286
|
+
readonly PLAN_CATALOG_UNREADABLE: "PLAN_CATALOG_UNREADABLE";
|
|
4287
|
+
/** It parsed, and then failed the schema or a cross-field rule. */
|
|
4288
|
+
readonly PLAN_CATALOG_INVALID: "PLAN_CATALOG_INVALID";
|
|
3733
4289
|
};
|
|
3734
4290
|
type CatalogErrorCode = (typeof CATALOG_ERROR_CODES)[keyof typeof CATALOG_ERROR_CODES];
|
|
3735
4291
|
/** Bundle bookings on a tenant subscription. */
|
|
@@ -3743,6 +4299,8 @@ declare const BILLING_ERROR_CODES: {
|
|
|
3743
4299
|
readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
|
|
3744
4300
|
readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
|
|
3745
4301
|
readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
|
|
4302
|
+
readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
|
|
4303
|
+
readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
|
|
3746
4304
|
readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
|
|
3747
4305
|
readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
|
|
3748
4306
|
readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
|
|
@@ -3758,6 +4316,72 @@ declare const BILLING_ERROR_CODES: {
|
|
|
3758
4316
|
readonly PLAN_NOT_SELF_SERVICE: "PLAN_NOT_SELF_SERVICE";
|
|
3759
4317
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
3760
4318
|
readonly PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED";
|
|
4319
|
+
/**
|
|
4320
|
+
* The subscription moved between the read a request was decided on and the
|
|
4321
|
+
* write it attempted, so nothing was written. The caller reloads and asks
|
|
4322
|
+
* again.
|
|
4323
|
+
*/
|
|
4324
|
+
readonly SUBSCRIPTION_CHANGED: "SUBSCRIPTION_CHANGED";
|
|
4325
|
+
/**
|
|
4326
|
+
* The tenant has no subscription to act on.
|
|
4327
|
+
*
|
|
4328
|
+
* `SUBSCRIPTION_NOT_FOUND` states the same fact on the read routes. Both
|
|
4329
|
+
* are already on the wire and a code is renamed only deliberately, so both
|
|
4330
|
+
* are named here rather than one being dropped behind a consumer's back.
|
|
4331
|
+
*/
|
|
4332
|
+
readonly NO_SUBSCRIPTION: "NO_SUBSCRIPTION";
|
|
4333
|
+
/**
|
|
4334
|
+
* The cancellation date the reader was shown is no longer the one the rules
|
|
4335
|
+
* return, so the confirmation is refused rather than silently applied.
|
|
4336
|
+
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
4337
|
+
*/
|
|
4338
|
+
readonly CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED";
|
|
4339
|
+
/** The subscription has ended; its plan can no longer be changed. */
|
|
4340
|
+
readonly SUBSCRIPTION_ENDED: "SUBSCRIPTION_ENDED";
|
|
4341
|
+
/** An active special contract blocks self-service plan changes. */
|
|
4342
|
+
readonly PLAN_LOCKED: "PLAN_LOCKED";
|
|
4343
|
+
/** Current usage of one quota exceeds what the target plan allows. */
|
|
4344
|
+
readonly QUOTA_OVER_TARGET: "QUOTA_OVER_TARGET";
|
|
4345
|
+
/** The change drops features the tenant has today. */
|
|
4346
|
+
readonly FEATURE_LOST: "FEATURE_LOST";
|
|
4347
|
+
readonly FEATURES_LOST: "FEATURES_LOST";
|
|
4348
|
+
/** Target plan and cycle already match what is in place. */
|
|
4349
|
+
readonly NO_CHANGE: "NO_CHANGE";
|
|
4350
|
+
/** A shorter cycle cannot start inside the term already running. */
|
|
4351
|
+
readonly CYCLE_SHORTENS_AT_TERM_END: "CYCLE_SHORTENS_AT_TERM_END";
|
|
4352
|
+
/** A cancelled subscription cannot change its billing cycle. */
|
|
4353
|
+
readonly CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE";
|
|
4354
|
+
/**
|
|
4355
|
+
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
4356
|
+
*
|
|
4357
|
+
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
4358
|
+
* same rule about a booking that has not been made yet. The two need
|
|
4359
|
+
* different sentences: this one can name the day the obstacle lifts and
|
|
4360
|
+
* tell the reader to cancel the booking, and that advice is wrong for
|
|
4361
|
+
* someone who is only about to book. One template cannot serve both.
|
|
4362
|
+
*/
|
|
4363
|
+
readonly BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE";
|
|
4364
|
+
/**
|
|
4365
|
+
* Features of the previewed bundle are already covered by the plan or by
|
|
4366
|
+
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
4367
|
+
* the customer's decision, and the preview only has to say so first.
|
|
4368
|
+
*/
|
|
4369
|
+
readonly REDUNDANT_FEATURES: "REDUNDANT_FEATURES";
|
|
4370
|
+
/**
|
|
4371
|
+
* A booking's minimum term outlasts the period being cancelled, so the
|
|
4372
|
+
* cancellation takes effect at the end of the term, not of the period.
|
|
4373
|
+
*/
|
|
4374
|
+
readonly MINIMUM_TERM_BINDS: "MINIMUM_TERM_BINDS";
|
|
4375
|
+
/**
|
|
4376
|
+
* The previewed bundle requires features that neither the plan nor an
|
|
4377
|
+
* active booking provides.
|
|
4378
|
+
*
|
|
4379
|
+
* The same string is a `StrictModeWarningCode` in `bundle.types.ts`, where
|
|
4380
|
+
* it names the catalogue-authoring reading of the rule and travels with its
|
|
4381
|
+
* own message. This declaration is the booking preview's blocker, which a
|
|
4382
|
+
* tenant reads and therefore needs a shipped text for.
|
|
4383
|
+
*/
|
|
4384
|
+
readonly BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
3761
4385
|
readonly NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION";
|
|
3762
4386
|
readonly ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED";
|
|
3763
4387
|
readonly BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS";
|
|
@@ -3782,6 +4406,8 @@ declare const CONTRACT_ERROR_CODES: {
|
|
|
3782
4406
|
readonly SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED";
|
|
3783
4407
|
readonly SUBSCRIPTION_CONTRACT_INVALID_DATE: "SUBSCRIPTION_CONTRACT_INVALID_DATE";
|
|
3784
4408
|
readonly SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW";
|
|
4409
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH";
|
|
4410
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH";
|
|
3785
4411
|
readonly SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START";
|
|
3786
4412
|
readonly CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND";
|
|
3787
4413
|
readonly CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED";
|
|
@@ -3850,6 +4476,12 @@ declare const PROMO_ERROR_CODES: {
|
|
|
3850
4476
|
readonly PROMO_MAX_REDEMPTIONS_LOWERED: "PROMO_MAX_REDEMPTIONS_LOWERED";
|
|
3851
4477
|
};
|
|
3852
4478
|
type PromoErrorCode = (typeof PROMO_ERROR_CODES)[keyof typeof PROMO_ERROR_CODES];
|
|
4479
|
+
/** Codes of the settings record (`GET /admin/settings`, the acknowledgement). */
|
|
4480
|
+
declare const SETTINGS_ERROR_CODES: {
|
|
4481
|
+
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
4482
|
+
readonly SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND";
|
|
4483
|
+
};
|
|
4484
|
+
type SettingsErrorCode = (typeof SETTINGS_ERROR_CODES)[keyof typeof SETTINGS_ERROR_CODES];
|
|
3853
4485
|
/**
|
|
3854
4486
|
* Every exception code the platform emits, in one object.
|
|
3855
4487
|
*
|
|
@@ -3858,6 +4490,8 @@ type PromoErrorCode = (typeof PROMO_ERROR_CODES)[keyof typeof PROMO_ERROR_CODES]
|
|
|
3858
4490
|
* removing one may not.
|
|
3859
4491
|
*/
|
|
3860
4492
|
declare const PLATFORM_ERROR_CODES: {
|
|
4493
|
+
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
4494
|
+
readonly SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND";
|
|
3861
4495
|
readonly PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND";
|
|
3862
4496
|
readonly PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED";
|
|
3863
4497
|
readonly INVALID_REGISTRATION_STATE: "INVALID_REGISTRATION_STATE";
|
|
@@ -3882,6 +4516,8 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3882
4516
|
readonly SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED";
|
|
3883
4517
|
readonly SUBSCRIPTION_CONTRACT_INVALID_DATE: "SUBSCRIPTION_CONTRACT_INVALID_DATE";
|
|
3884
4518
|
readonly SUBSCRIPTION_CONTRACT_INVALID_WINDOW: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW";
|
|
4519
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_TAX_MISMATCH";
|
|
4520
|
+
readonly SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH: "SUBSCRIPTION_CONTRACT_LINE_ITEM_CURRENCY_MISMATCH";
|
|
3885
4521
|
readonly SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START";
|
|
3886
4522
|
readonly CHECKOUT_OFFER_NOT_FOUND: "CHECKOUT_OFFER_NOT_FOUND";
|
|
3887
4523
|
readonly CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED";
|
|
@@ -3899,6 +4535,8 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3899
4535
|
readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
|
|
3900
4536
|
readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
|
|
3901
4537
|
readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
|
|
4538
|
+
readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
|
|
4539
|
+
readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
|
|
3902
4540
|
readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
|
|
3903
4541
|
readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
|
|
3904
4542
|
readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
|
|
@@ -3914,6 +4552,72 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3914
4552
|
readonly PLAN_NOT_SELF_SERVICE: "PLAN_NOT_SELF_SERVICE";
|
|
3915
4553
|
/** Plan change refused. Carries `blockers[]` with their own codes. */
|
|
3916
4554
|
readonly PLAN_CHANGE_BLOCKED: "PLAN_CHANGE_BLOCKED";
|
|
4555
|
+
/**
|
|
4556
|
+
* The subscription moved between the read a request was decided on and the
|
|
4557
|
+
* write it attempted, so nothing was written. The caller reloads and asks
|
|
4558
|
+
* again.
|
|
4559
|
+
*/
|
|
4560
|
+
readonly SUBSCRIPTION_CHANGED: "SUBSCRIPTION_CHANGED";
|
|
4561
|
+
/**
|
|
4562
|
+
* The tenant has no subscription to act on.
|
|
4563
|
+
*
|
|
4564
|
+
* `SUBSCRIPTION_NOT_FOUND` states the same fact on the read routes. Both
|
|
4565
|
+
* are already on the wire and a code is renamed only deliberately, so both
|
|
4566
|
+
* are named here rather than one being dropped behind a consumer's back.
|
|
4567
|
+
*/
|
|
4568
|
+
readonly NO_SUBSCRIPTION: "NO_SUBSCRIPTION";
|
|
4569
|
+
/**
|
|
4570
|
+
* The cancellation date the reader was shown is no longer the one the rules
|
|
4571
|
+
* return, so the confirmation is refused rather than silently applied.
|
|
4572
|
+
* Carries the recomputed dates, so the page can re-ask instead of guessing.
|
|
4573
|
+
*/
|
|
4574
|
+
readonly CANCELLATION_TERMS_CHANGED: "CANCELLATION_TERMS_CHANGED";
|
|
4575
|
+
/** The subscription has ended; its plan can no longer be changed. */
|
|
4576
|
+
readonly SUBSCRIPTION_ENDED: "SUBSCRIPTION_ENDED";
|
|
4577
|
+
/** An active special contract blocks self-service plan changes. */
|
|
4578
|
+
readonly PLAN_LOCKED: "PLAN_LOCKED";
|
|
4579
|
+
/** Current usage of one quota exceeds what the target plan allows. */
|
|
4580
|
+
readonly QUOTA_OVER_TARGET: "QUOTA_OVER_TARGET";
|
|
4581
|
+
/** The change drops features the tenant has today. */
|
|
4582
|
+
readonly FEATURE_LOST: "FEATURE_LOST";
|
|
4583
|
+
readonly FEATURES_LOST: "FEATURES_LOST";
|
|
4584
|
+
/** Target plan and cycle already match what is in place. */
|
|
4585
|
+
readonly NO_CHANGE: "NO_CHANGE";
|
|
4586
|
+
/** A shorter cycle cannot start inside the term already running. */
|
|
4587
|
+
readonly CYCLE_SHORTENS_AT_TERM_END: "CYCLE_SHORTENS_AT_TERM_END";
|
|
4588
|
+
/** A cancelled subscription cannot change its billing cycle. */
|
|
4589
|
+
readonly CANCELLATION_LOCKS_THE_CYCLE: "CANCELLATION_LOCKS_THE_CYCLE";
|
|
4590
|
+
/**
|
|
4591
|
+
* A bundle the tenant already holds runs past the cycle they are moving to.
|
|
4592
|
+
*
|
|
4593
|
+
* Its own code rather than `BUNDLE_CYCLE_EXCEEDS_PLAN`, which states the
|
|
4594
|
+
* same rule about a booking that has not been made yet. The two need
|
|
4595
|
+
* different sentences: this one can name the day the obstacle lifts and
|
|
4596
|
+
* tell the reader to cancel the booking, and that advice is wrong for
|
|
4597
|
+
* someone who is only about to book. One template cannot serve both.
|
|
4598
|
+
*/
|
|
4599
|
+
readonly BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE";
|
|
4600
|
+
/**
|
|
4601
|
+
* Features of the previewed bundle are already covered by the plan or by
|
|
4602
|
+
* another booked bundle. A warning rather than a blocker: paying twice is
|
|
4603
|
+
* the customer's decision, and the preview only has to say so first.
|
|
4604
|
+
*/
|
|
4605
|
+
readonly REDUNDANT_FEATURES: "REDUNDANT_FEATURES";
|
|
4606
|
+
/**
|
|
4607
|
+
* A booking's minimum term outlasts the period being cancelled, so the
|
|
4608
|
+
* cancellation takes effect at the end of the term, not of the period.
|
|
4609
|
+
*/
|
|
4610
|
+
readonly MINIMUM_TERM_BINDS: "MINIMUM_TERM_BINDS";
|
|
4611
|
+
/**
|
|
4612
|
+
* The previewed bundle requires features that neither the plan nor an
|
|
4613
|
+
* active booking provides.
|
|
4614
|
+
*
|
|
4615
|
+
* The same string is a `StrictModeWarningCode` in `bundle.types.ts`, where
|
|
4616
|
+
* it names the catalogue-authoring reading of the rule and travels with its
|
|
4617
|
+
* own message. This declaration is the booking preview's blocker, which a
|
|
4618
|
+
* tenant reads and therefore needs a shipped text for.
|
|
4619
|
+
*/
|
|
4620
|
+
readonly BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED";
|
|
3917
4621
|
readonly NO_PENDING_PLAN_VERSION: "NO_PENDING_PLAN_VERSION";
|
|
3918
4622
|
readonly ONBOARDING_CREATE_FAILED: "ONBOARDING_CREATE_FAILED";
|
|
3919
4623
|
readonly BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS: "BUNDLE_PREVIEW_ARGUMENT_AMBIGUOUS";
|
|
@@ -3952,6 +4656,8 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3952
4656
|
readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
|
|
3953
4657
|
readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
|
|
3954
4658
|
readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
|
|
4659
|
+
readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
|
|
4660
|
+
readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
|
|
3955
4661
|
readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
|
|
3956
4662
|
readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
|
|
3957
4663
|
readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
|
|
@@ -3979,6 +4685,10 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
3979
4685
|
readonly QUOTA_NOT_IN_DISCOVERY_SNAPSHOT: "QUOTA_NOT_IN_DISCOVERY_SNAPSHOT";
|
|
3980
4686
|
readonly DISCOVERY_STATUS_TRANSITION_INVALID: "DISCOVERY_STATUS_TRANSITION_INVALID";
|
|
3981
4687
|
readonly DISCOVERY_NOT_INITIALIZED: "DISCOVERY_NOT_INITIALIZED";
|
|
4688
|
+
/** The uploaded document is not a plan catalog — unparseable, or not an object. */
|
|
4689
|
+
readonly PLAN_CATALOG_UNREADABLE: "PLAN_CATALOG_UNREADABLE";
|
|
4690
|
+
/** It parsed, and then failed the schema or a cross-field rule. */
|
|
4691
|
+
readonly PLAN_CATALOG_INVALID: "PLAN_CATALOG_INVALID";
|
|
3982
4692
|
readonly PROMO_CODE_NOT_FOUND: "PROMO_CODE_NOT_FOUND";
|
|
3983
4693
|
readonly PROMO_CODE_ALREADY_EXISTS: "PROMO_CODE_ALREADY_EXISTS";
|
|
3984
4694
|
readonly PROMO_CODE_HAS_REDEMPTIONS: "PROMO_CODE_HAS_REDEMPTIONS";
|
|
@@ -4021,7 +4731,7 @@ declare const PLATFORM_ERROR_CODES: {
|
|
|
4021
4731
|
/** Email already taken (mapped from `PlatformUserExistsError`). */
|
|
4022
4732
|
readonly EMAIL_EXISTS: "EMAIL_EXISTS";
|
|
4023
4733
|
};
|
|
4024
|
-
type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | RegistrationErrorCode;
|
|
4734
|
+
type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | RegistrationErrorCode | SettingsErrorCode;
|
|
4025
4735
|
/**
|
|
4026
4736
|
* Shape of a coded error response.
|
|
4027
4737
|
*
|
|
@@ -4766,6 +5476,101 @@ interface SetupConfirmMfaResponse {
|
|
|
4766
5476
|
ok: boolean;
|
|
4767
5477
|
}
|
|
4768
5478
|
|
|
5479
|
+
/** What the adapter's schema can actually answer about a version's dates. */
|
|
5480
|
+
interface PlanVersionMappingFields {
|
|
5481
|
+
/** `validFrom`/`validUntil` are maintained; otherwise both read as null. */
|
|
5482
|
+
validityWindows: boolean;
|
|
5483
|
+
/** `endsAt` exists; otherwise the field is left off the record entirely. */
|
|
5484
|
+
endsAt: boolean;
|
|
5485
|
+
}
|
|
5486
|
+
/** A `plans` row as either adapter reads it back. */
|
|
5487
|
+
interface CanonicalPlanRow {
|
|
5488
|
+
id: string;
|
|
5489
|
+
planKey: string;
|
|
5490
|
+
label: string;
|
|
5491
|
+
description: string | null;
|
|
5492
|
+
icon: string | null;
|
|
5493
|
+
sortOrder: number;
|
|
5494
|
+
createdAt: Date;
|
|
5495
|
+
updatedAt: Date;
|
|
5496
|
+
deletedAt: Date | null;
|
|
5497
|
+
}
|
|
5498
|
+
/** A `plan_versions` row as either adapter reads it back. */
|
|
5499
|
+
interface CanonicalPlanVersionRow {
|
|
5500
|
+
id: string;
|
|
5501
|
+
version: number;
|
|
5502
|
+
baseVersionId: string | null;
|
|
5503
|
+
features: unknown;
|
|
5504
|
+
quotas: unknown;
|
|
5505
|
+
monthlyNet: unknown;
|
|
5506
|
+
yearlyNet: unknown;
|
|
5507
|
+
marketed: boolean;
|
|
5508
|
+
publishedAt: Date | null;
|
|
5509
|
+
supersededAt: Date | null;
|
|
5510
|
+
publishedChanges: unknown;
|
|
5511
|
+
changeNote: string;
|
|
5512
|
+
nonRegressive: boolean;
|
|
5513
|
+
validFrom?: Date | null;
|
|
5514
|
+
validUntil?: Date | null;
|
|
5515
|
+
endsAt?: Date | null;
|
|
5516
|
+
createdByUserId: string | null;
|
|
5517
|
+
publishedByUserId: string | null;
|
|
5518
|
+
createdAt: Date;
|
|
5519
|
+
updatedAt: Date;
|
|
5520
|
+
}
|
|
5521
|
+
declare function toPlanRow(row: CanonicalPlanRow): PlanRow;
|
|
5522
|
+
/**
|
|
5523
|
+
* `planKey` is passed rather than read off the row: the canonical schema stores
|
|
5524
|
+
* the plan key in `planId`, but an adapter translating a consumer schema with a
|
|
5525
|
+
* real foreign key has to resolve it first, and only the adapter knows which
|
|
5526
|
+
* shape it is looking at.
|
|
5527
|
+
*/
|
|
5528
|
+
declare function toPlanVersionRow(row: CanonicalPlanVersionRow, planKey: string, fields: PlanVersionMappingFields): PlanVersionRow;
|
|
5529
|
+
|
|
5530
|
+
/** A `subscription_contracts` row as either adapter reads it back. */
|
|
5531
|
+
interface CanonicalContractRow {
|
|
5532
|
+
id: string;
|
|
5533
|
+
tenantId: string;
|
|
5534
|
+
status: string;
|
|
5535
|
+
effectiveFrom: Date;
|
|
5536
|
+
effectiveUntil: Date | null;
|
|
5537
|
+
originalOfferId: string | null;
|
|
5538
|
+
originalPlanVersionId: string | null;
|
|
5539
|
+
originalBundleVersionIds: unknown;
|
|
5540
|
+
entitlementSnapshot: unknown;
|
|
5541
|
+
priceSnapshot: unknown;
|
|
5542
|
+
promotionSnapshots: unknown;
|
|
5543
|
+
promoCodeSnapshots: unknown;
|
|
5544
|
+
termsSnapshot: unknown;
|
|
5545
|
+
createdAt: Date;
|
|
5546
|
+
updatedAt: Date;
|
|
5547
|
+
}
|
|
5548
|
+
/** A `contract_line_items` row as either adapter reads it back. */
|
|
5549
|
+
interface CanonicalContractLineItemRow {
|
|
5550
|
+
id: string;
|
|
5551
|
+
contractId: string;
|
|
5552
|
+
kind: string;
|
|
5553
|
+
sourceKey: string;
|
|
5554
|
+
sourceVersionId: string | null;
|
|
5555
|
+
titleSnapshot: string;
|
|
5556
|
+
descriptionSnapshot: string | null;
|
|
5557
|
+
quantity: number;
|
|
5558
|
+
unit: string | null;
|
|
5559
|
+
priceNet: unknown;
|
|
5560
|
+
priceGross: unknown;
|
|
5561
|
+
billingCycle: string;
|
|
5562
|
+
currency: string;
|
|
5563
|
+
taxRate: unknown;
|
|
5564
|
+
taxAmount: unknown;
|
|
5565
|
+
minimumTermUntil: Date | null;
|
|
5566
|
+
featuresSnapshot: unknown;
|
|
5567
|
+
quotaEffectsSnapshot: unknown;
|
|
5568
|
+
metadata: unknown;
|
|
5569
|
+
createdAt: Date;
|
|
5570
|
+
}
|
|
5571
|
+
declare function toSubscriptionContractRecord(row: CanonicalContractRow, lineItems: CanonicalContractLineItemRow[]): SubscriptionContractRecord;
|
|
5572
|
+
declare function toContractLineItemRecord(row: CanonicalContractLineItemRow): ContractLineItemRecord;
|
|
5573
|
+
|
|
4769
5574
|
/** Why a version is editable (for UI badges + audit logs). */
|
|
4770
5575
|
type VersionEditableReason = 'draft' | 'pre-active';
|
|
4771
5576
|
interface VersionEditability {
|
|
@@ -4780,6 +5585,30 @@ interface VersionEditability {
|
|
|
4780
5585
|
*/
|
|
4781
5586
|
declare function isVersionEditable(v: VersionedEntityBase, now?: Date): VersionEditability;
|
|
4782
5587
|
|
|
5588
|
+
/** The part of a plan card this rule reads and writes. */
|
|
5589
|
+
interface RecommendablePlan {
|
|
5590
|
+
planKey: string;
|
|
5591
|
+
highlight: boolean;
|
|
5592
|
+
}
|
|
5593
|
+
/**
|
|
5594
|
+
* Leaves the mark on at most one plan, in place, and returns the winner.
|
|
5595
|
+
*
|
|
5596
|
+
* `inRequestedLocale` holds the keys of the plans whose card was described in
|
|
5597
|
+
* the language that was asked for. Where a caller has no fallback to model —
|
|
5598
|
+
* the SuperAdmin edits one language at a time — passing every key, or none,
|
|
5599
|
+
* gives the same answer: the first plan in the list order wins.
|
|
5600
|
+
*
|
|
5601
|
+
* The list order is the caller's, and it is what the reader sees, so the
|
|
5602
|
+
* answer is the first recommended card on the page. Said exactly, because it
|
|
5603
|
+
* is easy to overstate: the tie-break inherits whatever order the caller
|
|
5604
|
+
* arranged, and where two plans are equal by every criterion it sorted on,
|
|
5605
|
+
* that order is the repository's. `PlanRepository.list` promises none, so an
|
|
5606
|
+
* adapter that returns rows in a different order each time would move the mark
|
|
5607
|
+
* between two otherwise indistinguishable plans. The shipped adapters order
|
|
5608
|
+
* totally.
|
|
5609
|
+
*/
|
|
5610
|
+
declare function keepOneRecommended<T extends RecommendablePlan>(plans: T[], inRequestedLocale: ReadonlySet<string>): T | null;
|
|
5611
|
+
|
|
4783
5612
|
declare const ERROR_MESSAGES_EN: Record<PlatformErrorCode, string>;
|
|
4784
5613
|
/** Values available for interpolation into a message template. */
|
|
4785
5614
|
type ErrorMessageParams = Record<string, unknown>;
|
|
@@ -4789,6 +5618,23 @@ type ErrorMessageParams = Record<string, unknown>;
|
|
|
4789
5618
|
* vanishing.
|
|
4790
5619
|
*/
|
|
4791
5620
|
declare function formatErrorMessage(template: string, params?: ErrorMessageParams): string;
|
|
5621
|
+
/**
|
|
5622
|
+
* An error body this function can read.
|
|
5623
|
+
*
|
|
5624
|
+
* `code` is widened past `PlatformErrorCode` on purpose. The function checks
|
|
5625
|
+
* `typeof body.code === 'string'` and resolves whatever it finds, and the
|
|
5626
|
+
* `overrides` parameter exists so a consumer can bring its own codes — one
|
|
5627
|
+
* consumer carries 98 of them against the platform's 135, overlapping in five.
|
|
5628
|
+
* A closed union here would reject exactly the case the parameter is for, and
|
|
5629
|
+
* the cast that works around it is one a reader has to be told is deliberate.
|
|
5630
|
+
*
|
|
5631
|
+
* `PlatformErrorBody` stays closed: a body the *platform* produces really does
|
|
5632
|
+
* carry a platform code. It is the reader that has to accept more. The same
|
|
5633
|
+
* shape appears in `SaLocale` next door, for the same reason.
|
|
5634
|
+
*/
|
|
5635
|
+
type ResolvableErrorBody = Omit<Partial<PlatformErrorBody>, 'code'> & Record<string, unknown> & {
|
|
5636
|
+
code?: PlatformErrorCode | (string & {});
|
|
5637
|
+
};
|
|
4792
5638
|
/**
|
|
4793
5639
|
* Turns an error body into display text.
|
|
4794
5640
|
*
|
|
@@ -4802,8 +5648,8 @@ declare function formatErrorMessage(template: string, params?: ErrorMessageParam
|
|
|
4802
5648
|
* second, so a template may name either without the value being duplicated on
|
|
4803
5649
|
* the wire.
|
|
4804
5650
|
*/
|
|
4805
|
-
declare function resolveErrorMessage(body:
|
|
5651
|
+
declare function resolveErrorMessage(body: ResolvableErrorBody, overrides?: Partial<Record<string, string>>, defaults?: Partial<Record<string, string>>): string;
|
|
4806
5652
|
|
|
4807
5653
|
declare const ERROR_MESSAGES_DE: Record<PlatformErrorCode, string>;
|
|
4808
5654
|
|
|
4809
|
-
export { AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractLineItemKind, type ContractLineItemRecord, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionFilter, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETUP_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SetCatalogEntryReviewData, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StrictModeWarning, type StrictModeWarningCode, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, coverageExcludingSelf, formatErrorMessage, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, missingRequiresFor, pickActivePromo, promoStatus, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, startOfUtcDay };
|
|
5655
|
+
export { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type AppliedSettingsPort, type AppliedSettingsRecord, type AppliedSettingsValues, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, BUNDLE_PRICE_LOOKUP_LIMIT, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOGUE_KEYS, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CancelSubscriptionInput, type CancelSubscriptionResult, type CancellationNoticePeriods, type CanonicalContractLineItemRow, type CanonicalContractRow, type CanonicalPlanRow, type CanonicalPlanVersionRow, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractLineItemKind, type ContractLineItemRecord, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type EmailPort, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, type NewSettingsChange, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogNotifications, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanCatalogTenantBilling, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMappingFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishBundleVersionMeta, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RecommendablePlan, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResolvableErrorBody, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETTINGS_ERROR_CODES, SETUP_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SelfServiceBlockedPlans, type SetCatalogEntryReviewData, type SettingsChangeFilter, type SettingsChangeRecord, type SettingsDifference, type SettingsErrorCode, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StoredBundleStem, type StrictModeWarning, type StrictModeWarningCode, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, bundleDraftDefaults, bundleStemDefaults, canonicalJson, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, coverageExcludingSelf, definedFields, diffSettings, formatErrorMessage, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, keepOneRecommended, missingRequiresFor, pickActivePromo, previousUtcDay, promoStatus, readQuotaRecord, readQuotaValue, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, settingsSubtreeOf, startOfUtcDay, toBundleStemRow, toContractLineItemRecord, toPlanRow, toPlanVersionRow, toSubscriptionContractRecord };
|