@saasicat/core 1.0.0-rc.24 → 1.0.0-rc.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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()) {
@@ -404,7 +420,13 @@ var TABLE = "subscription_notices";
404
420
  var KINDS = {
405
421
  "version-offered": true,
406
422
  "version-retired": true,
407
- "version-retirement-reminder": true
423
+ "version-retirement-reminder": true,
424
+ "bundle-version-retired": true,
425
+ "bundle-version-retirement-reminder": true,
426
+ "bundle-version-offered": true,
427
+ "feature-withdrawn": true,
428
+ "feature-withdrawal-lifted": true,
429
+ "ended-at-once": true
408
430
  };
409
431
  function toSubscriptionNoticeRecord(row) {
410
432
  return {
@@ -513,6 +535,128 @@ function toVersionRetirementRecord(row) {
513
535
  };
514
536
  }
515
537
 
538
+ // src/bundle-version-retirement.types.ts
539
+ var BUNDLE_VERSION_RETIREMENT_CAPABILITY = "bundleVersions.retire";
540
+
541
+ // src/bundle-version-retirement-mapping.ts
542
+ function toBundleVersionRetirementRecord(row) {
543
+ return {
544
+ id: row.id,
545
+ retired: {
546
+ bundleVersionId: row.retiredBundleVersionId,
547
+ bundleKey: row.retiredBundleKey,
548
+ version: row.retiredVersion
549
+ },
550
+ replacement: {
551
+ bundleVersionId: row.replacementBundleVersionId,
552
+ bundleKey: row.replacementBundleKey,
553
+ version: row.replacementVersion
554
+ },
555
+ announcedAt: row.announcedAt,
556
+ announcedBy: row.announcedBy
557
+ };
558
+ }
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
+
516
660
  // src/scheduled-change-after-write.ts
517
661
  var NO_SCHEDULED_CHANGE = {
518
662
  pendingPlan: null,
@@ -649,6 +793,37 @@ function parseZonedInstant(text) {
649
793
  return Number.isNaN(moment.getTime()) ? null : moment;
650
794
  }
651
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
+
652
827
  // src/settings-subtree.ts
653
828
  var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
654
829
  "schemaVersion",
@@ -820,6 +995,13 @@ var CATALOG_ERROR_CODES = {
820
995
  PLAN_NOT_FOUND: "PLAN_NOT_FOUND",
821
996
  PLAN_VERSION_NOT_FOUND: "PLAN_VERSION_NOT_FOUND",
822
997
  BUNDLE_NOT_FOUND: "BUNDLE_NOT_FOUND",
998
+ /**
999
+ * Bookings told that their add-on version is being retired have still to
1000
+ * move onto a version of this add-on — waiting for their notice, for their
1001
+ * date, or past it — and a deleted add-on is booked by nothing, the move
1002
+ * included. Carries how many, and the add-on's key.
1003
+ */
1004
+ BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING",
823
1005
  BUNDLE_VERSION_NOT_FOUND: "BUNDLE_VERSION_NOT_FOUND",
824
1006
  FEATURE_NOT_FOUND: "FEATURE_NOT_FOUND",
825
1007
  QUOTA_NOT_FOUND: "QUOTA_NOT_FOUND",
@@ -1010,6 +1192,16 @@ var BILLING_ERROR_CODES = {
1010
1192
  * to pick, so the way past it is the add-on ending.
1011
1193
  */
1012
1194
  RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW",
1195
+ /**
1196
+ * As `RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW`, where only the version the
1197
+ * add-on continues on from a retirement's date cannot run beside the
1198
+ * replacement's plan. The version it is on can, so cancelling it — it then
1199
+ * ends before that date — lets the switch through. Given while that date
1200
+ * is ahead and the add-on is not cancelled yet; otherwise the earlier code
1201
+ * names the day it can end. Carries the add-on, that version, its date and
1202
+ * the plan.
1203
+ */
1204
+ RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW",
1013
1205
  /**
1014
1206
  * The subscription takes no switch now: a change is scheduled, it has
1015
1207
  * ended, or its plan is held for a special contract.
@@ -1020,6 +1212,149 @@ var BILLING_ERROR_CODES = {
1020
1212
  * the retirement as it now stands.
1021
1213
  */
1022
1214
  RETIREMENT_SWITCH_CHANGED: "RETIREMENT_SWITCH_CHANGED",
1215
+ // ── retiring an add-on version for running bookings ──
1216
+ //
1217
+ // An add-on retirement answers with the plan retirement's codes where the
1218
+ // sentence is the same — terms not confirmed, a version named as its own
1219
+ // replacement, twelve months, a preview that changed — and with these
1220
+ // where it names an add-on rather than a plan.
1221
+ /**
1222
+ * The add-on version is still on sale: a booking made after the
1223
+ * announcement would be on it without having been told. Carries the
1224
+ * add-on key and the version.
1225
+ */
1226
+ BUNDLE_RETIREMENT_VERSION_ON_SALE: "BUNDLE_RETIREMENT_VERSION_ON_SALE",
1227
+ /** The replacement is not on sale, so no booking can continue on it. Carries add-on key and version. */
1228
+ BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE",
1229
+ /**
1230
+ * The replacement is a version of another add-on. A booking continues on
1231
+ * a version of its own add-on, so it stays the same booking with its term.
1232
+ * Carries both add-on keys.
1233
+ */
1234
+ BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE",
1235
+ /**
1236
+ * Some of the bookings it reaches run, at their date, beside a plan the
1237
+ * replacement cannot run beside: not allowed there, without a price there
1238
+ * in the booking's rhythm, or in a longer rhythm than the plan's. Carries
1239
+ * how many, and the replacement's add-on key and version.
1240
+ */
1241
+ BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN",
1242
+ /** No running booking is on the version: there is nobody to tell. Carries add-on key and version. */
1243
+ BUNDLE_RETIREMENT_NOTHING_AFFECTED: "BUNDLE_RETIREMENT_NOTHING_AFFECTED",
1244
+ /**
1245
+ * The booking is on an add-on version being retired, and was cancelled to
1246
+ * end before it would move, so it was never told and nothing moves it.
1247
+ * Reinstated, it would run on past the date on a version nobody sells;
1248
+ * booking the replacement once it has ended is the way. Carries the add-on
1249
+ * key, the version, the replacement's version and the day it can be booked
1250
+ * from.
1251
+ */
1252
+ BUNDLE_RETIREMENT_REINSTATE_REFUSED: "BUNDLE_RETIREMENT_REINSTATE_REFUSED",
1253
+ /**
1254
+ * As `BUNDLE_RETIREMENT_REINSTATE_REFUSED`, where the replacement cannot
1255
+ * run beside the plan the subscription is on when the booking ends, or
1256
+ * beside one it is already set to move to after that, in any rhythm it
1257
+ * could be booked in, so there is no version of the add-on to book.
1258
+ * Carries the add-on key, the version and the replacement's version.
1259
+ */
1260
+ BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN",
1261
+ /**
1262
+ * As `BUNDLE_RETIREMENT_REINSTATE_REFUSED`, where the subscription ends by
1263
+ * the day the replacement could be booked from, so there is no day to
1264
+ * name. Carries the add-on key, the version and the replacement's version.
1265
+ */
1266
+ BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS",
1267
+ /**
1268
+ * The plan the add-on runs beside, or the rhythm it is billed in, changes
1269
+ * before the date the booking was told — by a change scheduled, or by a
1270
+ * retirement told that moves the subscription to another plan — so the
1271
+ * price a switch now would hold is not the one the booking pays until
1272
+ * then. Switching once the plan has changed, or waiting for the
1273
+ * date, is the way. Carries the add-on and the date.
1274
+ */
1275
+ BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES",
1276
+ /**
1277
+ * The add-on version the caller asked to switch a booking to is no longer
1278
+ * the one offered beside it — another is on sale, the booking moved, or
1279
+ * there is no offer. Nothing was switched. Carries the version named
1280
+ * (`bundleVersionId`) and the current `offer`, or `null`, so the page can
1281
+ * show it instead of guessing.
1282
+ */
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",
1023
1358
  // ── the preview routes' own blockers and warnings ──
1024
1359
  //
1025
1360
  // These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
@@ -1066,6 +1401,16 @@ var BILLING_ERROR_CODES = {
1066
1401
  * day the booking can end.
1067
1402
  */
1068
1403
  BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN",
1404
+ /**
1405
+ * A bundle the tenant already holds can run on the plan they are moving
1406
+ * to, but the version it continues on from a retirement's date cannot.
1407
+ * Cancelling it lets the change through at once: the booking then ends
1408
+ * before that date and never reaches the version. Given while that date is
1409
+ * ahead and the booking is not cancelled yet; otherwise
1410
+ * `BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN` names the day it can end.
1411
+ * Carries the bundle, that version, its date and the plan.
1412
+ */
1413
+ BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN",
1069
1414
  /**
1070
1415
  * Features of the previewed bundle are already covered by the plan or by
1071
1416
  * another booked bundle. A warning rather than a blocker: paying twice is
@@ -1135,6 +1480,21 @@ var CONTRACT_ERROR_CODES = {
1135
1480
  * describes. Nothing was written; load the offer and conclude it again.
1136
1481
  */
1137
1482
  CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
1483
+ /**
1484
+ * An offer concludes a tenant's first contract, and this tenant has a
1485
+ * contract in force at the moment the offer's would take effect, or one
1486
+ * beginning after it. Concluded, the two would run side by side. Nothing
1487
+ * was written; a running subscription changes through its plan and its
1488
+ * add-ons. Carries the offer and the contract that runs.
1489
+ */
1490
+ CHECKOUT_OFFER_CONTRACT_IN_FORCE: "CHECKOUT_OFFER_CONTRACT_IN_FORCE",
1491
+ /**
1492
+ * The offer names a version of an add-on the tenant has booked in another
1493
+ * version. Concluded, the contract would name a version the booking is not
1494
+ * on. Nothing was written; a booking changes its version through the offer
1495
+ * beside it. Carries the offer, the add-on, the booking and both versions.
1496
+ */
1497
+ CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION",
1138
1498
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
1139
1499
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
1140
1500
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED",
@@ -1145,7 +1505,61 @@ var CONTRACT_ERROR_CODES = {
1145
1505
  * second attempt, or a contract begins after the moment the successor
1146
1506
  * would take effect. Nothing was written.
1147
1507
  */
1148
- SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED"
1508
+ SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED",
1509
+ /**
1510
+ * A contract states a tax rate — `field` names where — other than the one
1511
+ * the installation's tax adapter decides for its subscriber.
1512
+ */
1513
+ SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED"
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"
1149
1563
  };
1150
1564
  var SUBSCRIBER_ERROR_CODES = {
1151
1565
  /**
@@ -1169,7 +1583,30 @@ var SUBSCRIBER_ERROR_CODES = {
1169
1583
  SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED",
1170
1584
  SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING",
1171
1585
  /** The operator declared another legal entity: that is a transfer, not an edit. */
1172
- SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER"
1586
+ SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER",
1587
+ /**
1588
+ * A contact change named whether the subscriber is a business. That is part
1589
+ * of its tax origin and changes as a recorded change naming who made it.
1590
+ */
1591
+ SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT",
1592
+ /** A change of the subscriber's contact details or business status did not say who makes it. */
1593
+ SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "SUBSCRIBER_CHANGE_ACTOR_REQUIRED",
1594
+ /**
1595
+ * Where a tax adapter decides, a contract names its subscriber only once
1596
+ * the address an invoice names is complete (`SC-PRIC-032`): a party copied
1597
+ * onto a contract incomplete cannot be completed afterwards. Carries
1598
+ * `missing`, the empty fields, for a screen to name by their labels; the
1599
+ * shipped sentence names all four rather than the keys.
1600
+ */
1601
+ SUBSCRIBER_IDENTITY_INCOMPLETE: "SUBSCRIBER_IDENTITY_INCOMPLETE",
1602
+ /**
1603
+ * A change of whether the subscriber is a business states no reason. It
1604
+ * decides the tax treatment of the next invoice, so the change log keeps
1605
+ * why it moved (`SC-PRIC-043`).
1606
+ */
1607
+ SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED",
1608
+ /** A check of the subscriber's VAT identification number was asked for, and it holds none. */
1609
+ SUBSCRIBER_VAT_ID_MISSING: "SUBSCRIBER_VAT_ID_MISSING"
1173
1610
  };
1174
1611
  var REGISTRATION_ERROR_CODES = {
1175
1612
  PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND",
@@ -1274,6 +1711,29 @@ var MAINTENANCE_ERROR_CODES = {
1274
1711
  /** The message is longer than `MAINTENANCE_MESSAGE_MAX_LENGTH`. */
1275
1712
  MAINTENANCE_MESSAGE_TOO_LONG: "MAINTENANCE_MESSAGE_TOO_LONG"
1276
1713
  };
1714
+ var TAX_ERROR_CODES = {
1715
+ /**
1716
+ * The tax adapter answers that it does not support the subscriber's case;
1717
+ * `adapter` names it and `reason` is its sentence. Nothing is concluded with
1718
+ * a guessed tax (`SC-PRIC-039`).
1719
+ */
1720
+ TAX_TREATMENT_NOT_SUPPORTED: "TAX_TREATMENT_NOT_SUPPORTED",
1721
+ /**
1722
+ * The tax adapter needs the VAT identification number checked to treat the
1723
+ * case, and the check did not complete — the service is unavailable, timed
1724
+ * out, or answered in a way the adapter cannot read (`503`). Nothing is
1725
+ * decided in the subscriber's favour on a number not checked
1726
+ * (`SC-PRIC-040`): try again later. `adapter` names the adapter and
1727
+ * `reason` says why.
1728
+ */
1729
+ TAX_VAT_ID_CHECK_NOT_COMPLETED: "TAX_VAT_ID_CHECK_NOT_COMPLETED",
1730
+ /**
1731
+ * A VAT identification number was to be checked, and `config/saas.yaml`
1732
+ * names no tax adapter, which is what names the service to check with
1733
+ * (`409`).
1734
+ */
1735
+ TAX_VAT_ID_CHECK_NOT_AVAILABLE: "TAX_VAT_ID_CHECK_NOT_AVAILABLE"
1736
+ };
1277
1737
  var PLATFORM_ERROR_CODES = {
1278
1738
  ...SETUP_ERROR_CODES,
1279
1739
  ...AUTH_ERROR_CODES,
@@ -1281,11 +1741,13 @@ var PLATFORM_ERROR_CODES = {
1281
1741
  ...CATALOG_ERROR_CODES,
1282
1742
  ...BILLING_ERROR_CODES,
1283
1743
  ...CONTRACT_ERROR_CODES,
1744
+ ...INVOICE_ERROR_CODES,
1284
1745
  ...SUBSCRIBER_ERROR_CODES,
1285
1746
  ...REGISTRATION_ERROR_CODES,
1286
1747
  ...PAYMENT_ERROR_CODES,
1287
1748
  ...SETTINGS_ERROR_CODES,
1288
- ...MAINTENANCE_ERROR_CODES
1749
+ ...MAINTENANCE_ERROR_CODES,
1750
+ ...TAX_ERROR_CODES
1289
1751
  };
1290
1752
 
1291
1753
  // src/errors.ts
@@ -1332,7 +1794,14 @@ var OPTIONAL_CANONICAL_MODELS = {
1332
1794
  MaintenanceWindow: ["core.maintenanceWindows"],
1333
1795
  SubscriptionNotice: ["tenantBilling.subscriptionNotices"],
1334
1796
  VersionRetirement: ["tenantBilling.versionRetirements"],
1797
+ BundleVersionRetirement: ["tenantBilling.bundleVersionRetirements"],
1798
+ FeatureWithdrawal: ["tenantBilling.featureWithdrawals"],
1335
1799
  SubscriberLedgerEntry: ["entitlement.subscriberLedgerRepository"],
1800
+ SubscriptionInvoice: ["entitlement.subscriptionInvoiceRepository"],
1801
+ SubscriptionInvoiceLine: ["entitlement.subscriptionInvoiceRepository"],
1802
+ SubscriptionInvoiceNumber: ["entitlement.subscriptionInvoiceRepository"],
1803
+ SubscriptionInvoiceDocument: ["entitlement.subscriptionInvoiceRepository"],
1804
+ SubscriptionInvoiceArchive: ["entitlement.subscriptionInvoiceArchive"],
1336
1805
  PromoCodeHold: ["promo.holdRepository"]
1337
1806
  };
1338
1807
  function membersLeftOut(notAdopted = []) {
@@ -1449,6 +1918,7 @@ var ERROR_MESSAGES_EN = {
1449
1918
  PLAN_NOT_FOUND: "Plan '{planId}' not found",
1450
1919
  PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' not found",
1451
1920
  BUNDLE_NOT_FOUND: "Bundle '{bundleId}' not found",
1921
+ BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "{count} bookings still move onto a version of {bundleKey}, as a retirement told them. The add-on can be deleted once they have.",
1452
1922
  BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' not found",
1453
1923
  FEATURE_NOT_FOUND: "Feature '{featureKey}' not found",
1454
1924
  QUOTA_NOT_FOUND: "Quota '{quotaKey}' not found",
@@ -1504,9 +1974,37 @@ var ERROR_MESSAGES_EN = {
1504
1974
  RETIREMENT_NOTHING_AFFECTED: "No running subscription is on version {version} of {planKey}, so there is nobody to tell.",
1505
1975
  RETIREMENT_WITHIN_TWELVE_MONTHS: "{count} of these subscriptions were reached by a retirement within the last twelve months. A subscription is reached at most once a year.",
1506
1976
  RETIREMENT_PREVIEW_CHANGED: "The subscriptions this retirement reaches changed since they were shown. Look at them again before announcing it.",
1977
+ BUNDLE_RETIREMENT_VERSION_ON_SALE: "Version {version} of {bundleKey} is still on sale. Publish the version that replaces it, and retire this one once its sale has ended, so nobody books it after the announcement.",
1978
+ BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} of {bundleKey} is not on sale, so bookings cannot continue on it.",
1979
+ BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "The replacement is a version of {replacementBundleKey}, not of {bundleKey}. A booking continues on a version of its own add-on.",
1980
+ BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "{count} of these bookings run beside a plan that version {version} of {bundleKey} cannot run beside, so they cannot continue on it.",
1981
+ BUNDLE_RETIREMENT_NOTHING_AFFECTED: "No running booking is on version {version} of {bundleKey}, so there is nobody to tell.",
1982
+ BUNDLE_RETIREMENT_REINSTATE_REFUSED: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Book version {replacementVersion} from {bookableFrom}, when this booking has ended.",
1983
+ BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Version {replacementVersion}, which replaces it, cannot run beside your plan, or beside one your subscription is set to move to.",
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.",
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.",
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.",
1507
2004
  RETIREMENT_SWITCH_NOT_PENDING: "No retirement of your version is waiting for its date, so there is nothing to switch to.",
1508
2005
  RETIREMENT_SWITCH_IN_TRIAL: "The switch opens when your trial ends.",
1509
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.",
2007
+ RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "{bundleName} continues on version {version} from {from}, which cannot run on {planName}. Once it is cancelled, the switch can be made.",
1510
2008
  RETIREMENT_SWITCH_NOT_OPEN: "This subscription cannot switch now: a change is scheduled, it has ended, or its plan is held for a special contract.",
1511
2009
  RETIREMENT_SWITCH_CHANGED: "The retirement changed since it was shown. Look at it again before switching.",
1512
2010
  SUBSCRIPTION_ENDED: "This subscription has ended. Its plan can no longer be changed.",
@@ -1519,6 +2017,7 @@ var ERROR_MESSAGES_EN = {
1519
2017
  CYCLE_SHORTENS_AT_TERM_END: "A monthly {planName} cannot start inside the yearly term you are in. The upgrade takes effect when that term ends; to have it today, keep the yearly cycle.",
1520
2018
  BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "{bundleName} is billed yearly and runs until {until} at the earliest, which a monthly plan cannot carry. Once it is cancelled, a change that takes effect on or after that day goes through \u2014 or keep the yearly cycle.",
1521
2019
  BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} cannot run on {planName} and runs until {until} at the earliest. Once it is cancelled, a change that takes effect on or after that day goes through \u2014 or choose another plan.",
2020
+ BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} continues on version {version} from {from}, which cannot run on {planName}. Cancel it, and the change goes through \u2014 or choose another plan.",
1522
2021
  REDUNDANT_FEATURES: "The plan or another booked bundle already includes {count} of the features in this bundle \u2014 booking it pays for them twice.",
1523
2022
  MINIMUM_TERM_BINDS: "The minimum term extends beyond the end of the period \u2014 the cancellation only takes effect when the minimum term ends.",
1524
2023
  BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "The bundle requires [{features}] \u2014 present neither in the plan nor in the active bundles.",
@@ -1543,6 +2042,16 @@ var ERROR_MESSAGES_EN = {
1543
2042
  PROMO_WOULD_PRODUCE_ZERO_INVOICE: "For absolute amounts the discount must stay below the lowest applicable plan price, or allowZeroInvoice must be enabled.",
1544
2043
  PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
1545
2044
  // ── contract ──
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.",
1546
2055
  CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
1547
2056
  CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
1548
2057
  CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
@@ -1567,6 +2076,8 @@ var ERROR_MESSAGES_EN = {
1567
2076
  CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
1568
2077
  CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' must be consumed before the contract is created",
1569
2078
  CHECKOUT_OFFER_CHANGED: "Checkout offer '{offerId}' changed while it was being concluded. Load it again.",
2079
+ CHECKOUT_OFFER_CONTRACT_IN_FORCE: "Checkout offer '{offerId}' concludes a first contract, and this tenant already has one. A running subscription changes through its plan and its add-ons.",
2080
+ CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "Checkout offer '{offerId}' names version {offeredVersion} of add-on '{bundleKey}', which this tenant has booked in version {bookedVersion} (booking {subscriptionBundleId}). Its version changes through the offer beside the add-on.",
1570
2081
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
1571
2082
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
1572
2083
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
@@ -1577,11 +2088,16 @@ var ERROR_MESSAGES_EN = {
1577
2088
  SUBSCRIBER_NOT_FOUND: "Subscriber '{subscriberId}' not found",
1578
2089
  SUBSCRIBER_LEGAL_NAME_REQUIRED: "A subscriber needs its legal name.",
1579
2090
  SUBSCRIBER_DETAIL_INVALID: "The subscriber's {field} is not valid.",
2091
+ SUBSCRIBER_IDENTITY_INCOMPLETE: "The subscriber's billing address is not complete yet: a contract names it only with street and number, postal code, city and country.",
1580
2092
  SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} is part of the subscriber's legal identity and changes only as a correction, with a reason.",
2093
+ SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "A change of whether the subscriber is a business needs a reason.",
2094
+ SUBSCRIBER_VAT_ID_MISSING: "The subscriber has no VAT identification number to check.",
1581
2095
  SUBSCRIBER_CORRECTION_REASON_REQUIRED: "A correction of the legal identity needs a reason.",
1582
2096
  SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "A correction of the legal identity has to say who makes it.",
1583
2097
  SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "The correction changes nothing: every value it names is already recorded.",
1584
2098
  SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "Another legal entity taking over a subscriber is a transfer, not a correction, and cannot be recorded as an edit.",
2099
+ SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "Whether the subscriber is a business is part of its tax origin and changes only as a recorded change that names who made it.",
2100
+ SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "A change of the subscriber's details has to say who makes it.",
1585
2101
  // ── registration ──
1586
2102
  PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
1587
2103
  PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
@@ -1615,7 +2131,11 @@ var ERROR_MESSAGES_EN = {
1615
2131
  MAINTENANCE_WINDOW_END_NOT_AFTER_START: "The end of a maintenance window has to be after its start.",
1616
2132
  MAINTENANCE_WINDOW_END_IN_PAST: "The end of a maintenance window cannot be in the past.",
1617
2133
  MAINTENANCE_TIME_INVALID: "The {field} has to be a date and time with its zone, such as 2026-10-02T22:00+02:00.",
1618
- MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long."
2134
+ MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long.",
2135
+ // ── tax ──
2136
+ TAX_TREATMENT_NOT_SUPPORTED: "The tax adapter {adapter} does not support this case: {reason}",
2137
+ TAX_VAT_ID_CHECK_NOT_COMPLETED: "The VAT identification number could not be checked just now ({reason}). Please try again later.",
2138
+ TAX_VAT_ID_CHECK_NOT_AVAILABLE: "This installation names no tax adapter, so no VAT identification number is checked."
1619
2139
  };
1620
2140
  function formatErrorMessage(template, params = {}) {
1621
2141
  return template.replace(/\{(\w+)\}/g, (match, name) => {
@@ -1972,18 +2492,22 @@ var ORIGINS = {
1972
2492
  renewal: true,
1973
2493
  planChange: true,
1974
2494
  bundleBooking: true,
2495
+ bundleChange: true,
1975
2496
  credit: true,
1976
- correction: true
2497
+ correction: true,
2498
+ reductionTakenBack: true
1977
2499
  };
1978
2500
  var SOURCES = {
1979
2501
  plan: true,
1980
2502
  bundle: true,
1981
2503
  discount: true
1982
2504
  };
1983
- var TABLE2 = "subscriber_ledger_entries";
2505
+ var SUBSCRIBER_CHARGE_ORIGINS = keysOf(ORIGINS);
2506
+ var SUBSCRIBER_CHARGE_SOURCES = keysOf(SOURCES);
2507
+ var TABLE3 = "subscriber_ledger_entries";
1984
2508
  var CENT_TOLERANCE = 1e-6;
1985
2509
  function toSubscriberChargeRecord(row) {
1986
- const where = (column) => ({ table: TABLE2, column, id: row.id });
2510
+ const where = (column) => ({ table: TABLE3, column, id: row.id });
1987
2511
  return {
1988
2512
  id: row.id,
1989
2513
  subscriberId: row.subscriberId,
@@ -1991,8 +2515,8 @@ function toSubscriberChargeRecord(row) {
1991
2515
  subscriptionId: row.subscriptionId,
1992
2516
  contractId: row.contractId,
1993
2517
  contractLineItemId: row.contractLineItemId,
1994
- origin: oneOf(keysOf(ORIGINS), row.origin, where("origin")),
1995
- source: oneOf(keysOf(SOURCES), row.source, where("source")),
2518
+ origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
2519
+ source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
1996
2520
  sourceRef: row.sourceRef,
1997
2521
  periodStart: row.periodStart,
1998
2522
  periodEnd: row.periodEnd,
@@ -2032,6 +2556,560 @@ function keysOf(record) {
2032
2556
  // src/subscriber-ledger.types.ts
2033
2557
  var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
2034
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
+
2568
+ // src/subscriber.types.ts
2569
+ var SUBSCRIBER_INVOICE_ADDRESS_FIELDS = [
2570
+ "addressLine1",
2571
+ "postalCode",
2572
+ "city",
2573
+ "country"
2574
+ ];
2575
+ var SUBSCRIBER_STANDING_CAPABILITY = "subscribers.read";
2576
+ var SUBSCRIBER_ATTENTION_CAPABILITY = "subscribers.attention";
2577
+ var SUBSCRIBER_ATTENTION_PAGE_SIZE = 200;
2578
+ var SUBSCRIBER_CORRECTION_CAPABILITY = "subscribers.correct";
2579
+ var SUBSCRIBER_TAX_ORIGIN_FIELDS = ["country", "business", "vatId"];
2580
+
2581
+ // src/tax.types.ts
2582
+ var TAX_TREATMENT_KINDS = [
2583
+ "standard",
2584
+ "reduced",
2585
+ "exempt",
2586
+ "reverse-charge",
2587
+ "not-taxable",
2588
+ "small-business"
2589
+ ];
2590
+
2591
+ // src/tax-origin.ts
2592
+ var VAT_ID_SEPARATORS = /[\s.-]/g;
2593
+ function canonicalVatId(written) {
2594
+ const vatId = written.replace(VAT_ID_SEPARATORS, "").toUpperCase();
2595
+ return vatId === "" ? null : vatId;
2596
+ }
2597
+ function taxOriginOf(subscriber, currentCheck) {
2598
+ const validated = subscriber.vatId !== null && currentCheck?.valid === true && currentCheck.vatId === subscriber.vatId;
2599
+ return {
2600
+ country: subscriber.country,
2601
+ business: subscriber.business,
2602
+ vatId: subscriber.vatId,
2603
+ validatedVatId: validated ? subscriber.vatId : null
2604
+ };
2605
+ }
2606
+ function keepsVatIdCheck(held, counting, check) {
2607
+ if (held.vatId === null || check.vatId !== held.vatId) return false;
2608
+ if (held.vatIdSince !== null && check.checkedAt.getTime() < held.vatIdSince.getTime()) {
2609
+ return false;
2610
+ }
2611
+ if (counting === null || counting.vatId !== held.vatId) return true;
2612
+ return counting.checkedAt.getTime() <= check.checkedAt.getTime();
2613
+ }
2614
+ function toSubscriberVatIdCheckRecord(row) {
2615
+ return {
2616
+ id: row.id,
2617
+ subscriberId: row.subscriberId,
2618
+ vatId: row.vatId,
2619
+ checkedAt: row.checkedAt,
2620
+ valid: row.valid,
2621
+ service: row.service,
2622
+ confirmation: textEntries(row.confirmation),
2623
+ recordedAt: row.recordedAt
2624
+ };
2625
+ }
2626
+ function taxTreatmentToJson(treatment) {
2627
+ return {
2628
+ kind: treatment.kind,
2629
+ rate: treatment.rate,
2630
+ note: treatment.note,
2631
+ adapter: { name: treatment.adapter.name, version: treatment.adapter.version }
2632
+ };
2633
+ }
2634
+ function toTaxTreatment(value) {
2635
+ if (!isObject(value)) return null;
2636
+ const { kind, rate, note, adapter } = value;
2637
+ if (!isTreatmentKind(kind)) return null;
2638
+ if (typeof rate !== "number" || !Number.isFinite(rate)) return null;
2639
+ if (note !== null && typeof note !== "string") return null;
2640
+ if (!isObject(adapter)) return null;
2641
+ if (typeof adapter.name !== "string" || typeof adapter.version !== "string") return null;
2642
+ return { kind, rate, note, adapter: { name: adapter.name, version: adapter.version } };
2643
+ }
2644
+ function taxOriginWrite(current, next) {
2645
+ const previous = {};
2646
+ const changed = {};
2647
+ for (const field of SUBSCRIBER_TAX_ORIGIN_FIELDS) {
2648
+ const value = next[field];
2649
+ if (value === void 0 || value === current[field]) continue;
2650
+ previous[field] = current[field];
2651
+ changed[field] = value;
2652
+ }
2653
+ return {
2654
+ previous,
2655
+ changed,
2656
+ moved: Object.keys(changed).length > 0,
2657
+ endsCountingVatIdCheck: Object.prototype.hasOwnProperty.call(changed, "vatId")
2658
+ };
2659
+ }
2660
+ function toTaxOriginValues(value) {
2661
+ if (!isObject(value)) return {};
2662
+ const values = {};
2663
+ for (const field of SUBSCRIBER_TAX_ORIGIN_FIELDS) {
2664
+ if (!Object.prototype.hasOwnProperty.call(value, field)) continue;
2665
+ const entry = value[field];
2666
+ if (field === "business") {
2667
+ values[field] = typeof entry === "boolean" ? entry : null;
2668
+ } else {
2669
+ values[field] = typeof entry === "string" ? entry : null;
2670
+ }
2671
+ }
2672
+ return values;
2673
+ }
2674
+ function isTreatmentKind(value) {
2675
+ return TAX_TREATMENT_KINDS.includes(value);
2676
+ }
2677
+ function isObject(value) {
2678
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2679
+ }
2680
+ function vatIdCheckFromStore(value) {
2681
+ if (!isObject(value)) return null;
2682
+ const { vatId, checkedAt, valid, service, confirmation } = value;
2683
+ const at = checkedAt instanceof Date ? checkedAt : new Date(String(checkedAt));
2684
+ if (typeof vatId !== "string" || typeof valid !== "boolean" || typeof service !== "string") {
2685
+ return null;
2686
+ }
2687
+ if (checkedAt === void 0 || checkedAt === null || Number.isNaN(at.getTime())) return null;
2688
+ return { vatId, checkedAt: at, valid, service, confirmation: textEntries(confirmation) };
2689
+ }
2690
+ function textEntries(value) {
2691
+ if (!isObject(value)) return {};
2692
+ const entries = {};
2693
+ for (const [key, entry] of Object.entries(value)) {
2694
+ if (typeof entry === "string") entries[key] = entry;
2695
+ }
2696
+ return entries;
2697
+ }
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
+
2035
3113
  // src/subscriber-mapping.ts
2036
3114
  function formatCustomerNumber(prefix, sequence) {
2037
3115
  return `${prefix}${sequence}`;
@@ -2049,6 +3127,7 @@ function toSubscriberRecord(row, tenantId) {
2049
3127
  vatId: row.vatId,
2050
3128
  taxNumber: row.taxNumber,
2051
3129
  invoiceEmail: row.invoiceEmail,
3130
+ business: row.business,
2052
3131
  tenantId,
2053
3132
  migrated: row.migrated,
2054
3133
  createdAt: row.createdAt,
@@ -2066,6 +3145,17 @@ function toSubscriberCorrectionRecord(row) {
2066
3145
  correctedAt: row.correctedAt
2067
3146
  };
2068
3147
  }
3148
+ function toSubscriberTaxOriginChangeRecord(row) {
3149
+ return {
3150
+ id: row.id,
3151
+ subscriberId: row.subscriberId,
3152
+ previous: toTaxOriginValues(row.previous),
3153
+ changed: toTaxOriginValues(row.changed),
3154
+ changedBy: row.changedBy,
3155
+ changedAt: row.changedAt,
3156
+ reason: row.reason
3157
+ };
3158
+ }
2069
3159
  function identityCorrectionDelta(current, corrected) {
2070
3160
  const previous = {};
2071
3161
  const next = {};
@@ -2129,7 +3219,9 @@ function subscriberFromRegistration(pending) {
2129
3219
  city: pending.city,
2130
3220
  country: pending.country,
2131
3221
  vatId: pending.vatId,
2132
- taxNumber: pending.taxNumber
3222
+ taxNumber: pending.taxNumber,
3223
+ business: pending.business,
3224
+ vatIdCheck: vatIdCheckFromStore(pending.vatIdCheck)
2133
3225
  };
2134
3226
  }
2135
3227
  function toIdentityValues(value) {
@@ -2147,7 +3239,7 @@ function toIdentityValues(value) {
2147
3239
  // src/subscriber-payment-method-mapping.ts
2148
3240
  var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
2149
3241
  var STATUSES = ["ACTIVE", "REPLACED"];
2150
- var TABLE3 = "subscriber_payment_methods";
3242
+ var TABLE4 = "subscriber_payment_methods";
2151
3243
  function toSubscriberPaymentMethodRecord(row) {
2152
3244
  return {
2153
3245
  id: row.id,
@@ -2156,7 +3248,7 @@ function toSubscriberPaymentMethodRecord(row) {
2156
3248
  provider: row.provider,
2157
3249
  customerRef: row.customerRef,
2158
3250
  paymentMethodRef: row.paymentMethodRef,
2159
- type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE3, column: "type", id: row.id }),
3251
+ type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE4, column: "type", id: row.id }),
2160
3252
  brand: row.brand,
2161
3253
  last4: row.last4,
2162
3254
  expiryMonth: row.expiryMonth,
@@ -2164,7 +3256,7 @@ function toSubscriberPaymentMethodRecord(row) {
2164
3256
  country: row.country,
2165
3257
  bankCode: row.bankCode,
2166
3258
  mandateReference: row.mandateReference,
2167
- status: oneOf(STATUSES, row.status, { table: TABLE3, column: "status", id: row.id }),
3259
+ status: oneOf(STATUSES, row.status, { table: TABLE4, column: "status", id: row.id }),
2168
3260
  confirmedAt: row.confirmedAt,
2169
3261
  replacedAt: row.replacedAt,
2170
3262
  createdAt: row.createdAt
@@ -2230,6 +3322,8 @@ function toSubscriptionBundleRecord(row) {
2230
3322
  }),
2231
3323
  currentPeriodStart: row.currentPeriodStart ?? null,
2232
3324
  currentPeriodEnd: row.currentPeriodEnd ?? null,
3325
+ pendingBundleVersionId: row.pendingBundleVersionId ?? null,
3326
+ pendingVersionEffectiveAt: row.pendingVersionEffectiveAt ?? null,
2233
3327
  canceledAt: row.canceledAt,
2234
3328
  canceledEffectiveAt: row.canceledEffectiveAt,
2235
3329
  createdAt: row.createdAt,
@@ -2246,6 +3340,7 @@ function toSubscriptionContractRecord(row, lineItems) {
2246
3340
  subscriber: toSubscriberParty(row.subscriberSnapshot),
2247
3341
  issuer: isPlainObject2(row.issuerSnapshot) ? toIssuerParty(row.issuerSnapshot) : null,
2248
3342
  partiesMigrated: row.partiesMigrated,
3343
+ taxTreatment: toTaxTreatment(row.taxTreatment),
2249
3344
  status: row.status,
2250
3345
  effectiveFrom: row.effectiveFrom,
2251
3346
  effectiveUntil: row.effectiveUntil,
@@ -2493,6 +3588,7 @@ var ERROR_MESSAGES_DE = {
2493
3588
  PLAN_NOT_FOUND: "Plan '{planId}' nicht gefunden",
2494
3589
  PLAN_VERSION_NOT_FOUND: "PlanVersion '{versionId}' nicht gefunden",
2495
3590
  BUNDLE_NOT_FOUND: "Bundle '{bundleId}' nicht gefunden",
3591
+ BUNDLE_DELETE_WHILE_RETIREMENT_MOVES_PENDING: "{count} Buchungen ziehen noch auf eine Version von {bundleKey} um, wie eine Stilllegung es ihnen mitgeteilt hat. Das Add-on l\xE4sst sich l\xF6schen, sobald sie umgezogen sind.",
2496
3592
  BUNDLE_VERSION_NOT_FOUND: "BundleVersion '{bundleVersionId}' nicht gefunden",
2497
3593
  FEATURE_NOT_FOUND: "Feature '{featureKey}' nicht gefunden",
2498
3594
  QUOTA_NOT_FOUND: "Quota '{quotaKey}' nicht gefunden",
@@ -2548,9 +3644,37 @@ var ERROR_MESSAGES_DE = {
2548
3644
  RETIREMENT_NOTHING_AFFECTED: "Kein laufendes Abonnement nutzt Version {version} von {planKey}. Es gibt niemanden zu benachrichtigen.",
2549
3645
  RETIREMENT_WITHIN_TWELVE_MONTHS: "{count} dieser Abonnements waren in den letzten zw\xF6lf Monaten schon von einer Stilllegung betroffen. Ein Abonnement ist h\xF6chstens einmal im Jahr betroffen.",
2550
3646
  RETIREMENT_PREVIEW_CHANGED: "Die Abonnements, die diese Stilllegung betrifft, haben sich seit der Anzeige ge\xE4ndert. Sieh sie dir noch einmal an, bevor du sie ank\xFCndigst.",
3647
+ BUNDLE_RETIREMENT_VERSION_ON_SALE: "Version {version} von {bundleKey} wird noch verkauft. Ver\xF6ffentliche die Version, die sie ersetzt, und lege diese still, sobald ihr Verkauf geendet hat, damit sie nach der Ank\xFCndigung niemand mehr bucht.",
3648
+ BUNDLE_RETIREMENT_REPLACEMENT_NOT_ON_SALE: "Version {version} von {bundleKey} wird nicht verkauft. Buchungen k\xF6nnen nicht auf ihr weiterlaufen.",
3649
+ BUNDLE_RETIREMENT_REPLACEMENT_OF_ANOTHER_BUNDLE: "Der Ersatz ist eine Version von {replacementBundleKey}, nicht von {bundleKey}. Eine Buchung l\xE4uft auf einer Version ihres eigenen Add-ons weiter.",
3650
+ BUNDLE_RETIREMENT_REPLACEMENT_CANNOT_RUN: "{count} dieser Buchungen laufen neben einem Plan, neben dem Version {version} von {bundleKey} nicht laufen kann. Sie k\xF6nnen nicht auf ihr weiterlaufen.",
3651
+ BUNDLE_RETIREMENT_NOTHING_AFFECTED: "Keine laufende Buchung nutzt Version {version} von {bundleKey}. Es gibt niemanden zu benachrichtigen.",
3652
+ BUNDLE_RETIREMENT_REINSTATE_REFUSED: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Buchen Sie Version {replacementVersion} ab dem {bookableFrom}, wenn diese Buchung beendet ist.",
3653
+ BUNDLE_RETIREMENT_REINSTATE_REPLACEMENT_CANNOT_RUN: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Version {replacementVersion}, die sie ersetzt, kann neben Ihrem Paket oder einem Paket, zu dem Ihr Abonnement wechselt, nicht laufen.",
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.",
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.",
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.",
2551
3674
  RETIREMENT_SWITCH_NOT_PENDING: "F\xFCr Ihre Version steht keine Einstellung mehr bevor. Es gibt nichts, wohin Sie wechseln k\xF6nnten.",
2552
3675
  RETIREMENT_SWITCH_IN_TRIAL: "Der Wechsel ist m\xF6glich, sobald Ihre Testphase endet.",
2553
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.",
3677
+ RETIREMENT_SWITCH_BUNDLE_REPLACEMENT_CANNOT_FOLLOW: "{bundleName} l\xE4uft ab dem {from} auf Version {version} weiter, die im Paket {planName} nicht laufen kann. Ist es gek\xFCndigt, ist der Wechsel m\xF6glich.",
2554
3678
  RETIREMENT_SWITCH_NOT_OPEN: "Dieses Abonnement kann gerade nicht wechseln: Eine \xC4nderung ist geplant, es ist beendet, oder sein Paket gilt f\xFCr einen Sondervertrag.",
2555
3679
  RETIREMENT_SWITCH_CHANGED: "Die Einstellung hat sich seit der Anzeige ge\xE4ndert. Sehen Sie sie sich noch einmal an, bevor Sie wechseln.",
2556
3680
  SUBSCRIPTION_ENDED: "Dieses Abonnement ist beendet. Sein Paket kann nicht mehr gewechselt werden.",
@@ -2563,6 +3687,7 @@ var ERROR_MESSAGES_DE = {
2563
3687
  CYCLE_SHORTENS_AT_TERM_END: "Ein monatlich abgerechnetes {planName} kann nicht innerhalb der laufenden Jahreslaufzeit beginnen. Das Upgrade greift zum Ende dieser Laufzeit; wer es sofort m\xF6chte, beh\xE4lt den j\xE4hrlichen Rhythmus.",
2564
3688
  BUNDLE_BOOKING_OUTLASTS_TARGET_CYCLE: "{bundleName} wird j\xE4hrlich abgerechnet und l\xE4uft fr\xFChestens bis {until}; ein monatlich abgerechnetes Paket kann es nicht tragen. Ist es gek\xFCndigt, geht ein Wechsel durch, der an diesem Tag oder sp\xE4ter wirksam wird \u2014 oder behalten Sie den j\xE4hrlichen Rhythmus.",
2565
3689
  BUNDLE_BOOKING_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} kann im Paket {planName} nicht laufen und l\xE4uft fr\xFChestens bis {until}. Ist es gek\xFCndigt, geht ein Wechsel durch, der an diesem Tag oder sp\xE4ter wirksam wird \u2014 oder w\xE4hlen Sie ein anderes Paket.",
3690
+ BUNDLE_REPLACEMENT_DOES_NOT_FIT_TARGET_PLAN: "{bundleName} l\xE4uft ab dem {from} auf Version {version} weiter, die im Paket {planName} nicht laufen kann. K\xFCndigen Sie es, dann geht der Wechsel durch \u2014 oder w\xE4hlen Sie ein anderes Paket.",
2566
3691
  REDUNDANT_FEATURES: "Der Plan oder ein anderes gebuchtes Bundle enth\xE4lt bereits {count} der Funktionen aus diesem Bundle \u2014 mit der Buchung werden sie doppelt bezahlt.",
2567
3692
  MINIMUM_TERM_BINDS: "Die Mindestlaufzeit reicht \xFCber das Ende der Periode hinaus \u2014 die K\xFCndigung wird erst zum Ende der Mindestlaufzeit wirksam.",
2568
3693
  BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED: "Das Bundle setzt [{features}] voraus \u2014 weder im Plan noch in den aktiven Bundles vorhanden.",
@@ -2587,6 +3712,16 @@ var ERROR_MESSAGES_DE = {
2587
3712
  PROMO_WOULD_PRODUCE_ZERO_INVOICE: "Bei absoluten Betr\xE4gen muss der Rabatt unter dem niedrigsten anwendbaren Planpreis bleiben, oder allowZeroInvoice muss aktiviert sein.",
2588
3713
  PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions l\xE4sst sich nicht senken.",
2589
3714
  // ── contract ──
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.",
2590
3725
  CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "Aus einem Checkout-Angebot entsteht nur ein einziger Vertrag, und das erst, wenn seine Positionen eingefroren sind.",
2591
3726
  CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "Ein Checkout-Angebot braucht eine eingefrorene Plan-Position.",
2592
3727
  CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
@@ -2611,6 +3746,8 @@ var ERROR_MESSAGES_DE = {
2611
3746
  CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout-Angebot '{offerId}' wurde bereits eingel\xF6st \u2014 '{action}' ist nicht mehr m\xF6glich",
2612
3747
  CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' muss eingel\xF6st sein, bevor der Vertrag entsteht",
2613
3748
  CHECKOUT_OFFER_CHANGED: "Checkout-Angebot '{offerId}' hat sich w\xE4hrend des Abschlusses ge\xE4ndert. Bitte neu laden.",
3749
+ CHECKOUT_OFFER_CONTRACT_IN_FORCE: "Checkout-Angebot '{offerId}' schlie\xDFt einen ersten Vertrag ab, und dieser Mandant hat bereits einen. Ein laufendes Abo \xE4ndert sich \xFCber seinen Plan und seine Add-ons.",
3750
+ CHECKOUT_OFFER_ADD_ON_BOOKED_IN_ANOTHER_VERSION: "Checkout-Angebot '{offerId}' nennt Version {offeredVersion} des Add-ons '{bundleKey}', das dieser Mandant in Version {bookedVersion} gebucht hat (Buchung {subscriptionBundleId}). Die Version wechselt \xFCber das Angebot neben dem Add-on.",
2614
3751
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
2615
3752
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
2616
3753
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
@@ -2621,11 +3758,16 @@ var ERROR_MESSAGES_DE = {
2621
3758
  SUBSCRIBER_NOT_FOUND: "Vertragspartner '{subscriberId}' nicht gefunden",
2622
3759
  SUBSCRIBER_LEGAL_NAME_REQUIRED: "Ein Vertragspartner braucht seinen rechtlichen Namen.",
2623
3760
  SUBSCRIBER_DETAIL_INVALID: "Das Feld {field} des Vertragspartners ist ung\xFCltig.",
3761
+ SUBSCRIBER_IDENTITY_INCOMPLETE: "Die Rechnungsadresse des Vertragspartners ist noch nicht vollst\xE4ndig: Ein Vertrag nennt ihn nur mit Stra\xDFe und Hausnummer, PLZ, Ort und Land.",
3762
+ SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED: "Eine \xC4nderung, ob der Vertragspartner ein Unternehmen ist, braucht eine Begr\xFCndung.",
3763
+ SUBSCRIBER_VAT_ID_MISSING: "Der Vertragspartner hat keine Umsatzsteuer-Identifikationsnummer, die sich pr\xFCfen lie\xDFe.",
2624
3764
  SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} geh\xF6rt zur rechtlichen Identit\xE4t des Vertragspartners und \xE4ndert sich nur als Korrektur mit Begr\xFCndung.",
2625
3765
  SUBSCRIBER_CORRECTION_REASON_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t braucht eine Begr\xFCndung.",
2626
3766
  SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t muss nennen, wer sie vornimmt.",
2627
3767
  SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "Die Korrektur \xE4ndert nichts: Jeder genannte Wert ist bereits erfasst.",
2628
3768
  SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "\xDCbernimmt eine andere Rechtsperson, ist das eine \xDCbertragung und keine Korrektur; sie l\xE4sst sich nicht als \xC4nderung erfassen.",
3769
+ SUBSCRIBER_BUSINESS_STATUS_NOT_A_CONTACT: "Ob der Vertragspartner ein Unternehmen ist, geh\xF6rt zu seiner steuerlichen Herkunft und \xE4ndert sich nur als erfasste \xC4nderung, die nennt, wer sie vornimmt.",
3770
+ SUBSCRIBER_CHANGE_ACTOR_REQUIRED: "Eine \xC4nderung der Angaben des Vertragspartners muss nennen, wer sie vornimmt.",
2629
3771
  // ── registration ──
2630
3772
  PENDING_REGISTRATION_NOT_FOUND: "Diese Registrierung konnten wir nicht finden. Vielleicht ist sie bereits abgeschlossen oder verworfen.",
2631
3773
  PENDING_REGISTRATION_EXPIRED: "Diese Registrierung ist abgelaufen. Bitte fange noch einmal von vorn an.",
@@ -2659,13 +3801,18 @@ var ERROR_MESSAGES_DE = {
2659
3801
  MAINTENANCE_WINDOW_END_NOT_AFTER_START: "Das Ende eines Wartungsfensters muss nach seinem Beginn liegen.",
2660
3802
  MAINTENANCE_WINDOW_END_IN_PAST: "Das Ende eines Wartungsfensters darf nicht in der Vergangenheit liegen.",
2661
3803
  MAINTENANCE_TIME_INVALID: "{field} muss Datum und Uhrzeit mit Zeitzone sein, etwa 2026-10-02T22:00+02:00.",
2662
- MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein."
3804
+ MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein.",
3805
+ // ── tax ──
3806
+ TAX_TREATMENT_NOT_SUPPORTED: "Der Steueradapter {adapter} unterst\xFCtzt diesen Fall nicht: {reason}",
3807
+ TAX_VAT_ID_CHECK_NOT_COMPLETED: "Die Umsatzsteuer-Identifikationsnummer lie\xDF sich gerade nicht pr\xFCfen ({reason}). Bitte sp\xE4ter erneut versuchen.",
3808
+ TAX_VAT_ID_CHECK_NOT_AVAILABLE: "Diese Installation nennt keinen Steueradapter, darum wird keine Umsatzsteuer-Identifikationsnummer gepr\xFCft."
2663
3809
  };
2664
3810
  export {
2665
3811
  ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
2666
3812
  AUTH_ERROR_CODES,
2667
3813
  BILLING_ERROR_CODES,
2668
3814
  BUNDLE_PRICE_LOOKUP_LIMIT,
3815
+ BUNDLE_VERSION_RETIREMENT_CAPABILITY,
2669
3816
  CATALOGUE_KEYS,
2670
3817
  CATALOG_ERROR_CODES,
2671
3818
  CONTRACT_ERROR_CODES,
@@ -2673,7 +3820,13 @@ export {
2673
3820
  ERROR_MESSAGES_DE,
2674
3821
  ERROR_MESSAGES_EN,
2675
3822
  FEATURE_NOT_LICENSED,
3823
+ FEATURE_WITHDRAWAL_CAPABILITY,
3824
+ FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
2676
3825
  ForeignPaymentMethodReferenceError,
3826
+ INVOICE_ARCHIVE_TRIGGERS,
3827
+ INVOICE_DOCUMENT_CAPABILITY,
3828
+ INVOICE_ERROR_CODES,
3829
+ INVOICE_SEQUENCE_DIGITS,
2677
3830
  LEGAL_IDENTITY_FIELDS,
2678
3831
  MAINTENANCE_CAPABILITY,
2679
3832
  MAINTENANCE_ERROR_CODES,
@@ -2706,9 +3859,21 @@ export {
2706
3859
  SETTINGS_ERROR_CODES,
2707
3860
  SETUP_ERROR_CODES,
2708
3861
  SUBSCRIBER_ACCOUNT_CAPABILITY,
3862
+ SUBSCRIBER_ATTENTION_CAPABILITY,
3863
+ SUBSCRIBER_ATTENTION_PAGE_SIZE,
3864
+ SUBSCRIBER_CHARGE_ORIGINS,
3865
+ SUBSCRIBER_CHARGE_SOURCES,
3866
+ SUBSCRIBER_CORRECTION_CAPABILITY,
2709
3867
  SUBSCRIBER_ERROR_CODES,
3868
+ SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
3869
+ SUBSCRIBER_STANDING_CAPABILITY,
3870
+ SUBSCRIBER_TAX_ORIGIN_FIELDS,
3871
+ TAX_ERROR_CODES,
3872
+ TAX_TREATMENT_KINDS,
2710
3873
  VERSION_RETIREMENT_CAPABILITY,
3874
+ addDaysToDay,
2711
3875
  applyPromo,
3876
+ asXmlReadsIt,
2712
3877
  assertPersistenceCapabilities,
2713
3878
  buildActivePlanVersionWhere,
2714
3879
  buildActiveVersionWhere,
@@ -2717,6 +3882,7 @@ export {
2717
3882
  bundleKeyTaken,
2718
3883
  bundleStemDefaults,
2719
3884
  canonicalJson,
3885
+ canonicalVatId,
2720
3886
  catalogDraftExists,
2721
3887
  catalogVersionAlreadyPublished,
2722
3888
  catalogVersionGone,
@@ -2731,14 +3897,28 @@ export {
2731
3897
  contractPartiesOf,
2732
3898
  coverageExcludingSelf,
2733
3899
  customLimitsReader,
3900
+ dayInZone,
2734
3901
  definedFields,
2735
3902
  diffSettings,
3903
+ draftSubscriptionInvoice,
3904
+ endedAtOnceSubjectOf,
3905
+ featureWithdrawalRowOf,
3906
+ featuresWithdrawnAt,
2736
3907
  formatCustomerNumber,
2737
3908
  formatErrorMessage,
3909
+ formatInvoiceNumber,
2738
3910
  grossFromNet,
3911
+ holdsOnlyXmlCharacters,
2739
3912
  identityCorrectionDelta,
3913
+ invoiceArchiveProtectionProblem,
3914
+ invoiceContentDraftOf,
3915
+ invoiceFooterOf,
3916
+ invoiceGroupsOf,
3917
+ invoiceIssuerOf,
2740
3918
  isBundleRedundant,
3919
+ isFeatureWithdrawnAt,
2741
3920
  isForeignPaymentMethodReferenceError,
3921
+ isInvoiceDocumentChecksum,
2742
3922
  isMaintenanceAnnouncementLapsed,
2743
3923
  isMaintenanceOverrun,
2744
3924
  isPaymentCallbackRejectedError,
@@ -2748,6 +3928,8 @@ export {
2748
3928
  isVersionEditable,
2749
3929
  issuerIdentityOf,
2750
3930
  keepOneRecommended,
3931
+ keepsVatIdCheck,
3932
+ lastDayInZone,
2751
3933
  maintenanceRetryAfterSeconds,
2752
3934
  maintenanceStatusOf,
2753
3935
  maintenanceWindowStatusOf,
@@ -2757,6 +3939,7 @@ export {
2757
3939
  missingRequiresFor,
2758
3940
  movedIdentityFields,
2759
3941
  netFromGross,
3942
+ nextWithdrawalChange,
2760
3943
  noActivePlanVersion,
2761
3944
  parseZonedInstant,
2762
3945
  percentOf,
@@ -2793,11 +3976,22 @@ export {
2793
3976
  subscriptionBundleGone,
2794
3977
  subscriptionContractGone,
2795
3978
  subscriptionGone,
3979
+ subscriptionInvoiceChargeInvoiced,
3980
+ subscriptionInvoiceColumns,
3981
+ subscriptionInvoiceDocumentChanged,
3982
+ subscriptionInvoiceLineColumns,
2796
3983
  sumToCents,
3984
+ taxOriginOf,
3985
+ taxOriginWrite,
3986
+ taxPerRate,
3987
+ taxTreatmentToJson,
3988
+ toArchivedInvoiceDocument,
2797
3989
  toBundleStemRow,
3990
+ toBundleVersionRetirementRecord,
2798
3991
  toCents,
2799
3992
  toContractLineItemRecord,
2800
3993
  toDecimalString,
3994
+ toFeatureWithdrawalRecord,
2801
3995
  toMaintenanceWindowRecord,
2802
3996
  toPlanRow,
2803
3997
  toPlanVersionRow,
@@ -2806,11 +4000,22 @@ export {
2806
4000
  toSubscriberCorrectionRecord,
2807
4001
  toSubscriberPaymentMethodRecord,
2808
4002
  toSubscriberRecord,
4003
+ toSubscriberTaxOriginChangeRecord,
4004
+ toSubscriberVatIdCheckRecord,
2809
4005
  toSubscriptionBundleRecord,
2810
4006
  toSubscriptionContractRecord,
4007
+ toSubscriptionInvoiceDocumentRecord,
4008
+ toSubscriptionInvoiceRecord,
2811
4009
  toSubscriptionNoticeRecord,
4010
+ toTaxOriginValues,
4011
+ toTaxTreatment,
2812
4012
  toVersionRetirementRecord,
4013
+ utcMidnightOf,
4014
+ vatCategoryOf,
4015
+ vatIdCheckFromStore,
2813
4016
  versionOnSale,
2814
4017
  versionOnSaleOrNext,
2815
- versionSale
4018
+ versionSale,
4019
+ withdrawnFeaturesOf,
4020
+ yearOfDay
2816
4021
  };