@saasicat/spec 1.0.0-rc.24 → 1.0.0-rc.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-api.openapi.yaml +888 -2
- package/package.json +1 -1
- package/prisma-fragments/05-bundle.prisma +4 -3
- package/prisma-fragments/08-subscription-contract.prisma +9 -0
- package/prisma-fragments/09-pending-registration.prisma +6 -0
- package/prisma-fragments/11-subscription-bundle.prisma +12 -2
- package/prisma-fragments/13-subscriber.prisma +82 -8
- package/prisma-fragments/15-subscriber-ledger.prisma +2 -1
- package/prisma-fragments/17-subscription-notice.prisma +5 -2
- package/prisma-fragments/19-bundle-version-retirement.prisma +44 -0
- package/prisma-fragments/20-subscription-invoice.prisma +145 -0
- package/prisma-fragments/21-feature-withdrawal.prisma +56 -0
- package/prisma-fragments/22-subscription-invoice-archive.prisma +34 -0
- package/prisma-fragments/README.md +26 -20
- package/schemas/admin-manifest.schema.json +2 -1
- package/schemas/plan-catalog.schema.json +57 -2
- package/schemas/subscriber-ledger.schema.json +4 -2
- package/sql/1.0-a-correction-carries-its-order.postgres.sql +101 -0
- package/sql/1.0-a-feature-is-withdrawn.postgres.sql +49 -0
- package/sql/1.0-a-sign-up-keeps-its-vat-id-check.postgres.sql +38 -0
- package/sql/1.0-a-subscriber-has-a-tax-origin.postgres.sql +113 -0
- package/sql/1.0-an-add-on-retirement-is-announced.postgres.sql +44 -0
- package/sql/1.0-an-add-on-switch-waits-for-its-term.postgres.sql +52 -0
- package/sql/1.0-an-invoice-document-is-archived.postgres.sql +123 -0
- package/sql/1.0-an-invoice-is-issued.postgres.sql +171 -0
- package/sql/constraints.postgres.sql +17 -0
- package/sql/reference-schema.postgres.sql +250 -1
package/package.json
CHANGED
|
@@ -90,9 +90,10 @@ model BundleVersion {
|
|
|
90
90
|
bundle Bundle @relation(fields: [bundleId], references: [id], onDelete: Cascade)
|
|
91
91
|
baseVersion BundleVersion? @relation("BundleVersionLineage", fields: [baseVersionId], references: [id])
|
|
92
92
|
derivedVersions BundleVersion[] @relation("BundleVersionLineage")
|
|
93
|
-
// Standalone add-on bookings pinned to this version
|
|
94
|
-
// 11-subscription-bundle.prisma).
|
|
95
|
-
subscriptionBundles
|
|
93
|
+
// Standalone add-on bookings pinned to this version, and the ones set to
|
|
94
|
+
// continue on it at the end of their term (see 11-subscription-bundle.prisma).
|
|
95
|
+
subscriptionBundles SubscriptionBundle[] @relation("SubscriptionBundleVersion")
|
|
96
|
+
subscriptionBundlesPending SubscriptionBundle[] @relation("SubscriptionBundlePendingVersion")
|
|
96
97
|
|
|
97
98
|
@@unique([bundleId, version])
|
|
98
99
|
@@index([bundleId, supersededAt])
|
|
@@ -47,6 +47,13 @@ model SubscriptionContract {
|
|
|
47
47
|
// what was agreed.
|
|
48
48
|
partiesMigrated Boolean @default(false)
|
|
49
49
|
|
|
50
|
+
// The tax treatment the installation's tax adapter decided when the
|
|
51
|
+
// contract was concluded: its kind, rate and note, with the adapter's name
|
|
52
|
+
// and version (ADR 0013). Null where no adapter was asked — an installation
|
|
53
|
+
// without one, a contract from before, or a path that does not ask one
|
|
54
|
+
// yet; each invoice then takes the adapter's answer when it is issued.
|
|
55
|
+
taxTreatment Json?
|
|
56
|
+
|
|
50
57
|
status SubscriptionContractStatus @default(active)
|
|
51
58
|
effectiveFrom DateTime
|
|
52
59
|
effectiveUntil DateTime?
|
|
@@ -66,6 +73,7 @@ model SubscriptionContract {
|
|
|
66
73
|
|
|
67
74
|
lineItems ContractLineItem[]
|
|
68
75
|
ledgerEntries SubscriberLedgerEntry[] // see 15-subscriber-ledger.prisma
|
|
76
|
+
invoices SubscriptionInvoice[] // see 20-subscription-invoice.prisma
|
|
69
77
|
|
|
70
78
|
createdAt DateTime @default(now())
|
|
71
79
|
updatedAt DateTime @updatedAt
|
|
@@ -124,6 +132,7 @@ model ContractLineItem {
|
|
|
124
132
|
|
|
125
133
|
contract SubscriptionContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
|
126
134
|
ledgerEntries SubscriberLedgerEntry[] // see 15-subscriber-ledger.prisma
|
|
135
|
+
invoiceLines SubscriptionInvoiceLine[] // see 20-subscription-invoice.prisma
|
|
127
136
|
|
|
128
137
|
@@index([contractId, kind])
|
|
129
138
|
@@index([sourceVersionId])
|
|
@@ -81,6 +81,12 @@ model PendingRegistration {
|
|
|
81
81
|
country String? // ISO 3166-1 alpha-2
|
|
82
82
|
vatId String?
|
|
83
83
|
taxNumber String?
|
|
84
|
+
// Whether the sign-up is a business, which a tax adapter decides from; and
|
|
85
|
+
// the check of `vatId` made in step 4, outside any transaction, where the
|
|
86
|
+
// adapter needed a validated number. The subscriber takes the check over
|
|
87
|
+
// when it is created, so its first contract is decided from it.
|
|
88
|
+
business Boolean?
|
|
89
|
+
vatIdCheck Json?
|
|
84
90
|
|
|
85
91
|
// The payment gateway's session for the payment method. A session id is
|
|
86
92
|
// unique only within the gateway account that opened it, so the account
|
|
@@ -46,6 +46,14 @@ model SubscriptionBundle {
|
|
|
46
46
|
currentPeriodStart DateTime?
|
|
47
47
|
currentPeriodEnd DateTime?
|
|
48
48
|
|
|
49
|
+
// A switch to a newer version of the add-on that takes something away,
|
|
50
|
+
// taken for the end of the booking's running term: the version it continues
|
|
51
|
+
// on, and from when. Both null, or both set — `constraints.postgres.sql`
|
|
52
|
+
// holds them to that. The platform's quarter-hourly run makes the switch at
|
|
53
|
+
// that moment and clears both.
|
|
54
|
+
pendingBundleVersionId String?
|
|
55
|
+
pendingVersionEffectiveAt DateTime?
|
|
56
|
+
|
|
49
57
|
// Cancellation: requested at `canceledAt`, effective at
|
|
50
58
|
// `canceledEffectiveAt` (period/term end). Both null = active booking.
|
|
51
59
|
canceledAt DateTime?
|
|
@@ -56,11 +64,13 @@ model SubscriptionBundle {
|
|
|
56
64
|
|
|
57
65
|
// The subscription this add-on is booked on. Restrict the BundleVersion so
|
|
58
66
|
// a booked version cannot be hard-deleted from the catalog.
|
|
59
|
-
subscription
|
|
60
|
-
bundleVersion
|
|
67
|
+
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
68
|
+
bundleVersion BundleVersion @relation("SubscriptionBundleVersion", fields: [bundleVersionId], references: [id], onDelete: Restrict)
|
|
69
|
+
pendingBundleVersion BundleVersion? @relation("SubscriptionBundlePendingVersion", fields: [pendingBundleVersionId], references: [id], onDelete: Restrict)
|
|
61
70
|
|
|
62
71
|
@@index([subscriptionId])
|
|
63
72
|
@@index([bundleVersionId])
|
|
64
73
|
@@index([canceledEffectiveAt])
|
|
74
|
+
@@index([pendingVersionEffectiveAt])
|
|
65
75
|
@@map("subscription_bundles")
|
|
66
76
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
|
-
// SaaS platform Prisma fragment: Subscriber, its tenants, its corrections
|
|
2
|
+
// SaaS platform Prisma fragment: Subscriber, its tenants, its corrections,
|
|
3
|
+
// the changes of its tax origin, the checks of its VAT id
|
|
3
4
|
// =============================================================================
|
|
4
5
|
//
|
|
5
6
|
// REFERENCE SNIPPET — see 01-subscription.prisma for conventions.
|
|
@@ -32,6 +33,25 @@ model Subscriber {
|
|
|
32
33
|
vatId String?
|
|
33
34
|
taxNumber String?
|
|
34
35
|
|
|
36
|
+
// The tax origin beside the country and the VAT id (ADR 0013): whether the
|
|
37
|
+
// subscriber is a business, as sign-up or the operator recorded it — null
|
|
38
|
+
// while nobody has said, and never derived from a tax identifier, since a
|
|
39
|
+
// business outside the European Union may have none. A change of the
|
|
40
|
+
// country, the business status or the VAT id is recorded in
|
|
41
|
+
// `SubscriberTaxOriginChange`.
|
|
42
|
+
business Boolean?
|
|
43
|
+
|
|
44
|
+
// Which recorded check of the VAT id counts now (`SubscriberVatIdCheck`):
|
|
45
|
+
// the latest completed one of the number the subscriber has. Null while
|
|
46
|
+
// none does — never checked, or the number corrected since. A reference
|
|
47
|
+
// without a relation: the check already points at its subscriber, and a
|
|
48
|
+
// key in both directions would tie each row to the other.
|
|
49
|
+
currentVatIdCheckId String?
|
|
50
|
+
// Since when the VAT id is held, set by the correction that wrote it; null
|
|
51
|
+
// while it is the one the subscriber was created with. A check completed
|
|
52
|
+
// before it does not count for the number.
|
|
53
|
+
vatIdSince DateTime?
|
|
54
|
+
|
|
35
55
|
// Contact details, which change at any time.
|
|
36
56
|
addressLine1 String?
|
|
37
57
|
addressLine2 String?
|
|
@@ -47,12 +67,15 @@ model Subscriber {
|
|
|
47
67
|
createdAt DateTime @default(now())
|
|
48
68
|
updatedAt DateTime @updatedAt
|
|
49
69
|
|
|
50
|
-
tenants
|
|
51
|
-
corrections
|
|
52
|
-
|
|
70
|
+
tenants SubscriberTenant[]
|
|
71
|
+
corrections SubscriberCorrection[]
|
|
72
|
+
taxOriginChanges SubscriberTaxOriginChange[]
|
|
73
|
+
vatIdChecks SubscriberVatIdCheck[]
|
|
74
|
+
contracts SubscriptionContract[]
|
|
53
75
|
paymentMethods SubscriberPaymentMethod[] // see 14-payments.prisma
|
|
54
76
|
paymentMethodSetups SubscriberPaymentMethodSetup[]
|
|
55
77
|
ledgerEntries SubscriberLedgerEntry[] // see 15-subscriber-ledger.prisma
|
|
78
|
+
invoices SubscriptionInvoice[] // see 20-subscription-invoice.prisma
|
|
56
79
|
|
|
57
80
|
@@map("subscribers")
|
|
58
81
|
}
|
|
@@ -76,11 +99,14 @@ model SubscriberTenant {
|
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
// A correction of a subscriber's legal identity: the values it replaced, the
|
|
79
|
-
// values it wrote, why,
|
|
80
|
-
//
|
|
81
|
-
// rewritten.
|
|
102
|
+
// values it wrote, why, who made it, and when the write held the subscriber's
|
|
103
|
+
// lock. Only the fields that moved are in the two JSON objects. Written in the
|
|
104
|
+
// same transaction as the change, and never rewritten.
|
|
82
105
|
model SubscriberCorrection {
|
|
83
106
|
id String @id @default(uuid())
|
|
107
|
+
// The order the corrections were written in, numbered by the database at
|
|
108
|
+
// the write, and the order the list is read in. Never set by the platform.
|
|
109
|
+
seq Int @unique @default(autoincrement())
|
|
84
110
|
subscriberId String
|
|
85
111
|
previous Json
|
|
86
112
|
corrected Json
|
|
@@ -90,6 +116,54 @@ model SubscriberCorrection {
|
|
|
90
116
|
|
|
91
117
|
subscriber Subscriber @relation(fields: [subscriberId], references: [id], onDelete: Restrict)
|
|
92
118
|
|
|
93
|
-
@@index([subscriberId,
|
|
119
|
+
@@index([subscriberId, seq])
|
|
94
120
|
@@map("subscriber_corrections")
|
|
95
121
|
}
|
|
122
|
+
|
|
123
|
+
// A change of a subscriber's tax origin — its country, whether it is a
|
|
124
|
+
// business, its VAT id — whichever way it arrived: a contact change, a
|
|
125
|
+
// correction of the legal identity, a change of the business status. The
|
|
126
|
+
// values it replaced, the values it wrote, who made it, and when the write
|
|
127
|
+
// held the subscriber's lock; only the fields that moved are in the two JSON
|
|
128
|
+
// objects. Written in the same transaction as the change, never rewritten, and
|
|
129
|
+
// applied from the subscriber's next invoice.
|
|
130
|
+
model SubscriberTaxOriginChange {
|
|
131
|
+
id String @id @default(uuid())
|
|
132
|
+
// The order the changes were written in, numbered by the database at the
|
|
133
|
+
// write, and the order the list is read in. Never set by the platform.
|
|
134
|
+
seq Int @unique @default(autoincrement())
|
|
135
|
+
subscriberId String
|
|
136
|
+
previous Json
|
|
137
|
+
changed Json
|
|
138
|
+
changedBy String
|
|
139
|
+
changedAt DateTime
|
|
140
|
+
// Why: the reason of the correction or of the change of business status
|
|
141
|
+
// that made it; null for a change of the country with the contact details.
|
|
142
|
+
reason String?
|
|
143
|
+
|
|
144
|
+
subscriber Subscriber @relation(fields: [subscriberId], references: [id], onDelete: Restrict)
|
|
145
|
+
|
|
146
|
+
@@index([subscriberId, seq])
|
|
147
|
+
@@map("subscriber_tax_origin_changes")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// A completed check of a subscriber's VAT id, as the service answered it: the
|
|
151
|
+
// number checked, when the answer came, whether it found the number valid, the
|
|
152
|
+
// service and its confirmation. Recorded for every check, whether it counts or
|
|
153
|
+
// not, and never rewritten: a reverse charge rests on the confirmation that
|
|
154
|
+
// held when the invoice was issued (ADR 0013).
|
|
155
|
+
model SubscriberVatIdCheck {
|
|
156
|
+
id String @id @default(uuid())
|
|
157
|
+
subscriberId String
|
|
158
|
+
vatId String
|
|
159
|
+
checkedAt DateTime
|
|
160
|
+
valid Boolean
|
|
161
|
+
service String
|
|
162
|
+
confirmation Json
|
|
163
|
+
recordedAt DateTime @default(now())
|
|
164
|
+
|
|
165
|
+
subscriber Subscriber @relation(fields: [subscriberId], references: [id], onDelete: Restrict)
|
|
166
|
+
|
|
167
|
+
@@index([subscriberId, checkedAt])
|
|
168
|
+
@@map("subscriber_vat_id_checks")
|
|
169
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
//
|
|
24
24
|
// `origin` and `source` are text holding a closed set, checked when a row is
|
|
25
25
|
// read (`toSubscriberChargeRecord` in @saasicat/core):
|
|
26
|
-
// origin: activation | renewal | planChange | bundleBooking | credit | correction
|
|
26
|
+
// origin: activation | renewal | planChange | bundleBooking | bundleChange | credit | correction
|
|
27
27
|
// source: plan | bundle | discount
|
|
28
28
|
//
|
|
29
29
|
// Optional: adopt it where `tenantBilling.chargeJournal` is configured, which
|
|
@@ -57,6 +57,7 @@ model SubscriberLedgerEntry {
|
|
|
57
57
|
subscriber Subscriber @relation(fields: [subscriberId], references: [id], onDelete: Restrict)
|
|
58
58
|
contract SubscriptionContract @relation(fields: [contractId], references: [id], onDelete: Restrict)
|
|
59
59
|
contractLineItem ContractLineItem @relation(fields: [contractLineItemId], references: [id], onDelete: Restrict)
|
|
60
|
+
invoiceLine SubscriptionInvoiceLine? // see 20-subscription-invoice.prisma
|
|
60
61
|
|
|
61
62
|
@@unique([subscriptionId, source, sourceRef, periodStart, origin])
|
|
62
63
|
@@index([subscriberId, bookedAt])
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
// `subscriptionId` are kept as values, not as keys.
|
|
22
22
|
//
|
|
23
23
|
// `kind` is text holding a closed set: version-offered, version-retired,
|
|
24
|
-
// version-retirement-reminder
|
|
24
|
+
// version-retirement-reminder, bundle-version-retired,
|
|
25
|
+
// bundle-version-retirement-reminder.
|
|
25
26
|
//
|
|
26
27
|
// Optional: adopt it where `tenantBilling.versionNotices` is configured, which
|
|
27
28
|
// refuses to start without a place to keep notices; a retirement needs it too.
|
|
@@ -37,7 +38,9 @@ model SubscriptionNotice {
|
|
|
37
38
|
// What the notice is about — for version-offered, the id of the version
|
|
38
39
|
// offered; for version-retired and version-retirement-reminder, the id of
|
|
39
40
|
// the version retired, so a subscription is told of a version's retirement
|
|
40
|
-
// once and reminded of it once
|
|
41
|
+
// once and reminded of it once; for bundle-version-retired and
|
|
42
|
+
// bundle-version-retirement-reminder, the id of the add-on version retired,
|
|
43
|
+
// so the subscription's booking of it is told once and reminded of it once.
|
|
41
44
|
subject String
|
|
42
45
|
// The notice as it was last handed to the application.
|
|
43
46
|
content Json
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaS platform Prisma fragment: BundleVersionRetirement
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — see 01-subscription.prisma for conventions.
|
|
6
|
+
//
|
|
7
|
+
// One row per add-on retirement an operator announces: an add-on version ended
|
|
8
|
+
// for the bookings already on it, with the version of the same add-on they
|
|
9
|
+
// continue on. Kept as evidence of what was announced, when and by whom, as
|
|
10
|
+
// `version_retirements` is for a plan version.
|
|
11
|
+
//
|
|
12
|
+
// What it means for each booking it reaches — the date it takes effect, the
|
|
13
|
+
// last day to cancel without the minimum term, and to whom and how that was
|
|
14
|
+
// told — is a `subscription_notices` row of kind bundle-version-retired,
|
|
15
|
+
// written in the same transaction: its subject is the retired add-on version,
|
|
16
|
+
// its content names this row's id as `retirementId`.
|
|
17
|
+
//
|
|
18
|
+
// The versions are kept as values, not as keys: the record outlives the
|
|
19
|
+
// catalogue entries it names, as the notices do (ADR 0012).
|
|
20
|
+
//
|
|
21
|
+
// Optional: adopt it where an operator may retire add-on versions
|
|
22
|
+
// (`tenantBilling.orderlyRetirement` in config/saas.yaml). Without it, retiring
|
|
23
|
+
// an add-on version is off; retiring a plan version is not affected.
|
|
24
|
+
//
|
|
25
|
+
// Contract: @saasicat/core (BundleVersionRetirementRepository).
|
|
26
|
+
|
|
27
|
+
model BundleVersionRetirement {
|
|
28
|
+
id String @id @default(uuid())
|
|
29
|
+
|
|
30
|
+
retiredBundleVersionId String
|
|
31
|
+
retiredBundleKey String
|
|
32
|
+
retiredVersion Int
|
|
33
|
+
|
|
34
|
+
replacementBundleVersionId String
|
|
35
|
+
replacementBundleKey String
|
|
36
|
+
replacementVersion Int
|
|
37
|
+
|
|
38
|
+
announcedAt DateTime @default(now())
|
|
39
|
+
// Who announced it — an actor tag, as the audit log writes it.
|
|
40
|
+
announcedBy String
|
|
41
|
+
|
|
42
|
+
@@index([retiredBundleVersionId])
|
|
43
|
+
@@map("bundle_version_retirements")
|
|
44
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaS platform Prisma fragment: SubscriptionInvoice
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — see 01-subscription.prisma for conventions.
|
|
6
|
+
//
|
|
7
|
+
// The invoices issued from the charge journal (15-subscriber-ledger.prisma):
|
|
8
|
+
// the charges a billing period opens with on one invoice, a charge that arises
|
|
9
|
+
// later on an invoice of its own. An invoice copies everything it states when
|
|
10
|
+
// it is issued — the issuer, the subscriber, the tax treatment the adapter
|
|
11
|
+
// decided, the tax per rate — so a later change leaves it as it was. Nothing
|
|
12
|
+
// updates or deletes an invoice; a correction is a cancellation invoice and its
|
|
13
|
+
// replacement.
|
|
14
|
+
//
|
|
15
|
+
// A line invoices one charge, and a charge stands on one line: the unique
|
|
16
|
+
// `chargeId` is what makes a second run issuing the same charges write
|
|
17
|
+
// nothing, however many run at the same time.
|
|
18
|
+
//
|
|
19
|
+
// The number range is the table `subscription_invoice_numbers`, one row per
|
|
20
|
+
// year holding the last number drawn. An invoice draws its number by raising
|
|
21
|
+
// that row in the transaction that writes the invoice, so an invoice that
|
|
22
|
+
// fails to be written leaves no gap, and two at the same time wait for each
|
|
23
|
+
// other rather than drawing the same number. One installation is the range of
|
|
24
|
+
// one issuer; the number is `<prefix>-<year>-<sequence>`.
|
|
25
|
+
//
|
|
26
|
+
// The days an invoice states — its issue date, its due date, the period its
|
|
27
|
+
// charges cover — are calendar days in the installation's time zone, kept as
|
|
28
|
+
// `date` columns; `issuedAt` is the instant it was issued.
|
|
29
|
+
//
|
|
30
|
+
// `origin` and `source` on a line are text holding the charge's closed sets,
|
|
31
|
+
// checked when a row is read (`toSubscriptionInvoiceRecord` in @saasicat/core).
|
|
32
|
+
//
|
|
33
|
+
// Optional: adopt it, all four models together, where
|
|
34
|
+
// `tenantBilling.chargeJournal.invoices` is configured, which needs the charge
|
|
35
|
+
// journal.
|
|
36
|
+
//
|
|
37
|
+
// Contract: @saasicat/core (SubscriptionInvoiceRepository).
|
|
38
|
+
|
|
39
|
+
model SubscriptionInvoice {
|
|
40
|
+
id String @id @default(uuid())
|
|
41
|
+
number String @unique
|
|
42
|
+
numberPrefix String
|
|
43
|
+
numberYear Int
|
|
44
|
+
numberSequence Int
|
|
45
|
+
|
|
46
|
+
tenantId String
|
|
47
|
+
subscriberId String
|
|
48
|
+
subscriptionId String
|
|
49
|
+
contractId String
|
|
50
|
+
|
|
51
|
+
issuedAt DateTime
|
|
52
|
+
issueDate DateTime @db.Date
|
|
53
|
+
dueDate DateTime @db.Date
|
|
54
|
+
servicePeriodFrom DateTime @db.Date
|
|
55
|
+
servicePeriodUntil DateTime @db.Date
|
|
56
|
+
|
|
57
|
+
currency String
|
|
58
|
+
// The parties as the invoice names them, copied when it was issued.
|
|
59
|
+
issuerParty Json
|
|
60
|
+
subscriberParty Json
|
|
61
|
+
// The footer lines config/saas.yaml named on the issue date (SC-PRIC-077).
|
|
62
|
+
footer Json @default("[]")
|
|
63
|
+
// The adapter's treatment, with its name and version, and the tax per rate.
|
|
64
|
+
taxTreatment Json
|
|
65
|
+
taxes Json
|
|
66
|
+
totalNet Decimal @db.Decimal(12, 2)
|
|
67
|
+
totalTax Decimal @db.Decimal(12, 2)
|
|
68
|
+
totalGross Decimal @db.Decimal(12, 2)
|
|
69
|
+
|
|
70
|
+
createdAt DateTime @default(now())
|
|
71
|
+
|
|
72
|
+
subscriber Subscriber @relation(fields: [subscriberId], references: [id], onDelete: Restrict)
|
|
73
|
+
contract SubscriptionContract @relation(fields: [contractId], references: [id], onDelete: Restrict)
|
|
74
|
+
lines SubscriptionInvoiceLine[]
|
|
75
|
+
document SubscriptionInvoiceDocument?
|
|
76
|
+
archive SubscriptionInvoiceArchive?
|
|
77
|
+
|
|
78
|
+
@@unique([numberYear, numberSequence])
|
|
79
|
+
@@index([subscriberId, issuedAt])
|
|
80
|
+
@@index([subscriptionId])
|
|
81
|
+
@@map("subscription_invoices")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
model SubscriptionInvoiceLine {
|
|
85
|
+
id String @id @default(uuid())
|
|
86
|
+
invoiceId String
|
|
87
|
+
position Int
|
|
88
|
+
chargeId String @unique
|
|
89
|
+
contractLineItemId String
|
|
90
|
+
|
|
91
|
+
title String
|
|
92
|
+
origin String
|
|
93
|
+
source String
|
|
94
|
+
|
|
95
|
+
periodFrom DateTime @db.Date
|
|
96
|
+
periodUntil DateTime @db.Date
|
|
97
|
+
|
|
98
|
+
amountNet Decimal @db.Decimal(10, 2)
|
|
99
|
+
taxRate Decimal @db.Decimal(5, 2)
|
|
100
|
+
|
|
101
|
+
invoice SubscriptionInvoice @relation(fields: [invoiceId], references: [id], onDelete: Restrict)
|
|
102
|
+
charge SubscriberLedgerEntry @relation(fields: [chargeId], references: [id], onDelete: Restrict)
|
|
103
|
+
contractLineItem ContractLineItem @relation(fields: [contractLineItemId], references: [id], onDelete: Restrict)
|
|
104
|
+
|
|
105
|
+
@@unique([invoiceId, position])
|
|
106
|
+
@@index([contractLineItemId])
|
|
107
|
+
@@map("subscription_invoice_lines")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Where an invoice's document stands. The checksum is recorded once, before
|
|
111
|
+
// the file is archived, and every later download is checked against it; the
|
|
112
|
+
// archive's confirmation and the delivery are set once each. A rendering with
|
|
113
|
+
// another checksum is refused rather than recorded over the first. A run claims
|
|
114
|
+
// the mail before it sends it, so of several instances one does.
|
|
115
|
+
model SubscriptionInvoiceDocument {
|
|
116
|
+
invoiceId String @id
|
|
117
|
+
// SHA-256 of the rendered file, 64 lower-case hex digits.
|
|
118
|
+
checksum String
|
|
119
|
+
mediaType String
|
|
120
|
+
fileName String
|
|
121
|
+
// In bytes.
|
|
122
|
+
size Int
|
|
123
|
+
renderedAt DateTime
|
|
124
|
+
// Null while the invoice waits for its document.
|
|
125
|
+
archivedAt DateTime?
|
|
126
|
+
// When the run mailing it took it on; null while no run holds it.
|
|
127
|
+
deliveryClaimedAt DateTime?
|
|
128
|
+
// Null until the invoice is handed to the mail.
|
|
129
|
+
deliveredAt DateTime?
|
|
130
|
+
deliveredTo String?
|
|
131
|
+
|
|
132
|
+
invoice SubscriptionInvoice @relation(fields: [invoiceId], references: [id], onDelete: Restrict)
|
|
133
|
+
|
|
134
|
+
@@index([archivedAt])
|
|
135
|
+
@@map("subscription_invoice_documents")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
model SubscriptionInvoiceNumber {
|
|
139
|
+
// The year the numbers count in; the range restarts at 1 each year.
|
|
140
|
+
year Int @id
|
|
141
|
+
// The last number drawn in that year.
|
|
142
|
+
last Int
|
|
143
|
+
|
|
144
|
+
@@map("subscription_invoice_numbers")
|
|
145
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaS platform Prisma fragment: FeatureWithdrawal
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — see 01-subscription.prisma for conventions.
|
|
6
|
+
//
|
|
7
|
+
// One row per feature withdrawn for a reason outside the platform: an external
|
|
8
|
+
// service the feature depends on stopped, or a law took it away or changed it.
|
|
9
|
+
// From `effectiveFrom` the platform grants the feature to nobody, until the
|
|
10
|
+
// operator lifts the withdrawal from `liftedFrom`. Kept as evidence of what was
|
|
11
|
+
// announced, why, when and by whom, and of the reductions the operator named.
|
|
12
|
+
//
|
|
13
|
+
// What it means for each subscription it reaches — which lines, what each is
|
|
14
|
+
// reduced by — is a `subscription_notices` row of kind feature-withdrawn whose
|
|
15
|
+
// subject is this row's id. Those are written once this row is: who it reaches
|
|
16
|
+
// is read after it is recorded, so a change concluded while it was being
|
|
17
|
+
// announced is reached, and `toldAt` is set in the transaction that writes
|
|
18
|
+
// them. A row whose `toldAt` is still null is told by the next run.
|
|
19
|
+
//
|
|
20
|
+
// `reductions` is a JSON list of `{ kind, key, billingCycle, amountNet }`: the
|
|
21
|
+
// net amount a plan or an add-on, by its key, is reduced by for one whole
|
|
22
|
+
// period of that rhythm without the feature.
|
|
23
|
+
//
|
|
24
|
+
// At most ONE withdrawal of a feature is not lifted at a time. Prisma cannot
|
|
25
|
+
// express a partial unique index; the one that holds it is in
|
|
26
|
+
// sql/constraints.postgres.sql, and it is what makes two operators announcing
|
|
27
|
+
// at the same moment land one withdrawal rather than two.
|
|
28
|
+
//
|
|
29
|
+
// Optional: adopt it where an operator may withdraw features. Without it,
|
|
30
|
+
// withdrawing a feature is off.
|
|
31
|
+
//
|
|
32
|
+
// Contract: @saasicat/core (FeatureWithdrawalRepository).
|
|
33
|
+
|
|
34
|
+
model FeatureWithdrawal {
|
|
35
|
+
id String @id @default(uuid())
|
|
36
|
+
featureKey String
|
|
37
|
+
// Why, in the operator's words, shown to the subscribers as written.
|
|
38
|
+
reason String
|
|
39
|
+
|
|
40
|
+
effectiveFrom DateTime
|
|
41
|
+
// Null until the operator lifts it.
|
|
42
|
+
liftedFrom DateTime?
|
|
43
|
+
|
|
44
|
+
reductions Json @default("[]")
|
|
45
|
+
|
|
46
|
+
announcedAt DateTime @default(now())
|
|
47
|
+
// Who announced it, and who lifted it — actor tags, as the audit log writes them.
|
|
48
|
+
announcedBy String
|
|
49
|
+
liftedAt DateTime?
|
|
50
|
+
liftedBy String?
|
|
51
|
+
// When the subscriptions it reaches were told; null until then.
|
|
52
|
+
toldAt DateTime?
|
|
53
|
+
|
|
54
|
+
@@index([featureKey])
|
|
55
|
+
@@map("feature_withdrawals")
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SaaSiCat platform Prisma fragment: SubscriptionInvoiceArchive
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// REFERENCE SNIPPET — see 01-subscription.prisma for conventions.
|
|
6
|
+
//
|
|
7
|
+
// The database archive of the invoices' documents (20-subscription-invoice.prisma):
|
|
8
|
+
// one file per invoice, under its invoice, with the checksum recorded when it
|
|
9
|
+
// was rendered. A row is written once and never changed: the migration
|
|
10
|
+
// `1.0-an-invoice-document-is-archived.postgres.sql` installs a trigger that
|
|
11
|
+
// refuses every UPDATE and DELETE, whoever asks. Keep it, with its database
|
|
12
|
+
// backups, as long as the law asks — in Germany 8 years from the end of the
|
|
13
|
+
// calendar year an invoice was issued in — and in the European Union.
|
|
14
|
+
//
|
|
15
|
+
// Optional: adopt it where the invoices' documents are kept in the database,
|
|
16
|
+
// as `persistence.entitlement.subscriptionInvoiceArchive` keeps them. An
|
|
17
|
+
// installation that archives elsewhere, such as object storage with a
|
|
18
|
+
// retention lock, binds its own `InvoiceArchivePort` instead.
|
|
19
|
+
//
|
|
20
|
+
// Contract: @saasicat/core (InvoiceArchivePort).
|
|
21
|
+
|
|
22
|
+
model SubscriptionInvoiceArchive {
|
|
23
|
+
invoiceId String @id
|
|
24
|
+
// SHA-256 of `content`, 64 lower-case hex digits.
|
|
25
|
+
checksum String
|
|
26
|
+
mediaType String
|
|
27
|
+
fileName String
|
|
28
|
+
content Bytes
|
|
29
|
+
archivedAt DateTime
|
|
30
|
+
|
|
31
|
+
invoice SubscriptionInvoice @relation(fields: [invoiceId], references: [id], onDelete: Restrict)
|
|
32
|
+
|
|
33
|
+
@@map("subscription_invoice_archive")
|
|
34
|
+
}
|
|
@@ -28,25 +28,30 @@ regenerated after fragment changes (`tests/reference-sql-drift.test.js`).
|
|
|
28
28
|
|
|
29
29
|
## Files
|
|
30
30
|
|
|
31
|
-
| File
|
|
32
|
-
|
|
|
33
|
-
| [`01-subscription.prisma`](01-subscription.prisma)
|
|
34
|
-
| [`02-promo-code.prisma`](02-promo-code.prisma)
|
|
35
|
-
| [`03-plan-versions.prisma`](03-plan-versions.prisma)
|
|
36
|
-
| [`04-audit-log.prisma`](04-audit-log.prisma)
|
|
37
|
-
| [`05-bundle.prisma`](05-bundle.prisma)
|
|
38
|
-
| [`06-catalog-entries.prisma`](06-catalog-entries.prisma)
|
|
39
|
-
| [`07-promotion.prisma`](07-promotion.prisma)
|
|
40
|
-
| [`08-subscription-contract.prisma`](08-subscription-contract.prisma)
|
|
41
|
-
| [`09-pending-registration.prisma`](09-pending-registration.prisma)
|
|
42
|
-
| [`10-super-admin.prisma`](10-super-admin.prisma)
|
|
43
|
-
| [`11-subscription-bundle.prisma`](11-subscription-bundle.prisma)
|
|
44
|
-
| [`12-applied-settings.prisma`](12-applied-settings.prisma)
|
|
45
|
-
| [`13-subscriber.prisma`](13-subscriber.prisma)
|
|
46
|
-
| [`14-payments.prisma`](14-payments.prisma)
|
|
47
|
-
| [`15-subscriber-ledger.prisma`](15-subscriber-ledger.prisma)
|
|
48
|
-
| [`16-maintenance-window.prisma`](16-maintenance-window.prisma)
|
|
49
|
-
| [`17-subscription-notice.prisma`](17-subscription-notice.prisma)
|
|
31
|
+
| File | Models |
|
|
32
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| [`01-subscription.prisma`](01-subscription.prisma) | `Subscription`, `CheckoutOffer` + Enums |
|
|
34
|
+
| [`02-promo-code.prisma`](02-promo-code.prisma) | `PromoCode`, `PromoCodeRedemption`, `PromoCodeHold`, `PromoCodeValidationLog` + Enums |
|
|
35
|
+
| [`03-plan-versions.prisma`](03-plan-versions.prisma) | `Plan`, `PlanVersion` |
|
|
36
|
+
| [`04-audit-log.prisma`](04-audit-log.prisma) | `AuditLog` |
|
|
37
|
+
| [`05-bundle.prisma`](05-bundle.prisma) | `Bundle`, `BundleVersion` |
|
|
38
|
+
| [`06-catalog-entries.prisma`](06-catalog-entries.prisma) | Capability, feature, quota and marketing catalog models |
|
|
39
|
+
| [`07-promotion.prisma`](07-promotion.prisma) | `Promotion` |
|
|
40
|
+
| [`08-subscription-contract.prisma`](08-subscription-contract.prisma) | `SubscriptionContract`, `ContractLineItem` |
|
|
41
|
+
| [`09-pending-registration.prisma`](09-pending-registration.prisma) | `PendingRegistration` + `RegistrationStatus` |
|
|
42
|
+
| [`10-super-admin.prisma`](10-super-admin.prisma) | `SuperAdminUser`, `SuperAdminMfa` |
|
|
43
|
+
| [`11-subscription-bundle.prisma`](11-subscription-bundle.prisma) | `SubscriptionBundle` |
|
|
44
|
+
| [`12-applied-settings.prisma`](12-applied-settings.prisma) | `AppliedSettings`, `SettingsChange` |
|
|
45
|
+
| [`13-subscriber.prisma`](13-subscriber.prisma) | `Subscriber`, `SubscriberTenant`, `SubscriberCorrection`, `SubscriberTaxOriginChange`, `SubscriberVatIdCheck` |
|
|
46
|
+
| [`14-payments.prisma`](14-payments.prisma) | `SubscriberPaymentMethod`, `SubscriberPaymentMethodSetup`, `PaymentEventLog` |
|
|
47
|
+
| [`15-subscriber-ledger.prisma`](15-subscriber-ledger.prisma) | `SubscriberLedgerEntry` |
|
|
48
|
+
| [`16-maintenance-window.prisma`](16-maintenance-window.prisma) | `MaintenanceWindow` |
|
|
49
|
+
| [`17-subscription-notice.prisma`](17-subscription-notice.prisma) | `SubscriptionNotice` |
|
|
50
|
+
| [`18-version-retirement.prisma`](18-version-retirement.prisma) | `VersionRetirement` |
|
|
51
|
+
| [`19-bundle-version-retirement.prisma`](19-bundle-version-retirement.prisma) | `BundleVersionRetirement` |
|
|
52
|
+
| [`20-subscription-invoice.prisma`](20-subscription-invoice.prisma) | `SubscriptionInvoice`, `SubscriptionInvoiceLine`, `SubscriptionInvoiceNumber`, `SubscriptionInvoiceDocument` |
|
|
53
|
+
| [`21-feature-withdrawal.prisma`](21-feature-withdrawal.prisma) | `FeatureWithdrawal` |
|
|
54
|
+
| [`22-subscription-invoice-archive.prisma`](22-subscription-invoice-archive.prisma) | `SubscriptionInvoiceArchive` |
|
|
50
55
|
|
|
51
56
|
## How the consumer uses the fragments
|
|
52
57
|
|
|
@@ -102,7 +107,8 @@ from ever being deleted.
|
|
|
102
107
|
`quota_catalog_entries`, `marketing_projections`, `marketing_settings`,
|
|
103
108
|
`promotions`, `subscription_contracts`, `contract_line_items`,
|
|
104
109
|
`super_admin_users`, `super_admin_mfa`, `subscription_bundles`, `subscribers`,
|
|
105
|
-
`subscriber_tenants`, `subscriber_corrections`, `
|
|
110
|
+
`subscriber_tenants`, `subscriber_corrections`, `subscriber_tax_origin_changes`,
|
|
111
|
+
`subscriber_vat_id_checks`, `subscriber_payment_methods`,
|
|
106
112
|
`subscriber_payment_method_setups`.
|
|
107
113
|
`PendingRegistration` and `PaymentEventLog` carry no `@@map` and keep Prisma's
|
|
108
114
|
default names, `"PendingRegistration"` and `"PaymentEventLog"`.
|
|
@@ -142,7 +142,8 @@
|
|
|
142
142
|
"platformEmail": { "$ref": "#/$defs/StandardPageDef" },
|
|
143
143
|
"platformEmailHistory": { "$ref": "#/$defs/StandardPageDef" },
|
|
144
144
|
"settings": { "$ref": "#/$defs/StandardPageDef" },
|
|
145
|
-
"maintenance": { "$ref": "#/$defs/StandardPageDef" }
|
|
145
|
+
"maintenance": { "$ref": "#/$defs/StandardPageDef" },
|
|
146
|
+
"featureWithdrawals": { "$ref": "#/$defs/StandardPageDef" }
|
|
146
147
|
}
|
|
147
148
|
},
|
|
148
149
|
"projectPages": {
|