@saasicat/core 1.0.0-rc.15 → 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
- 785f84b789c4fbc325a4d35f81dec979a2575832412363bbcf2fbbe87995e875
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)) {
@@ -674,6 +686,27 @@ var CONTRACT_ERROR_CODES = {
674
686
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
675
687
  SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED"
676
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
+ };
677
710
  var REGISTRATION_ERROR_CODES = {
678
711
  PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND",
679
712
  PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED",
@@ -737,6 +770,7 @@ var PLATFORM_ERROR_CODES = {
737
770
  ...CATALOG_ERROR_CODES,
738
771
  ...BILLING_ERROR_CODES,
739
772
  ...CONTRACT_ERROR_CODES,
773
+ ...SUBSCRIBER_ERROR_CODES,
740
774
  ...REGISTRATION_ERROR_CODES,
741
775
  ...SETTINGS_ERROR_CODES
742
776
  };
@@ -896,11 +930,113 @@ function toQuotaEffects(value) {
896
930
  return quotas;
897
931
  }
898
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
+
899
1031
  // src/subscription-contract-mapping.ts
900
1032
  function toSubscriptionContractRecord(row, lineItems) {
901
1033
  return {
902
1034
  id: row.id,
903
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,
904
1040
  status: row.status,
905
1041
  effectiveFrom: row.effectiveFrom,
906
1042
  effectiveUntil: row.effectiveUntil,
@@ -941,6 +1077,32 @@ function toContractLineItemRecord(row) {
941
1077
  createdAt: row.createdAt
942
1078
  };
943
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
+ }
944
1106
  function isPlainObject2(value) {
945
1107
  return value !== null && typeof value === "object" && !Array.isArray(value);
946
1108
  }
@@ -1157,6 +1319,17 @@ var ERROR_MESSAGES_EN = {
1157
1319
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
1158
1320
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
1159
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.",
1160
1333
  // ── registration ──
1161
1334
  PENDING_REGISTRATION_NOT_FOUND: "This registration could not be found. It may have already been completed or discarded.",
1162
1335
  PENDING_REGISTRATION_EXPIRED: "This registration has expired. Please start again.",
@@ -1347,6 +1520,17 @@ var ERROR_MESSAGES_DE = {
1347
1520
  SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
1348
1521
  NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
1349
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.",
1350
1534
  // ── registration ──
1351
1535
  PENDING_REGISTRATION_NOT_FOUND: "Diese Registrierung konnten wir nicht finden. Vielleicht ist sie bereits abgeschlossen oder verworfen.",
1352
1536
  PENDING_REGISTRATION_EXPIRED: "Diese Registrierung ist abgelaufen. Bitte fange noch einmal von vorn an.",
@@ -1398,6 +1582,8 @@ var ERROR_MESSAGES_DE = {
1398
1582
  REGISTRATION_STEP_BY_STATUS,
1399
1583
  SETTINGS_ERROR_CODES,
1400
1584
  SETUP_ERROR_CODES,
1585
+ SUBSCRIBER_ERROR_CODES,
1586
+ SUBSCRIBER_IDENTITY_FIELDS,
1401
1587
  applyPromo,
1402
1588
  assertPersistenceCapabilities,
1403
1589
  buildActivePlanVersionWhere,
@@ -1409,16 +1595,20 @@ var ERROR_MESSAGES_DE = {
1409
1595
  classifyBundleVersionDiff,
1410
1596
  classifyPlanDiff,
1411
1597
  collectUnsatisfiedRequires,
1598
+ contractPartiesOf,
1412
1599
  coverageExcludingSelf,
1413
1600
  definedFields,
1414
1601
  diffSettings,
1602
+ formatCustomerNumber,
1415
1603
  formatErrorMessage,
1604
+ identityCorrectionDelta,
1416
1605
  isBundleRedundant,
1417
1606
  isPlatformUserExistsError,
1418
1607
  isVersionEditable,
1419
1608
  keepOneRecommended,
1420
1609
  missingRequiresFor,
1421
1610
  pickActivePromo,
1611
+ planCatalogSettingsOf,
1422
1612
  previousUtcDay,
1423
1613
  promoStatus,
1424
1614
  readQuotaRecord,
@@ -1428,9 +1618,12 @@ var ERROR_MESSAGES_DE = {
1428
1618
  selectChargeableBundles,
1429
1619
  settingsSubtreeOf,
1430
1620
  startOfUtcDay,
1621
+ subscriberFromRegistration,
1431
1622
  toBundleStemRow,
1432
1623
  toContractLineItemRecord,
1433
1624
  toPlanRow,
1434
1625
  toPlanVersionRow,
1626
+ toSubscriberCorrectionRecord,
1627
+ toSubscriberRecord,
1435
1628
  toSubscriptionContractRecord
1436
1629
  });