@saasicat/core 1.0.0-rc.14 → 1.0.0-rc.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- ffbbf3de488fa65a264bc00b14097f9d84d9da3428b766d0c2c6b74ccac9b249
1
+ ac1a4c0280cf9333a264b4d84b290ab32ab44dd67287ec6d1abd99c01bc863b9
package/dist/index.cjs CHANGED
@@ -49,6 +49,8 @@ __export(index_exports, {
49
49
  REGISTRATION_STEP_BY_STATUS: () => REGISTRATION_STEP_BY_STATUS,
50
50
  SETTINGS_ERROR_CODES: () => SETTINGS_ERROR_CODES,
51
51
  SETUP_ERROR_CODES: () => SETUP_ERROR_CODES,
52
+ SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
53
+ SUBSCRIBER_IDENTITY_FIELDS: () => SUBSCRIBER_IDENTITY_FIELDS,
52
54
  applyPromo: () => applyPromo,
53
55
  assertPersistenceCapabilities: () => assertPersistenceCapabilities,
54
56
  buildActivePlanVersionWhere: () => buildActivePlanVersionWhere,
@@ -60,16 +62,20 @@ __export(index_exports, {
60
62
  classifyBundleVersionDiff: () => classifyBundleVersionDiff,
61
63
  classifyPlanDiff: () => classifyPlanDiff,
62
64
  collectUnsatisfiedRequires: () => collectUnsatisfiedRequires,
65
+ contractPartiesOf: () => contractPartiesOf,
63
66
  coverageExcludingSelf: () => coverageExcludingSelf,
64
67
  definedFields: () => definedFields,
65
68
  diffSettings: () => diffSettings,
69
+ formatCustomerNumber: () => formatCustomerNumber,
66
70
  formatErrorMessage: () => formatErrorMessage,
71
+ identityCorrectionDelta: () => identityCorrectionDelta,
67
72
  isBundleRedundant: () => isBundleRedundant,
68
73
  isPlatformUserExistsError: () => isPlatformUserExistsError,
69
74
  isVersionEditable: () => isVersionEditable,
70
75
  keepOneRecommended: () => keepOneRecommended,
71
76
  missingRequiresFor: () => missingRequiresFor,
72
77
  pickActivePromo: () => pickActivePromo,
78
+ planCatalogSettingsOf: () => planCatalogSettingsOf,
73
79
  previousUtcDay: () => previousUtcDay,
74
80
  promoStatus: () => promoStatus,
75
81
  readQuotaRecord: () => readQuotaRecord,
@@ -79,10 +85,13 @@ __export(index_exports, {
79
85
  selectChargeableBundles: () => selectChargeableBundles,
80
86
  settingsSubtreeOf: () => settingsSubtreeOf,
81
87
  startOfUtcDay: () => startOfUtcDay,
88
+ subscriberFromRegistration: () => subscriberFromRegistration,
82
89
  toBundleStemRow: () => toBundleStemRow,
83
90
  toContractLineItemRecord: () => toContractLineItemRecord,
84
91
  toPlanRow: () => toPlanRow,
85
92
  toPlanVersionRow: () => toPlanVersionRow,
93
+ toSubscriberCorrectionRecord: () => toSubscriberCorrectionRecord,
94
+ toSubscriberRecord: () => toSubscriberRecord,
86
95
  toSubscriptionContractRecord: () => toSubscriptionContractRecord
87
96
  });
88
97
  module.exports = __toCommonJS(index_exports);
@@ -364,6 +373,9 @@ var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
364
373
  "features",
365
374
  "plans"
366
375
  ]);
376
+ function planCatalogSettingsOf(catalog) {
377
+ return settingsSubtreeOf(catalog);
378
+ }
367
379
  function settingsSubtreeOf(catalog) {
368
380
  const settings = {};
369
381
  for (const [key, value] of Object.entries(catalog)) {
@@ -664,10 +676,37 @@ var CONTRACT_ERROR_CODES = {
664
676
  CHECKOUT_OFFER_EXPIRED: "CHECKOUT_OFFER_EXPIRED",
665
677
  CHECKOUT_OFFER_ALREADY_CONSUMED: "CHECKOUT_OFFER_ALREADY_CONSUMED",
666
678
  CHECKOUT_OFFER_NOT_CONSUMED: "CHECKOUT_OFFER_NOT_CONSUMED",
679
+ /**
680
+ * The offer changed between the checks of `conclude` and the transaction
681
+ * that consumed it, so the contract checked is not the one the offer now
682
+ * describes. Nothing was written; load the offer and conclude it again.
683
+ */
684
+ CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
667
685
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
668
686
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
669
687
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED"
670
688
  };
689
+ var SUBSCRIBER_ERROR_CODES = {
690
+ /**
691
+ * A contract, a plan change or a booking for a tenant that has no
692
+ * subscriber. Nothing is agreed or charged without the party to it.
693
+ * Carries `tenantId`.
694
+ */
695
+ SUBSCRIBER_REQUIRED: "SUBSCRIBER_REQUIRED",
696
+ /** The tenant already has a live subscriber. Carries `tenantId`. */
697
+ SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS",
698
+ SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND",
699
+ SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED",
700
+ /** A detail that has a form — the country, the invoice email — is not in it. Carries `field`. */
701
+ SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID",
702
+ /** A contact change named a field of the legal identity. Carries `field`. */
703
+ SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT",
704
+ SUBSCRIBER_CORRECTION_REASON_REQUIRED: "SUBSCRIBER_CORRECTION_REASON_REQUIRED",
705
+ SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "SUBSCRIBER_CORRECTION_ACTOR_REQUIRED",
706
+ SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "SUBSCRIBER_CORRECTION_CHANGES_NOTHING",
707
+ /** The operator declared another legal entity: that is a transfer, not an edit. */
708
+ SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "SUBSCRIBER_TAKEOVER_IS_A_TRANSFER"
709
+ };
671
710
  var REGISTRATION_ERROR_CODES = {
672
711
  PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND",
673
712
  PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED",
@@ -731,6 +770,7 @@ var PLATFORM_ERROR_CODES = {
731
770
  ...CATALOG_ERROR_CODES,
732
771
  ...BILLING_ERROR_CODES,
733
772
  ...CONTRACT_ERROR_CODES,
773
+ ...SUBSCRIBER_ERROR_CODES,
734
774
  ...REGISTRATION_ERROR_CODES,
735
775
  ...SETTINGS_ERROR_CODES
736
776
  };
@@ -890,11 +930,113 @@ function toQuotaEffects(value) {
890
930
  return quotas;
891
931
  }
892
932
 
933
+ // src/subscriber-mapping.ts
934
+ var SUBSCRIBER_IDENTITY_FIELDS = [
935
+ "legalName",
936
+ "vatId",
937
+ "taxNumber"
938
+ ];
939
+ function formatCustomerNumber(prefix, sequence) {
940
+ return `${prefix}${sequence}`;
941
+ }
942
+ function toSubscriberRecord(row, tenantId) {
943
+ return {
944
+ id: row.id,
945
+ customerNumber: formatCustomerNumber(row.customerNumberPrefix, row.customerSequence),
946
+ legalName: row.legalName,
947
+ addressLine1: row.addressLine1,
948
+ addressLine2: row.addressLine2,
949
+ postalCode: row.postalCode,
950
+ city: row.city,
951
+ country: row.country,
952
+ vatId: row.vatId,
953
+ taxNumber: row.taxNumber,
954
+ invoiceEmail: row.invoiceEmail,
955
+ tenantId,
956
+ migrated: row.migrated,
957
+ createdAt: row.createdAt,
958
+ updatedAt: row.updatedAt
959
+ };
960
+ }
961
+ function toSubscriberCorrectionRecord(row) {
962
+ return {
963
+ id: row.id,
964
+ subscriberId: row.subscriberId,
965
+ previous: toIdentityValues(row.previous),
966
+ corrected: toIdentityValues(row.corrected),
967
+ reason: row.reason,
968
+ correctedBy: row.correctedBy,
969
+ correctedAt: row.correctedAt
970
+ };
971
+ }
972
+ function identityCorrectionDelta(current, corrected) {
973
+ const previous = {};
974
+ const next = {};
975
+ for (const field of SUBSCRIBER_IDENTITY_FIELDS) {
976
+ const value = corrected[field];
977
+ if (value === void 0 || value === current[field]) continue;
978
+ previous[field] = current[field];
979
+ next[field] = value;
980
+ }
981
+ return {
982
+ previous,
983
+ corrected: next
984
+ };
985
+ }
986
+ function contractPartiesOf(subscriber, issuer) {
987
+ const party = {
988
+ customerNumber: subscriber.customerNumber,
989
+ legalName: subscriber.legalName,
990
+ vatId: subscriber.vatId,
991
+ taxNumber: subscriber.taxNumber,
992
+ addressLine1: subscriber.addressLine1,
993
+ addressLine2: subscriber.addressLine2,
994
+ postalCode: subscriber.postalCode,
995
+ city: subscriber.city,
996
+ country: subscriber.country
997
+ };
998
+ return {
999
+ subscriberId: subscriber.id,
1000
+ subscriber: party,
1001
+ issuer: issuer ? issuerPartyOf(issuer) : null
1002
+ };
1003
+ }
1004
+ function issuerPartyOf(issuer) {
1005
+ return {
1006
+ legalName: issuer.legalName,
1007
+ vatId: issuer.vatId ?? null,
1008
+ taxNumber: issuer.taxNumber ?? null,
1009
+ addressLine1: issuer.addressLine1 ?? null,
1010
+ addressLine2: issuer.addressLine2 ?? null,
1011
+ postalCode: issuer.postalCode ?? null,
1012
+ city: issuer.city ?? null,
1013
+ country: issuer.country ?? null
1014
+ };
1015
+ }
1016
+ function subscriberFromRegistration(pending) {
1017
+ return { legalName: pending.tenantName, invoiceEmail: pending.email };
1018
+ }
1019
+ function toIdentityValues(value) {
1020
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
1021
+ const source = value;
1022
+ const values = {};
1023
+ for (const field of SUBSCRIBER_IDENTITY_FIELDS) {
1024
+ if (!Object.prototype.hasOwnProperty.call(source, field)) continue;
1025
+ const entry = source[field];
1026
+ values[field] = typeof entry === "string" ? entry : null;
1027
+ }
1028
+ return values;
1029
+ }
1030
+
893
1031
  // src/subscription-contract-mapping.ts
894
1032
  function toSubscriptionContractRecord(row, lineItems) {
895
1033
  return {
896
1034
  id: row.id,
897
1035
  tenantId: row.tenantId,
1036
+ subscriberId: row.subscriberId,
1037
+ subscriber: toSubscriberParty(row.subscriberSnapshot),
1038
+ issuer: isPlainObject2(row.issuerSnapshot) ? toIssuerParty(row.issuerSnapshot) : null,
1039
+ partiesMigrated: row.partiesMigrated,
898
1040
  status: row.status,
899
1041
  effectiveFrom: row.effectiveFrom,
900
1042
  effectiveUntil: row.effectiveUntil,
@@ -935,6 +1077,32 @@ function toContractLineItemRecord(row) {
935
1077
  createdAt: row.createdAt
936
1078
  };
937
1079
  }
1080
+ function toSubscriberParty(value) {
1081
+ const source = isPlainObject2(value) ? value : {};
1082
+ return {
1083
+ customerNumber: stringOr(source.customerNumber, ""),
1084
+ ...toIssuerParty(source)
1085
+ };
1086
+ }
1087
+ function toIssuerParty(value) {
1088
+ const source = value;
1089
+ return {
1090
+ legalName: stringOr(source.legalName, ""),
1091
+ vatId: stringOrNull(source.vatId),
1092
+ taxNumber: stringOrNull(source.taxNumber),
1093
+ addressLine1: stringOrNull(source.addressLine1),
1094
+ addressLine2: stringOrNull(source.addressLine2),
1095
+ postalCode: stringOrNull(source.postalCode),
1096
+ city: stringOrNull(source.city),
1097
+ country: stringOrNull(source.country)
1098
+ };
1099
+ }
1100
+ function stringOr(value, fallback) {
1101
+ return typeof value === "string" ? value : fallback;
1102
+ }
1103
+ function stringOrNull(value) {
1104
+ return typeof value === "string" ? value : null;
1105
+ }
938
1106
  function isPlainObject2(value) {
939
1107
  return value !== null && typeof value === "object" && !Array.isArray(value);
940
1108
  }
@@ -1147,9 +1315,21 @@ var ERROR_MESSAGES_EN = {
1147
1315
  CHECKOUT_OFFER_EXPIRED: "Checkout offer '{offerId}' has expired and cannot be {action}",
1148
1316
  CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout offer '{offerId}' has already been consumed and cannot be {action}",
1149
1317
  CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' must be consumed before the contract is created",
1318
+ CHECKOUT_OFFER_CHANGED: "Checkout offer '{offerId}' changed while it was being concluded. Load it again.",
1150
1319
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
1151
1320
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
1152
1321
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
1322
+ // ── subscriber ──
1323
+ SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
1324
+ SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
1325
+ SUBSCRIBER_NOT_FOUND: "Subscriber '{subscriberId}' not found",
1326
+ SUBSCRIBER_LEGAL_NAME_REQUIRED: "A subscriber needs its legal name.",
1327
+ SUBSCRIBER_DETAIL_INVALID: "The subscriber's {field} is not valid.",
1328
+ SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} is part of the subscriber's legal identity and changes only as a correction, with a reason.",
1329
+ SUBSCRIBER_CORRECTION_REASON_REQUIRED: "A correction of the legal identity needs a reason.",
1330
+ SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "A correction of the legal identity has to say who makes it.",
1331
+ SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "The correction changes nothing: every value it names is already recorded.",
1332
+ 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.",
1153
1333
  // ── registration ──
1154
1334
  PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
1155
1335
  PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
@@ -1336,9 +1516,21 @@ var ERROR_MESSAGES_DE = {
1336
1516
  CHECKOUT_OFFER_EXPIRED: "Checkout-Angebot '{offerId}' ist abgelaufen \u2014 '{action}' ist nicht mehr m\xF6glich",
1337
1517
  CHECKOUT_OFFER_ALREADY_CONSUMED: "Checkout-Angebot '{offerId}' wurde bereits eingel\xF6st \u2014 '{action}' ist nicht mehr m\xF6glich",
1338
1518
  CHECKOUT_OFFER_NOT_CONSUMED: "CheckoutOffer '{offerId}' muss eingel\xF6st sein, bevor der Vertrag entsteht",
1519
+ CHECKOUT_OFFER_CHANGED: "Checkout-Angebot '{offerId}' hat sich w\xE4hrend des Abschlusses ge\xE4ndert. Bitte neu laden.",
1339
1520
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
1340
1521
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
1341
1522
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
1523
+ // ── subscriber ──
1524
+ SUBSCRIBER_REQUIRED: "Mandant '{tenantId}' hat keinen Vertragspartner. Ohne ihn wird nichts vereinbart oder berechnet: Zuerst muss der Vertragspartner des Mandanten angelegt werden.",
1525
+ SUBSCRIBER_ALREADY_EXISTS: "Mandant '{tenantId}' hat bereits einen Vertragspartner.",
1526
+ SUBSCRIBER_NOT_FOUND: "Vertragspartner '{subscriberId}' nicht gefunden",
1527
+ SUBSCRIBER_LEGAL_NAME_REQUIRED: "Ein Vertragspartner braucht seinen rechtlichen Namen.",
1528
+ SUBSCRIBER_DETAIL_INVALID: "Das Feld {field} des Vertragspartners ist ung\xFCltig.",
1529
+ SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "{field} geh\xF6rt zur rechtlichen Identit\xE4t des Vertragspartners und \xE4ndert sich nur als Korrektur mit Begr\xFCndung.",
1530
+ SUBSCRIBER_CORRECTION_REASON_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t braucht eine Begr\xFCndung.",
1531
+ SUBSCRIBER_CORRECTION_ACTOR_REQUIRED: "Eine Korrektur der rechtlichen Identit\xE4t muss nennen, wer sie vornimmt.",
1532
+ SUBSCRIBER_CORRECTION_CHANGES_NOTHING: "Die Korrektur \xE4ndert nichts: Jeder genannte Wert ist bereits erfasst.",
1533
+ SUBSCRIBER_TAKEOVER_IS_A_TRANSFER: "\xDCbernimmt eine andere Rechtsperson, ist das eine \xDCbertragung und keine Korrektur; sie l\xE4sst sich nicht als \xC4nderung erfassen.",
1342
1534
  // ── registration ──
1343
1535
  PENDING_REGISTRATION_NOT_FOUND: "Diese Registrierung konnten wir nicht finden. Vielleicht ist sie bereits abgeschlossen oder verworfen.",
1344
1536
  PENDING_REGISTRATION_EXPIRED: "Diese Registrierung ist abgelaufen. Bitte fange noch einmal von vorn an.",
@@ -1390,6 +1582,8 @@ var ERROR_MESSAGES_DE = {
1390
1582
  REGISTRATION_STEP_BY_STATUS,
1391
1583
  SETTINGS_ERROR_CODES,
1392
1584
  SETUP_ERROR_CODES,
1585
+ SUBSCRIBER_ERROR_CODES,
1586
+ SUBSCRIBER_IDENTITY_FIELDS,
1393
1587
  applyPromo,
1394
1588
  assertPersistenceCapabilities,
1395
1589
  buildActivePlanVersionWhere,
@@ -1401,16 +1595,20 @@ var ERROR_MESSAGES_DE = {
1401
1595
  classifyBundleVersionDiff,
1402
1596
  classifyPlanDiff,
1403
1597
  collectUnsatisfiedRequires,
1598
+ contractPartiesOf,
1404
1599
  coverageExcludingSelf,
1405
1600
  definedFields,
1406
1601
  diffSettings,
1602
+ formatCustomerNumber,
1407
1603
  formatErrorMessage,
1604
+ identityCorrectionDelta,
1408
1605
  isBundleRedundant,
1409
1606
  isPlatformUserExistsError,
1410
1607
  isVersionEditable,
1411
1608
  keepOneRecommended,
1412
1609
  missingRequiresFor,
1413
1610
  pickActivePromo,
1611
+ planCatalogSettingsOf,
1414
1612
  previousUtcDay,
1415
1613
  promoStatus,
1416
1614
  readQuotaRecord,
@@ -1420,9 +1618,12 @@ var ERROR_MESSAGES_DE = {
1420
1618
  selectChargeableBundles,
1421
1619
  settingsSubtreeOf,
1422
1620
  startOfUtcDay,
1621
+ subscriberFromRegistration,
1423
1622
  toBundleStemRow,
1424
1623
  toContractLineItemRecord,
1425
1624
  toPlanRow,
1426
1625
  toPlanVersionRow,
1626
+ toSubscriberCorrectionRecord,
1627
+ toSubscriberRecord,
1427
1628
  toSubscriptionContractRecord
1428
1629
  });