@saasicat/core 1.0.0-rc.25 → 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/dist/index.cjs CHANGED
@@ -32,7 +32,13 @@ __export(index_exports, {
32
32
  ERROR_MESSAGES_DE: () => ERROR_MESSAGES_DE,
33
33
  ERROR_MESSAGES_EN: () => ERROR_MESSAGES_EN,
34
34
  FEATURE_NOT_LICENSED: () => FEATURE_NOT_LICENSED,
35
+ FEATURE_WITHDRAWAL_CAPABILITY: () => FEATURE_WITHDRAWAL_CAPABILITY,
36
+ FEATURE_WITHDRAWAL_REASON_MAX_LENGTH: () => FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
35
37
  ForeignPaymentMethodReferenceError: () => ForeignPaymentMethodReferenceError,
38
+ INVOICE_ARCHIVE_TRIGGERS: () => INVOICE_ARCHIVE_TRIGGERS,
39
+ INVOICE_DOCUMENT_CAPABILITY: () => INVOICE_DOCUMENT_CAPABILITY,
40
+ INVOICE_ERROR_CODES: () => INVOICE_ERROR_CODES,
41
+ INVOICE_SEQUENCE_DIGITS: () => INVOICE_SEQUENCE_DIGITS,
36
42
  LEGAL_IDENTITY_FIELDS: () => LEGAL_IDENTITY_FIELDS,
37
43
  MAINTENANCE_CAPABILITY: () => MAINTENANCE_CAPABILITY,
38
44
  MAINTENANCE_ERROR_CODES: () => MAINTENANCE_ERROR_CODES,
@@ -67,6 +73,8 @@ __export(index_exports, {
67
73
  SUBSCRIBER_ACCOUNT_CAPABILITY: () => SUBSCRIBER_ACCOUNT_CAPABILITY,
68
74
  SUBSCRIBER_ATTENTION_CAPABILITY: () => SUBSCRIBER_ATTENTION_CAPABILITY,
69
75
  SUBSCRIBER_ATTENTION_PAGE_SIZE: () => SUBSCRIBER_ATTENTION_PAGE_SIZE,
76
+ SUBSCRIBER_CHARGE_ORIGINS: () => SUBSCRIBER_CHARGE_ORIGINS,
77
+ SUBSCRIBER_CHARGE_SOURCES: () => SUBSCRIBER_CHARGE_SOURCES,
70
78
  SUBSCRIBER_CORRECTION_CAPABILITY: () => SUBSCRIBER_CORRECTION_CAPABILITY,
71
79
  SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
72
80
  SUBSCRIBER_INVOICE_ADDRESS_FIELDS: () => SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
@@ -75,7 +83,9 @@ __export(index_exports, {
75
83
  TAX_ERROR_CODES: () => TAX_ERROR_CODES,
76
84
  TAX_TREATMENT_KINDS: () => TAX_TREATMENT_KINDS,
77
85
  VERSION_RETIREMENT_CAPABILITY: () => VERSION_RETIREMENT_CAPABILITY,
86
+ addDaysToDay: () => addDaysToDay,
78
87
  applyPromo: () => applyPromo,
88
+ asXmlReadsIt: () => asXmlReadsIt,
79
89
  assertPersistenceCapabilities: () => assertPersistenceCapabilities,
80
90
  buildActivePlanVersionWhere: () => buildActivePlanVersionWhere,
81
91
  buildActiveVersionWhere: () => buildActiveVersionWhere,
@@ -99,14 +109,28 @@ __export(index_exports, {
99
109
  contractPartiesOf: () => contractPartiesOf,
100
110
  coverageExcludingSelf: () => coverageExcludingSelf,
101
111
  customLimitsReader: () => customLimitsReader,
112
+ dayInZone: () => dayInZone,
102
113
  definedFields: () => definedFields,
103
114
  diffSettings: () => diffSettings,
115
+ draftSubscriptionInvoice: () => draftSubscriptionInvoice,
116
+ endedAtOnceSubjectOf: () => endedAtOnceSubjectOf,
117
+ featureWithdrawalRowOf: () => featureWithdrawalRowOf,
118
+ featuresWithdrawnAt: () => featuresWithdrawnAt,
104
119
  formatCustomerNumber: () => formatCustomerNumber,
105
120
  formatErrorMessage: () => formatErrorMessage,
121
+ formatInvoiceNumber: () => formatInvoiceNumber,
106
122
  grossFromNet: () => grossFromNet,
123
+ holdsOnlyXmlCharacters: () => holdsOnlyXmlCharacters,
107
124
  identityCorrectionDelta: () => identityCorrectionDelta,
125
+ invoiceArchiveProtectionProblem: () => invoiceArchiveProtectionProblem,
126
+ invoiceContentDraftOf: () => invoiceContentDraftOf,
127
+ invoiceFooterOf: () => invoiceFooterOf,
128
+ invoiceGroupsOf: () => invoiceGroupsOf,
129
+ invoiceIssuerOf: () => invoiceIssuerOf,
108
130
  isBundleRedundant: () => isBundleRedundant,
131
+ isFeatureWithdrawnAt: () => isFeatureWithdrawnAt,
109
132
  isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
133
+ isInvoiceDocumentChecksum: () => isInvoiceDocumentChecksum,
110
134
  isMaintenanceAnnouncementLapsed: () => isMaintenanceAnnouncementLapsed,
111
135
  isMaintenanceOverrun: () => isMaintenanceOverrun,
112
136
  isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
@@ -117,6 +141,7 @@ __export(index_exports, {
117
141
  issuerIdentityOf: () => issuerIdentityOf,
118
142
  keepOneRecommended: () => keepOneRecommended,
119
143
  keepsVatIdCheck: () => keepsVatIdCheck,
144
+ lastDayInZone: () => lastDayInZone,
120
145
  maintenanceRetryAfterSeconds: () => maintenanceRetryAfterSeconds,
121
146
  maintenanceStatusOf: () => maintenanceStatusOf,
122
147
  maintenanceWindowStatusOf: () => maintenanceWindowStatusOf,
@@ -126,6 +151,7 @@ __export(index_exports, {
126
151
  missingRequiresFor: () => missingRequiresFor,
127
152
  movedIdentityFields: () => movedIdentityFields,
128
153
  netFromGross: () => netFromGross,
154
+ nextWithdrawalChange: () => nextWithdrawalChange,
129
155
  noActivePlanVersion: () => noActivePlanVersion,
130
156
  parseZonedInstant: () => parseZonedInstant,
131
157
  percentOf: () => percentOf,
@@ -162,15 +188,22 @@ __export(index_exports, {
162
188
  subscriptionBundleGone: () => subscriptionBundleGone,
163
189
  subscriptionContractGone: () => subscriptionContractGone,
164
190
  subscriptionGone: () => subscriptionGone,
191
+ subscriptionInvoiceChargeInvoiced: () => subscriptionInvoiceChargeInvoiced,
192
+ subscriptionInvoiceColumns: () => subscriptionInvoiceColumns,
193
+ subscriptionInvoiceDocumentChanged: () => subscriptionInvoiceDocumentChanged,
194
+ subscriptionInvoiceLineColumns: () => subscriptionInvoiceLineColumns,
165
195
  sumToCents: () => sumToCents,
166
196
  taxOriginOf: () => taxOriginOf,
167
197
  taxOriginWrite: () => taxOriginWrite,
198
+ taxPerRate: () => taxPerRate,
168
199
  taxTreatmentToJson: () => taxTreatmentToJson,
200
+ toArchivedInvoiceDocument: () => toArchivedInvoiceDocument,
169
201
  toBundleStemRow: () => toBundleStemRow,
170
202
  toBundleVersionRetirementRecord: () => toBundleVersionRetirementRecord,
171
203
  toCents: () => toCents,
172
204
  toContractLineItemRecord: () => toContractLineItemRecord,
173
205
  toDecimalString: () => toDecimalString,
206
+ toFeatureWithdrawalRecord: () => toFeatureWithdrawalRecord,
174
207
  toMaintenanceWindowRecord: () => toMaintenanceWindowRecord,
175
208
  toPlanRow: () => toPlanRow,
176
209
  toPlanVersionRow: () => toPlanVersionRow,
@@ -183,14 +216,20 @@ __export(index_exports, {
183
216
  toSubscriberVatIdCheckRecord: () => toSubscriberVatIdCheckRecord,
184
217
  toSubscriptionBundleRecord: () => toSubscriptionBundleRecord,
185
218
  toSubscriptionContractRecord: () => toSubscriptionContractRecord,
219
+ toSubscriptionInvoiceDocumentRecord: () => toSubscriptionInvoiceDocumentRecord,
220
+ toSubscriptionInvoiceRecord: () => toSubscriptionInvoiceRecord,
186
221
  toSubscriptionNoticeRecord: () => toSubscriptionNoticeRecord,
187
222
  toTaxOriginValues: () => toTaxOriginValues,
188
223
  toTaxTreatment: () => toTaxTreatment,
189
224
  toVersionRetirementRecord: () => toVersionRetirementRecord,
225
+ utcMidnightOf: () => utcMidnightOf,
226
+ vatCategoryOf: () => vatCategoryOf,
190
227
  vatIdCheckFromStore: () => vatIdCheckFromStore,
191
228
  versionOnSale: () => versionOnSale,
192
229
  versionOnSaleOrNext: () => versionOnSaleOrNext,
193
- versionSale: () => versionSale
230
+ versionSale: () => versionSale,
231
+ withdrawnFeaturesOf: () => withdrawnFeaturesOf,
232
+ yearOfDay: () => yearOfDay
194
233
  });
195
234
  module.exports = __toCommonJS(index_exports);
196
235
 
@@ -353,6 +392,22 @@ function computeIncludedVat(gross, vatRate) {
353
392
  const rate = exactDecimal(vatRate);
354
393
  return amountOf(cents(exactDecimal(gross), rate, sum(HUNDRED, rate)));
355
394
  }
395
+ function taxPerRate(lines) {
396
+ const nets = /* @__PURE__ */ new Map();
397
+ for (const line of lines) {
398
+ const key = String(line.rate);
399
+ const entry = nets.get(key) ?? { rate: line.rate, terms: [] };
400
+ entry.terms.push(line.net);
401
+ nets.set(key, entry);
402
+ }
403
+ const rates = [...nets.values()].sort((a, b) => a.rate - b.rate).map(({ rate, terms }) => {
404
+ const net2 = sumToCents(...terms);
405
+ return { rate, net: net2, tax: percentOf(net2, rate) };
406
+ });
407
+ const net = sumToCents(...rates.map((entry) => entry.net));
408
+ const tax = sumToCents(...rates.map((entry) => entry.tax));
409
+ return { rates, net, tax, gross: sumToCents(net, tax) };
410
+ }
356
411
 
357
412
  // src/promotion.types.ts
358
413
  function promoStatus(promo, today = /* @__PURE__ */ new Date()) {
@@ -603,7 +658,10 @@ var KINDS = {
603
658
  "version-retirement-reminder": true,
604
659
  "bundle-version-retired": true,
605
660
  "bundle-version-retirement-reminder": true,
606
- "bundle-version-offered": true
661
+ "bundle-version-offered": true,
662
+ "feature-withdrawn": true,
663
+ "feature-withdrawal-lifted": true,
664
+ "ended-at-once": true
607
665
  };
608
666
  function toSubscriptionNoticeRecord(row) {
609
667
  return {
@@ -734,6 +792,106 @@ function toBundleVersionRetirementRecord(row) {
734
792
  };
735
793
  }
736
794
 
795
+ // src/feature-withdrawal.types.ts
796
+ var FEATURE_WITHDRAWAL_REASON_MAX_LENGTH = 1e3;
797
+ function isFeatureWithdrawnAt(withdrawal, at) {
798
+ return withdrawal.effectiveFrom <= at && (withdrawal.liftedFrom === null || at < withdrawal.liftedFrom);
799
+ }
800
+ function featuresWithdrawnAt(withdrawals, at) {
801
+ return new Set(
802
+ withdrawals.filter((withdrawal) => isFeatureWithdrawnAt(withdrawal, at)).map((withdrawal) => withdrawal.featureKey)
803
+ );
804
+ }
805
+ function nextWithdrawalChange(withdrawals, at) {
806
+ let next = null;
807
+ for (const { effectiveFrom, liftedFrom } of withdrawals) {
808
+ for (const moment of [effectiveFrom, liftedFrom]) {
809
+ if (moment === null || moment <= at) continue;
810
+ if (next === null || moment < next) next = moment;
811
+ }
812
+ }
813
+ return next;
814
+ }
815
+ var FEATURE_WITHDRAWAL_CAPABILITY = "features.withdraw";
816
+ function featureWithdrawalRowOf(record) {
817
+ return {
818
+ id: record.id,
819
+ featureKey: record.featureKey,
820
+ reason: record.reason,
821
+ effectiveFrom: record.effectiveFrom.toISOString(),
822
+ liftedFrom: record.liftedFrom?.toISOString() ?? null,
823
+ reductions: record.reductions,
824
+ announcedAt: record.announcedAt.toISOString(),
825
+ announcedBy: record.announcedBy,
826
+ liftedAt: record.liftedAt?.toISOString() ?? null,
827
+ liftedBy: record.liftedBy,
828
+ toldAt: record.toldAt?.toISOString() ?? null
829
+ };
830
+ }
831
+ function withdrawnFeaturesOf(withdrawals, now) {
832
+ return withdrawals.filter(
833
+ ({ effectiveFrom, liftedFrom }) => liftedFrom === null || liftedFrom > now && effectiveFrom < liftedFrom
834
+ ).sort((a, b) => a.effectiveFrom.getTime() - b.effectiveFrom.getTime()).map((withdrawal) => ({
835
+ featureKey: withdrawal.featureKey,
836
+ reason: withdrawal.reason,
837
+ effectiveFrom: withdrawal.effectiveFrom.toISOString(),
838
+ liftedFrom: withdrawal.liftedFrom?.toISOString() ?? null
839
+ }));
840
+ }
841
+ function endedAtOnceSubjectOf(subscriptionOrBookingId, at) {
842
+ return `${subscriptionOrBookingId}@${at.toISOString()}`;
843
+ }
844
+
845
+ // src/feature-withdrawal-mapping.ts
846
+ var TABLE2 = "feature_withdrawals";
847
+ var KINDS2 = { plan: true, bundle: true };
848
+ var CYCLES = { MONTHLY: true, YEARLY: true };
849
+ function toFeatureWithdrawalRecord(row) {
850
+ return {
851
+ id: row.id,
852
+ featureKey: row.featureKey,
853
+ reason: row.reason,
854
+ effectiveFrom: row.effectiveFrom,
855
+ liftedFrom: row.liftedFrom,
856
+ reductions: reductionsOf(row),
857
+ announcedAt: row.announcedAt,
858
+ announcedBy: row.announcedBy,
859
+ liftedAt: row.liftedAt,
860
+ liftedBy: row.liftedBy,
861
+ toldAt: row.toldAt
862
+ };
863
+ }
864
+ function reductionsOf(row) {
865
+ if (!Array.isArray(row.reductions)) {
866
+ throw new Error(`${TABLE2} row '${row.id}' holds reductions that are not a list.`);
867
+ }
868
+ return row.reductions.map((entry, index) => {
869
+ const column = `reductions[${index}]`;
870
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
871
+ throw new Error(`${TABLE2} row '${row.id}' holds ${column} that is not an object.`);
872
+ }
873
+ const { kind, key, billingCycle, amountNet } = entry;
874
+ if (typeof kind !== "string" || typeof key !== "string" || typeof billingCycle !== "string" || typeof amountNet !== "number" || !Number.isFinite(amountNet)) {
875
+ throw new Error(
876
+ `${TABLE2} row '${row.id}' holds ${column} without a kind, a key, a billing cycle and a finite amount.`
877
+ );
878
+ }
879
+ const source = { table: TABLE2, id: row.id };
880
+ return {
881
+ kind: oneOf(Object.keys(KINDS2), kind, {
882
+ ...source,
883
+ column: `${column}.kind`
884
+ }),
885
+ key,
886
+ billingCycle: oneOf(Object.keys(CYCLES), billingCycle, {
887
+ ...source,
888
+ column: `${column}.billingCycle`
889
+ }),
890
+ amountNet
891
+ };
892
+ });
893
+ }
894
+
737
895
  // src/scheduled-change-after-write.ts
738
896
  var NO_SCHEDULED_CHANGE = {
739
897
  pendingPlan: null,
@@ -870,6 +1028,37 @@ function parseZonedInstant(text) {
870
1028
  return Number.isNaN(moment.getTime()) ? null : moment;
871
1029
  }
872
1030
 
1031
+ // src/zoned-day.ts
1032
+ function dayInZone(at, timeZone) {
1033
+ if (typeof timeZone !== "string" || timeZone === "") {
1034
+ throw new RangeError("A day is read in a named time zone.");
1035
+ }
1036
+ const parts = new Intl.DateTimeFormat("en-US", {
1037
+ timeZone,
1038
+ year: "numeric",
1039
+ month: "2-digit",
1040
+ day: "2-digit"
1041
+ }).formatToParts(at);
1042
+ const part = (type) => parts.find((candidate) => candidate.type === type)?.value;
1043
+ return `${part("year")}-${part("month")}-${part("day")}`;
1044
+ }
1045
+ function lastDayInZone(period, timeZone) {
1046
+ if (!(period.until.getTime() > period.from.getTime())) {
1047
+ throw new RangeError("A period ends after it begins.");
1048
+ }
1049
+ return dayInZone(new Date(period.until.getTime() - 1), timeZone);
1050
+ }
1051
+ function addDaysToDay(day, days) {
1052
+ if (!Number.isInteger(days)) throw new RangeError("Days are counted in whole days.");
1053
+ const [year, month, date] = day.split("-").map(Number);
1054
+ if (!year || !month || !date) throw new RangeError(`${day} is not a day.`);
1055
+ const moved = new Date(Date.UTC(year, month - 1, date + days));
1056
+ return moved.toISOString().slice(0, 10);
1057
+ }
1058
+ function yearOfDay(day) {
1059
+ return Number(day.slice(0, 4));
1060
+ }
1061
+
873
1062
  // src/settings-subtree.ts
874
1063
  var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
875
1064
  "schemaVersion",
@@ -1327,6 +1516,80 @@ var BILLING_ERROR_CODES = {
1327
1516
  * show it instead of guessing.
1328
1517
  */
1329
1518
  BUNDLE_VERSION_OFFER_CHANGED: "BUNDLE_VERSION_OFFER_CHANGED",
1519
+ // ── withdrawing a feature for an external reason ──
1520
+ /**
1521
+ * Withdrawing a feature is not offered: the installation's stores cannot
1522
+ * list everybody a withdrawal would reach.
1523
+ */
1524
+ FEATURE_WITHDRAWAL_UNAVAILABLE: "FEATURE_WITHDRAWAL_UNAVAILABLE",
1525
+ /** The catalogue knows no feature of that key, so there is nothing to withdraw. Carries `featureKey`. */
1526
+ FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "FEATURE_WITHDRAWAL_FEATURE_UNKNOWN",
1527
+ /**
1528
+ * The date names a moment before now. A feature is withdrawn from the
1529
+ * moment it is announced or from a later date: the days before were
1530
+ * delivered.
1531
+ */
1532
+ FEATURE_WITHDRAWAL_DATE_IN_PAST: "FEATURE_WITHDRAWAL_DATE_IN_PAST",
1533
+ /** A withdrawal of the feature is not lifted yet; one at a time. Carries `featureKey`. */
1534
+ FEATURE_WITHDRAWAL_OPEN: "FEATURE_WITHDRAWAL_OPEN",
1535
+ /**
1536
+ * A withdrawal of the feature is lifted only from a date after the one
1537
+ * named, so the two would overlap. Carries `featureKey` and the `date` a
1538
+ * new one may begin on.
1539
+ */
1540
+ FEATURE_WITHDRAWAL_OVERLAPS: "FEATURE_WITHDRAWAL_OVERLAPS",
1541
+ /**
1542
+ * A reduction names a plan or an add-on, in a rhythm, that no line the
1543
+ * withdrawal reaches is. Carries `kind`, `key` and `billingCycle`.
1544
+ */
1545
+ FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED",
1546
+ /** A reduction is named twice. Carries `kind`, `key` and `billingCycle`. */
1547
+ FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE",
1548
+ /**
1549
+ * A reduction is more than the lowest price among the lines it reduces:
1550
+ * a price is reduced to nothing at most. Carries `key`, `billingCycle` and
1551
+ * that `price`.
1552
+ */
1553
+ FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE",
1554
+ /**
1555
+ * The subscriptions the withdrawal reaches changed since the preview the
1556
+ * operator looked at. Nothing was announced; carries the current `preview`.
1557
+ */
1558
+ FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "FEATURE_WITHDRAWAL_PREVIEW_CHANGED",
1559
+ /** No withdrawal has that id. Carries `withdrawalId`. */
1560
+ FEATURE_WITHDRAWAL_NOT_FOUND: "FEATURE_WITHDRAWAL_NOT_FOUND",
1561
+ /** The withdrawal was lifted already. Carries the `date` it is lifted from. */
1562
+ FEATURE_WITHDRAWAL_ALREADY_LIFTED: "FEATURE_WITHDRAWAL_ALREADY_LIFTED",
1563
+ /**
1564
+ * Nobody the withdrawal reaches has been told of it yet, and telling them
1565
+ * failed just now: a withdrawal is lifted only once they were. Nothing was
1566
+ * lifted; the next try, or the quarter-hourly run, tells them first.
1567
+ */
1568
+ FEATURE_WITHDRAWAL_NOT_TOLD_YET: "FEATURE_WITHDRAWAL_NOT_TOLD_YET",
1569
+ /**
1570
+ * The date a withdrawal would be lifted from lies before now: what was
1571
+ * withdrawn until then was withdrawn.
1572
+ */
1573
+ FEATURE_WITHDRAWAL_LIFT_IN_PAST: "FEATURE_WITHDRAWAL_LIFT_IN_PAST",
1574
+ /**
1575
+ * The withdrawal does not take its feature away now — its date is ahead,
1576
+ * or it was lifted — so nothing ends at once because of it. Carries
1577
+ * `featureKey`.
1578
+ */
1579
+ FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "FEATURE_WITHDRAWAL_NOT_IN_EFFECT",
1580
+ /**
1581
+ * The withdrawal did not reach the subscription, or the line to end: it
1582
+ * was not running at the announcement, or no longer holds the feature.
1583
+ */
1584
+ FEATURE_WITHDRAWAL_DOES_NOT_REACH: "FEATURE_WITHDRAWAL_DOES_NOT_REACH",
1585
+ /** What was to be ended at once has ended already. */
1586
+ FEATURE_WITHDRAWAL_ALREADY_ENDED: "FEATURE_WITHDRAWAL_ALREADY_ENDED",
1587
+ /**
1588
+ * A cancellation is declared for a later date, and the installation's
1589
+ * subscription write or booking store cannot bring it forward (`endNow`).
1590
+ * It ends as declared. Carries that `date`.
1591
+ */
1592
+ FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED",
1330
1593
  // ── the preview routes' own blockers and warnings ──
1331
1594
  //
1332
1595
  // These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
@@ -1484,6 +1747,55 @@ var CONTRACT_ERROR_CODES = {
1484
1747
  */
1485
1748
  SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED"
1486
1749
  };
1750
+ var INVOICE_ERROR_CODES = {
1751
+ /**
1752
+ * A charge already stands on an invoice: a second run issuing the same
1753
+ * charges lost the race. Nothing is written and no number is drawn.
1754
+ */
1755
+ SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "SUBSCRIPTION_INVOICE_CHARGE_INVOICED",
1756
+ /**
1757
+ * The invoice lacks content the tax adapter requires of it — `missing`
1758
+ * names the fields, `adapter` the adapter. It draws no number and waits
1759
+ * until what is missing is supplied (`SC-PRIC-027`).
1760
+ */
1761
+ SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE",
1762
+ /**
1763
+ * The contract's parties were copied by a migration and the operator has
1764
+ * not confirmed them yet; its charges are not invoiced until then
1765
+ * (`SC-AUD-012`).
1766
+ */
1767
+ SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED",
1768
+ /**
1769
+ * A file of the invoice's document has another checksum than the one
1770
+ * recorded or archived for it (`invoiceId`, `recorded`, `rendered`), and
1771
+ * the first stays. The run takes the archived file as the one issued, or —
1772
+ * where nothing is archived — records the new rendering in place of the
1773
+ * old (`SC-AUD-019`); the invoice waits only where another run changed the
1774
+ * record or the archive in between, and the next run settles it.
1775
+ */
1776
+ SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED",
1777
+ /**
1778
+ * The tax adapter refuses the rendered document — `missing` names what it
1779
+ * lacks or disagrees with, `adapter` the adapter. The invoice keeps its
1780
+ * number and waits for a document that passes (`SC-PRIC-042`).
1781
+ */
1782
+ SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED",
1783
+ /**
1784
+ * The archived document does not match the checksum recorded when it was
1785
+ * rendered (`invoiceId`). It is not handed out (`SC-AUD-019`).
1786
+ */
1787
+ SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH",
1788
+ /** The invoice's document is not archived yet (`invoiceId`); nothing is offered until it is (`SC-AUD-014`). */
1789
+ SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED",
1790
+ /** No invoice with this id (`invoiceId`). */
1791
+ SUBSCRIPTION_INVOICE_NOT_FOUND: "SUBSCRIPTION_INVOICE_NOT_FOUND",
1792
+ /**
1793
+ * The subscriber's record holds no invoice email (`subscriberId`), so the
1794
+ * archived invoice cannot be sent (`SC-PRIC-047`); it is sent once one is
1795
+ * entered.
1796
+ */
1797
+ SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL"
1798
+ };
1487
1799
  var SUBSCRIBER_ERROR_CODES = {
1488
1800
  /**
1489
1801
  * A contract, a plan change or a booking for a tenant that has no
@@ -1664,6 +1976,7 @@ var PLATFORM_ERROR_CODES = {
1664
1976
  ...CATALOG_ERROR_CODES,
1665
1977
  ...BILLING_ERROR_CODES,
1666
1978
  ...CONTRACT_ERROR_CODES,
1979
+ ...INVOICE_ERROR_CODES,
1667
1980
  ...SUBSCRIBER_ERROR_CODES,
1668
1981
  ...REGISTRATION_ERROR_CODES,
1669
1982
  ...PAYMENT_ERROR_CODES,
@@ -1717,7 +2030,13 @@ var OPTIONAL_CANONICAL_MODELS = {
1717
2030
  SubscriptionNotice: ["tenantBilling.subscriptionNotices"],
1718
2031
  VersionRetirement: ["tenantBilling.versionRetirements"],
1719
2032
  BundleVersionRetirement: ["tenantBilling.bundleVersionRetirements"],
2033
+ FeatureWithdrawal: ["tenantBilling.featureWithdrawals"],
1720
2034
  SubscriberLedgerEntry: ["entitlement.subscriberLedgerRepository"],
2035
+ SubscriptionInvoice: ["entitlement.subscriptionInvoiceRepository"],
2036
+ SubscriptionInvoiceLine: ["entitlement.subscriptionInvoiceRepository"],
2037
+ SubscriptionInvoiceNumber: ["entitlement.subscriptionInvoiceRepository"],
2038
+ SubscriptionInvoiceDocument: ["entitlement.subscriptionInvoiceRepository"],
2039
+ SubscriptionInvoiceArchive: ["entitlement.subscriptionInvoiceArchive"],
1721
2040
  PromoCodeHold: ["promo.holdRepository"]
1722
2041
  };
1723
2042
  function membersLeftOut(notAdopted = []) {
@@ -1900,6 +2219,23 @@ var ERROR_MESSAGES_EN = {
1900
2219
  BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Your subscription ends by then as well.",
1901
2220
  BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} moves to its new version on {date}, and your plan changes before then, so the price a switch would keep is not known yet. Switch once your plan has changed, or let it move on that date.",
1902
2221
  BUNDLE_VERSION_OFFER_CHANGED: "The offer changed since it was shown. Look at the current one before switching.",
2222
+ FEATURE_WITHDRAWAL_UNAVAILABLE: "Withdrawing a feature is not offered here: the stores cannot list everybody a withdrawal reaches.",
2223
+ FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "The catalogue knows no feature {featureKey}.",
2224
+ FEATURE_WITHDRAWAL_DATE_IN_PAST: "A feature is withdrawn from now on or from a later date, never from a date already past.",
2225
+ FEATURE_WITHDRAWAL_OPEN: "{featureKey} is withdrawn already, and that withdrawal is not lifted. Lift it before announcing another.",
2226
+ FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} is withdrawn until {date}. Another withdrawal can begin on that date at the earliest.",
2227
+ FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "The reduction for {key} billed {billingCycle} names nothing this withdrawal reaches.",
2228
+ FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "The reduction for {key} billed {billingCycle} is named twice.",
2229
+ FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "The reduction for {key} billed {billingCycle} is more than the lowest price it reduces, {price}.",
2230
+ FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "The subscriptions this withdrawal reaches changed since they were shown. Look at them again before announcing it.",
2231
+ FEATURE_WITHDRAWAL_NOT_FOUND: "Feature withdrawal {withdrawalId} not found.",
2232
+ FEATURE_WITHDRAWAL_ALREADY_LIFTED: "This withdrawal is lifted already, from {date}.",
2233
+ FEATURE_WITHDRAWAL_NOT_TOLD_YET: "Nobody has been told of this withdrawal yet, and it is not lifted before they are. Try again in a moment.",
2234
+ FEATURE_WITHDRAWAL_LIFT_IN_PAST: "A withdrawal is lifted from now on or from a later date, never from a date already past.",
2235
+ FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} is not withdrawn now, so nothing can be ended at once because of it.",
2236
+ FEATURE_WITHDRAWAL_DOES_NOT_REACH: "This withdrawal does not reach your subscription, or no longer this booking.",
2237
+ FEATURE_WITHDRAWAL_ALREADY_ENDED: "This has ended already.",
2238
+ FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "Your cancellation for {date} is recorded, and it cannot be brought forward here. It ends on that date, and until then the reduction applies.",
1903
2239
  RETIREMENT_SWITCH_NOT_PENDING: "No retirement of your version is waiting for its date, so there is nothing to switch to.",
1904
2240
  RETIREMENT_SWITCH_IN_TRIAL: "The switch opens when your trial ends.",
1905
2241
  RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} cannot run on {planName} and runs until {until} at the earliest. Once it is cancelled, the switch can be made from that day.",
@@ -1942,6 +2278,15 @@ var ERROR_MESSAGES_EN = {
1942
2278
  PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
1943
2279
  // ── contract ──
1944
2280
  SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "The contract states {stated} % in {field}, and the tax adapter decides {decided} %.",
2281
+ SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Charge '{chargeId}' already stands on an invoice.",
2282
+ SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "The invoice lacks what the tax adapter {adapter} requires of it: {missing}. It is issued once that is supplied.",
2283
+ SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "Contract {contractId} names no confirmed issuer and subscriber \u2014 a migration copied its parties, or it names no issuer \u2014 so its charges are not invoiced.",
2284
+ SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "The document of invoice {invoiceId} differs from the one recorded or archived for it ({rendered} instead of {recorded}); the first stays, and the next run settles which one the invoice keeps.",
2285
+ SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "The tax adapter {adapter} refuses the rendered invoice: {missing}. The invoice keeps its number and waits for a document that passes.",
2286
+ SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "The archived document of invoice {invoiceId} does not match the checksum recorded when it was rendered, and is not handed out.",
2287
+ SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "The document of invoice {invoiceId} is not archived yet.",
2288
+ SUBSCRIPTION_INVOICE_NOT_FOUND: "No invoice '{invoiceId}'.",
2289
+ SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Subscriber {subscriberId} has no invoice email, so its invoice cannot be sent; it is sent once one is entered.",
1945
2290
  CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
1946
2291
  CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
1947
2292
  CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
@@ -2384,17 +2729,20 @@ var ORIGINS = {
2384
2729
  bundleBooking: true,
2385
2730
  bundleChange: true,
2386
2731
  credit: true,
2387
- correction: true
2732
+ correction: true,
2733
+ reductionTakenBack: true
2388
2734
  };
2389
2735
  var SOURCES = {
2390
2736
  plan: true,
2391
2737
  bundle: true,
2392
2738
  discount: true
2393
2739
  };
2394
- var TABLE2 = "subscriber_ledger_entries";
2740
+ var SUBSCRIBER_CHARGE_ORIGINS = keysOf(ORIGINS);
2741
+ var SUBSCRIBER_CHARGE_SOURCES = keysOf(SOURCES);
2742
+ var TABLE3 = "subscriber_ledger_entries";
2395
2743
  var CENT_TOLERANCE = 1e-6;
2396
2744
  function toSubscriberChargeRecord(row) {
2397
- const where = (column) => ({ table: TABLE2, column, id: row.id });
2745
+ const where = (column) => ({ table: TABLE3, column, id: row.id });
2398
2746
  return {
2399
2747
  id: row.id,
2400
2748
  subscriberId: row.subscriberId,
@@ -2402,8 +2750,8 @@ function toSubscriberChargeRecord(row) {
2402
2750
  subscriptionId: row.subscriptionId,
2403
2751
  contractId: row.contractId,
2404
2752
  contractLineItemId: row.contractLineItemId,
2405
- origin: oneOf(keysOf(ORIGINS), row.origin, where("origin")),
2406
- source: oneOf(keysOf(SOURCES), row.source, where("source")),
2753
+ origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
2754
+ source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
2407
2755
  sourceRef: row.sourceRef,
2408
2756
  periodStart: row.periodStart,
2409
2757
  periodEnd: row.periodEnd,
@@ -2443,6 +2791,15 @@ function keysOf(record) {
2443
2791
  // src/subscriber-ledger.types.ts
2444
2792
  var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
2445
2793
 
2794
+ // src/subscription-invoice.types.ts
2795
+ var INVOICE_SEQUENCE_DIGITS = 6;
2796
+ function formatInvoiceNumber(prefix, year, sequence) {
2797
+ if (!Number.isInteger(sequence) || sequence < 1) {
2798
+ throw new RangeError(`An invoice number's sequence starts at 1; ${sequence} is not one.`);
2799
+ }
2800
+ return `${prefix}-${year}-${String(sequence).padStart(INVOICE_SEQUENCE_DIGITS, "0")}`;
2801
+ }
2802
+
2446
2803
  // src/subscriber.types.ts
2447
2804
  var SUBSCRIBER_INVOICE_ADDRESS_FIELDS = [
2448
2805
  "addressLine1",
@@ -2574,6 +2931,420 @@ function textEntries(value) {
2574
2931
  return entries;
2575
2932
  }
2576
2933
 
2934
+ // src/subscription-invoice-mapping.ts
2935
+ var INVOICES = "subscription_invoices";
2936
+ var LINES = "subscription_invoice_lines";
2937
+ var CENT_TOLERANCE2 = 1e-6;
2938
+ function toSubscriptionInvoiceRecord(row, lines) {
2939
+ const where = (column) => ({ table: INVOICES, column, id: row.id });
2940
+ const treatment = toTaxTreatment(row.taxTreatment);
2941
+ if (!treatment) {
2942
+ throw new Error(`${INVOICES}.taxTreatment of ${row.id} is not a tax treatment.`);
2943
+ }
2944
+ return {
2945
+ id: row.id,
2946
+ number: row.number,
2947
+ numberPrefix: row.numberPrefix,
2948
+ numberYear: row.numberYear,
2949
+ numberSequence: row.numberSequence,
2950
+ tenantId: row.tenantId,
2951
+ subscriberId: row.subscriberId,
2952
+ subscriptionId: row.subscriptionId,
2953
+ contractId: row.contractId,
2954
+ issuedAt: row.issuedAt,
2955
+ issueDate: dayOf2(row.issueDate, where("issueDate")),
2956
+ dueDate: dayOf2(row.dueDate, where("dueDate")),
2957
+ servicePeriodFrom: dayOf2(row.servicePeriodFrom, where("servicePeriodFrom")),
2958
+ servicePeriodUntil: dayOf2(row.servicePeriodUntil, where("servicePeriodUntil")),
2959
+ currency: row.currency,
2960
+ issuer: issuerOf(row.issuerParty, where("issuerParty")),
2961
+ subscriber: subscriberOf(row.subscriberParty, where("subscriberParty")),
2962
+ footer: footerOf(row.footer, where("footer")),
2963
+ taxTreatment: treatment,
2964
+ taxes: taxesOf(row.taxes, where("taxes")),
2965
+ totalNet: amountOf2(row.totalNet, where("totalNet")),
2966
+ totalTax: amountOf2(row.totalTax, where("totalTax")),
2967
+ totalGross: amountOf2(row.totalGross, where("totalGross")),
2968
+ lines: [...lines].sort((a, b) => a.position - b.position).map((line) => toSubscriptionInvoiceLineRecord(line)),
2969
+ createdAt: row.createdAt
2970
+ };
2971
+ }
2972
+ function toSubscriptionInvoiceLineRecord(row) {
2973
+ const where = (column) => ({ table: LINES, column, id: row.id });
2974
+ return {
2975
+ id: row.id,
2976
+ invoiceId: row.invoiceId,
2977
+ position: row.position,
2978
+ chargeId: row.chargeId,
2979
+ contractLineItemId: row.contractLineItemId,
2980
+ title: row.title,
2981
+ origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
2982
+ source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
2983
+ periodFrom: dayOf2(row.periodFrom, where("periodFrom")),
2984
+ periodUntil: dayOf2(row.periodUntil, where("periodUntil")),
2985
+ amountNet: amountOf2(row.amountNet, where("amountNet")),
2986
+ taxRate: amountOf2(row.taxRate, where("taxRate"))
2987
+ };
2988
+ }
2989
+ function subscriptionInvoiceColumns(invoice, drawn) {
2990
+ return {
2991
+ number: drawn.number,
2992
+ numberPrefix: invoice.numberPrefix,
2993
+ numberYear: invoice.numberYear,
2994
+ numberSequence: drawn.numberSequence,
2995
+ tenantId: invoice.tenantId,
2996
+ subscriberId: invoice.subscriberId,
2997
+ subscriptionId: invoice.subscriptionId,
2998
+ contractId: invoice.contractId,
2999
+ issuedAt: invoice.issuedAt,
3000
+ issueDate: invoice.issueDate,
3001
+ dueDate: invoice.dueDate,
3002
+ servicePeriodFrom: invoice.servicePeriodFrom,
3003
+ servicePeriodUntil: invoice.servicePeriodUntil,
3004
+ currency: invoice.currency,
3005
+ issuerParty: { ...invoice.issuer },
3006
+ subscriberParty: { ...invoice.subscriber },
3007
+ footer: [...invoice.footer],
3008
+ taxTreatment: taxTreatmentToJson(invoice.taxTreatment),
3009
+ taxes: invoice.taxes.map((entry) => ({ rate: entry.rate, net: entry.net, tax: entry.tax })),
3010
+ totalNet: centsText(invoice.totalNet, "totalNet"),
3011
+ totalTax: centsText(invoice.totalTax, "totalTax"),
3012
+ totalGross: centsText(invoice.totalGross, "totalGross")
3013
+ };
3014
+ }
3015
+ function subscriptionInvoiceLineColumns(line, invoiceId) {
3016
+ return {
3017
+ invoiceId,
3018
+ position: line.position,
3019
+ chargeId: line.chargeId,
3020
+ contractLineItemId: line.contractLineItemId,
3021
+ title: line.title,
3022
+ origin: line.origin,
3023
+ source: line.source,
3024
+ periodFrom: line.periodFrom,
3025
+ periodUntil: line.periodUntil,
3026
+ amountNet: centsText(line.amountNet, "amountNet"),
3027
+ taxRate: centsText(line.taxRate, "taxRate")
3028
+ };
3029
+ }
3030
+ function utcMidnightOf(day) {
3031
+ return /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
3032
+ }
3033
+ var DAY_LENGTH = "YYYY-MM-DD".length;
3034
+ function dayOf2(value, where) {
3035
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
3036
+ return value.toISOString().slice(0, DAY_LENGTH);
3037
+ }
3038
+ if (typeof value === "string" && value.length === DAY_LENGTH && !Number.isNaN(Date.parse(value))) {
3039
+ return value;
3040
+ }
3041
+ throw new Error(`${where.table}.${where.column} of ${where.id} is not a day.`);
3042
+ }
3043
+ function footerOf(value, where) {
3044
+ if (!Array.isArray(value) || !value.every((line) => typeof line === "string")) {
3045
+ throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of lines.`);
3046
+ }
3047
+ return [...value];
3048
+ }
3049
+ function amountOf2(value, where) {
3050
+ const amount = Number(value === null || value === void 0 ? NaN : String(value));
3051
+ if (!Number.isFinite(amount)) {
3052
+ throw new Error(`${where.table}.${where.column} of ${where.id} is not an amount.`);
3053
+ }
3054
+ return amount;
3055
+ }
3056
+ function centsText(amount, field) {
3057
+ const cents2 = amount * 100;
3058
+ if (!Number.isFinite(cents2) || Math.abs(cents2 - Math.round(cents2)) > CENT_TOLERANCE2) {
3059
+ throw new Error(
3060
+ `An invoice's ${field} of ${amount} is not a whole number of cents; it is rounded once, where the invoice is drafted, before it is written.`
3061
+ );
3062
+ }
3063
+ return amount.toFixed(2);
3064
+ }
3065
+ function textOrNull2(source, key, where) {
3066
+ const value = source[key];
3067
+ if (value === null || value === void 0) return null;
3068
+ if (typeof value !== "string") {
3069
+ throw new Error(`${where.table}.${where.column}.${key} of ${where.id} is not text.`);
3070
+ }
3071
+ return value;
3072
+ }
3073
+ function partyOf(value, where) {
3074
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3075
+ throw new Error(`${where.table}.${where.column} of ${where.id} is not a party.`);
3076
+ }
3077
+ const source = value;
3078
+ const legalName = textOrNull2(source, "legalName", where);
3079
+ if (legalName === null) {
3080
+ throw new Error(`${where.table}.${where.column} of ${where.id} names no legal name.`);
3081
+ }
3082
+ return {
3083
+ legalName,
3084
+ vatId: textOrNull2(source, "vatId", where),
3085
+ taxNumber: textOrNull2(source, "taxNumber", where),
3086
+ addressLine1: textOrNull2(source, "addressLine1", where),
3087
+ addressLine2: textOrNull2(source, "addressLine2", where),
3088
+ postalCode: textOrNull2(source, "postalCode", where),
3089
+ city: textOrNull2(source, "city", where),
3090
+ country: textOrNull2(source, "country", where)
3091
+ };
3092
+ }
3093
+ function issuerOf(value, where) {
3094
+ return partyOf(value, where);
3095
+ }
3096
+ function subscriberOf(value, where) {
3097
+ const party = partyOf(value, where);
3098
+ const customerNumber = textOrNull2(value, "customerNumber", where);
3099
+ if (customerNumber === null) {
3100
+ throw new Error(`${where.table}.${where.column} of ${where.id} names no customer number.`);
3101
+ }
3102
+ return { ...party, customerNumber };
3103
+ }
3104
+ function taxesOf(value, where) {
3105
+ if (!Array.isArray(value)) {
3106
+ throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of rates.`);
3107
+ }
3108
+ return value.map((entry) => {
3109
+ const item = entry ?? {};
3110
+ return {
3111
+ rate: amountOf2(item.rate, where),
3112
+ net: amountOf2(item.net, where),
3113
+ tax: amountOf2(item.tax, where)
3114
+ };
3115
+ });
3116
+ }
3117
+
3118
+ // src/subscription-invoice-refusals.ts
3119
+ function subscriptionInvoiceChargeInvoiced(chargeId) {
3120
+ return new PersistenceRefusal(
3121
+ INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_CHARGE_INVOICED,
3122
+ "moved",
3123
+ `Charge '${chargeId}' already stands on an invoice.`,
3124
+ { chargeId }
3125
+ );
3126
+ }
3127
+ function subscriptionInvoiceDocumentChanged(invoiceId, recorded, rendered) {
3128
+ return new PersistenceRefusal(
3129
+ INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED,
3130
+ "moved",
3131
+ `The document rendered for invoice '${invoiceId}' differs from the one recorded for it.`,
3132
+ { invoiceId, recorded, rendered }
3133
+ );
3134
+ }
3135
+
3136
+ // src/subscription-invoice-draft.ts
3137
+ function invoiceGroupsOf(charges) {
3138
+ const groups = /* @__PURE__ */ new Map();
3139
+ for (const charge of charges) {
3140
+ const key = `${charge.contractId}|${charge.bookedAt.toISOString()}`;
3141
+ const group = groups.get(key);
3142
+ if (group) group.charges.push(charge);
3143
+ else
3144
+ groups.set(key, {
3145
+ contractId: charge.contractId,
3146
+ bookedAt: charge.bookedAt,
3147
+ charges: [charge]
3148
+ });
3149
+ }
3150
+ return [...groups.values()].filter((group) => group.charges.some((charge) => charge.amountNet !== 0)).sort(
3151
+ (a, b) => a.bookedAt.getTime() - b.bookedAt.getTime() || a.contractId.localeCompare(b.contractId)
3152
+ );
3153
+ }
3154
+ function draftSubscriptionInvoice(input) {
3155
+ const { group, treatment, timeZone } = input;
3156
+ const lines = linesOf(input);
3157
+ const issueDate = dayInZone(input.issuedAt, timeZone);
3158
+ const tax = input.tax(lines.map((line) => ({ net: line.amountNet, rate: line.taxRate })));
3159
+ return {
3160
+ numberPrefix: input.numberPrefix,
3161
+ numberYear: yearOfDay(issueDate),
3162
+ tenantId: input.tenantId,
3163
+ subscriberId: input.subscriberId,
3164
+ subscriptionId: group.charges[0].subscriptionId,
3165
+ contractId: group.contractId,
3166
+ issuedAt: input.issuedAt,
3167
+ issueDate,
3168
+ dueDate: addDaysToDay(issueDate, input.paymentTermDays),
3169
+ servicePeriodFrom: lines.map((line) => line.periodFrom).sort()[0],
3170
+ servicePeriodUntil: lines.map((line) => line.periodUntil).sort().at(-1),
3171
+ currency: currencyOf(group),
3172
+ issuer: input.issuer,
3173
+ subscriber: input.subscriber,
3174
+ footer: [...input.footer],
3175
+ taxTreatment: treatment,
3176
+ taxes: tax.rates,
3177
+ totalNet: tax.net,
3178
+ totalTax: tax.tax,
3179
+ totalGross: tax.gross,
3180
+ lines
3181
+ };
3182
+ }
3183
+ function invoiceContentDraftOf(invoice) {
3184
+ return {
3185
+ issuer: invoice.issuer,
3186
+ subscriber: invoice.subscriber,
3187
+ treatment: invoice.taxTreatment,
3188
+ issueDate: invoice.issueDate,
3189
+ dueDate: invoice.dueDate,
3190
+ servicePeriod: { from: invoice.servicePeriodFrom, until: invoice.servicePeriodUntil },
3191
+ lines: invoice.lines.map((line) => ({
3192
+ title: line.title,
3193
+ net: line.amountNet,
3194
+ rate: line.taxRate
3195
+ }))
3196
+ };
3197
+ }
3198
+ function linesOf(input) {
3199
+ const placeOf = new Map(input.contractLines.map((line, index) => [line.id, index]));
3200
+ const place = (charge) => {
3201
+ const index = placeOf.get(charge.contractLineItemId);
3202
+ if (index === void 0) {
3203
+ throw new Error(
3204
+ `Charge ${charge.id} names contract line ${charge.contractLineItemId}, which contract ${input.group.contractId} does not have.`
3205
+ );
3206
+ }
3207
+ return index;
3208
+ };
3209
+ return [...input.group.charges].sort(
3210
+ (a, b) => place(a) - place(b) || a.periodStart.getTime() - b.periodStart.getTime() || a.origin.localeCompare(b.origin)
3211
+ ).map((charge, index) => ({
3212
+ position: index + 1,
3213
+ chargeId: charge.id,
3214
+ contractLineItemId: charge.contractLineItemId,
3215
+ title: input.contractLines[place(charge)].titleSnapshot,
3216
+ origin: charge.origin,
3217
+ source: charge.source,
3218
+ periodFrom: dayInZone(charge.periodStart, input.timeZone),
3219
+ periodUntil: lastDayInZone(
3220
+ { from: charge.periodStart, until: charge.periodEnd },
3221
+ input.timeZone
3222
+ ),
3223
+ amountNet: charge.amountNet,
3224
+ taxRate: input.treatment.rate
3225
+ }));
3226
+ }
3227
+ function currencyOf(group) {
3228
+ const currencies = new Set(group.charges.map((charge) => charge.currency));
3229
+ if (currencies.size !== 1) {
3230
+ throw new Error(
3231
+ `The charges of contract ${group.contractId} booked at ${group.bookedAt.toISOString()} are in ${[...currencies].join(" and ")}; an invoice is in one currency.`
3232
+ );
3233
+ }
3234
+ return [...currencies][0];
3235
+ }
3236
+
3237
+ // src/invoice-issuer.ts
3238
+ function invoiceIssuerOf(copy, configured, history) {
3239
+ const target = configured && identityOf2(configured);
3240
+ let reached = identityOf2(copy);
3241
+ if (!target || !reached) return { ...copy };
3242
+ for (const step of history) {
3243
+ if (sameLegalIdentity(reached, target)) break;
3244
+ const change = classifyIssuerChange(
3245
+ recordedIssuerIdentity(step.previous),
3246
+ issuerBlockOf(step.current)
3247
+ );
3248
+ if (change.kind === "corrected" && sameLegalIdentity(change.recorded, reached)) {
3249
+ reached = change.current;
3250
+ }
3251
+ }
3252
+ return sameLegalIdentity(reached, target) ? { ...configured } : { ...copy };
3253
+ }
3254
+ function invoiceFooterOf(issuer, configured, footer) {
3255
+ const named = identityOf2(issuer);
3256
+ const target = configured && identityOf2(configured);
3257
+ return named && target && sameLegalIdentity(named, target) ? [...footer] : [];
3258
+ }
3259
+ function identityOf2(party) {
3260
+ return recordedIssuerIdentity({ issuer: party });
3261
+ }
3262
+ function issuerBlockOf(values) {
3263
+ const issuer = values.issuer;
3264
+ if (issuer === null || typeof issuer !== "object" || Array.isArray(issuer)) return void 0;
3265
+ return issuer;
3266
+ }
3267
+
3268
+ // src/invoice-document.types.ts
3269
+ var INVOICE_DOCUMENT_CAPABILITY = "invoices.read";
3270
+
3271
+ // src/invoice-document-mapping.ts
3272
+ var DOCUMENTS = "subscription_invoice_documents";
3273
+ var ARCHIVE = "subscription_invoice_archive";
3274
+ var CHECKSUM = /^[0-9a-f]{64}$/;
3275
+ function isInvoiceDocumentChecksum(value) {
3276
+ return typeof value === "string" && CHECKSUM.test(value);
3277
+ }
3278
+ function toSubscriptionInvoiceDocumentRecord(row) {
3279
+ if (!isInvoiceDocumentChecksum(row.checksum)) {
3280
+ throw new Error(`${DOCUMENTS}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
3281
+ }
3282
+ const size = Number(row.size);
3283
+ if (!Number.isSafeInteger(size) || size < 0) {
3284
+ throw new Error(`${DOCUMENTS}.size of ${row.invoiceId} is not a size in bytes.`);
3285
+ }
3286
+ return {
3287
+ invoiceId: row.invoiceId,
3288
+ checksum: row.checksum,
3289
+ mediaType: row.mediaType,
3290
+ fileName: row.fileName,
3291
+ size,
3292
+ renderedAt: row.renderedAt,
3293
+ archivedAt: row.archivedAt,
3294
+ deliveryClaimedAt: row.deliveryClaimedAt,
3295
+ deliveredAt: row.deliveredAt,
3296
+ deliveredTo: row.deliveredTo
3297
+ };
3298
+ }
3299
+ function toArchivedInvoiceDocument(row) {
3300
+ if (!isInvoiceDocumentChecksum(row.checksum)) {
3301
+ throw new Error(`${ARCHIVE}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
3302
+ }
3303
+ if (!(row.content instanceof Uint8Array)) {
3304
+ throw new Error(`${ARCHIVE}.content of ${row.invoiceId} is not a file.`);
3305
+ }
3306
+ return {
3307
+ invoiceId: row.invoiceId,
3308
+ checksum: row.checksum,
3309
+ mediaType: row.mediaType,
3310
+ fileName: row.fileName,
3311
+ // A copy that owns its bytes: a driver's `Buffer` may be a view into a
3312
+ // pooled allocation, and a caller handing it on must not see that pool.
3313
+ content: new Uint8Array(row.content),
3314
+ archivedAt: row.archivedAt
3315
+ };
3316
+ }
3317
+ var INVOICE_ARCHIVE_TRIGGERS = [
3318
+ "subscription_invoice_archive_is_kept",
3319
+ "subscription_invoice_archive_is_not_emptied"
3320
+ ];
3321
+ function invoiceArchiveProtectionProblem(enabledTriggers) {
3322
+ const missing = INVOICE_ARCHIVE_TRIGGERS.filter((name) => !enabledTriggers.includes(name));
3323
+ if (missing.length === 0) return null;
3324
+ return `${ARCHIVE} lacks ${missing.join(" and ")}, which keep an archived invoice document as it was issued: run @saasicat/spec/sql/1.0-an-invoice-document-is-archived.postgres.sql after db push, and enable the triggers where they were disabled.`;
3325
+ }
3326
+
3327
+ // src/e-invoice-format.ts
3328
+ var CATEGORY = {
3329
+ standard: "S",
3330
+ reduced: "S",
3331
+ exempt: "E",
3332
+ "small-business": "E",
3333
+ "reverse-charge": "AE",
3334
+ "not-taxable": "O"
3335
+ };
3336
+ function vatCategoryOf(kind) {
3337
+ return CATEGORY[kind];
3338
+ }
3339
+ var NOT_XML = /[^\t\n\r\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu;
3340
+ function holdsOnlyXmlCharacters(text) {
3341
+ return text.search(NOT_XML) === -1;
3342
+ }
3343
+ var CARRIAGE_RETURN = /\r\n?/g;
3344
+ function asXmlReadsIt(text) {
3345
+ return text.replace(NOT_XML, "?").replace(CARRIAGE_RETURN, "\n");
3346
+ }
3347
+
2577
3348
  // src/subscriber-mapping.ts
2578
3349
  function formatCustomerNumber(prefix, sequence) {
2579
3350
  return `${prefix}${sequence}`;
@@ -2703,7 +3474,7 @@ function toIdentityValues(value) {
2703
3474
  // src/subscriber-payment-method-mapping.ts
2704
3475
  var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
2705
3476
  var STATUSES = ["ACTIVE", "REPLACED"];
2706
- var TABLE3 = "subscriber_payment_methods";
3477
+ var TABLE4 = "subscriber_payment_methods";
2707
3478
  function toSubscriberPaymentMethodRecord(row) {
2708
3479
  return {
2709
3480
  id: row.id,
@@ -2712,7 +3483,7 @@ function toSubscriberPaymentMethodRecord(row) {
2712
3483
  provider: row.provider,
2713
3484
  customerRef: row.customerRef,
2714
3485
  paymentMethodRef: row.paymentMethodRef,
2715
- type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE3, column: "type", id: row.id }),
3486
+ type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE4, column: "type", id: row.id }),
2716
3487
  brand: row.brand,
2717
3488
  last4: row.last4,
2718
3489
  expiryMonth: row.expiryMonth,
@@ -2720,7 +3491,7 @@ function toSubscriberPaymentMethodRecord(row) {
2720
3491
  country: row.country,
2721
3492
  bankCode: row.bankCode,
2722
3493
  mandateReference: row.mandateReference,
2723
- status: oneOf(STATUSES, row.status, { table: TABLE3, column: "status", id: row.id }),
3494
+ status: oneOf(STATUSES, row.status, { table: TABLE4, column: "status", id: row.id }),
2724
3495
  confirmedAt: row.confirmedAt,
2725
3496
  replacedAt: row.replacedAt,
2726
3497
  createdAt: row.createdAt
@@ -3118,6 +3889,23 @@ var ERROR_MESSAGES_DE = {
3118
3889
  BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Ihr Abonnement endet bis dahin ebenfalls.",
3119
3890
  BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} zieht am {date} auf seine neue Version um, und Ihr Paket \xE4ndert sich vorher. Der Preis, den ein Wechsel halten w\xFCrde, steht deshalb noch nicht fest. Wechseln Sie, sobald Ihr Paket gewechselt hat, oder lassen Sie es an diesem Tag umziehen.",
3120
3891
  BUNDLE_VERSION_OFFER_CHANGED: "Das Angebot hat sich seit der Anzeige ge\xE4ndert. Bitte sieh dir das aktuelle an, bevor du wechselst.",
3892
+ FEATURE_WITHDRAWAL_UNAVAILABLE: "Ein Feature zur\xFCckzuziehen ist hier nicht m\xF6glich: Die Speicher k\xF6nnen nicht alle auflisten, die ein R\xFCckzug erreicht.",
3893
+ FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "Der Katalog kennt kein Feature {featureKey}.",
3894
+ FEATURE_WITHDRAWAL_DATE_IN_PAST: "Ein Feature wird ab jetzt oder ab einem sp\xE4teren Datum zur\xFCckgezogen, nie ab einem vergangenen.",
3895
+ FEATURE_WITHDRAWAL_OPEN: "{featureKey} ist bereits zur\xFCckgezogen, und dieser R\xFCckzug ist nicht aufgehoben. Heben Sie ihn auf, bevor Sie einen weiteren ank\xFCndigen.",
3896
+ FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} ist bis zum {date} zur\xFCckgezogen. Ein weiterer R\xFCckzug kann fr\xFChestens an diesem Tag beginnen.",
3897
+ FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} betrifft nichts, was dieser R\xFCckzug erreicht.",
3898
+ FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist doppelt angegeben.",
3899
+ FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist h\xF6her als der niedrigste Preis, den sie mindert: {price}.",
3900
+ FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "Die Abonnements, die dieser R\xFCckzug erreicht, haben sich seit der Anzeige ge\xE4ndert. Sehen Sie sie erneut an, bevor Sie ihn ank\xFCndigen.",
3901
+ FEATURE_WITHDRAWAL_NOT_FOUND: "R\xFCckzug {withdrawalId} nicht gefunden.",
3902
+ FEATURE_WITHDRAWAL_ALREADY_LIFTED: "Dieser R\xFCckzug ist bereits aufgehoben, ab dem {date}.",
3903
+ FEATURE_WITHDRAWAL_NOT_TOLD_YET: "\xDCber diesen R\xFCckzug ist noch niemand informiert, und er wird nicht aufgehoben, bevor das geschehen ist. Versuchen Sie es gleich noch einmal.",
3904
+ FEATURE_WITHDRAWAL_LIFT_IN_PAST: "Ein R\xFCckzug wird ab jetzt oder ab einem sp\xE4teren Datum aufgehoben, nie ab einem vergangenen.",
3905
+ FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} ist gerade nicht zur\xFCckgezogen. Deshalb kann nichts sofort beendet werden.",
3906
+ FEATURE_WITHDRAWAL_DOES_NOT_REACH: "Dieser R\xFCckzug betrifft Ihr Abonnement nicht, oder diese Buchung nicht mehr.",
3907
+ FEATURE_WITHDRAWAL_ALREADY_ENDED: "Das ist bereits beendet.",
3908
+ FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "Ihre K\xFCndigung zum {date} ist erfasst und kann hier nicht vorgezogen werden. Sie endet an diesem Tag, und bis dahin gilt die Minderung.",
3121
3909
  RETIREMENT_SWITCH_NOT_PENDING: "F\xFCr Ihre Version steht keine Einstellung mehr bevor. Es gibt nichts, wohin Sie wechseln k\xF6nnten.",
3122
3910
  RETIREMENT_SWITCH_IN_TRIAL: "Der Wechsel ist m\xF6glich, sobald Ihre Testphase endet.",
3123
3911
  RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} kann im Paket {planName} nicht laufen und l\xE4uft fr\xFChestens bis {until}. Ist es gek\xFCndigt, ist der Wechsel ab diesem Tag m\xF6glich.",
@@ -3160,6 +3948,15 @@ var ERROR_MESSAGES_DE = {
3160
3948
  PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions l\xE4sst sich nicht senken.",
3161
3949
  // ── contract ──
3162
3950
  SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "Der Vertrag nennt in {field} {stated} %, der Steueradapter entscheidet {decided} %.",
3951
+ SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Die Forderung '{chargeId}' steht bereits auf einer Rechnung.",
3952
+ SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "Der Rechnung fehlt, was der Steueradapter {adapter} verlangt: {missing}. Sie wird ausgestellt, sobald es erg\xE4nzt ist.",
3953
+ SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "Der Vertrag {contractId} nennt keinen best\xE4tigten Aussteller und Abonnenten \u2013 eine Migration hat seine Parteien \xFCbernommen, oder er nennt keinen Aussteller \u2013, deshalb werden seine Forderungen nicht in Rechnung gestellt.",
3954
+ SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "Das Dokument der Rechnung {invoiceId} weicht vom festgehaltenen oder archivierten ab ({rendered} statt {recorded}); das erste bleibt, und der n\xE4chste Lauf kl\xE4rt, welches die Rechnung beh\xE4lt.",
3955
+ SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "Der Steueradapter {adapter} weist die gerenderte Rechnung zur\xFCck: {missing}. Die Rechnung beh\xE4lt ihre Nummer und wartet auf ein Dokument, das die Pr\xFCfung besteht.",
3956
+ SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "Das archivierte Dokument der Rechnung {invoiceId} passt nicht zur beim Rendern festgehaltenen Pr\xFCfsumme und wird nicht herausgegeben.",
3957
+ SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "Das Dokument der Rechnung {invoiceId} ist noch nicht archiviert.",
3958
+ SUBSCRIPTION_INVOICE_NOT_FOUND: "Keine Rechnung '{invoiceId}'.",
3959
+ SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Der Abonnent {subscriberId} hat keine Rechnungs-E-Mail; seine Rechnung wird versendet, sobald eine eingetragen ist.",
3163
3960
  CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "Aus einem Checkout-Angebot entsteht nur ein einziger Vertrag, und das erst, wenn seine Positionen eingefroren sind.",
3164
3961
  CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "Ein Checkout-Angebot braucht eine eingefrorene Plan-Position.",
3165
3962
  CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
@@ -3259,7 +4056,13 @@ var ERROR_MESSAGES_DE = {
3259
4056
  ERROR_MESSAGES_DE,
3260
4057
  ERROR_MESSAGES_EN,
3261
4058
  FEATURE_NOT_LICENSED,
4059
+ FEATURE_WITHDRAWAL_CAPABILITY,
4060
+ FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
3262
4061
  ForeignPaymentMethodReferenceError,
4062
+ INVOICE_ARCHIVE_TRIGGERS,
4063
+ INVOICE_DOCUMENT_CAPABILITY,
4064
+ INVOICE_ERROR_CODES,
4065
+ INVOICE_SEQUENCE_DIGITS,
3263
4066
  LEGAL_IDENTITY_FIELDS,
3264
4067
  MAINTENANCE_CAPABILITY,
3265
4068
  MAINTENANCE_ERROR_CODES,
@@ -3294,6 +4097,8 @@ var ERROR_MESSAGES_DE = {
3294
4097
  SUBSCRIBER_ACCOUNT_CAPABILITY,
3295
4098
  SUBSCRIBER_ATTENTION_CAPABILITY,
3296
4099
  SUBSCRIBER_ATTENTION_PAGE_SIZE,
4100
+ SUBSCRIBER_CHARGE_ORIGINS,
4101
+ SUBSCRIBER_CHARGE_SOURCES,
3297
4102
  SUBSCRIBER_CORRECTION_CAPABILITY,
3298
4103
  SUBSCRIBER_ERROR_CODES,
3299
4104
  SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
@@ -3302,7 +4107,9 @@ var ERROR_MESSAGES_DE = {
3302
4107
  TAX_ERROR_CODES,
3303
4108
  TAX_TREATMENT_KINDS,
3304
4109
  VERSION_RETIREMENT_CAPABILITY,
4110
+ addDaysToDay,
3305
4111
  applyPromo,
4112
+ asXmlReadsIt,
3306
4113
  assertPersistenceCapabilities,
3307
4114
  buildActivePlanVersionWhere,
3308
4115
  buildActiveVersionWhere,
@@ -3326,14 +4133,28 @@ var ERROR_MESSAGES_DE = {
3326
4133
  contractPartiesOf,
3327
4134
  coverageExcludingSelf,
3328
4135
  customLimitsReader,
4136
+ dayInZone,
3329
4137
  definedFields,
3330
4138
  diffSettings,
4139
+ draftSubscriptionInvoice,
4140
+ endedAtOnceSubjectOf,
4141
+ featureWithdrawalRowOf,
4142
+ featuresWithdrawnAt,
3331
4143
  formatCustomerNumber,
3332
4144
  formatErrorMessage,
4145
+ formatInvoiceNumber,
3333
4146
  grossFromNet,
4147
+ holdsOnlyXmlCharacters,
3334
4148
  identityCorrectionDelta,
4149
+ invoiceArchiveProtectionProblem,
4150
+ invoiceContentDraftOf,
4151
+ invoiceFooterOf,
4152
+ invoiceGroupsOf,
4153
+ invoiceIssuerOf,
3335
4154
  isBundleRedundant,
4155
+ isFeatureWithdrawnAt,
3336
4156
  isForeignPaymentMethodReferenceError,
4157
+ isInvoiceDocumentChecksum,
3337
4158
  isMaintenanceAnnouncementLapsed,
3338
4159
  isMaintenanceOverrun,
3339
4160
  isPaymentCallbackRejectedError,
@@ -3344,6 +4165,7 @@ var ERROR_MESSAGES_DE = {
3344
4165
  issuerIdentityOf,
3345
4166
  keepOneRecommended,
3346
4167
  keepsVatIdCheck,
4168
+ lastDayInZone,
3347
4169
  maintenanceRetryAfterSeconds,
3348
4170
  maintenanceStatusOf,
3349
4171
  maintenanceWindowStatusOf,
@@ -3353,6 +4175,7 @@ var ERROR_MESSAGES_DE = {
3353
4175
  missingRequiresFor,
3354
4176
  movedIdentityFields,
3355
4177
  netFromGross,
4178
+ nextWithdrawalChange,
3356
4179
  noActivePlanVersion,
3357
4180
  parseZonedInstant,
3358
4181
  percentOf,
@@ -3389,15 +4212,22 @@ var ERROR_MESSAGES_DE = {
3389
4212
  subscriptionBundleGone,
3390
4213
  subscriptionContractGone,
3391
4214
  subscriptionGone,
4215
+ subscriptionInvoiceChargeInvoiced,
4216
+ subscriptionInvoiceColumns,
4217
+ subscriptionInvoiceDocumentChanged,
4218
+ subscriptionInvoiceLineColumns,
3392
4219
  sumToCents,
3393
4220
  taxOriginOf,
3394
4221
  taxOriginWrite,
4222
+ taxPerRate,
3395
4223
  taxTreatmentToJson,
4224
+ toArchivedInvoiceDocument,
3396
4225
  toBundleStemRow,
3397
4226
  toBundleVersionRetirementRecord,
3398
4227
  toCents,
3399
4228
  toContractLineItemRecord,
3400
4229
  toDecimalString,
4230
+ toFeatureWithdrawalRecord,
3401
4231
  toMaintenanceWindowRecord,
3402
4232
  toPlanRow,
3403
4233
  toPlanVersionRow,
@@ -3410,12 +4240,18 @@ var ERROR_MESSAGES_DE = {
3410
4240
  toSubscriberVatIdCheckRecord,
3411
4241
  toSubscriptionBundleRecord,
3412
4242
  toSubscriptionContractRecord,
4243
+ toSubscriptionInvoiceDocumentRecord,
4244
+ toSubscriptionInvoiceRecord,
3413
4245
  toSubscriptionNoticeRecord,
3414
4246
  toTaxOriginValues,
3415
4247
  toTaxTreatment,
3416
4248
  toVersionRetirementRecord,
4249
+ utcMidnightOf,
4250
+ vatCategoryOf,
3417
4251
  vatIdCheckFromStore,
3418
4252
  versionOnSale,
3419
4253
  versionOnSaleOrNext,
3420
- versionSale
4254
+ versionSale,
4255
+ withdrawnFeaturesOf,
4256
+ yearOfDay
3421
4257
  });