@saasicat/core 1.0.0-rc.18 → 1.0.0-rc.19

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
@@ -871,12 +871,86 @@ function toQuotaEffects(value) {
871
871
  return quotas;
872
872
  }
873
873
 
874
- // src/subscriber-mapping.ts
875
- var SUBSCRIBER_IDENTITY_FIELDS = [
874
+ // src/legal-identity.ts
875
+ var LEGAL_IDENTITY_FIELDS = [
876
876
  "legalName",
877
877
  "vatId",
878
878
  "taxNumber"
879
879
  ];
880
+ function sameLegalIdentity(before, after) {
881
+ return LEGAL_IDENTITY_FIELDS.every((field) => before[field] === after[field]);
882
+ }
883
+ function movedIdentityFields(before, after) {
884
+ return LEGAL_IDENTITY_FIELDS.filter((field) => before[field] !== after[field]);
885
+ }
886
+
887
+ // src/issuer-identity.ts
888
+ function issuerIdentityOf(issuer) {
889
+ if (!issuer) return null;
890
+ return identityOf({
891
+ legalName: issuer.legalName,
892
+ vatId: issuer.vatId,
893
+ taxNumber: issuer.taxNumber
894
+ });
895
+ }
896
+ function recordedIssuerIdentity(settings) {
897
+ const issuer = settings?.issuer;
898
+ if (issuer === null || typeof issuer !== "object" || Array.isArray(issuer)) return null;
899
+ return identityOf(issuer);
900
+ }
901
+ function identityOf(source) {
902
+ const legalName = textOrNull(source.legalName);
903
+ if (legalName === null) return null;
904
+ return {
905
+ legalName,
906
+ vatId: textOrNull(source.vatId),
907
+ taxNumber: textOrNull(source.taxNumber)
908
+ };
909
+ }
910
+ function classifyIssuerChange(recorded, issuer) {
911
+ const current = issuerIdentityOf(issuer);
912
+ if (!recorded)
913
+ return current ? { kind: "first-naming", identity: current } : { kind: "none-named" };
914
+ if (current && sameLegalIdentity(recorded, current))
915
+ return { kind: "unchanged", identity: current };
916
+ if (!current) {
917
+ const moved2 = LEGAL_IDENTITY_FIELDS.filter((field) => recorded[field] !== null);
918
+ return {
919
+ kind: "undeclared",
920
+ recorded,
921
+ current: null,
922
+ moved: moved2,
923
+ fault: { kind: "names-no-issuer" }
924
+ };
925
+ }
926
+ const moved = movedIdentityFields(recorded, current);
927
+ const fault = faultIn(issuer?.correctionOf, recorded, moved);
928
+ if (fault) return { kind: "undeclared", recorded, current, moved, fault };
929
+ const declaration = issuer?.correctionOf;
930
+ return { kind: "corrected", recorded, current, moved, reason: declaration.reason };
931
+ }
932
+ function faultIn(declaration, recorded, moved) {
933
+ if (!declaration) return { kind: "absent" };
934
+ for (const field of LEGAL_IDENTITY_FIELDS) {
935
+ if (!Object.prototype.hasOwnProperty.call(declaration, field)) continue;
936
+ const declared = textOrNull(declaration[field]);
937
+ if (declared !== recorded[field]) {
938
+ return { kind: "names-another-value", field, declared, recorded: recorded[field] };
939
+ }
940
+ }
941
+ for (const field of moved) {
942
+ if (Object.prototype.hasOwnProperty.call(declaration, field)) continue;
943
+ return { kind: "leaves-a-field-out", field, recorded: recorded[field] };
944
+ }
945
+ return null;
946
+ }
947
+ function textOrNull(value) {
948
+ if (typeof value !== "string") return null;
949
+ const trimmed = value.trim();
950
+ return trimmed === "" ? null : trimmed;
951
+ }
952
+
953
+ // src/subscriber-mapping.ts
880
954
  function formatCustomerNumber(prefix, sequence) {
881
955
  return `${prefix}${sequence}`;
882
956
  }
@@ -913,7 +987,7 @@ function toSubscriberCorrectionRecord(row) {
913
987
  function identityCorrectionDelta(current, corrected) {
914
988
  const previous = {};
915
989
  const next = {};
916
- for (const field of SUBSCRIBER_IDENTITY_FIELDS) {
990
+ for (const field of LEGAL_IDENTITY_FIELDS) {
917
991
  const value = corrected[field];
918
992
  if (value === void 0 || value === current[field]) continue;
919
993
  previous[field] = current[field];
@@ -943,17 +1017,26 @@ function contractPartiesOf(subscriber, issuer) {
943
1017
  };
944
1018
  }
945
1019
  function issuerPartyOf(issuer) {
1020
+ const identity = issuerIdentityOf(issuer);
946
1021
  return {
947
- legalName: issuer.legalName,
948
- vatId: issuer.vatId ?? null,
949
- taxNumber: issuer.taxNumber ?? null,
950
- addressLine1: issuer.addressLine1 ?? null,
951
- addressLine2: issuer.addressLine2 ?? null,
952
- postalCode: issuer.postalCode ?? null,
953
- city: issuer.city ?? null,
954
- country: issuer.country ?? null
1022
+ legalName: identity?.legalName ?? "",
1023
+ vatId: identity?.vatId ?? null,
1024
+ taxNumber: identity?.taxNumber ?? null,
1025
+ // Settled like the three above rather than copied verbatim: one copy
1026
+ // whose halves were settled differently is a row that reads two ways,
1027
+ // and a city written with a trailing space is not another city.
1028
+ addressLine1: settledText(issuer.addressLine1),
1029
+ addressLine2: settledText(issuer.addressLine2),
1030
+ postalCode: settledText(issuer.postalCode),
1031
+ city: settledText(issuer.city),
1032
+ country: settledText(issuer.country)
955
1033
  };
956
1034
  }
1035
+ function settledText(value) {
1036
+ if (typeof value !== "string") return null;
1037
+ const trimmed = value.trim();
1038
+ return trimmed === "" ? null : trimmed;
1039
+ }
957
1040
  function subscriberFromRegistration(pending) {
958
1041
  return {
959
1042
  legalName: pending.tenantName,
@@ -971,7 +1054,7 @@ function toIdentityValues(value) {
971
1054
  if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
972
1055
  const source = value;
973
1056
  const values = {};
974
- for (const field of SUBSCRIBER_IDENTITY_FIELDS) {
1057
+ for (const field of LEGAL_IDENTITY_FIELDS) {
975
1058
  if (!Object.prototype.hasOwnProperty.call(source, field)) continue;
976
1059
  const entry = source[field];
977
1060
  values[field] = typeof entry === "string" ? entry : null;
@@ -1088,6 +1171,15 @@ function toSubscriberParty(value) {
1088
1171
  ...toIssuerParty(source)
1089
1172
  };
1090
1173
  }
1174
+ function toRunningContractIssuer(row) {
1175
+ const issuer = isPlainObject2(row.issuerSnapshot) ? row.issuerSnapshot : null;
1176
+ return {
1177
+ id: row.id,
1178
+ tenantId: row.tenantId,
1179
+ issuerLegalName: issuer ? stringOrNull(issuer.legalName) : null,
1180
+ effectiveFrom: row.effectiveFrom
1181
+ };
1182
+ }
1091
1183
  function toIssuerParty(value) {
1092
1184
  const source = value;
1093
1185
  return {
@@ -1578,6 +1670,7 @@ export {
1578
1670
  ERROR_MESSAGES_DE,
1579
1671
  ERROR_MESSAGES_EN,
1580
1672
  FEATURE_NOT_LICENSED,
1673
+ LEGAL_IDENTITY_FIELDS,
1581
1674
  MARKETING_PRIORITY_MAX,
1582
1675
  MARKETING_PRIORITY_MIN,
1583
1676
  OTP_RATE_LIMIT_MAX_SENDS,
@@ -1601,7 +1694,6 @@ export {
1601
1694
  SETTINGS_ERROR_CODES,
1602
1695
  SETUP_ERROR_CODES,
1603
1696
  SUBSCRIBER_ERROR_CODES,
1604
- SUBSCRIBER_IDENTITY_FIELDS,
1605
1697
  applyPromo,
1606
1698
  assertPersistenceCapabilities,
1607
1699
  buildActivePlanVersionWhere,
@@ -1611,6 +1703,7 @@ export {
1611
1703
  bundleStemDefaults,
1612
1704
  canonicalJson,
1613
1705
  classifyBundleVersionDiff,
1706
+ classifyIssuerChange,
1614
1707
  classifyPlanDiff,
1615
1708
  collectUnsatisfiedRequires,
1616
1709
  contractPartiesOf,
@@ -1624,16 +1717,20 @@ export {
1624
1717
  isPaymentCallbackRejectedError,
1625
1718
  isPlatformUserExistsError,
1626
1719
  isVersionEditable,
1720
+ issuerIdentityOf,
1627
1721
  keepOneRecommended,
1628
1722
  missingRequiresFor,
1723
+ movedIdentityFields,
1629
1724
  pickActivePromo,
1630
1725
  planCatalogSettingsOf,
1631
1726
  previousUtcDay,
1632
1727
  promoStatus,
1633
1728
  readQuotaRecord,
1634
1729
  readQuotaValue,
1730
+ recordedIssuerIdentity,
1635
1731
  resolveBundleAvailability,
1636
1732
  resolveErrorMessage,
1733
+ sameLegalIdentity,
1637
1734
  selectChargeableBundles,
1638
1735
  settingsSubtreeOf,
1639
1736
  startOfUtcDay,
@@ -1643,6 +1740,7 @@ export {
1643
1740
  toContractLineItemRecord,
1644
1741
  toPlanRow,
1645
1742
  toPlanVersionRow,
1743
+ toRunningContractIssuer,
1646
1744
  toSubscriberCorrectionRecord,
1647
1745
  toSubscriberPaymentMethodRecord,
1648
1746
  toSubscriberRecord,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasicat/core",
3
- "version": "1.0.0-rc.18",
3
+ "version": "1.0.0-rc.19",
4
4
  "description": "TypeScript contracts for SaaSiCat — capabilities, catalogs, subscriptions, entitlements, admin manifests, persistence ports — and the pure domain logic both server and browser run: error catalogues, plan-diff classification, promo arithmetic, feature requirements.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",