@happyvertical/smrt-sales 0.57.22 → 0.57.24

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.
Files changed (67) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +34 -0
  3. package/agents/agreements.md +32 -0
  4. package/agents/commissions.md +2 -1
  5. package/agents/crm.md +150 -0
  6. package/agents/svelte.md +7 -0
  7. package/dist/agreements/collections/AgreementCollection.d.ts +3 -2
  8. package/dist/agreements/collections/AgreementCollection.d.ts.map +1 -1
  9. package/dist/agreements/models/Agreement.d.ts.map +1 -1
  10. package/dist/agreements/services/AgreementExecutionService.d.ts +9 -0
  11. package/dist/agreements/services/AgreementExecutionService.d.ts.map +1 -1
  12. package/dist/agreements/types.d.ts +2 -0
  13. package/dist/agreements/types.d.ts.map +1 -1
  14. package/dist/agreements.js +2 -2
  15. package/dist/chunks/__smrt-register__-Yzz7_2xD.js +6 -0
  16. package/dist/chunks/__smrt-register__-Yzz7_2xD.js.map +1 -0
  17. package/dist/chunks/{agreements-jBiVmttD.js → agreements-RsyRLeDn.js} +35 -16
  18. package/dist/chunks/agreements-RsyRLeDn.js.map +1 -0
  19. package/dist/chunks/{commissions-D5x24d4o.js → commissions-nMBMa0DT.js} +410 -344
  20. package/dist/chunks/commissions-nMBMa0DT.js.map +1 -0
  21. package/dist/chunks/{crm-BRICSwf2.js → crm-BENK-hHd.js} +793 -123
  22. package/dist/chunks/crm-BENK-hHd.js.map +1 -0
  23. package/dist/chunks/{referrals-BRdTkEFt.js → referrals-BPEaXqCz.js} +2 -2
  24. package/dist/chunks/{referrals-BRdTkEFt.js.map → referrals-BPEaXqCz.js.map} +1 -1
  25. package/dist/commissions/collections/CommissionPayoutCollection.d.ts +3 -1
  26. package/dist/commissions/collections/CommissionPayoutCollection.d.ts.map +1 -1
  27. package/dist/commissions/models/CommissionPayout.d.ts +15 -1
  28. package/dist/commissions/models/CommissionPayout.d.ts.map +1 -1
  29. package/dist/commissions/services/CommissionPayoutService.d.ts.map +1 -1
  30. package/dist/commissions/types.d.ts +2 -0
  31. package/dist/commissions/types.d.ts.map +1 -1
  32. package/dist/commissions.js +2 -2
  33. package/dist/crm/collections/SalesGovernanceEventCollection.d.ts +7 -0
  34. package/dist/crm/collections/SalesGovernanceEventCollection.d.ts.map +1 -0
  35. package/dist/crm/collections/SalesGovernanceStateCollection.d.ts +13 -0
  36. package/dist/crm/collections/SalesGovernanceStateCollection.d.ts.map +1 -0
  37. package/dist/crm/index.d.ts +6 -0
  38. package/dist/crm/index.d.ts.map +1 -1
  39. package/dist/crm/models/SalesGovernanceEvent.d.ts +19 -0
  40. package/dist/crm/models/SalesGovernanceEvent.d.ts.map +1 -0
  41. package/dist/crm/models/SalesGovernanceState.d.ts +17 -0
  42. package/dist/crm/models/SalesGovernanceState.d.ts.map +1 -0
  43. package/dist/crm/services/SalesGovernanceService.d.ts +68 -0
  44. package/dist/crm/services/SalesGovernanceService.d.ts.map +1 -0
  45. package/dist/crm/services/governance-write.d.ts +4 -0
  46. package/dist/crm/services/governance-write.d.ts.map +1 -0
  47. package/dist/crm/services/sales-governance-types.d.ts +108 -0
  48. package/dist/crm/services/sales-governance-types.d.ts.map +1 -0
  49. package/dist/crm.js +3 -3
  50. package/dist/index.js +6 -6
  51. package/dist/manifest.json +592 -5
  52. package/dist/referrals.js +2 -2
  53. package/dist/smrt-knowledge.json +320 -14
  54. package/dist/svelte/components/SellerGovernancePanel.svelte +65 -0
  55. package/dist/svelte/components/SellerGovernancePanel.svelte.d.ts +13 -0
  56. package/dist/svelte/components/SellerGovernancePanel.svelte.d.ts.map +1 -0
  57. package/dist/svelte/index.d.ts +2 -1
  58. package/dist/svelte/index.d.ts.map +1 -1
  59. package/dist/svelte/index.js +1 -0
  60. package/dist/svelte/types.d.ts +22 -1
  61. package/dist/svelte/types.d.ts.map +1 -1
  62. package/package.json +6 -6
  63. package/dist/chunks/__smrt-register__-bn1C2AUF.js +0 -6
  64. package/dist/chunks/__smrt-register__-bn1C2AUF.js.map +0 -1
  65. package/dist/chunks/agreements-jBiVmttD.js.map +0 -1
  66. package/dist/chunks/commissions-D5x24d4o.js.map +0 -1
  67. package/dist/chunks/crm-BRICSwf2.js.map +0 -1
@@ -534,7 +534,7 @@ var Commission = class extends SmrtObject {
534
534
  async assertDedupeKeyNotTaken() {
535
535
  if (!this.dedupeKey) return;
536
536
  try {
537
- const res = await this.db.query(`SELECT id FROM ${this.tableName} WHERE dedupe_key = $1`, this.dedupeKey);
537
+ const res = await this.db.query(`SELECT CAST(id AS VARCHAR) AS id FROM ${this.tableName} WHERE dedupe_key = $1`, this.dedupeKey);
538
538
  if ((Array.isArray(res) ? res : res.rows ?? []).find((row) => row.id !== this.id)) throw new Error(`Commission (dedupeKey '${this.dedupeKey}'): a commission with this dedupe key already exists \u2014 commissions are immutable audit rows; corrections append CommissionAdjustments.`);
539
539
  } catch (error) {
540
540
  if (error instanceof Error && error.message.includes("immutable")) throw error;
@@ -702,7 +702,81 @@ var CommissionCollection = class extends SmrtCollection {
702
702
  }
703
703
  };
704
704
  //#endregion
705
- //#region src/commissions/models/CommissionPayout.ts
705
+ //#region src/commissions/types.ts
706
+ var EARNER_STATUSES = [
707
+ "pending",
708
+ "active",
709
+ "suspended"
710
+ ];
711
+ var EARNER_SOURCE_ATTRIBUTION_STATUSES = ["active", "inactive"];
712
+ var EARNER_KINDS = [
713
+ "person",
714
+ "organisation",
715
+ "treasury"
716
+ ];
717
+ var PAYOUT_METHODS = [
718
+ "bank_transfer",
719
+ "check",
720
+ "paypal",
721
+ "credit",
722
+ "other",
723
+ "stripe_connect",
724
+ "crypto"
725
+ ];
726
+ var PAYOUT_METHODS_REQUIRING_DESTINATION = ["stripe_connect", "crypto"];
727
+ var COMMISSION_PLAN_STATUSES = [
728
+ "draft",
729
+ "active",
730
+ "superseded",
731
+ "retired"
732
+ ];
733
+ var COMMISSION_STATUSES = [
734
+ "pending",
735
+ "earned",
736
+ "approved",
737
+ "payable",
738
+ "paid"
739
+ ];
740
+ var COMMISSION_BASES = [
741
+ "fixed",
742
+ "gross",
743
+ "net",
744
+ "margin",
745
+ "custom"
746
+ ];
747
+ var COMMISSION_ADJUSTMENT_KINDS = [
748
+ "refund",
749
+ "credit",
750
+ "chargeback",
751
+ "dispute",
752
+ "correction"
753
+ ];
754
+ var COMMISSION_PAYOUT_STATUSES = [
755
+ "pending",
756
+ "approved",
757
+ "processing",
758
+ "completed",
759
+ "failed",
760
+ "rejected"
761
+ ];
762
+ var EARNING_EVENT_KINDS = [
763
+ "conversion",
764
+ "agreement_execution",
765
+ "invoice_payment",
766
+ "collected_revenue",
767
+ "recognized_margin",
768
+ "milestone"
769
+ ];
770
+ var ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES = [
771
+ "earned",
772
+ "approved",
773
+ "payable",
774
+ "paid"
775
+ ];
776
+ var WATERFALL_POOL_RESIDUAL_RULES = ["remainder_sink", "largest_remainder"];
777
+ var WATERFALL_POOL_ZERO_WEIGHT_RULES = ["remainder_sink", "equal"];
778
+ //#endregion
779
+ //#region src/commissions/models/Earner.ts
706
780
  var __defProp$4 = Object.defineProperty;
707
781
  var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
708
782
  var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
@@ -718,6 +792,197 @@ var __decorateClass$4 = (decorators, target, key, kind) => {
718
792
  return result;
719
793
  };
720
794
  var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
795
+ function validatePayoutDestination(destination) {
796
+ const value = destination;
797
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Earner payoutDestination must be an object");
798
+ const requireText = (key) => {
799
+ if (typeof value[key] !== "string" || !value[key].trim()) throw new Error(`Earner payoutDestination.${key} is required`);
800
+ };
801
+ let allowed;
802
+ if (value.method === "stripe_connect") {
803
+ allowed = ["method", "connectedAccountId"];
804
+ requireText("connectedAccountId");
805
+ if (!/^acct_[A-Za-z0-9]+$/.test(value.connectedAccountId)) throw new Error("Earner payoutDestination.connectedAccountId must be a Stripe connected account id ('acct_…')");
806
+ } else if (value.method === "crypto") {
807
+ allowed = [
808
+ "method",
809
+ "network",
810
+ "asset",
811
+ "address",
812
+ "memo"
813
+ ];
814
+ requireText("network");
815
+ requireText("asset");
816
+ requireText("address");
817
+ if (value.memo !== void 0 && typeof value.memo !== "string") throw new Error("Earner payoutDestination.memo must be a string");
818
+ } else throw new Error(`Earner payoutDestination.method must be one of ${PAYOUT_METHODS_REQUIRING_DESTINATION.join(", ")}`);
819
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`Earner payoutDestination has an unexpected field '${key}'`);
820
+ }
821
+ var Earner = class extends SmrtObject {
822
+ tenantId = null;
823
+ /**
824
+ * Party kind: `person` (default), `organisation`, or `treasury`.
825
+ * Organisation and treasury earners need no person Profile.
826
+ */
827
+ kind = "person";
828
+ profileId = "";
829
+ /** Human-readable display name for portals and operator views. */
830
+ displayName = "";
831
+ /** Account lifecycle: `pending` (default) → `active` / `suspended`. */
832
+ status = "pending";
833
+ /** Preferred payout delivery method. */
834
+ payoutMethod = "bank_transfer";
835
+ /**
836
+ * Typed payout-rail descriptor as a JSON string (`{}` when none). Required
837
+ * for an active earner whose method is `stripe_connect` or `crypto`, and
838
+ * its `method` must match {@link payoutMethod}. Use
839
+ * {@link getPayoutDestination}/{@link setPayoutDestination}.
840
+ */
841
+ payoutDestination = "{}";
842
+ /**
843
+ * Minimum unsettled balance (integer cents) before a payout batch is
844
+ * created. Default $50.00 = 5000 cents.
845
+ */
846
+ payoutThresholdCents = 5e3;
847
+ /**
848
+ * Payout cadence key. Open string so applications can define their own
849
+ * schedules (`manual`, `monthly`, `weekly`, `net_30`, …); `manual` means
850
+ * an operator triggers batches explicitly.
851
+ */
852
+ payoutScheduleKey = "manual";
853
+ /** ISO 4217 currency all of this earner's balances settle in. */
854
+ currency = "USD";
855
+ /**
856
+ * Additional metadata as a JSON string (tax info, payout-rail details,
857
+ * …). Use {@link getMetadata}/{@link setMetadata}.
858
+ */
859
+ metadata = "{}";
860
+ constructor(options = {}) {
861
+ super(options);
862
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
863
+ if (options.kind !== void 0) this.kind = options.kind;
864
+ if (options.profileId !== void 0) this.profileId = options.profileId;
865
+ if (options.displayName !== void 0) this.displayName = options.displayName;
866
+ if (options.status !== void 0) this.status = options.status;
867
+ if (options.payoutMethod !== void 0) this.payoutMethod = options.payoutMethod;
868
+ if (options.payoutDestination !== void 0) this.payoutDestination = options.payoutDestination;
869
+ if (options.payoutThresholdCents !== void 0) this.payoutThresholdCents = options.payoutThresholdCents;
870
+ if (options.payoutScheduleKey !== void 0) this.payoutScheduleKey = options.payoutScheduleKey;
871
+ if (options.currency !== void 0) this.currency = options.currency;
872
+ if (options.metadata !== void 0) this.metadata = options.metadata;
873
+ }
874
+ isActive() {
875
+ return this.status === "active";
876
+ }
877
+ isPending() {
878
+ return this.status === "pending";
879
+ }
880
+ isSuspended() {
881
+ return this.status === "suspended";
882
+ }
883
+ /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
884
+ getMetadata() {
885
+ if (!this.metadata) return {};
886
+ try {
887
+ const parsed = JSON.parse(this.metadata);
888
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
889
+ } catch {
890
+ return {};
891
+ }
892
+ }
893
+ /** Serialize and store {@link metadata}. */
894
+ setMetadata(data) {
895
+ this.metadata = JSON.stringify(data ?? {});
896
+ }
897
+ isOrganisation() {
898
+ return this.kind === "organisation";
899
+ }
900
+ isTreasury() {
901
+ return this.kind === "treasury";
902
+ }
903
+ /** Parse {@link payoutDestination}; `null` when none or unparseable. */
904
+ getPayoutDestination() {
905
+ try {
906
+ const parsed = JSON.parse(this.payoutDestination || "{}");
907
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.keys(parsed).length > 0 ? parsed : null;
908
+ } catch {
909
+ return null;
910
+ }
911
+ }
912
+ /**
913
+ * Validate and store a payout destination, and set {@link payoutMethod}
914
+ * to its method. `null` clears it (the method is left unchanged).
915
+ */
916
+ setPayoutDestination(destination) {
917
+ if (destination === null) {
918
+ this.payoutDestination = "{}";
919
+ return;
920
+ }
921
+ validatePayoutDestination(destination);
922
+ this.payoutDestination = JSON.stringify(destination);
923
+ this.payoutMethod = destination.method;
924
+ }
925
+ /**
926
+ * Save with field guards: `kind` and `payoutMethod` must be known values,
927
+ * a stored destination must be valid and match `payoutMethod`, and an
928
+ * active Stripe Connect / crypto earner must have a destination.
929
+ */
930
+ async save() {
931
+ this.assertPayoutTerms();
932
+ return await super.save();
933
+ }
934
+ assertPayoutTerms() {
935
+ const label = `Earner ${this.id ?? "<new>"}`;
936
+ if (!EARNER_KINDS.includes(this.kind)) throw new Error(`${label}: unknown kind '${this.kind}'`);
937
+ if (!PAYOUT_METHODS.includes(this.payoutMethod)) throw new Error(`${label}: unknown payoutMethod '${this.payoutMethod}'`);
938
+ let parsed;
939
+ try {
940
+ parsed = JSON.parse(this.payoutDestination || "{}");
941
+ } catch {
942
+ throw new Error(`${label}: payoutDestination must be valid JSON`);
943
+ }
944
+ const hasDestination = !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.keys(parsed).length > 0;
945
+ if (hasDestination) {
946
+ validatePayoutDestination(parsed);
947
+ if (parsed.method !== this.payoutMethod) throw new Error(`${label}: payoutDestination method '${parsed.method}' does not match payoutMethod '${this.payoutMethod}'`);
948
+ } else if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${label}: payoutDestination must be a JSON object`);
949
+ if (this.status === "active" && !hasDestination && PAYOUT_METHODS_REQUIRING_DESTINATION.includes(this.payoutMethod)) throw new Error(`${label}: an active ${this.payoutMethod} earner requires a payoutDestination`);
950
+ }
951
+ };
952
+ __publicField$4(Earner, "__smrtPackage__", "@happyvertical/smrt-sales");
953
+ __decorateClass$4([tenantId({ nullable: true })], Earner.prototype, "tenantId", 2);
954
+ __decorateClass$4([crossPackageRef("@happyvertical/smrt-profiles:Profile")], Earner.prototype, "profileId", 2);
955
+ Earner = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
956
+ api: { include: [
957
+ "list",
958
+ "get",
959
+ "create",
960
+ "update"
961
+ ] },
962
+ mcp: { include: [
963
+ "list",
964
+ "get",
965
+ "create"
966
+ ] },
967
+ cli: { skipApiCheck: true }
968
+ })], Earner);
969
+ //#endregion
970
+ //#region src/commissions/models/CommissionPayout.ts
971
+ var __defProp$3 = Object.defineProperty;
972
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
973
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, {
974
+ enumerable: true,
975
+ configurable: true,
976
+ writable: true,
977
+ value
978
+ }) : obj[key] = value;
979
+ var __decorateClass$3 = (decorators, target, key, kind) => {
980
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
981
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
982
+ if (kind && result) __defProp$3(target, key, result);
983
+ return result;
984
+ };
985
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
721
986
  var PAYOUT_STATUS_TRANSITIONS = {
722
987
  pending: ["approved", "rejected"],
723
988
  approved: [
@@ -755,6 +1020,13 @@ var CommissionPayout = class extends SmrtObject {
755
1020
  /** Delivery method for this batch (defaulted from the Earner). */
756
1021
  payoutMethod = "bank_transfer";
757
1022
  /**
1023
+ * Routing descriptor captured at batch creation, never the live Earner's
1024
+ * preference. Routing is immutable after the first save, including pending
1025
+ * and failed/reset batches; reject and create a new batch to change it.
1026
+ * Legacy/manual methods have no typed descriptor (`{}`).
1027
+ */
1028
+ payoutDestination = "{}";
1029
+ /**
758
1030
  * Lifecycle status — see the class doc. Mutate via {@link approve} /
759
1031
  * {@link markProcessing} / {@link complete} / {@link fail} /
760
1032
  * {@link resetFromFailed}.
@@ -802,6 +1074,7 @@ var CommissionPayout = class extends SmrtObject {
802
1074
  if (options.totalAmountCents !== void 0) this.totalAmountCents = options.totalAmountCents;
803
1075
  if (options.currency !== void 0) this.currency = options.currency;
804
1076
  if (options.payoutMethod !== void 0) this.payoutMethod = options.payoutMethod;
1077
+ if (options.payoutDestination !== void 0) this.payoutDestination = options.payoutDestination;
805
1078
  if (options.status !== void 0) this.status = options.status;
806
1079
  if (options.paymentReference !== void 0) this.paymentReference = options.paymentReference;
807
1080
  if (options.providerRef !== void 0) this.providerRef = options.providerRef;
@@ -847,6 +1120,7 @@ var CommissionPayout = class extends SmrtObject {
847
1120
  approve() {
848
1121
  if (this.status !== "pending") throw new Error(`CommissionPayout ${this.id ?? "<new>"}: cannot approve from status '${this.status}'`);
849
1122
  if (this.totalAmountCents <= 0) throw new Error(`CommissionPayout ${this.id ?? "<new>"}: cannot approve a batch with non-positive total (${this.totalAmountCents} cents)`);
1123
+ this.validateRouting(true);
850
1124
  this.status = "approved";
851
1125
  }
852
1126
  /** `approved → processing`. Does NOT save — the caller saves. */
@@ -908,6 +1182,16 @@ ${memo}` : memo;
908
1182
  this.paymentReference = "";
909
1183
  this.paidAt = null;
910
1184
  }
1185
+ /** Read the batch's validated routing snapshot, never a live Earner. */
1186
+ getPayoutDestination() {
1187
+ return this.validateRouting(false);
1188
+ }
1189
+ /** Set a descriptor before first persistence; save enforces routing freeze. */
1190
+ setPayoutDestination(destination) {
1191
+ if (destination) validatePayoutDestination(destination);
1192
+ this.payoutDestination = JSON.stringify(destination ?? {});
1193
+ if (destination) this.payoutMethod = destination.method;
1194
+ }
911
1195
  /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
912
1196
  getMetadata() {
913
1197
  if (!this.metadata) return {};
@@ -937,13 +1221,51 @@ ${memo}` : memo;
937
1221
  */
938
1222
  async save() {
939
1223
  this.validateTotals();
1224
+ await this.assertRoutingUnchanged();
940
1225
  const prior = await this.resolvePriorStatus();
1226
+ this.validateRouting(prior !== this.status && [
1227
+ "approved",
1228
+ "processing",
1229
+ "completed"
1230
+ ].includes(this.status));
941
1231
  this.assertStatusTransition(prior);
942
1232
  if (this.status === "completed" && !this.paymentReference) throw new Error(`CommissionPayout ${this.id ?? "<new>"}: a completed payout requires a paymentReference (use complete()).`);
943
1233
  const result = await super.save();
944
1234
  loadedPayoutStatus.set(this, this.status);
945
1235
  return result;
946
1236
  }
1237
+ validateRouting(requireDestination) {
1238
+ const label = `CommissionPayout ${this.id ?? "<new>"}`;
1239
+ if (!PAYOUT_METHODS.includes(this.payoutMethod)) throw new Error(`${label}: unknown payoutMethod '${this.payoutMethod}'`);
1240
+ let parsed;
1241
+ try {
1242
+ parsed = JSON.parse(this.payoutDestination);
1243
+ } catch {
1244
+ throw new Error(`${label}: payoutDestination must be valid JSON`);
1245
+ }
1246
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${label}: payoutDestination must be a JSON object`);
1247
+ if (Object.keys(parsed).length > 0) {
1248
+ validatePayoutDestination(parsed);
1249
+ const destination = parsed;
1250
+ if (destination.method !== this.payoutMethod) throw new Error(`${label}: payoutDestination method does not match payoutMethod`);
1251
+ return destination;
1252
+ }
1253
+ if (requireDestination && PAYOUT_METHODS_REQUIRING_DESTINATION.includes(this.payoutMethod)) throw new Error(`${label}: ${this.payoutMethod} requires a payoutDestination snapshot; reject and recreate the batch`);
1254
+ return null;
1255
+ }
1256
+ /** Authoritative read also closes blind-id and natural-key upsert doors. */
1257
+ async assertRoutingUnchanged() {
1258
+ const { rows } = await this.db.query(`SELECT CAST(id AS VARCHAR) AS id, payout_method, payout_destination FROM ${this.tableName} WHERE id = ? OR idempotency_key = ?`, this.id ?? null, this.idempotencyKey || null);
1259
+ for (const row of rows) {
1260
+ if (row.id !== this.id) throw new Error("CommissionPayout: existing batch routing is immutable; reuse its idempotency key through the payout service");
1261
+ const priorDestination = String(row.payout_destination ?? "{}");
1262
+ const canonical = (raw) => {
1263
+ const parsed = JSON.parse(raw);
1264
+ return JSON.stringify(Object.entries(parsed).sort(([a], [b]) => a.localeCompare(b)));
1265
+ };
1266
+ if (row.payout_method !== this.payoutMethod || canonical(priorDestination) !== canonical(this.payoutDestination)) throw new Error("CommissionPayout: routing is immutable; reject and recreate the batch");
1267
+ }
1268
+ }
947
1269
  /** Throws when the totals invariant doesn't hold. */
948
1270
  validateTotals() {
949
1271
  for (const [name, value] of [
@@ -976,13 +1298,13 @@ ${memo}` : memo;
976
1298
  return null;
977
1299
  }
978
1300
  };
979
- __publicField$4(CommissionPayout, "__smrtPackage__", "@happyvertical/smrt-sales");
980
- __decorateClass$4([tenantId({ nullable: true })], CommissionPayout.prototype, "tenantId", 2);
981
- __decorateClass$4([foreignKey("Earner", { required: true })], CommissionPayout.prototype, "earnerId", 2);
982
- __decorateClass$4([crossPackageRef("@happyvertical/smrt-commerce:Invoice")], CommissionPayout.prototype, "invoiceId", 2);
983
- __decorateClass$4([field({ required: true })], CommissionPayout.prototype, "idempotencyKey", 2);
984
- __decorateClass$4([field({ indexed: true })], CommissionPayout.prototype, "sourceId", 2);
985
- CommissionPayout = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
1301
+ __publicField$3(CommissionPayout, "__smrtPackage__", "@happyvertical/smrt-sales");
1302
+ __decorateClass$3([tenantId({ nullable: true })], CommissionPayout.prototype, "tenantId", 2);
1303
+ __decorateClass$3([foreignKey("Earner", { required: true })], CommissionPayout.prototype, "earnerId", 2);
1304
+ __decorateClass$3([crossPackageRef("@happyvertical/smrt-commerce:Invoice")], CommissionPayout.prototype, "invoiceId", 2);
1305
+ __decorateClass$3([field({ required: true })], CommissionPayout.prototype, "idempotencyKey", 2);
1306
+ __decorateClass$3([field({ indexed: true })], CommissionPayout.prototype, "sourceId", 2);
1307
+ CommissionPayout = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
986
1308
  conflictColumns: ["idempotency_key"],
987
1309
  api: { include: ["list", "get"] },
988
1310
  mcp: { include: ["list", "get"] },
@@ -992,135 +1314,68 @@ CommissionPayout = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
992
1314
  //#region src/commissions/collections/CommissionPayoutCollection.ts
993
1315
  var CommissionPayoutCollection = class extends SmrtCollection {
994
1316
  static _itemClass = CommissionPayout;
995
- /** All payout batches for an earner, newest first. */
996
- async findByEarner(earnerId) {
997
- return await this.list({
998
- where: { earnerId },
999
- orderBy: "created_at DESC"
1000
- });
1001
- }
1002
- /** Payout batches by status, newest first. */
1003
- async findByStatus(status) {
1004
- return await this.list({
1005
- where: { status },
1006
- orderBy: "created_at DESC"
1007
- });
1008
- }
1009
- /** Look up a payout by its idempotency natural key. */
1010
- async findByIdempotencyKey(idempotencyKey) {
1011
- if (!idempotencyKey) return null;
1012
- return (await this.list({
1013
- where: { idempotencyKey },
1014
- limit: 1
1015
- }))[0] ?? null;
1016
- }
1017
- /**
1018
- * One page of payouts carrying the derived single-source stamp for
1019
- * `(sourceKind, sourceId)`, newest first with a deterministic id
1020
- * tiebreak. This is the RAW indexed page — stamped rows only, membership
1021
- * unverified. Consumers want
1022
- * `CommissionPayoutService.getSourcePayoutHistory`, which re-verifies
1023
- * each page's membership and fails closed on rows the stamp alone cannot
1024
- * prove.
1025
- */
1026
- async findBySource(sourceKind, sourceId, page) {
1027
- if (!sourceKind || !sourceId) return [];
1028
- return await this.list({
1029
- where: {
1030
- sourceKind,
1031
- sourceId
1032
- },
1033
- orderBy: ["created_at DESC", "id DESC"],
1034
- limit: page.limit,
1035
- offset: page.offset
1036
- });
1037
- }
1038
- /**
1039
- * Σ totalAmountCents of COMPLETED payouts for an earner+currency —
1040
- * lifetime settled earnings (integer cents).
1041
- */
1042
- async sumPaidByEarner(earnerId, currency) {
1043
- return (await this.list({ where: {
1044
- earnerId,
1045
- currency,
1046
- status: "completed"
1047
- } })).reduce((sum, p) => sum + p.totalAmountCents, 0);
1317
+ /** Persist the first routing snapshot atomically; a duplicate key must not upsert it. */
1318
+ async create(options, context) {
1319
+ return super.create({
1320
+ ...options,
1321
+ _insertOnly: true
1322
+ }, context);
1048
1323
  }
1049
- };
1050
- //#endregion
1051
- //#region src/commissions/types.ts
1052
- var EARNER_STATUSES = [
1053
- "pending",
1054
- "active",
1055
- "suspended"
1056
- ];
1057
- var EARNER_SOURCE_ATTRIBUTION_STATUSES = ["active", "inactive"];
1058
- var EARNER_KINDS = [
1059
- "person",
1060
- "organisation",
1061
- "treasury"
1062
- ];
1063
- var PAYOUT_METHODS = [
1064
- "bank_transfer",
1065
- "check",
1066
- "paypal",
1067
- "credit",
1068
- "other",
1069
- "stripe_connect",
1070
- "crypto"
1071
- ];
1072
- var PAYOUT_METHODS_REQUIRING_DESTINATION = ["stripe_connect", "crypto"];
1073
- var COMMISSION_PLAN_STATUSES = [
1074
- "draft",
1075
- "active",
1076
- "superseded",
1077
- "retired"
1078
- ];
1079
- var COMMISSION_STATUSES = [
1080
- "pending",
1081
- "earned",
1082
- "approved",
1083
- "payable",
1084
- "paid"
1085
- ];
1086
- var COMMISSION_BASES = [
1087
- "fixed",
1088
- "gross",
1089
- "net",
1090
- "margin",
1091
- "custom"
1092
- ];
1093
- var COMMISSION_ADJUSTMENT_KINDS = [
1094
- "refund",
1095
- "credit",
1096
- "chargeback",
1097
- "dispute",
1098
- "correction"
1099
- ];
1100
- var COMMISSION_PAYOUT_STATUSES = [
1101
- "pending",
1102
- "approved",
1103
- "processing",
1104
- "completed",
1105
- "failed",
1106
- "rejected"
1107
- ];
1108
- var EARNING_EVENT_KINDS = [
1109
- "conversion",
1110
- "agreement_execution",
1111
- "invoice_payment",
1112
- "collected_revenue",
1113
- "recognized_margin",
1114
- "milestone"
1115
- ];
1116
- var ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES = [
1117
- "earned",
1118
- "approved",
1119
- "payable",
1120
- "paid"
1121
- ];
1122
- var WATERFALL_POOL_RESIDUAL_RULES = ["remainder_sink", "largest_remainder"];
1123
- var WATERFALL_POOL_ZERO_WEIGHT_RULES = ["remainder_sink", "equal"];
1324
+ /** All payout batches for an earner, newest first. */
1325
+ async findByEarner(earnerId) {
1326
+ return await this.list({
1327
+ where: { earnerId },
1328
+ orderBy: "created_at DESC"
1329
+ });
1330
+ }
1331
+ /** Payout batches by status, newest first. */
1332
+ async findByStatus(status) {
1333
+ return await this.list({
1334
+ where: { status },
1335
+ orderBy: "created_at DESC"
1336
+ });
1337
+ }
1338
+ /** Look up a payout by its idempotency natural key. */
1339
+ async findByIdempotencyKey(idempotencyKey) {
1340
+ if (!idempotencyKey) return null;
1341
+ return (await this.list({
1342
+ where: { idempotencyKey },
1343
+ limit: 1
1344
+ }))[0] ?? null;
1345
+ }
1346
+ /**
1347
+ * One page of payouts carrying the derived single-source stamp for
1348
+ * `(sourceKind, sourceId)`, newest first with a deterministic id
1349
+ * tiebreak. This is the RAW indexed page — stamped rows only, membership
1350
+ * unverified. Consumers want
1351
+ * `CommissionPayoutService.getSourcePayoutHistory`, which re-verifies
1352
+ * each page's membership and fails closed on rows the stamp alone cannot
1353
+ * prove.
1354
+ */
1355
+ async findBySource(sourceKind, sourceId, page) {
1356
+ if (!sourceKind || !sourceId) return [];
1357
+ return await this.list({
1358
+ where: {
1359
+ sourceKind,
1360
+ sourceId
1361
+ },
1362
+ orderBy: ["created_at DESC", "id DESC"],
1363
+ limit: page.limit,
1364
+ offset: page.offset
1365
+ });
1366
+ }
1367
+ /**
1368
+ * Σ totalAmountCents of COMPLETED payouts for an earner+currency —
1369
+ * lifetime settled earnings (integer cents).
1370
+ */
1371
+ async sumPaidByEarner(earnerId, currency) {
1372
+ return (await this.list({ where: {
1373
+ earnerId,
1374
+ currency,
1375
+ status: "completed"
1376
+ } })).reduce((sum, p) => sum + p.totalAmountCents, 0);
1377
+ }
1378
+ };
1124
1379
  //#endregion
1125
1380
  //#region src/commissions/services/WaterfallCalculationService.ts
1126
1381
  var WaterfallRecipientResolverRegistry = class {
@@ -1616,21 +1871,21 @@ function assertWholeNonNegative(value, label) {
1616
1871
  }
1617
1872
  //#endregion
1618
1873
  //#region src/commissions/models/CommissionPlan.ts
1619
- var __defProp$3 = Object.defineProperty;
1620
- var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
1621
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, {
1874
+ var __defProp$2 = Object.defineProperty;
1875
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
1876
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
1622
1877
  enumerable: true,
1623
1878
  configurable: true,
1624
1879
  writable: true,
1625
1880
  value
1626
1881
  }) : obj[key] = value;
1627
- var __decorateClass$3 = (decorators, target, key, kind) => {
1628
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
1882
+ var __decorateClass$2 = (decorators, target, key, kind) => {
1883
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
1629
1884
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1630
- if (kind && result) __defProp$3(target, key, result);
1885
+ if (kind && result) __defProp$2(target, key, result);
1631
1886
  return result;
1632
1887
  };
1633
- var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
1888
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
1634
1889
  var PLAN_STATUS_TRANSITIONS = {
1635
1890
  draft: ["active", "retired"],
1636
1891
  active: ["superseded", "retired"],
@@ -1971,10 +2226,10 @@ var CommissionPlan = class extends SmrtObject {
1971
2226
  return null;
1972
2227
  }
1973
2228
  };
1974
- __publicField$3(CommissionPlan, "__smrtPackage__", "@happyvertical/smrt-sales");
1975
- __decorateClass$3([tenantId({ nullable: true })], CommissionPlan.prototype, "tenantId", 2);
1976
- __decorateClass$3([field({ required: true })], CommissionPlan.prototype, "planKey", 2);
1977
- CommissionPlan = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
2229
+ __publicField$2(CommissionPlan, "__smrtPackage__", "@happyvertical/smrt-sales");
2230
+ __decorateClass$2([tenantId({ nullable: true })], CommissionPlan.prototype, "tenantId", 2);
2231
+ __decorateClass$2([field({ required: true })], CommissionPlan.prototype, "planKey", 2);
2232
+ CommissionPlan = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
1978
2233
  conflictColumns: [
1979
2234
  "tenant_id",
1980
2235
  "plan_key",
@@ -2086,197 +2341,6 @@ var CommissionPlanCollection = class extends SmrtCollection {
2086
2341
  }
2087
2342
  };
2088
2343
  //#endregion
2089
- //#region src/commissions/models/Earner.ts
2090
- var __defProp$2 = Object.defineProperty;
2091
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
2092
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
2093
- enumerable: true,
2094
- configurable: true,
2095
- writable: true,
2096
- value
2097
- }) : obj[key] = value;
2098
- var __decorateClass$2 = (decorators, target, key, kind) => {
2099
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
2100
- for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
2101
- if (kind && result) __defProp$2(target, key, result);
2102
- return result;
2103
- };
2104
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
2105
- function validatePayoutDestination(destination) {
2106
- const value = destination;
2107
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Earner payoutDestination must be an object");
2108
- const requireText = (key) => {
2109
- if (typeof value[key] !== "string" || !value[key].trim()) throw new Error(`Earner payoutDestination.${key} is required`);
2110
- };
2111
- let allowed;
2112
- if (value.method === "stripe_connect") {
2113
- allowed = ["method", "connectedAccountId"];
2114
- requireText("connectedAccountId");
2115
- if (!/^acct_[A-Za-z0-9]+$/.test(value.connectedAccountId)) throw new Error("Earner payoutDestination.connectedAccountId must be a Stripe connected account id ('acct_…')");
2116
- } else if (value.method === "crypto") {
2117
- allowed = [
2118
- "method",
2119
- "network",
2120
- "asset",
2121
- "address",
2122
- "memo"
2123
- ];
2124
- requireText("network");
2125
- requireText("asset");
2126
- requireText("address");
2127
- if (value.memo !== void 0 && typeof value.memo !== "string") throw new Error("Earner payoutDestination.memo must be a string");
2128
- } else throw new Error(`Earner payoutDestination.method must be one of ${PAYOUT_METHODS_REQUIRING_DESTINATION.join(", ")}`);
2129
- for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`Earner payoutDestination has an unexpected field '${key}'`);
2130
- }
2131
- var Earner = class extends SmrtObject {
2132
- tenantId = null;
2133
- /**
2134
- * Party kind: `person` (default), `organisation`, or `treasury`.
2135
- * Organisation and treasury earners need no person Profile.
2136
- */
2137
- kind = "person";
2138
- profileId = "";
2139
- /** Human-readable display name for portals and operator views. */
2140
- displayName = "";
2141
- /** Account lifecycle: `pending` (default) → `active` / `suspended`. */
2142
- status = "pending";
2143
- /** Preferred payout delivery method. */
2144
- payoutMethod = "bank_transfer";
2145
- /**
2146
- * Typed payout-rail descriptor as a JSON string (`{}` when none). Required
2147
- * for an active earner whose method is `stripe_connect` or `crypto`, and
2148
- * its `method` must match {@link payoutMethod}. Use
2149
- * {@link getPayoutDestination}/{@link setPayoutDestination}.
2150
- */
2151
- payoutDestination = "{}";
2152
- /**
2153
- * Minimum unsettled balance (integer cents) before a payout batch is
2154
- * created. Default $50.00 = 5000 cents.
2155
- */
2156
- payoutThresholdCents = 5e3;
2157
- /**
2158
- * Payout cadence key. Open string so applications can define their own
2159
- * schedules (`manual`, `monthly`, `weekly`, `net_30`, …); `manual` means
2160
- * an operator triggers batches explicitly.
2161
- */
2162
- payoutScheduleKey = "manual";
2163
- /** ISO 4217 currency all of this earner's balances settle in. */
2164
- currency = "USD";
2165
- /**
2166
- * Additional metadata as a JSON string (tax info, payout-rail details,
2167
- * …). Use {@link getMetadata}/{@link setMetadata}.
2168
- */
2169
- metadata = "{}";
2170
- constructor(options = {}) {
2171
- super(options);
2172
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
2173
- if (options.kind !== void 0) this.kind = options.kind;
2174
- if (options.profileId !== void 0) this.profileId = options.profileId;
2175
- if (options.displayName !== void 0) this.displayName = options.displayName;
2176
- if (options.status !== void 0) this.status = options.status;
2177
- if (options.payoutMethod !== void 0) this.payoutMethod = options.payoutMethod;
2178
- if (options.payoutDestination !== void 0) this.payoutDestination = options.payoutDestination;
2179
- if (options.payoutThresholdCents !== void 0) this.payoutThresholdCents = options.payoutThresholdCents;
2180
- if (options.payoutScheduleKey !== void 0) this.payoutScheduleKey = options.payoutScheduleKey;
2181
- if (options.currency !== void 0) this.currency = options.currency;
2182
- if (options.metadata !== void 0) this.metadata = options.metadata;
2183
- }
2184
- isActive() {
2185
- return this.status === "active";
2186
- }
2187
- isPending() {
2188
- return this.status === "pending";
2189
- }
2190
- isSuspended() {
2191
- return this.status === "suspended";
2192
- }
2193
- /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
2194
- getMetadata() {
2195
- if (!this.metadata) return {};
2196
- try {
2197
- const parsed = JSON.parse(this.metadata);
2198
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2199
- } catch {
2200
- return {};
2201
- }
2202
- }
2203
- /** Serialize and store {@link metadata}. */
2204
- setMetadata(data) {
2205
- this.metadata = JSON.stringify(data ?? {});
2206
- }
2207
- isOrganisation() {
2208
- return this.kind === "organisation";
2209
- }
2210
- isTreasury() {
2211
- return this.kind === "treasury";
2212
- }
2213
- /** Parse {@link payoutDestination}; `null` when none or unparseable. */
2214
- getPayoutDestination() {
2215
- try {
2216
- const parsed = JSON.parse(this.payoutDestination || "{}");
2217
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.keys(parsed).length > 0 ? parsed : null;
2218
- } catch {
2219
- return null;
2220
- }
2221
- }
2222
- /**
2223
- * Validate and store a payout destination, and set {@link payoutMethod}
2224
- * to its method. `null` clears it (the method is left unchanged).
2225
- */
2226
- setPayoutDestination(destination) {
2227
- if (destination === null) {
2228
- this.payoutDestination = "{}";
2229
- return;
2230
- }
2231
- validatePayoutDestination(destination);
2232
- this.payoutDestination = JSON.stringify(destination);
2233
- this.payoutMethod = destination.method;
2234
- }
2235
- /**
2236
- * Save with field guards: `kind` and `payoutMethod` must be known values,
2237
- * a stored destination must be valid and match `payoutMethod`, and an
2238
- * active Stripe Connect / crypto earner must have a destination.
2239
- */
2240
- async save() {
2241
- this.assertPayoutTerms();
2242
- return await super.save();
2243
- }
2244
- assertPayoutTerms() {
2245
- const label = `Earner ${this.id ?? "<new>"}`;
2246
- if (!EARNER_KINDS.includes(this.kind)) throw new Error(`${label}: unknown kind '${this.kind}'`);
2247
- if (!PAYOUT_METHODS.includes(this.payoutMethod)) throw new Error(`${label}: unknown payoutMethod '${this.payoutMethod}'`);
2248
- let parsed;
2249
- try {
2250
- parsed = JSON.parse(this.payoutDestination || "{}");
2251
- } catch {
2252
- throw new Error(`${label}: payoutDestination must be valid JSON`);
2253
- }
2254
- const hasDestination = !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.keys(parsed).length > 0;
2255
- if (hasDestination) {
2256
- validatePayoutDestination(parsed);
2257
- if (parsed.method !== this.payoutMethod) throw new Error(`${label}: payoutDestination method '${parsed.method}' does not match payoutMethod '${this.payoutMethod}'`);
2258
- } else if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${label}: payoutDestination must be a JSON object`);
2259
- if (this.status === "active" && !hasDestination && PAYOUT_METHODS_REQUIRING_DESTINATION.includes(this.payoutMethod)) throw new Error(`${label}: an active ${this.payoutMethod} earner requires a payoutDestination`);
2260
- }
2261
- };
2262
- __publicField$2(Earner, "__smrtPackage__", "@happyvertical/smrt-sales");
2263
- __decorateClass$2([tenantId({ nullable: true })], Earner.prototype, "tenantId", 2);
2264
- __decorateClass$2([crossPackageRef("@happyvertical/smrt-profiles:Profile")], Earner.prototype, "profileId", 2);
2265
- Earner = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
2266
- api: { include: [
2267
- "list",
2268
- "get",
2269
- "create",
2270
- "update"
2271
- ] },
2272
- mcp: { include: [
2273
- "list",
2274
- "get",
2275
- "create"
2276
- ] },
2277
- cli: { skipApiCheck: true }
2278
- })], Earner);
2279
- //#endregion
2280
2344
  //#region src/commissions/collections/EarnerCollection.ts
2281
2345
  var EarnerCollection = class extends SmrtCollection {
2282
2346
  static _itemClass = Earner;
@@ -3322,20 +3386,20 @@ var CommissionPayoutService = class CommissionPayoutService {
3322
3386
  settledCommissionIds: [],
3323
3387
  settledAdjustmentIds: []
3324
3388
  };
3325
- const minted = await this.deps.payouts.create({
3389
+ const payout = await this.deps.payouts.create({
3326
3390
  tenantId: earner.tenantId,
3327
3391
  earnerId: input.earnerId,
3328
3392
  currency: input.currency,
3329
3393
  periodStart: input.periodStart ?? null,
3330
3394
  periodEnd: input.periodEnd ?? null,
3331
3395
  payoutMethod,
3396
+ payoutDestination: JSON.stringify(earner.getPayoutDestination()?.method === payoutMethod ? earner.getPayoutDestination() : {}),
3332
3397
  status: "pending",
3333
3398
  commissionTotalCents,
3334
3399
  adjustmentTotalCents,
3335
3400
  totalAmountCents: netTotalCents,
3336
3401
  idempotencyKey
3337
3402
  });
3338
- const payout = await this.deps.payouts.findByIdempotencyKey(idempotencyKey) ?? minted;
3339
3403
  return {
3340
3404
  ...await this.claimAndReconcile(payout, input),
3341
3405
  created: true
@@ -3429,6 +3493,8 @@ var CommissionPayoutService = class CommissionPayoutService {
3429
3493
  const payout = await this.requirePayout(payoutId);
3430
3494
  if (!payout.isProcessing()) throw new Error(`CommissionPayout ${payout.id ?? "<new>"}: cannot complete from status '${payout.status}'`);
3431
3495
  if (!paymentReference) throw new Error(`CommissionPayout ${payout.id ?? "<new>"}: complete() requires a paymentReference`);
3496
+ const destination = payout.getPayoutDestination();
3497
+ if (PAYOUT_METHODS_REQUIRING_DESTINATION.includes(payout.payoutMethod) && !destination) throw new Error(`CommissionPayout ${payout.id ?? "<new>"}: ${payout.payoutMethod} requires a payoutDestination snapshot; reject and recreate the batch`);
3432
3498
  const members = await this.deps.commissions.findByPayout(payoutId);
3433
3499
  for (const commission of members) if (commission.isPayable()) {
3434
3500
  commission.markPaid(now);
@@ -3817,7 +3883,7 @@ var CommissionPayoutService = class CommissionPayoutService {
3817
3883
  const ids = payoutIds.filter((id) => CommissionPayoutService.UUID_RE.test(id));
3818
3884
  if (ids.length === 0) return counts;
3819
3885
  const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ");
3820
- const res = await db.query(`SELECT payout_id, COUNT(*) AS row_count FROM ${table} WHERE payout_id IN (${placeholders}) GROUP BY payout_id`, ...ids);
3886
+ const res = await db.query(`SELECT CAST(payout_id AS VARCHAR) AS payout_id, COUNT(*) AS row_count FROM ${table} WHERE payout_id IN (${placeholders}) GROUP BY payout_id`, ...ids);
3821
3887
  const rows = Array.isArray(res) ? res : res.rows ?? [];
3822
3888
  for (const row of rows) counts.set(String(row.payout_id), Number(row.row_count));
3823
3889
  return counts;
@@ -4336,6 +4402,6 @@ var EarnerAttributionService = class EarnerAttributionService {
4336
4402
  }
4337
4403
  };
4338
4404
  //#endregion
4339
- export { COMMISSION_BASES as A, WATERFALL_POOL_RESIDUAL_RULES as B, WaterfallRecipientResolverRegistry as C, validateWaterfallPlan as D, prepareWaterfallPools as E, EARNER_SOURCE_ATTRIBUTION_STATUSES as F, Commission as G, CommissionPayoutCollection as H, EARNER_STATUSES as I, CommissionAdjustmentCollection as J, CommissionAdjustmentOperationCollection as K, EARNING_EVENT_KINDS as L, COMMISSION_PLAN_STATUSES as M, COMMISSION_STATUSES as N, ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES as O, EARNER_KINDS as P, PAYOUT_METHODS as R, validateCommissionPlanComponents as S, calculateWaterfallWithPools as T, CommissionPayout as U, WATERFALL_POOL_ZERO_WEIGHT_RULES as V, CommissionCollection as W, CommissionAdjustment as Y, EarnerCollection as _, CommissionBalanceService as a, CommissionPlanCollection as b, CommissionAdjustmentValidationError as c, centsToAmount as d, roundCents as f, EarnerSourceAttribution as g, EarnerSourceAttributionCollection as h, CommissionCalculationService as i, COMMISSION_PAYOUT_STATUSES as j, COMMISSION_ADJUSTMENT_KINDS as k, amountToCents as l, EarningEvent as m, CommissionSettlementService as n, CommissionAdjustmentReplayConflictError as o, EarningEventCollection as p, CommissionAdjustmentOperation as q, CommissionPayoutService as r, CommissionAdjustmentService as s, EarnerAttributionService as t, calculateCommissionAmountCents as u, Earner as v, calculateWaterfall as w, CommissionPlan as x, validatePayoutDestination as y, PAYOUT_METHODS_REQUIRING_DESTINATION as z };
4405
+ export { ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES as A, PAYOUT_METHODS as B, calculateWaterfallWithPools as C, CommissionPayout as D, CommissionPayoutCollection as E, COMMISSION_STATUSES as F, Commission as G, WATERFALL_POOL_RESIDUAL_RULES as H, EARNER_KINDS as I, CommissionAdjustmentCollection as J, CommissionAdjustmentOperationCollection as K, EARNER_SOURCE_ATTRIBUTION_STATUSES as L, COMMISSION_BASES as M, COMMISSION_PAYOUT_STATUSES as N, Earner as O, COMMISSION_PLAN_STATUSES as P, EARNER_STATUSES as R, calculateWaterfall as S, validateWaterfallPlan as T, WATERFALL_POOL_ZERO_WEIGHT_RULES as U, PAYOUT_METHODS_REQUIRING_DESTINATION as V, CommissionCollection as W, CommissionAdjustment as Y, EarnerCollection as _, CommissionBalanceService as a, validateCommissionPlanComponents as b, CommissionAdjustmentValidationError as c, centsToAmount as d, roundCents as f, EarnerSourceAttribution as g, EarnerSourceAttributionCollection as h, CommissionCalculationService as i, COMMISSION_ADJUSTMENT_KINDS as j, validatePayoutDestination as k, amountToCents as l, EarningEvent as m, CommissionSettlementService as n, CommissionAdjustmentReplayConflictError as o, EarningEventCollection as p, CommissionAdjustmentOperation as q, CommissionPayoutService as r, CommissionAdjustmentService as s, EarnerAttributionService as t, calculateCommissionAmountCents as u, CommissionPlanCollection as v, prepareWaterfallPools as w, WaterfallRecipientResolverRegistry as x, CommissionPlan as y, EARNING_EVENT_KINDS as z };
4340
4406
 
4341
- //# sourceMappingURL=commissions-D5x24d4o.js.map
4407
+ //# sourceMappingURL=commissions-nMBMa0DT.js.map