@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.js
CHANGED
|
@@ -157,6 +157,22 @@ function computeIncludedVat(gross, vatRate) {
|
|
|
157
157
|
const rate = exactDecimal(vatRate);
|
|
158
158
|
return amountOf(cents(exactDecimal(gross), rate, sum(HUNDRED, rate)));
|
|
159
159
|
}
|
|
160
|
+
function taxPerRate(lines) {
|
|
161
|
+
const nets = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
const key = String(line.rate);
|
|
164
|
+
const entry = nets.get(key) ?? { rate: line.rate, terms: [] };
|
|
165
|
+
entry.terms.push(line.net);
|
|
166
|
+
nets.set(key, entry);
|
|
167
|
+
}
|
|
168
|
+
const rates = [...nets.values()].sort((a, b) => a.rate - b.rate).map(({ rate, terms }) => {
|
|
169
|
+
const net2 = sumToCents(...terms);
|
|
170
|
+
return { rate, net: net2, tax: percentOf(net2, rate) };
|
|
171
|
+
});
|
|
172
|
+
const net = sumToCents(...rates.map((entry) => entry.net));
|
|
173
|
+
const tax = sumToCents(...rates.map((entry) => entry.tax));
|
|
174
|
+
return { rates, net, tax, gross: sumToCents(net, tax) };
|
|
175
|
+
}
|
|
160
176
|
|
|
161
177
|
// src/promotion.types.ts
|
|
162
178
|
function promoStatus(promo, today = /* @__PURE__ */ new Date()) {
|
|
@@ -407,7 +423,10 @@ var KINDS = {
|
|
|
407
423
|
"version-retirement-reminder": true,
|
|
408
424
|
"bundle-version-retired": true,
|
|
409
425
|
"bundle-version-retirement-reminder": true,
|
|
410
|
-
"bundle-version-offered": true
|
|
426
|
+
"bundle-version-offered": true,
|
|
427
|
+
"feature-withdrawn": true,
|
|
428
|
+
"feature-withdrawal-lifted": true,
|
|
429
|
+
"ended-at-once": true
|
|
411
430
|
};
|
|
412
431
|
function toSubscriptionNoticeRecord(row) {
|
|
413
432
|
return {
|
|
@@ -538,6 +557,106 @@ function toBundleVersionRetirementRecord(row) {
|
|
|
538
557
|
};
|
|
539
558
|
}
|
|
540
559
|
|
|
560
|
+
// src/feature-withdrawal.types.ts
|
|
561
|
+
var FEATURE_WITHDRAWAL_REASON_MAX_LENGTH = 1e3;
|
|
562
|
+
function isFeatureWithdrawnAt(withdrawal, at) {
|
|
563
|
+
return withdrawal.effectiveFrom <= at && (withdrawal.liftedFrom === null || at < withdrawal.liftedFrom);
|
|
564
|
+
}
|
|
565
|
+
function featuresWithdrawnAt(withdrawals, at) {
|
|
566
|
+
return new Set(
|
|
567
|
+
withdrawals.filter((withdrawal) => isFeatureWithdrawnAt(withdrawal, at)).map((withdrawal) => withdrawal.featureKey)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
function nextWithdrawalChange(withdrawals, at) {
|
|
571
|
+
let next = null;
|
|
572
|
+
for (const { effectiveFrom, liftedFrom } of withdrawals) {
|
|
573
|
+
for (const moment of [effectiveFrom, liftedFrom]) {
|
|
574
|
+
if (moment === null || moment <= at) continue;
|
|
575
|
+
if (next === null || moment < next) next = moment;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return next;
|
|
579
|
+
}
|
|
580
|
+
var FEATURE_WITHDRAWAL_CAPABILITY = "features.withdraw";
|
|
581
|
+
function featureWithdrawalRowOf(record) {
|
|
582
|
+
return {
|
|
583
|
+
id: record.id,
|
|
584
|
+
featureKey: record.featureKey,
|
|
585
|
+
reason: record.reason,
|
|
586
|
+
effectiveFrom: record.effectiveFrom.toISOString(),
|
|
587
|
+
liftedFrom: record.liftedFrom?.toISOString() ?? null,
|
|
588
|
+
reductions: record.reductions,
|
|
589
|
+
announcedAt: record.announcedAt.toISOString(),
|
|
590
|
+
announcedBy: record.announcedBy,
|
|
591
|
+
liftedAt: record.liftedAt?.toISOString() ?? null,
|
|
592
|
+
liftedBy: record.liftedBy,
|
|
593
|
+
toldAt: record.toldAt?.toISOString() ?? null
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function withdrawnFeaturesOf(withdrawals, now) {
|
|
597
|
+
return withdrawals.filter(
|
|
598
|
+
({ effectiveFrom, liftedFrom }) => liftedFrom === null || liftedFrom > now && effectiveFrom < liftedFrom
|
|
599
|
+
).sort((a, b) => a.effectiveFrom.getTime() - b.effectiveFrom.getTime()).map((withdrawal) => ({
|
|
600
|
+
featureKey: withdrawal.featureKey,
|
|
601
|
+
reason: withdrawal.reason,
|
|
602
|
+
effectiveFrom: withdrawal.effectiveFrom.toISOString(),
|
|
603
|
+
liftedFrom: withdrawal.liftedFrom?.toISOString() ?? null
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
function endedAtOnceSubjectOf(subscriptionOrBookingId, at) {
|
|
607
|
+
return `${subscriptionOrBookingId}@${at.toISOString()}`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/feature-withdrawal-mapping.ts
|
|
611
|
+
var TABLE2 = "feature_withdrawals";
|
|
612
|
+
var KINDS2 = { plan: true, bundle: true };
|
|
613
|
+
var CYCLES = { MONTHLY: true, YEARLY: true };
|
|
614
|
+
function toFeatureWithdrawalRecord(row) {
|
|
615
|
+
return {
|
|
616
|
+
id: row.id,
|
|
617
|
+
featureKey: row.featureKey,
|
|
618
|
+
reason: row.reason,
|
|
619
|
+
effectiveFrom: row.effectiveFrom,
|
|
620
|
+
liftedFrom: row.liftedFrom,
|
|
621
|
+
reductions: reductionsOf(row),
|
|
622
|
+
announcedAt: row.announcedAt,
|
|
623
|
+
announcedBy: row.announcedBy,
|
|
624
|
+
liftedAt: row.liftedAt,
|
|
625
|
+
liftedBy: row.liftedBy,
|
|
626
|
+
toldAt: row.toldAt
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function reductionsOf(row) {
|
|
630
|
+
if (!Array.isArray(row.reductions)) {
|
|
631
|
+
throw new Error(`${TABLE2} row '${row.id}' holds reductions that are not a list.`);
|
|
632
|
+
}
|
|
633
|
+
return row.reductions.map((entry, index) => {
|
|
634
|
+
const column = `reductions[${index}]`;
|
|
635
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
636
|
+
throw new Error(`${TABLE2} row '${row.id}' holds ${column} that is not an object.`);
|
|
637
|
+
}
|
|
638
|
+
const { kind, key, billingCycle, amountNet } = entry;
|
|
639
|
+
if (typeof kind !== "string" || typeof key !== "string" || typeof billingCycle !== "string" || typeof amountNet !== "number" || !Number.isFinite(amountNet)) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`${TABLE2} row '${row.id}' holds ${column} without a kind, a key, a billing cycle and a finite amount.`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
const source = { table: TABLE2, id: row.id };
|
|
645
|
+
return {
|
|
646
|
+
kind: oneOf(Object.keys(KINDS2), kind, {
|
|
647
|
+
...source,
|
|
648
|
+
column: `${column}.kind`
|
|
649
|
+
}),
|
|
650
|
+
key,
|
|
651
|
+
billingCycle: oneOf(Object.keys(CYCLES), billingCycle, {
|
|
652
|
+
...source,
|
|
653
|
+
column: `${column}.billingCycle`
|
|
654
|
+
}),
|
|
655
|
+
amountNet
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
541
660
|
// src/scheduled-change-after-write.ts
|
|
542
661
|
var NO_SCHEDULED_CHANGE = {
|
|
543
662
|
pendingPlan: null,
|
|
@@ -674,6 +793,37 @@ function parseZonedInstant(text) {
|
|
|
674
793
|
return Number.isNaN(moment.getTime()) ? null : moment;
|
|
675
794
|
}
|
|
676
795
|
|
|
796
|
+
// src/zoned-day.ts
|
|
797
|
+
function dayInZone(at, timeZone) {
|
|
798
|
+
if (typeof timeZone !== "string" || timeZone === "") {
|
|
799
|
+
throw new RangeError("A day is read in a named time zone.");
|
|
800
|
+
}
|
|
801
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
802
|
+
timeZone,
|
|
803
|
+
year: "numeric",
|
|
804
|
+
month: "2-digit",
|
|
805
|
+
day: "2-digit"
|
|
806
|
+
}).formatToParts(at);
|
|
807
|
+
const part = (type) => parts.find((candidate) => candidate.type === type)?.value;
|
|
808
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
809
|
+
}
|
|
810
|
+
function lastDayInZone(period, timeZone) {
|
|
811
|
+
if (!(period.until.getTime() > period.from.getTime())) {
|
|
812
|
+
throw new RangeError("A period ends after it begins.");
|
|
813
|
+
}
|
|
814
|
+
return dayInZone(new Date(period.until.getTime() - 1), timeZone);
|
|
815
|
+
}
|
|
816
|
+
function addDaysToDay(day, days) {
|
|
817
|
+
if (!Number.isInteger(days)) throw new RangeError("Days are counted in whole days.");
|
|
818
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
819
|
+
if (!year || !month || !date) throw new RangeError(`${day} is not a day.`);
|
|
820
|
+
const moved = new Date(Date.UTC(year, month - 1, date + days));
|
|
821
|
+
return moved.toISOString().slice(0, 10);
|
|
822
|
+
}
|
|
823
|
+
function yearOfDay(day) {
|
|
824
|
+
return Number(day.slice(0, 4));
|
|
825
|
+
}
|
|
826
|
+
|
|
677
827
|
// src/settings-subtree.ts
|
|
678
828
|
var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
|
|
679
829
|
"schemaVersion",
|
|
@@ -1131,6 +1281,80 @@ var BILLING_ERROR_CODES = {
|
|
|
1131
1281
|
* show it instead of guessing.
|
|
1132
1282
|
*/
|
|
1133
1283
|
BUNDLE_VERSION_OFFER_CHANGED: "BUNDLE_VERSION_OFFER_CHANGED",
|
|
1284
|
+
// ── withdrawing a feature for an external reason ──
|
|
1285
|
+
/**
|
|
1286
|
+
* Withdrawing a feature is not offered: the installation's stores cannot
|
|
1287
|
+
* list everybody a withdrawal would reach.
|
|
1288
|
+
*/
|
|
1289
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "FEATURE_WITHDRAWAL_UNAVAILABLE",
|
|
1290
|
+
/** The catalogue knows no feature of that key, so there is nothing to withdraw. Carries `featureKey`. */
|
|
1291
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "FEATURE_WITHDRAWAL_FEATURE_UNKNOWN",
|
|
1292
|
+
/**
|
|
1293
|
+
* The date names a moment before now. A feature is withdrawn from the
|
|
1294
|
+
* moment it is announced or from a later date: the days before were
|
|
1295
|
+
* delivered.
|
|
1296
|
+
*/
|
|
1297
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "FEATURE_WITHDRAWAL_DATE_IN_PAST",
|
|
1298
|
+
/** A withdrawal of the feature is not lifted yet; one at a time. Carries `featureKey`. */
|
|
1299
|
+
FEATURE_WITHDRAWAL_OPEN: "FEATURE_WITHDRAWAL_OPEN",
|
|
1300
|
+
/**
|
|
1301
|
+
* A withdrawal of the feature is lifted only from a date after the one
|
|
1302
|
+
* named, so the two would overlap. Carries `featureKey` and the `date` a
|
|
1303
|
+
* new one may begin on.
|
|
1304
|
+
*/
|
|
1305
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "FEATURE_WITHDRAWAL_OVERLAPS",
|
|
1306
|
+
/**
|
|
1307
|
+
* A reduction names a plan or an add-on, in a rhythm, that no line the
|
|
1308
|
+
* withdrawal reaches is. Carries `kind`, `key` and `billingCycle`.
|
|
1309
|
+
*/
|
|
1310
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED",
|
|
1311
|
+
/** A reduction is named twice. Carries `kind`, `key` and `billingCycle`. */
|
|
1312
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE",
|
|
1313
|
+
/**
|
|
1314
|
+
* A reduction is more than the lowest price among the lines it reduces:
|
|
1315
|
+
* a price is reduced to nothing at most. Carries `key`, `billingCycle` and
|
|
1316
|
+
* that `price`.
|
|
1317
|
+
*/
|
|
1318
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE",
|
|
1319
|
+
/**
|
|
1320
|
+
* The subscriptions the withdrawal reaches changed since the preview the
|
|
1321
|
+
* operator looked at. Nothing was announced; carries the current `preview`.
|
|
1322
|
+
*/
|
|
1323
|
+
FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "FEATURE_WITHDRAWAL_PREVIEW_CHANGED",
|
|
1324
|
+
/** No withdrawal has that id. Carries `withdrawalId`. */
|
|
1325
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "FEATURE_WITHDRAWAL_NOT_FOUND",
|
|
1326
|
+
/** The withdrawal was lifted already. Carries the `date` it is lifted from. */
|
|
1327
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "FEATURE_WITHDRAWAL_ALREADY_LIFTED",
|
|
1328
|
+
/**
|
|
1329
|
+
* Nobody the withdrawal reaches has been told of it yet, and telling them
|
|
1330
|
+
* failed just now: a withdrawal is lifted only once they were. Nothing was
|
|
1331
|
+
* lifted; the next try, or the quarter-hourly run, tells them first.
|
|
1332
|
+
*/
|
|
1333
|
+
FEATURE_WITHDRAWAL_NOT_TOLD_YET: "FEATURE_WITHDRAWAL_NOT_TOLD_YET",
|
|
1334
|
+
/**
|
|
1335
|
+
* The date a withdrawal would be lifted from lies before now: what was
|
|
1336
|
+
* withdrawn until then was withdrawn.
|
|
1337
|
+
*/
|
|
1338
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "FEATURE_WITHDRAWAL_LIFT_IN_PAST",
|
|
1339
|
+
/**
|
|
1340
|
+
* The withdrawal does not take its feature away now — its date is ahead,
|
|
1341
|
+
* or it was lifted — so nothing ends at once because of it. Carries
|
|
1342
|
+
* `featureKey`.
|
|
1343
|
+
*/
|
|
1344
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "FEATURE_WITHDRAWAL_NOT_IN_EFFECT",
|
|
1345
|
+
/**
|
|
1346
|
+
* The withdrawal did not reach the subscription, or the line to end: it
|
|
1347
|
+
* was not running at the announcement, or no longer holds the feature.
|
|
1348
|
+
*/
|
|
1349
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "FEATURE_WITHDRAWAL_DOES_NOT_REACH",
|
|
1350
|
+
/** What was to be ended at once has ended already. */
|
|
1351
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "FEATURE_WITHDRAWAL_ALREADY_ENDED",
|
|
1352
|
+
/**
|
|
1353
|
+
* A cancellation is declared for a later date, and the installation's
|
|
1354
|
+
* subscription write or booking store cannot bring it forward (`endNow`).
|
|
1355
|
+
* It ends as declared. Carries that `date`.
|
|
1356
|
+
*/
|
|
1357
|
+
FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED",
|
|
1134
1358
|
// ── the preview routes' own blockers and warnings ──
|
|
1135
1359
|
//
|
|
1136
1360
|
// These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
|
|
@@ -1288,6 +1512,55 @@ var CONTRACT_ERROR_CODES = {
|
|
|
1288
1512
|
*/
|
|
1289
1513
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED"
|
|
1290
1514
|
};
|
|
1515
|
+
var INVOICE_ERROR_CODES = {
|
|
1516
|
+
/**
|
|
1517
|
+
* A charge already stands on an invoice: a second run issuing the same
|
|
1518
|
+
* charges lost the race. Nothing is written and no number is drawn.
|
|
1519
|
+
*/
|
|
1520
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "SUBSCRIPTION_INVOICE_CHARGE_INVOICED",
|
|
1521
|
+
/**
|
|
1522
|
+
* The invoice lacks content the tax adapter requires of it — `missing`
|
|
1523
|
+
* names the fields, `adapter` the adapter. It draws no number and waits
|
|
1524
|
+
* until what is missing is supplied (`SC-PRIC-027`).
|
|
1525
|
+
*/
|
|
1526
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE",
|
|
1527
|
+
/**
|
|
1528
|
+
* The contract's parties were copied by a migration and the operator has
|
|
1529
|
+
* not confirmed them yet; its charges are not invoiced until then
|
|
1530
|
+
* (`SC-AUD-012`).
|
|
1531
|
+
*/
|
|
1532
|
+
SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED",
|
|
1533
|
+
/**
|
|
1534
|
+
* A file of the invoice's document has another checksum than the one
|
|
1535
|
+
* recorded or archived for it (`invoiceId`, `recorded`, `rendered`), and
|
|
1536
|
+
* the first stays. The run takes the archived file as the one issued, or —
|
|
1537
|
+
* where nothing is archived — records the new rendering in place of the
|
|
1538
|
+
* old (`SC-AUD-019`); the invoice waits only where another run changed the
|
|
1539
|
+
* record or the archive in between, and the next run settles it.
|
|
1540
|
+
*/
|
|
1541
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED",
|
|
1542
|
+
/**
|
|
1543
|
+
* The tax adapter refuses the rendered document — `missing` names what it
|
|
1544
|
+
* lacks or disagrees with, `adapter` the adapter. The invoice keeps its
|
|
1545
|
+
* number and waits for a document that passes (`SC-PRIC-042`).
|
|
1546
|
+
*/
|
|
1547
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED",
|
|
1548
|
+
/**
|
|
1549
|
+
* The archived document does not match the checksum recorded when it was
|
|
1550
|
+
* rendered (`invoiceId`). It is not handed out (`SC-AUD-019`).
|
|
1551
|
+
*/
|
|
1552
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH",
|
|
1553
|
+
/** The invoice's document is not archived yet (`invoiceId`); nothing is offered until it is (`SC-AUD-014`). */
|
|
1554
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED",
|
|
1555
|
+
/** No invoice with this id (`invoiceId`). */
|
|
1556
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "SUBSCRIPTION_INVOICE_NOT_FOUND",
|
|
1557
|
+
/**
|
|
1558
|
+
* The subscriber's record holds no invoice email (`subscriberId`), so the
|
|
1559
|
+
* archived invoice cannot be sent (`SC-PRIC-047`); it is sent once one is
|
|
1560
|
+
* entered.
|
|
1561
|
+
*/
|
|
1562
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL"
|
|
1563
|
+
};
|
|
1291
1564
|
var SUBSCRIBER_ERROR_CODES = {
|
|
1292
1565
|
/**
|
|
1293
1566
|
* A contract, a plan change or a booking for a tenant that has no
|
|
@@ -1468,6 +1741,7 @@ var PLATFORM_ERROR_CODES = {
|
|
|
1468
1741
|
...CATALOG_ERROR_CODES,
|
|
1469
1742
|
...BILLING_ERROR_CODES,
|
|
1470
1743
|
...CONTRACT_ERROR_CODES,
|
|
1744
|
+
...INVOICE_ERROR_CODES,
|
|
1471
1745
|
...SUBSCRIBER_ERROR_CODES,
|
|
1472
1746
|
...REGISTRATION_ERROR_CODES,
|
|
1473
1747
|
...PAYMENT_ERROR_CODES,
|
|
@@ -1521,7 +1795,13 @@ var OPTIONAL_CANONICAL_MODELS = {
|
|
|
1521
1795
|
SubscriptionNotice: ["tenantBilling.subscriptionNotices"],
|
|
1522
1796
|
VersionRetirement: ["tenantBilling.versionRetirements"],
|
|
1523
1797
|
BundleVersionRetirement: ["tenantBilling.bundleVersionRetirements"],
|
|
1798
|
+
FeatureWithdrawal: ["tenantBilling.featureWithdrawals"],
|
|
1524
1799
|
SubscriberLedgerEntry: ["entitlement.subscriberLedgerRepository"],
|
|
1800
|
+
SubscriptionInvoice: ["entitlement.subscriptionInvoiceRepository"],
|
|
1801
|
+
SubscriptionInvoiceLine: ["entitlement.subscriptionInvoiceRepository"],
|
|
1802
|
+
SubscriptionInvoiceNumber: ["entitlement.subscriptionInvoiceRepository"],
|
|
1803
|
+
SubscriptionInvoiceDocument: ["entitlement.subscriptionInvoiceRepository"],
|
|
1804
|
+
SubscriptionInvoiceArchive: ["entitlement.subscriptionInvoiceArchive"],
|
|
1525
1805
|
PromoCodeHold: ["promo.holdRepository"]
|
|
1526
1806
|
};
|
|
1527
1807
|
function membersLeftOut(notAdopted = []) {
|
|
@@ -1704,6 +1984,23 @@ var ERROR_MESSAGES_EN = {
|
|
|
1704
1984
|
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.",
|
|
1705
1985
|
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.",
|
|
1706
1986
|
BUNDLE_VERSION_OFFER_CHANGED: "The offer changed since it was shown. Look at the current one before switching.",
|
|
1987
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "Withdrawing a feature is not offered here: the stores cannot list everybody a withdrawal reaches.",
|
|
1988
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "The catalogue knows no feature {featureKey}.",
|
|
1989
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "A feature is withdrawn from now on or from a later date, never from a date already past.",
|
|
1990
|
+
FEATURE_WITHDRAWAL_OPEN: "{featureKey} is withdrawn already, and that withdrawal is not lifted. Lift it before announcing another.",
|
|
1991
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} is withdrawn until {date}. Another withdrawal can begin on that date at the earliest.",
|
|
1992
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "The reduction for {key} billed {billingCycle} names nothing this withdrawal reaches.",
|
|
1993
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "The reduction for {key} billed {billingCycle} is named twice.",
|
|
1994
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "The reduction for {key} billed {billingCycle} is more than the lowest price it reduces, {price}.",
|
|
1995
|
+
FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "The subscriptions this withdrawal reaches changed since they were shown. Look at them again before announcing it.",
|
|
1996
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "Feature withdrawal {withdrawalId} not found.",
|
|
1997
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "This withdrawal is lifted already, from {date}.",
|
|
1998
|
+
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.",
|
|
1999
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "A withdrawal is lifted from now on or from a later date, never from a date already past.",
|
|
2000
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} is not withdrawn now, so nothing can be ended at once because of it.",
|
|
2001
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "This withdrawal does not reach your subscription, or no longer this booking.",
|
|
2002
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "This has ended already.",
|
|
2003
|
+
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.",
|
|
1707
2004
|
RETIREMENT_SWITCH_NOT_PENDING: "No retirement of your version is waiting for its date, so there is nothing to switch to.",
|
|
1708
2005
|
RETIREMENT_SWITCH_IN_TRIAL: "The switch opens when your trial ends.",
|
|
1709
2006
|
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.",
|
|
@@ -1746,6 +2043,15 @@ var ERROR_MESSAGES_EN = {
|
|
|
1746
2043
|
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
|
|
1747
2044
|
// ── contract ──
|
|
1748
2045
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "The contract states {stated} % in {field}, and the tax adapter decides {decided} %.",
|
|
2046
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Charge '{chargeId}' already stands on an invoice.",
|
|
2047
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "The invoice lacks what the tax adapter {adapter} requires of it: {missing}. It is issued once that is supplied.",
|
|
2048
|
+
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.",
|
|
2049
|
+
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.",
|
|
2050
|
+
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.",
|
|
2051
|
+
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.",
|
|
2052
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "The document of invoice {invoiceId} is not archived yet.",
|
|
2053
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "No invoice '{invoiceId}'.",
|
|
2054
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Subscriber {subscriberId} has no invoice email, so its invoice cannot be sent; it is sent once one is entered.",
|
|
1749
2055
|
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
|
|
1750
2056
|
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
|
|
1751
2057
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
|
|
@@ -2188,17 +2494,20 @@ var ORIGINS = {
|
|
|
2188
2494
|
bundleBooking: true,
|
|
2189
2495
|
bundleChange: true,
|
|
2190
2496
|
credit: true,
|
|
2191
|
-
correction: true
|
|
2497
|
+
correction: true,
|
|
2498
|
+
reductionTakenBack: true
|
|
2192
2499
|
};
|
|
2193
2500
|
var SOURCES = {
|
|
2194
2501
|
plan: true,
|
|
2195
2502
|
bundle: true,
|
|
2196
2503
|
discount: true
|
|
2197
2504
|
};
|
|
2198
|
-
var
|
|
2505
|
+
var SUBSCRIBER_CHARGE_ORIGINS = keysOf(ORIGINS);
|
|
2506
|
+
var SUBSCRIBER_CHARGE_SOURCES = keysOf(SOURCES);
|
|
2507
|
+
var TABLE3 = "subscriber_ledger_entries";
|
|
2199
2508
|
var CENT_TOLERANCE = 1e-6;
|
|
2200
2509
|
function toSubscriberChargeRecord(row) {
|
|
2201
|
-
const where = (column) => ({ table:
|
|
2510
|
+
const where = (column) => ({ table: TABLE3, column, id: row.id });
|
|
2202
2511
|
return {
|
|
2203
2512
|
id: row.id,
|
|
2204
2513
|
subscriberId: row.subscriberId,
|
|
@@ -2206,8 +2515,8 @@ function toSubscriberChargeRecord(row) {
|
|
|
2206
2515
|
subscriptionId: row.subscriptionId,
|
|
2207
2516
|
contractId: row.contractId,
|
|
2208
2517
|
contractLineItemId: row.contractLineItemId,
|
|
2209
|
-
origin: oneOf(
|
|
2210
|
-
source: oneOf(
|
|
2518
|
+
origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
|
|
2519
|
+
source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
|
|
2211
2520
|
sourceRef: row.sourceRef,
|
|
2212
2521
|
periodStart: row.periodStart,
|
|
2213
2522
|
periodEnd: row.periodEnd,
|
|
@@ -2247,6 +2556,15 @@ function keysOf(record) {
|
|
|
2247
2556
|
// src/subscriber-ledger.types.ts
|
|
2248
2557
|
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
2249
2558
|
|
|
2559
|
+
// src/subscription-invoice.types.ts
|
|
2560
|
+
var INVOICE_SEQUENCE_DIGITS = 6;
|
|
2561
|
+
function formatInvoiceNumber(prefix, year, sequence) {
|
|
2562
|
+
if (!Number.isInteger(sequence) || sequence < 1) {
|
|
2563
|
+
throw new RangeError(`An invoice number's sequence starts at 1; ${sequence} is not one.`);
|
|
2564
|
+
}
|
|
2565
|
+
return `${prefix}-${year}-${String(sequence).padStart(INVOICE_SEQUENCE_DIGITS, "0")}`;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2250
2568
|
// src/subscriber.types.ts
|
|
2251
2569
|
var SUBSCRIBER_INVOICE_ADDRESS_FIELDS = [
|
|
2252
2570
|
"addressLine1",
|
|
@@ -2378,6 +2696,420 @@ function textEntries(value) {
|
|
|
2378
2696
|
return entries;
|
|
2379
2697
|
}
|
|
2380
2698
|
|
|
2699
|
+
// src/subscription-invoice-mapping.ts
|
|
2700
|
+
var INVOICES = "subscription_invoices";
|
|
2701
|
+
var LINES = "subscription_invoice_lines";
|
|
2702
|
+
var CENT_TOLERANCE2 = 1e-6;
|
|
2703
|
+
function toSubscriptionInvoiceRecord(row, lines) {
|
|
2704
|
+
const where = (column) => ({ table: INVOICES, column, id: row.id });
|
|
2705
|
+
const treatment = toTaxTreatment(row.taxTreatment);
|
|
2706
|
+
if (!treatment) {
|
|
2707
|
+
throw new Error(`${INVOICES}.taxTreatment of ${row.id} is not a tax treatment.`);
|
|
2708
|
+
}
|
|
2709
|
+
return {
|
|
2710
|
+
id: row.id,
|
|
2711
|
+
number: row.number,
|
|
2712
|
+
numberPrefix: row.numberPrefix,
|
|
2713
|
+
numberYear: row.numberYear,
|
|
2714
|
+
numberSequence: row.numberSequence,
|
|
2715
|
+
tenantId: row.tenantId,
|
|
2716
|
+
subscriberId: row.subscriberId,
|
|
2717
|
+
subscriptionId: row.subscriptionId,
|
|
2718
|
+
contractId: row.contractId,
|
|
2719
|
+
issuedAt: row.issuedAt,
|
|
2720
|
+
issueDate: dayOf2(row.issueDate, where("issueDate")),
|
|
2721
|
+
dueDate: dayOf2(row.dueDate, where("dueDate")),
|
|
2722
|
+
servicePeriodFrom: dayOf2(row.servicePeriodFrom, where("servicePeriodFrom")),
|
|
2723
|
+
servicePeriodUntil: dayOf2(row.servicePeriodUntil, where("servicePeriodUntil")),
|
|
2724
|
+
currency: row.currency,
|
|
2725
|
+
issuer: issuerOf(row.issuerParty, where("issuerParty")),
|
|
2726
|
+
subscriber: subscriberOf(row.subscriberParty, where("subscriberParty")),
|
|
2727
|
+
footer: footerOf(row.footer, where("footer")),
|
|
2728
|
+
taxTreatment: treatment,
|
|
2729
|
+
taxes: taxesOf(row.taxes, where("taxes")),
|
|
2730
|
+
totalNet: amountOf2(row.totalNet, where("totalNet")),
|
|
2731
|
+
totalTax: amountOf2(row.totalTax, where("totalTax")),
|
|
2732
|
+
totalGross: amountOf2(row.totalGross, where("totalGross")),
|
|
2733
|
+
lines: [...lines].sort((a, b) => a.position - b.position).map((line) => toSubscriptionInvoiceLineRecord(line)),
|
|
2734
|
+
createdAt: row.createdAt
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
function toSubscriptionInvoiceLineRecord(row) {
|
|
2738
|
+
const where = (column) => ({ table: LINES, column, id: row.id });
|
|
2739
|
+
return {
|
|
2740
|
+
id: row.id,
|
|
2741
|
+
invoiceId: row.invoiceId,
|
|
2742
|
+
position: row.position,
|
|
2743
|
+
chargeId: row.chargeId,
|
|
2744
|
+
contractLineItemId: row.contractLineItemId,
|
|
2745
|
+
title: row.title,
|
|
2746
|
+
origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
|
|
2747
|
+
source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
|
|
2748
|
+
periodFrom: dayOf2(row.periodFrom, where("periodFrom")),
|
|
2749
|
+
periodUntil: dayOf2(row.periodUntil, where("periodUntil")),
|
|
2750
|
+
amountNet: amountOf2(row.amountNet, where("amountNet")),
|
|
2751
|
+
taxRate: amountOf2(row.taxRate, where("taxRate"))
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
function subscriptionInvoiceColumns(invoice, drawn) {
|
|
2755
|
+
return {
|
|
2756
|
+
number: drawn.number,
|
|
2757
|
+
numberPrefix: invoice.numberPrefix,
|
|
2758
|
+
numberYear: invoice.numberYear,
|
|
2759
|
+
numberSequence: drawn.numberSequence,
|
|
2760
|
+
tenantId: invoice.tenantId,
|
|
2761
|
+
subscriberId: invoice.subscriberId,
|
|
2762
|
+
subscriptionId: invoice.subscriptionId,
|
|
2763
|
+
contractId: invoice.contractId,
|
|
2764
|
+
issuedAt: invoice.issuedAt,
|
|
2765
|
+
issueDate: invoice.issueDate,
|
|
2766
|
+
dueDate: invoice.dueDate,
|
|
2767
|
+
servicePeriodFrom: invoice.servicePeriodFrom,
|
|
2768
|
+
servicePeriodUntil: invoice.servicePeriodUntil,
|
|
2769
|
+
currency: invoice.currency,
|
|
2770
|
+
issuerParty: { ...invoice.issuer },
|
|
2771
|
+
subscriberParty: { ...invoice.subscriber },
|
|
2772
|
+
footer: [...invoice.footer],
|
|
2773
|
+
taxTreatment: taxTreatmentToJson(invoice.taxTreatment),
|
|
2774
|
+
taxes: invoice.taxes.map((entry) => ({ rate: entry.rate, net: entry.net, tax: entry.tax })),
|
|
2775
|
+
totalNet: centsText(invoice.totalNet, "totalNet"),
|
|
2776
|
+
totalTax: centsText(invoice.totalTax, "totalTax"),
|
|
2777
|
+
totalGross: centsText(invoice.totalGross, "totalGross")
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function subscriptionInvoiceLineColumns(line, invoiceId) {
|
|
2781
|
+
return {
|
|
2782
|
+
invoiceId,
|
|
2783
|
+
position: line.position,
|
|
2784
|
+
chargeId: line.chargeId,
|
|
2785
|
+
contractLineItemId: line.contractLineItemId,
|
|
2786
|
+
title: line.title,
|
|
2787
|
+
origin: line.origin,
|
|
2788
|
+
source: line.source,
|
|
2789
|
+
periodFrom: line.periodFrom,
|
|
2790
|
+
periodUntil: line.periodUntil,
|
|
2791
|
+
amountNet: centsText(line.amountNet, "amountNet"),
|
|
2792
|
+
taxRate: centsText(line.taxRate, "taxRate")
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function utcMidnightOf(day) {
|
|
2796
|
+
return /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
|
|
2797
|
+
}
|
|
2798
|
+
var DAY_LENGTH = "YYYY-MM-DD".length;
|
|
2799
|
+
function dayOf2(value, where) {
|
|
2800
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
2801
|
+
return value.toISOString().slice(0, DAY_LENGTH);
|
|
2802
|
+
}
|
|
2803
|
+
if (typeof value === "string" && value.length === DAY_LENGTH && !Number.isNaN(Date.parse(value))) {
|
|
2804
|
+
return value;
|
|
2805
|
+
}
|
|
2806
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a day.`);
|
|
2807
|
+
}
|
|
2808
|
+
function footerOf(value, where) {
|
|
2809
|
+
if (!Array.isArray(value) || !value.every((line) => typeof line === "string")) {
|
|
2810
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of lines.`);
|
|
2811
|
+
}
|
|
2812
|
+
return [...value];
|
|
2813
|
+
}
|
|
2814
|
+
function amountOf2(value, where) {
|
|
2815
|
+
const amount = Number(value === null || value === void 0 ? NaN : String(value));
|
|
2816
|
+
if (!Number.isFinite(amount)) {
|
|
2817
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not an amount.`);
|
|
2818
|
+
}
|
|
2819
|
+
return amount;
|
|
2820
|
+
}
|
|
2821
|
+
function centsText(amount, field) {
|
|
2822
|
+
const cents2 = amount * 100;
|
|
2823
|
+
if (!Number.isFinite(cents2) || Math.abs(cents2 - Math.round(cents2)) > CENT_TOLERANCE2) {
|
|
2824
|
+
throw new Error(
|
|
2825
|
+
`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.`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
return amount.toFixed(2);
|
|
2829
|
+
}
|
|
2830
|
+
function textOrNull2(source, key, where) {
|
|
2831
|
+
const value = source[key];
|
|
2832
|
+
if (value === null || value === void 0) return null;
|
|
2833
|
+
if (typeof value !== "string") {
|
|
2834
|
+
throw new Error(`${where.table}.${where.column}.${key} of ${where.id} is not text.`);
|
|
2835
|
+
}
|
|
2836
|
+
return value;
|
|
2837
|
+
}
|
|
2838
|
+
function partyOf(value, where) {
|
|
2839
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2840
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a party.`);
|
|
2841
|
+
}
|
|
2842
|
+
const source = value;
|
|
2843
|
+
const legalName = textOrNull2(source, "legalName", where);
|
|
2844
|
+
if (legalName === null) {
|
|
2845
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} names no legal name.`);
|
|
2846
|
+
}
|
|
2847
|
+
return {
|
|
2848
|
+
legalName,
|
|
2849
|
+
vatId: textOrNull2(source, "vatId", where),
|
|
2850
|
+
taxNumber: textOrNull2(source, "taxNumber", where),
|
|
2851
|
+
addressLine1: textOrNull2(source, "addressLine1", where),
|
|
2852
|
+
addressLine2: textOrNull2(source, "addressLine2", where),
|
|
2853
|
+
postalCode: textOrNull2(source, "postalCode", where),
|
|
2854
|
+
city: textOrNull2(source, "city", where),
|
|
2855
|
+
country: textOrNull2(source, "country", where)
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
function issuerOf(value, where) {
|
|
2859
|
+
return partyOf(value, where);
|
|
2860
|
+
}
|
|
2861
|
+
function subscriberOf(value, where) {
|
|
2862
|
+
const party = partyOf(value, where);
|
|
2863
|
+
const customerNumber = textOrNull2(value, "customerNumber", where);
|
|
2864
|
+
if (customerNumber === null) {
|
|
2865
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} names no customer number.`);
|
|
2866
|
+
}
|
|
2867
|
+
return { ...party, customerNumber };
|
|
2868
|
+
}
|
|
2869
|
+
function taxesOf(value, where) {
|
|
2870
|
+
if (!Array.isArray(value)) {
|
|
2871
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of rates.`);
|
|
2872
|
+
}
|
|
2873
|
+
return value.map((entry) => {
|
|
2874
|
+
const item = entry ?? {};
|
|
2875
|
+
return {
|
|
2876
|
+
rate: amountOf2(item.rate, where),
|
|
2877
|
+
net: amountOf2(item.net, where),
|
|
2878
|
+
tax: amountOf2(item.tax, where)
|
|
2879
|
+
};
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
// src/subscription-invoice-refusals.ts
|
|
2884
|
+
function subscriptionInvoiceChargeInvoiced(chargeId) {
|
|
2885
|
+
return new PersistenceRefusal(
|
|
2886
|
+
INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_CHARGE_INVOICED,
|
|
2887
|
+
"moved",
|
|
2888
|
+
`Charge '${chargeId}' already stands on an invoice.`,
|
|
2889
|
+
{ chargeId }
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
function subscriptionInvoiceDocumentChanged(invoiceId, recorded, rendered) {
|
|
2893
|
+
return new PersistenceRefusal(
|
|
2894
|
+
INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED,
|
|
2895
|
+
"moved",
|
|
2896
|
+
`The document rendered for invoice '${invoiceId}' differs from the one recorded for it.`,
|
|
2897
|
+
{ invoiceId, recorded, rendered }
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// src/subscription-invoice-draft.ts
|
|
2902
|
+
function invoiceGroupsOf(charges) {
|
|
2903
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2904
|
+
for (const charge of charges) {
|
|
2905
|
+
const key = `${charge.contractId}|${charge.bookedAt.toISOString()}`;
|
|
2906
|
+
const group = groups.get(key);
|
|
2907
|
+
if (group) group.charges.push(charge);
|
|
2908
|
+
else
|
|
2909
|
+
groups.set(key, {
|
|
2910
|
+
contractId: charge.contractId,
|
|
2911
|
+
bookedAt: charge.bookedAt,
|
|
2912
|
+
charges: [charge]
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
return [...groups.values()].filter((group) => group.charges.some((charge) => charge.amountNet !== 0)).sort(
|
|
2916
|
+
(a, b) => a.bookedAt.getTime() - b.bookedAt.getTime() || a.contractId.localeCompare(b.contractId)
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2919
|
+
function draftSubscriptionInvoice(input) {
|
|
2920
|
+
const { group, treatment, timeZone } = input;
|
|
2921
|
+
const lines = linesOf(input);
|
|
2922
|
+
const issueDate = dayInZone(input.issuedAt, timeZone);
|
|
2923
|
+
const tax = input.tax(lines.map((line) => ({ net: line.amountNet, rate: line.taxRate })));
|
|
2924
|
+
return {
|
|
2925
|
+
numberPrefix: input.numberPrefix,
|
|
2926
|
+
numberYear: yearOfDay(issueDate),
|
|
2927
|
+
tenantId: input.tenantId,
|
|
2928
|
+
subscriberId: input.subscriberId,
|
|
2929
|
+
subscriptionId: group.charges[0].subscriptionId,
|
|
2930
|
+
contractId: group.contractId,
|
|
2931
|
+
issuedAt: input.issuedAt,
|
|
2932
|
+
issueDate,
|
|
2933
|
+
dueDate: addDaysToDay(issueDate, input.paymentTermDays),
|
|
2934
|
+
servicePeriodFrom: lines.map((line) => line.periodFrom).sort()[0],
|
|
2935
|
+
servicePeriodUntil: lines.map((line) => line.periodUntil).sort().at(-1),
|
|
2936
|
+
currency: currencyOf(group),
|
|
2937
|
+
issuer: input.issuer,
|
|
2938
|
+
subscriber: input.subscriber,
|
|
2939
|
+
footer: [...input.footer],
|
|
2940
|
+
taxTreatment: treatment,
|
|
2941
|
+
taxes: tax.rates,
|
|
2942
|
+
totalNet: tax.net,
|
|
2943
|
+
totalTax: tax.tax,
|
|
2944
|
+
totalGross: tax.gross,
|
|
2945
|
+
lines
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
function invoiceContentDraftOf(invoice) {
|
|
2949
|
+
return {
|
|
2950
|
+
issuer: invoice.issuer,
|
|
2951
|
+
subscriber: invoice.subscriber,
|
|
2952
|
+
treatment: invoice.taxTreatment,
|
|
2953
|
+
issueDate: invoice.issueDate,
|
|
2954
|
+
dueDate: invoice.dueDate,
|
|
2955
|
+
servicePeriod: { from: invoice.servicePeriodFrom, until: invoice.servicePeriodUntil },
|
|
2956
|
+
lines: invoice.lines.map((line) => ({
|
|
2957
|
+
title: line.title,
|
|
2958
|
+
net: line.amountNet,
|
|
2959
|
+
rate: line.taxRate
|
|
2960
|
+
}))
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
function linesOf(input) {
|
|
2964
|
+
const placeOf = new Map(input.contractLines.map((line, index) => [line.id, index]));
|
|
2965
|
+
const place = (charge) => {
|
|
2966
|
+
const index = placeOf.get(charge.contractLineItemId);
|
|
2967
|
+
if (index === void 0) {
|
|
2968
|
+
throw new Error(
|
|
2969
|
+
`Charge ${charge.id} names contract line ${charge.contractLineItemId}, which contract ${input.group.contractId} does not have.`
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
return index;
|
|
2973
|
+
};
|
|
2974
|
+
return [...input.group.charges].sort(
|
|
2975
|
+
(a, b) => place(a) - place(b) || a.periodStart.getTime() - b.periodStart.getTime() || a.origin.localeCompare(b.origin)
|
|
2976
|
+
).map((charge, index) => ({
|
|
2977
|
+
position: index + 1,
|
|
2978
|
+
chargeId: charge.id,
|
|
2979
|
+
contractLineItemId: charge.contractLineItemId,
|
|
2980
|
+
title: input.contractLines[place(charge)].titleSnapshot,
|
|
2981
|
+
origin: charge.origin,
|
|
2982
|
+
source: charge.source,
|
|
2983
|
+
periodFrom: dayInZone(charge.periodStart, input.timeZone),
|
|
2984
|
+
periodUntil: lastDayInZone(
|
|
2985
|
+
{ from: charge.periodStart, until: charge.periodEnd },
|
|
2986
|
+
input.timeZone
|
|
2987
|
+
),
|
|
2988
|
+
amountNet: charge.amountNet,
|
|
2989
|
+
taxRate: input.treatment.rate
|
|
2990
|
+
}));
|
|
2991
|
+
}
|
|
2992
|
+
function currencyOf(group) {
|
|
2993
|
+
const currencies = new Set(group.charges.map((charge) => charge.currency));
|
|
2994
|
+
if (currencies.size !== 1) {
|
|
2995
|
+
throw new Error(
|
|
2996
|
+
`The charges of contract ${group.contractId} booked at ${group.bookedAt.toISOString()} are in ${[...currencies].join(" and ")}; an invoice is in one currency.`
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
return [...currencies][0];
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
// src/invoice-issuer.ts
|
|
3003
|
+
function invoiceIssuerOf(copy, configured, history) {
|
|
3004
|
+
const target = configured && identityOf2(configured);
|
|
3005
|
+
let reached = identityOf2(copy);
|
|
3006
|
+
if (!target || !reached) return { ...copy };
|
|
3007
|
+
for (const step of history) {
|
|
3008
|
+
if (sameLegalIdentity(reached, target)) break;
|
|
3009
|
+
const change = classifyIssuerChange(
|
|
3010
|
+
recordedIssuerIdentity(step.previous),
|
|
3011
|
+
issuerBlockOf(step.current)
|
|
3012
|
+
);
|
|
3013
|
+
if (change.kind === "corrected" && sameLegalIdentity(change.recorded, reached)) {
|
|
3014
|
+
reached = change.current;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return sameLegalIdentity(reached, target) ? { ...configured } : { ...copy };
|
|
3018
|
+
}
|
|
3019
|
+
function invoiceFooterOf(issuer, configured, footer) {
|
|
3020
|
+
const named = identityOf2(issuer);
|
|
3021
|
+
const target = configured && identityOf2(configured);
|
|
3022
|
+
return named && target && sameLegalIdentity(named, target) ? [...footer] : [];
|
|
3023
|
+
}
|
|
3024
|
+
function identityOf2(party) {
|
|
3025
|
+
return recordedIssuerIdentity({ issuer: party });
|
|
3026
|
+
}
|
|
3027
|
+
function issuerBlockOf(values) {
|
|
3028
|
+
const issuer = values.issuer;
|
|
3029
|
+
if (issuer === null || typeof issuer !== "object" || Array.isArray(issuer)) return void 0;
|
|
3030
|
+
return issuer;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
// src/invoice-document.types.ts
|
|
3034
|
+
var INVOICE_DOCUMENT_CAPABILITY = "invoices.read";
|
|
3035
|
+
|
|
3036
|
+
// src/invoice-document-mapping.ts
|
|
3037
|
+
var DOCUMENTS = "subscription_invoice_documents";
|
|
3038
|
+
var ARCHIVE = "subscription_invoice_archive";
|
|
3039
|
+
var CHECKSUM = /^[0-9a-f]{64}$/;
|
|
3040
|
+
function isInvoiceDocumentChecksum(value) {
|
|
3041
|
+
return typeof value === "string" && CHECKSUM.test(value);
|
|
3042
|
+
}
|
|
3043
|
+
function toSubscriptionInvoiceDocumentRecord(row) {
|
|
3044
|
+
if (!isInvoiceDocumentChecksum(row.checksum)) {
|
|
3045
|
+
throw new Error(`${DOCUMENTS}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
|
|
3046
|
+
}
|
|
3047
|
+
const size = Number(row.size);
|
|
3048
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3049
|
+
throw new Error(`${DOCUMENTS}.size of ${row.invoiceId} is not a size in bytes.`);
|
|
3050
|
+
}
|
|
3051
|
+
return {
|
|
3052
|
+
invoiceId: row.invoiceId,
|
|
3053
|
+
checksum: row.checksum,
|
|
3054
|
+
mediaType: row.mediaType,
|
|
3055
|
+
fileName: row.fileName,
|
|
3056
|
+
size,
|
|
3057
|
+
renderedAt: row.renderedAt,
|
|
3058
|
+
archivedAt: row.archivedAt,
|
|
3059
|
+
deliveryClaimedAt: row.deliveryClaimedAt,
|
|
3060
|
+
deliveredAt: row.deliveredAt,
|
|
3061
|
+
deliveredTo: row.deliveredTo
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
function toArchivedInvoiceDocument(row) {
|
|
3065
|
+
if (!isInvoiceDocumentChecksum(row.checksum)) {
|
|
3066
|
+
throw new Error(`${ARCHIVE}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
|
|
3067
|
+
}
|
|
3068
|
+
if (!(row.content instanceof Uint8Array)) {
|
|
3069
|
+
throw new Error(`${ARCHIVE}.content of ${row.invoiceId} is not a file.`);
|
|
3070
|
+
}
|
|
3071
|
+
return {
|
|
3072
|
+
invoiceId: row.invoiceId,
|
|
3073
|
+
checksum: row.checksum,
|
|
3074
|
+
mediaType: row.mediaType,
|
|
3075
|
+
fileName: row.fileName,
|
|
3076
|
+
// A copy that owns its bytes: a driver's `Buffer` may be a view into a
|
|
3077
|
+
// pooled allocation, and a caller handing it on must not see that pool.
|
|
3078
|
+
content: new Uint8Array(row.content),
|
|
3079
|
+
archivedAt: row.archivedAt
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
var INVOICE_ARCHIVE_TRIGGERS = [
|
|
3083
|
+
"subscription_invoice_archive_is_kept",
|
|
3084
|
+
"subscription_invoice_archive_is_not_emptied"
|
|
3085
|
+
];
|
|
3086
|
+
function invoiceArchiveProtectionProblem(enabledTriggers) {
|
|
3087
|
+
const missing = INVOICE_ARCHIVE_TRIGGERS.filter((name) => !enabledTriggers.includes(name));
|
|
3088
|
+
if (missing.length === 0) return null;
|
|
3089
|
+
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.`;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/e-invoice-format.ts
|
|
3093
|
+
var CATEGORY = {
|
|
3094
|
+
standard: "S",
|
|
3095
|
+
reduced: "S",
|
|
3096
|
+
exempt: "E",
|
|
3097
|
+
"small-business": "E",
|
|
3098
|
+
"reverse-charge": "AE",
|
|
3099
|
+
"not-taxable": "O"
|
|
3100
|
+
};
|
|
3101
|
+
function vatCategoryOf(kind) {
|
|
3102
|
+
return CATEGORY[kind];
|
|
3103
|
+
}
|
|
3104
|
+
var NOT_XML = /[^\t\n\r\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu;
|
|
3105
|
+
function holdsOnlyXmlCharacters(text) {
|
|
3106
|
+
return text.search(NOT_XML) === -1;
|
|
3107
|
+
}
|
|
3108
|
+
var CARRIAGE_RETURN = /\r\n?/g;
|
|
3109
|
+
function asXmlReadsIt(text) {
|
|
3110
|
+
return text.replace(NOT_XML, "?").replace(CARRIAGE_RETURN, "\n");
|
|
3111
|
+
}
|
|
3112
|
+
|
|
2381
3113
|
// src/subscriber-mapping.ts
|
|
2382
3114
|
function formatCustomerNumber(prefix, sequence) {
|
|
2383
3115
|
return `${prefix}${sequence}`;
|
|
@@ -2507,7 +3239,7 @@ function toIdentityValues(value) {
|
|
|
2507
3239
|
// src/subscriber-payment-method-mapping.ts
|
|
2508
3240
|
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
2509
3241
|
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
2510
|
-
var
|
|
3242
|
+
var TABLE4 = "subscriber_payment_methods";
|
|
2511
3243
|
function toSubscriberPaymentMethodRecord(row) {
|
|
2512
3244
|
return {
|
|
2513
3245
|
id: row.id,
|
|
@@ -2516,7 +3248,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
2516
3248
|
provider: row.provider,
|
|
2517
3249
|
customerRef: row.customerRef,
|
|
2518
3250
|
paymentMethodRef: row.paymentMethodRef,
|
|
2519
|
-
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table:
|
|
3251
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE4, column: "type", id: row.id }),
|
|
2520
3252
|
brand: row.brand,
|
|
2521
3253
|
last4: row.last4,
|
|
2522
3254
|
expiryMonth: row.expiryMonth,
|
|
@@ -2524,7 +3256,7 @@ function toSubscriberPaymentMethodRecord(row) {
|
|
|
2524
3256
|
country: row.country,
|
|
2525
3257
|
bankCode: row.bankCode,
|
|
2526
3258
|
mandateReference: row.mandateReference,
|
|
2527
|
-
status: oneOf(STATUSES, row.status, { table:
|
|
3259
|
+
status: oneOf(STATUSES, row.status, { table: TABLE4, column: "status", id: row.id }),
|
|
2528
3260
|
confirmedAt: row.confirmedAt,
|
|
2529
3261
|
replacedAt: row.replacedAt,
|
|
2530
3262
|
createdAt: row.createdAt
|
|
@@ -2922,6 +3654,23 @@ var ERROR_MESSAGES_DE = {
|
|
|
2922
3654
|
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.",
|
|
2923
3655
|
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.",
|
|
2924
3656
|
BUNDLE_VERSION_OFFER_CHANGED: "Das Angebot hat sich seit der Anzeige ge\xE4ndert. Bitte sieh dir das aktuelle an, bevor du wechselst.",
|
|
3657
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "Ein Feature zur\xFCckzuziehen ist hier nicht m\xF6glich: Die Speicher k\xF6nnen nicht alle auflisten, die ein R\xFCckzug erreicht.",
|
|
3658
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "Der Katalog kennt kein Feature {featureKey}.",
|
|
3659
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "Ein Feature wird ab jetzt oder ab einem sp\xE4teren Datum zur\xFCckgezogen, nie ab einem vergangenen.",
|
|
3660
|
+
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.",
|
|
3661
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} ist bis zum {date} zur\xFCckgezogen. Ein weiterer R\xFCckzug kann fr\xFChestens an diesem Tag beginnen.",
|
|
3662
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} betrifft nichts, was dieser R\xFCckzug erreicht.",
|
|
3663
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist doppelt angegeben.",
|
|
3664
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist h\xF6her als der niedrigste Preis, den sie mindert: {price}.",
|
|
3665
|
+
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.",
|
|
3666
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "R\xFCckzug {withdrawalId} nicht gefunden.",
|
|
3667
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "Dieser R\xFCckzug ist bereits aufgehoben, ab dem {date}.",
|
|
3668
|
+
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.",
|
|
3669
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "Ein R\xFCckzug wird ab jetzt oder ab einem sp\xE4teren Datum aufgehoben, nie ab einem vergangenen.",
|
|
3670
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} ist gerade nicht zur\xFCckgezogen. Deshalb kann nichts sofort beendet werden.",
|
|
3671
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "Dieser R\xFCckzug betrifft Ihr Abonnement nicht, oder diese Buchung nicht mehr.",
|
|
3672
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "Das ist bereits beendet.",
|
|
3673
|
+
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.",
|
|
2925
3674
|
RETIREMENT_SWITCH_NOT_PENDING: "F\xFCr Ihre Version steht keine Einstellung mehr bevor. Es gibt nichts, wohin Sie wechseln k\xF6nnten.",
|
|
2926
3675
|
RETIREMENT_SWITCH_IN_TRIAL: "Der Wechsel ist m\xF6glich, sobald Ihre Testphase endet.",
|
|
2927
3676
|
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.",
|
|
@@ -2964,6 +3713,15 @@ var ERROR_MESSAGES_DE = {
|
|
|
2964
3713
|
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions l\xE4sst sich nicht senken.",
|
|
2965
3714
|
// ── contract ──
|
|
2966
3715
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "Der Vertrag nennt in {field} {stated} %, der Steueradapter entscheidet {decided} %.",
|
|
3716
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Die Forderung '{chargeId}' steht bereits auf einer Rechnung.",
|
|
3717
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "Der Rechnung fehlt, was der Steueradapter {adapter} verlangt: {missing}. Sie wird ausgestellt, sobald es erg\xE4nzt ist.",
|
|
3718
|
+
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.",
|
|
3719
|
+
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.",
|
|
3720
|
+
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.",
|
|
3721
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "Das archivierte Dokument der Rechnung {invoiceId} passt nicht zur beim Rendern festgehaltenen Pr\xFCfsumme und wird nicht herausgegeben.",
|
|
3722
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "Das Dokument der Rechnung {invoiceId} ist noch nicht archiviert.",
|
|
3723
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "Keine Rechnung '{invoiceId}'.",
|
|
3724
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Der Abonnent {subscriberId} hat keine Rechnungs-E-Mail; seine Rechnung wird versendet, sobald eine eingetragen ist.",
|
|
2967
3725
|
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "Aus einem Checkout-Angebot entsteht nur ein einziger Vertrag, und das erst, wenn seine Positionen eingefroren sind.",
|
|
2968
3726
|
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "Ein Checkout-Angebot braucht eine eingefrorene Plan-Position.",
|
|
2969
3727
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
|
|
@@ -3062,7 +3820,13 @@ export {
|
|
|
3062
3820
|
ERROR_MESSAGES_DE,
|
|
3063
3821
|
ERROR_MESSAGES_EN,
|
|
3064
3822
|
FEATURE_NOT_LICENSED,
|
|
3823
|
+
FEATURE_WITHDRAWAL_CAPABILITY,
|
|
3824
|
+
FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
|
|
3065
3825
|
ForeignPaymentMethodReferenceError,
|
|
3826
|
+
INVOICE_ARCHIVE_TRIGGERS,
|
|
3827
|
+
INVOICE_DOCUMENT_CAPABILITY,
|
|
3828
|
+
INVOICE_ERROR_CODES,
|
|
3829
|
+
INVOICE_SEQUENCE_DIGITS,
|
|
3066
3830
|
LEGAL_IDENTITY_FIELDS,
|
|
3067
3831
|
MAINTENANCE_CAPABILITY,
|
|
3068
3832
|
MAINTENANCE_ERROR_CODES,
|
|
@@ -3097,6 +3861,8 @@ export {
|
|
|
3097
3861
|
SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
3098
3862
|
SUBSCRIBER_ATTENTION_CAPABILITY,
|
|
3099
3863
|
SUBSCRIBER_ATTENTION_PAGE_SIZE,
|
|
3864
|
+
SUBSCRIBER_CHARGE_ORIGINS,
|
|
3865
|
+
SUBSCRIBER_CHARGE_SOURCES,
|
|
3100
3866
|
SUBSCRIBER_CORRECTION_CAPABILITY,
|
|
3101
3867
|
SUBSCRIBER_ERROR_CODES,
|
|
3102
3868
|
SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
|
|
@@ -3105,7 +3871,9 @@ export {
|
|
|
3105
3871
|
TAX_ERROR_CODES,
|
|
3106
3872
|
TAX_TREATMENT_KINDS,
|
|
3107
3873
|
VERSION_RETIREMENT_CAPABILITY,
|
|
3874
|
+
addDaysToDay,
|
|
3108
3875
|
applyPromo,
|
|
3876
|
+
asXmlReadsIt,
|
|
3109
3877
|
assertPersistenceCapabilities,
|
|
3110
3878
|
buildActivePlanVersionWhere,
|
|
3111
3879
|
buildActiveVersionWhere,
|
|
@@ -3129,14 +3897,28 @@ export {
|
|
|
3129
3897
|
contractPartiesOf,
|
|
3130
3898
|
coverageExcludingSelf,
|
|
3131
3899
|
customLimitsReader,
|
|
3900
|
+
dayInZone,
|
|
3132
3901
|
definedFields,
|
|
3133
3902
|
diffSettings,
|
|
3903
|
+
draftSubscriptionInvoice,
|
|
3904
|
+
endedAtOnceSubjectOf,
|
|
3905
|
+
featureWithdrawalRowOf,
|
|
3906
|
+
featuresWithdrawnAt,
|
|
3134
3907
|
formatCustomerNumber,
|
|
3135
3908
|
formatErrorMessage,
|
|
3909
|
+
formatInvoiceNumber,
|
|
3136
3910
|
grossFromNet,
|
|
3911
|
+
holdsOnlyXmlCharacters,
|
|
3137
3912
|
identityCorrectionDelta,
|
|
3913
|
+
invoiceArchiveProtectionProblem,
|
|
3914
|
+
invoiceContentDraftOf,
|
|
3915
|
+
invoiceFooterOf,
|
|
3916
|
+
invoiceGroupsOf,
|
|
3917
|
+
invoiceIssuerOf,
|
|
3138
3918
|
isBundleRedundant,
|
|
3919
|
+
isFeatureWithdrawnAt,
|
|
3139
3920
|
isForeignPaymentMethodReferenceError,
|
|
3921
|
+
isInvoiceDocumentChecksum,
|
|
3140
3922
|
isMaintenanceAnnouncementLapsed,
|
|
3141
3923
|
isMaintenanceOverrun,
|
|
3142
3924
|
isPaymentCallbackRejectedError,
|
|
@@ -3147,6 +3929,7 @@ export {
|
|
|
3147
3929
|
issuerIdentityOf,
|
|
3148
3930
|
keepOneRecommended,
|
|
3149
3931
|
keepsVatIdCheck,
|
|
3932
|
+
lastDayInZone,
|
|
3150
3933
|
maintenanceRetryAfterSeconds,
|
|
3151
3934
|
maintenanceStatusOf,
|
|
3152
3935
|
maintenanceWindowStatusOf,
|
|
@@ -3156,6 +3939,7 @@ export {
|
|
|
3156
3939
|
missingRequiresFor,
|
|
3157
3940
|
movedIdentityFields,
|
|
3158
3941
|
netFromGross,
|
|
3942
|
+
nextWithdrawalChange,
|
|
3159
3943
|
noActivePlanVersion,
|
|
3160
3944
|
parseZonedInstant,
|
|
3161
3945
|
percentOf,
|
|
@@ -3192,15 +3976,22 @@ export {
|
|
|
3192
3976
|
subscriptionBundleGone,
|
|
3193
3977
|
subscriptionContractGone,
|
|
3194
3978
|
subscriptionGone,
|
|
3979
|
+
subscriptionInvoiceChargeInvoiced,
|
|
3980
|
+
subscriptionInvoiceColumns,
|
|
3981
|
+
subscriptionInvoiceDocumentChanged,
|
|
3982
|
+
subscriptionInvoiceLineColumns,
|
|
3195
3983
|
sumToCents,
|
|
3196
3984
|
taxOriginOf,
|
|
3197
3985
|
taxOriginWrite,
|
|
3986
|
+
taxPerRate,
|
|
3198
3987
|
taxTreatmentToJson,
|
|
3988
|
+
toArchivedInvoiceDocument,
|
|
3199
3989
|
toBundleStemRow,
|
|
3200
3990
|
toBundleVersionRetirementRecord,
|
|
3201
3991
|
toCents,
|
|
3202
3992
|
toContractLineItemRecord,
|
|
3203
3993
|
toDecimalString,
|
|
3994
|
+
toFeatureWithdrawalRecord,
|
|
3204
3995
|
toMaintenanceWindowRecord,
|
|
3205
3996
|
toPlanRow,
|
|
3206
3997
|
toPlanVersionRow,
|
|
@@ -3213,12 +4004,18 @@ export {
|
|
|
3213
4004
|
toSubscriberVatIdCheckRecord,
|
|
3214
4005
|
toSubscriptionBundleRecord,
|
|
3215
4006
|
toSubscriptionContractRecord,
|
|
4007
|
+
toSubscriptionInvoiceDocumentRecord,
|
|
4008
|
+
toSubscriptionInvoiceRecord,
|
|
3216
4009
|
toSubscriptionNoticeRecord,
|
|
3217
4010
|
toTaxOriginValues,
|
|
3218
4011
|
toTaxTreatment,
|
|
3219
4012
|
toVersionRetirementRecord,
|
|
4013
|
+
utcMidnightOf,
|
|
4014
|
+
vatCategoryOf,
|
|
3220
4015
|
vatIdCheckFromStore,
|
|
3221
4016
|
versionOnSale,
|
|
3222
4017
|
versionOnSaleOrNext,
|
|
3223
|
-
versionSale
|
|
4018
|
+
versionSale,
|
|
4019
|
+
withdrawnFeaturesOf,
|
|
4020
|
+
yearOfDay
|
|
3224
4021
|
};
|