@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/.build-stamp +1 -1
- package/dist/index.cjs +847 -11
- package/dist/index.d.cts +1530 -167
- package/dist/index.d.ts +1530 -167
- package/dist/index.js +807 -10
- package/package.json +1 -1
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
|
|
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:
|
|
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(
|
|
2406
|
-
source: oneOf(
|
|
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
|
|
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:
|
|
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:
|
|
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
|
});
|