@happyvertical/smrt-sales 0.39.2

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 (201) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/__smrt-register__-kWdT7LOt.js +6 -0
  8. package/dist/chunks/__smrt-register__-kWdT7LOt.js.map +1 -0
  9. package/dist/chunks/commissions-BCxqOKVM.js +2181 -0
  10. package/dist/chunks/commissions-BCxqOKVM.js.map +1 -0
  11. package/dist/chunks/crm-DwEz7E2r.js +1428 -0
  12. package/dist/chunks/crm-DwEz7E2r.js.map +1 -0
  13. package/dist/chunks/referrals-BY47p70T.js +2880 -0
  14. package/dist/chunks/referrals-BY47p70T.js.map +1 -0
  15. package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts +26 -0
  16. package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts.map +1 -0
  17. package/dist/commissions/collections/CommissionCollection.d.ts +43 -0
  18. package/dist/commissions/collections/CommissionCollection.d.ts.map +1 -0
  19. package/dist/commissions/collections/CommissionPayoutCollection.d.ts +19 -0
  20. package/dist/commissions/collections/CommissionPayoutCollection.d.ts.map +1 -0
  21. package/dist/commissions/collections/CommissionPlanCollection.d.ts +46 -0
  22. package/dist/commissions/collections/CommissionPlanCollection.d.ts.map +1 -0
  23. package/dist/commissions/collections/EarnerCollection.d.ts +14 -0
  24. package/dist/commissions/collections/EarnerCollection.d.ts.map +1 -0
  25. package/dist/commissions/collections/EarningEventCollection.d.ts +27 -0
  26. package/dist/commissions/collections/EarningEventCollection.d.ts.map +1 -0
  27. package/dist/commissions/collections/index.d.ts +11 -0
  28. package/dist/commissions/collections/index.d.ts.map +1 -0
  29. package/dist/commissions/index.d.ts +20 -0
  30. package/dist/commissions/index.d.ts.map +1 -0
  31. package/dist/commissions/models/Commission.d.ts +150 -0
  32. package/dist/commissions/models/Commission.d.ts.map +1 -0
  33. package/dist/commissions/models/CommissionAdjustment.d.ts +62 -0
  34. package/dist/commissions/models/CommissionAdjustment.d.ts.map +1 -0
  35. package/dist/commissions/models/CommissionPayout.d.ts +119 -0
  36. package/dist/commissions/models/CommissionPayout.d.ts.map +1 -0
  37. package/dist/commissions/models/CommissionPlan.d.ts +117 -0
  38. package/dist/commissions/models/CommissionPlan.d.ts.map +1 -0
  39. package/dist/commissions/models/Earner.d.ts +49 -0
  40. package/dist/commissions/models/Earner.d.ts.map +1 -0
  41. package/dist/commissions/models/EarningEvent.d.ts +89 -0
  42. package/dist/commissions/models/EarningEvent.d.ts.map +1 -0
  43. package/dist/commissions/models/index.d.ts +11 -0
  44. package/dist/commissions/models/index.d.ts.map +1 -0
  45. package/dist/commissions/money.d.ts +42 -0
  46. package/dist/commissions/money.d.ts.map +1 -0
  47. package/dist/commissions/services/CommissionBalanceService.d.ts +14 -0
  48. package/dist/commissions/services/CommissionBalanceService.d.ts.map +1 -0
  49. package/dist/commissions/services/CommissionCalculationService.d.ts +142 -0
  50. package/dist/commissions/services/CommissionCalculationService.d.ts.map +1 -0
  51. package/dist/commissions/services/CommissionPayoutService.d.ts +134 -0
  52. package/dist/commissions/services/CommissionPayoutService.d.ts.map +1 -0
  53. package/dist/commissions/services/CommissionSettlementService.d.ts +42 -0
  54. package/dist/commissions/services/CommissionSettlementService.d.ts.map +1 -0
  55. package/dist/commissions/services/index.d.ts +9 -0
  56. package/dist/commissions/services/index.d.ts.map +1 -0
  57. package/dist/commissions/types.d.ts +243 -0
  58. package/dist/commissions/types.d.ts.map +1 -0
  59. package/dist/commissions.d.ts +2 -0
  60. package/dist/commissions.d.ts.map +1 -0
  61. package/dist/commissions.js +3 -0
  62. package/dist/crm/collections/LeadCollection.d.ts +79 -0
  63. package/dist/crm/collections/LeadCollection.d.ts.map +1 -0
  64. package/dist/crm/collections/OpportunityCollection.d.ts +42 -0
  65. package/dist/crm/collections/OpportunityCollection.d.ts.map +1 -0
  66. package/dist/crm/collections/OpportunityConversionCollection.d.ts +26 -0
  67. package/dist/crm/collections/OpportunityConversionCollection.d.ts.map +1 -0
  68. package/dist/crm/collections/PipelineDefinitionCollection.d.ts +46 -0
  69. package/dist/crm/collections/PipelineDefinitionCollection.d.ts.map +1 -0
  70. package/dist/crm/collections/PipelineStageCollection.d.ts +22 -0
  71. package/dist/crm/collections/PipelineStageCollection.d.ts.map +1 -0
  72. package/dist/crm/collections/SalesActivityCollection.d.ts +24 -0
  73. package/dist/crm/collections/SalesActivityCollection.d.ts.map +1 -0
  74. package/dist/crm/collections/SalesRepresentativeCollection.d.ts +19 -0
  75. package/dist/crm/collections/SalesRepresentativeCollection.d.ts.map +1 -0
  76. package/dist/crm/index.d.ts +27 -0
  77. package/dist/crm/index.d.ts.map +1 -0
  78. package/dist/crm/models/Lead.d.ts +135 -0
  79. package/dist/crm/models/Lead.d.ts.map +1 -0
  80. package/dist/crm/models/Opportunity.d.ts +111 -0
  81. package/dist/crm/models/Opportunity.d.ts.map +1 -0
  82. package/dist/crm/models/OpportunityConversion.d.ts +66 -0
  83. package/dist/crm/models/OpportunityConversion.d.ts.map +1 -0
  84. package/dist/crm/models/PipelineDefinition.d.ts +58 -0
  85. package/dist/crm/models/PipelineDefinition.d.ts.map +1 -0
  86. package/dist/crm/models/PipelineStage.d.ts +66 -0
  87. package/dist/crm/models/PipelineStage.d.ts.map +1 -0
  88. package/dist/crm/models/SalesActivity.d.ts +76 -0
  89. package/dist/crm/models/SalesActivity.d.ts.map +1 -0
  90. package/dist/crm/models/SalesRepresentative.d.ts +60 -0
  91. package/dist/crm/models/SalesRepresentative.d.ts.map +1 -0
  92. package/dist/crm/types.d.ts +228 -0
  93. package/dist/crm/types.d.ts.map +1 -0
  94. package/dist/crm.d.ts +2 -0
  95. package/dist/crm.d.ts.map +1 -0
  96. package/dist/crm.js +3 -0
  97. package/dist/index.d.ts +4 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +5 -0
  100. package/dist/manifest.json +9340 -0
  101. package/dist/referrals/collections/AttributionExceptionCollection.d.ts +17 -0
  102. package/dist/referrals/collections/AttributionExceptionCollection.d.ts.map +1 -0
  103. package/dist/referrals/collections/AttributionPolicyCollection.d.ts +50 -0
  104. package/dist/referrals/collections/AttributionPolicyCollection.d.ts.map +1 -0
  105. package/dist/referrals/collections/ReferralAgreementCollection.d.ts +48 -0
  106. package/dist/referrals/collections/ReferralAgreementCollection.d.ts.map +1 -0
  107. package/dist/referrals/collections/ReferralCollection.d.ts +25 -0
  108. package/dist/referrals/collections/ReferralCollection.d.ts.map +1 -0
  109. package/dist/referrals/collections/ReferralLinkCollection.d.ts +96 -0
  110. package/dist/referrals/collections/ReferralLinkCollection.d.ts.map +1 -0
  111. package/dist/referrals/collections/ReferralProgramCollection.d.ts +18 -0
  112. package/dist/referrals/collections/ReferralProgramCollection.d.ts.map +1 -0
  113. package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts +13 -0
  114. package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts.map +1 -0
  115. package/dist/referrals/collections/ReferralTouchCollection.d.ts +30 -0
  116. package/dist/referrals/collections/ReferralTouchCollection.d.ts.map +1 -0
  117. package/dist/referrals/collections/ReferrerCollection.d.ts +17 -0
  118. package/dist/referrals/collections/ReferrerCollection.d.ts.map +1 -0
  119. package/dist/referrals/index.d.ts +38 -0
  120. package/dist/referrals/index.d.ts.map +1 -0
  121. package/dist/referrals/models/AttributionException.d.ts +85 -0
  122. package/dist/referrals/models/AttributionException.d.ts.map +1 -0
  123. package/dist/referrals/models/AttributionPolicy.d.ts +128 -0
  124. package/dist/referrals/models/AttributionPolicy.d.ts.map +1 -0
  125. package/dist/referrals/models/Referral.d.ts +112 -0
  126. package/dist/referrals/models/Referral.d.ts.map +1 -0
  127. package/dist/referrals/models/ReferralAgreement.d.ts +125 -0
  128. package/dist/referrals/models/ReferralAgreement.d.ts.map +1 -0
  129. package/dist/referrals/models/ReferralLink.d.ts +71 -0
  130. package/dist/referrals/models/ReferralLink.d.ts.map +1 -0
  131. package/dist/referrals/models/ReferralProgram.d.ts +57 -0
  132. package/dist/referrals/models/ReferralProgram.d.ts.map +1 -0
  133. package/dist/referrals/models/ReferralTermSnapshot.d.ts +61 -0
  134. package/dist/referrals/models/ReferralTermSnapshot.d.ts.map +1 -0
  135. package/dist/referrals/models/ReferralTouch.d.ts +55 -0
  136. package/dist/referrals/models/ReferralTouch.d.ts.map +1 -0
  137. package/dist/referrals/models/Referrer.d.ts +69 -0
  138. package/dist/referrals/models/Referrer.d.ts.map +1 -0
  139. package/dist/referrals/services/AttributionService.d.ts +248 -0
  140. package/dist/referrals/services/AttributionService.d.ts.map +1 -0
  141. package/dist/referrals/services/ReferralCommissionService.d.ts +91 -0
  142. package/dist/referrals/services/ReferralCommissionService.d.ts.map +1 -0
  143. package/dist/referrals/services/ReferralQualificationService.d.ts +135 -0
  144. package/dist/referrals/services/ReferralQualificationService.d.ts.map +1 -0
  145. package/dist/referrals/types.d.ts +226 -0
  146. package/dist/referrals/types.d.ts.map +1 -0
  147. package/dist/referrals.d.ts +2 -0
  148. package/dist/referrals.d.ts.map +1 -0
  149. package/dist/referrals.js +3 -0
  150. package/dist/smrt-knowledge.json +5501 -0
  151. package/dist/svelte/__tests__/format.test.js +68 -0
  152. package/dist/svelte/__tests__/types.test.js +349 -0
  153. package/dist/svelte/components/AttributionConflictQueue.svelte +361 -0
  154. package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts +15 -0
  155. package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts.map +1 -0
  156. package/dist/svelte/components/CommissionBreakdown.svelte +251 -0
  157. package/dist/svelte/components/CommissionBreakdown.svelte.d.ts +11 -0
  158. package/dist/svelte/components/CommissionBreakdown.svelte.d.ts.map +1 -0
  159. package/dist/svelte/components/CommissionExpenseSummary.svelte +186 -0
  160. package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts +13 -0
  161. package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts.map +1 -0
  162. package/dist/svelte/components/ExecutedAgreementsList.svelte +136 -0
  163. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts +11 -0
  164. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts.map +1 -0
  165. package/dist/svelte/components/LeadList.svelte +224 -0
  166. package/dist/svelte/components/LeadList.svelte.d.ts +19 -0
  167. package/dist/svelte/components/LeadList.svelte.d.ts.map +1 -0
  168. package/dist/svelte/components/OpportunityBoard.svelte +230 -0
  169. package/dist/svelte/components/OpportunityBoard.svelte.d.ts +17 -0
  170. package/dist/svelte/components/OpportunityBoard.svelte.d.ts.map +1 -0
  171. package/dist/svelte/components/OpportunityDetail.svelte +472 -0
  172. package/dist/svelte/components/OpportunityDetail.svelte.d.ts +27 -0
  173. package/dist/svelte/components/OpportunityDetail.svelte.d.ts.map +1 -0
  174. package/dist/svelte/components/PayoutBatchReview.svelte +285 -0
  175. package/dist/svelte/components/PayoutBatchReview.svelte.d.ts +21 -0
  176. package/dist/svelte/components/PayoutBatchReview.svelte.d.ts.map +1 -0
  177. package/dist/svelte/components/PayoutHistoryList.svelte +245 -0
  178. package/dist/svelte/components/PayoutHistoryList.svelte.d.ts +11 -0
  179. package/dist/svelte/components/PayoutHistoryList.svelte.d.ts.map +1 -0
  180. package/dist/svelte/components/ReferralLinkManager.svelte +216 -0
  181. package/dist/svelte/components/ReferralLinkManager.svelte.d.ts +17 -0
  182. package/dist/svelte/components/ReferralLinkManager.svelte.d.ts.map +1 -0
  183. package/dist/svelte/components/ReferralStatusList.svelte +118 -0
  184. package/dist/svelte/components/ReferralStatusList.svelte.d.ts +11 -0
  185. package/dist/svelte/components/ReferralStatusList.svelte.d.ts.map +1 -0
  186. package/dist/svelte/components/ReferrerEarningsSummary.svelte +106 -0
  187. package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts +11 -0
  188. package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts.map +1 -0
  189. package/dist/svelte/components/SalesDashboard.svelte +192 -0
  190. package/dist/svelte/components/SalesDashboard.svelte.d.ts +29 -0
  191. package/dist/svelte/components/SalesDashboard.svelte.d.ts.map +1 -0
  192. package/dist/svelte/format.d.ts +46 -0
  193. package/dist/svelte/format.d.ts.map +1 -0
  194. package/dist/svelte/format.js +75 -0
  195. package/dist/svelte/index.d.ts +50 -0
  196. package/dist/svelte/index.d.ts.map +1 -0
  197. package/dist/svelte/index.js +37 -0
  198. package/dist/svelte/types.d.ts +422 -0
  199. package/dist/svelte/types.d.ts.map +1 -0
  200. package/dist/svelte/types.js +376 -0
  201. package/package.json +89 -0
@@ -0,0 +1,117 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { CommissionPlanComponent, CommissionPlanOptions, CommissionPlanStatus } from '../types.js';
3
+ /**
4
+ * Validate a components array. Throws a descriptive error on the first
5
+ * violation. Exported for reuse by the calculation service's input guards
6
+ * and by referral-terms builders that assemble component arrays.
7
+ *
8
+ * Rules:
9
+ * - component keys are non-empty and unique within the plan
10
+ * - `trigger` is a non-empty string (`'*'` matches every event kind)
11
+ * - `basis` is one of {@link COMMISSION_BASES}
12
+ * - basis `fixed` requires an integer `fixedAmountCents`
13
+ * - every other basis requires `rate` in `[0, 1]`
14
+ * - basis `custom` additionally requires a non-empty `customBasisKey`
15
+ * - `recurrence.kind` (when present) is `one_time` or `recurring`;
16
+ * `maxOccurrences` / `windowMonths` (when present) are positive integers
17
+ */
18
+ export declare function validateCommissionPlanComponents(components: CommissionPlanComponent[]): void;
19
+ export declare class CommissionPlan extends SmrtObject {
20
+ /** Tenant ID for multi-tenant isolation (nullable → global plans). */
21
+ tenantId: string | null;
22
+ /** Stable plan identity shared by every version of the plan. */
23
+ planKey: string;
24
+ /** Monotonic version within `planKey`. Amendments insert `max + 1`. */
25
+ version: number;
26
+ /** Human-readable plan name. */
27
+ name: string;
28
+ /** Longer human-readable description of the terms. */
29
+ description: string;
30
+ /**
31
+ * Lifecycle status — see {@link PLAN_STATUS_TRANSITIONS}. Mutate via
32
+ * {@link activate} / {@link supersede} / {@link retire} (or a legal
33
+ * single-step assignment; the save-time guard rejects illegal edges).
34
+ */
35
+ status: CommissionPlanStatus;
36
+ /** When this version takes effect. Frozen once the plan activates. */
37
+ effectiveFrom: Date | null;
38
+ /** ISO 4217 currency the plan's terms are denominated in. */
39
+ currency: string;
40
+ /**
41
+ * Calculation components as a JSON-string array — see
42
+ * {@link CommissionPlanComponent}. Use {@link getComponents} /
43
+ * {@link setComponents} (the setter validates).
44
+ */
45
+ components: string;
46
+ /** Additional metadata as a JSON string. */
47
+ metadata: string;
48
+ constructor(options?: CommissionPlanOptions);
49
+ /**
50
+ * Re-coerce date fields after the framework reapplies raw option values,
51
+ * record the loaded status for the transition guard, and capture the
52
+ * frozen snapshot when the row arrived already activated. The snapshot is
53
+ * captured for every non-draft status (not just `active`) so a superseded
54
+ * or retired version — history — can't be rewritten either.
55
+ */
56
+ initialize(): Promise<this>;
57
+ isDraft(): boolean;
58
+ isActive(): boolean;
59
+ /** Parse {@link components}; returns `[]` on empty/invalid JSON. */
60
+ getComponents(): CommissionPlanComponent[];
61
+ /**
62
+ * Validate and store the components array. Throws on invalid components —
63
+ * see {@link validateCommissionPlanComponents} for the rules.
64
+ */
65
+ setComponents(components: CommissionPlanComponent[]): void;
66
+ /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
67
+ getMetadata(): Record<string, unknown>;
68
+ /** Serialize and store {@link metadata}. */
69
+ setMetadata(data: Record<string, unknown>): void;
70
+ /**
71
+ * Transition `draft → active`. Validates components first so no active
72
+ * plan can carry malformed terms.
73
+ */
74
+ activate(): void;
75
+ /** Transition `active → superseded` (a newer version took over). */
76
+ supersede(): void;
77
+ /** Transition `draft | active → retired` (terminal). */
78
+ retire(): void;
79
+ /**
80
+ * Save with two guards:
81
+ *
82
+ * 1. **Status transition** — the about-to-be-written status must be a
83
+ * legal edge from the authoritative prior persisted status (re-read
84
+ * from the DB so a `create({ id: <existing>, _skipLoad: true })` upsert
85
+ * can't sidestep the guard — commerce pattern).
86
+ * 2. **Frozen calculation identity** — once the row has been saved
87
+ * non-draft, `components` / `currency` / `planKey` / `version` /
88
+ * `effectiveFrom` must match the captured snapshot. Amend by inserting
89
+ * a new version instead.
90
+ *
91
+ * Activating saves also re-validate components, so an `active` row always
92
+ * carries well-formed terms regardless of which write path set them.
93
+ */
94
+ save(): Promise<this>;
95
+ /**
96
+ * Refuse a save whose `(tenantId, planKey, version)` natural key already
97
+ * belongs to a DIFFERENT row. The frozen-identity guard above is
98
+ * instance-local (WeakMap), so a FRESH instance carrying an existing
99
+ * natural key would otherwise sail through and the conflict-column
100
+ * upsert would rewrite the persisted terms (and rotate the row id).
101
+ * Edit drafts by hydrating them; change terms with
102
+ * `CommissionPlanCollection.createAmendment()`.
103
+ */
104
+ private assertNaturalKeyNotTaken;
105
+ /**
106
+ * Resolve the AUTHORITATIVE prior status from the database; fall back to
107
+ * the loaded-status WeakMap only when the DB is unavailable. `undefined`
108
+ * means no persisted row exists (genuinely new).
109
+ */
110
+ private resolvePriorStatus;
111
+ private assertStatusTransition;
112
+ private assertFrozenIdentityUnchanged;
113
+ private serializeFrozenSnapshot;
114
+ private static coerceDate;
115
+ }
116
+ export default CommissionPlan;
117
+ //# sourceMappingURL=CommissionPlan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommissionPlan.d.ts","sourceRoot":"","sources":["../../../src/commissions/models/CommissionPlan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAS,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,aAAa,CAAC;AAmCrB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,uBAAuB,EAAE,GACpC,IAAI,CA6EN;AAED,qBAgBa,cAAe,SAAQ,UAAU;IAC5C,sEAAsE;IAEtE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B,gEAAgE;IAEhE,OAAO,EAAE,MAAM,CAAM;IAErB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAK;IAEpB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAM;IAElB,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAM;IAEzB;;;;OAIG;IACH,MAAM,EAAE,oBAAoB,CAAW;IAEvC,sEAAsE;IACtE,aAAa,EAAE,IAAI,GAAG,IAAI,CAAQ;IAElC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAS;IAEzB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAQ;IAE1B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAQ;gBAEZ,OAAO,GAAE,qBAA0B;IAgB/C;;;;;;OAMG;IACY,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAc1C,OAAO,IAAI,OAAO;IAIlB,QAAQ,IAAI,OAAO;IAMnB,oEAAoE;IACpE,aAAa,IAAI,uBAAuB,EAAE;IAU1C;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,uBAAuB,EAAE,GAAG,IAAI;IAK1D,kEAAkE;IAClE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAYtC,4CAA4C;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAMhD;;;OAGG;IACH,QAAQ,IAAI,IAAI;IAUhB,oEAAoE;IACpE,SAAS,IAAI,IAAI;IASjB,wDAAwD;IACxD,MAAM,IAAI,IAAI;IAWd;;;;;;;;;;;;;;OAcG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBpC;;;;;;;;OAQG;YACW,wBAAwB;IAgCtC;;;;OAIG;YACW,kBAAkB;IAgBhC,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,6BAA6B;IAerC,OAAO,CAAC,uBAAuB;IAa/B,OAAO,CAAC,MAAM,CAAC,UAAU;CAS1B;AAED,eAAe,cAAc,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { EarnerOptions, EarnerStatus, PayoutMethod } from '../types.js';
3
+ export declare class Earner extends SmrtObject {
4
+ /**
5
+ * Tenant ID for multi-tenant isolation. Nullable so global/operator-level
6
+ * earners remain possible; unlike legacy affiliates, sales earners are
7
+ * tenant-owned by default.
8
+ */
9
+ tenantId: string | null;
10
+ /**
11
+ * Identity link to a smrt-profiles Profile (cross-package string
12
+ * reference — never a DDL foreign key).
13
+ */
14
+ profileId: string;
15
+ /** Human-readable display name for portals and operator views. */
16
+ displayName: string;
17
+ /** Account lifecycle: `pending` (default) → `active` / `suspended`. */
18
+ status: EarnerStatus;
19
+ /** Preferred payout delivery method. */
20
+ payoutMethod: PayoutMethod;
21
+ /**
22
+ * Minimum unsettled balance (integer cents) before a payout batch is
23
+ * created. Default $50.00 = 5000 cents.
24
+ */
25
+ payoutThresholdCents: number;
26
+ /**
27
+ * Payout cadence key. Open string so applications can define their own
28
+ * schedules (`manual`, `monthly`, `weekly`, `net_30`, …); `manual` means
29
+ * an operator triggers batches explicitly.
30
+ */
31
+ payoutScheduleKey: string;
32
+ /** ISO 4217 currency all of this earner's balances settle in. */
33
+ currency: string;
34
+ /**
35
+ * Additional metadata as a JSON string (tax info, payout-rail details,
36
+ * …). Use {@link getMetadata}/{@link setMetadata}.
37
+ */
38
+ metadata: string;
39
+ constructor(options?: EarnerOptions);
40
+ isActive(): boolean;
41
+ isPending(): boolean;
42
+ isSuspended(): boolean;
43
+ /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
44
+ getMetadata(): Record<string, unknown>;
45
+ /** Serialize and store {@link metadata}. */
46
+ setMetadata(data: Record<string, unknown>): void;
47
+ }
48
+ export default Earner;
49
+ //# sourceMappingURL=Earner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Earner.d.ts","sourceRoot":"","sources":["../../../src/commissions/models/Earner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAmB,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7E,qBAMa,MAAO,SAAQ,UAAU;IACpC;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;;OAGG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAM;IAEzB,uEAAuE;IACvE,MAAM,EAAE,YAAY,CAAa;IAEjC,wCAAwC;IACxC,YAAY,EAAE,YAAY,CAAmB;IAE7C;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAQ;IAEpC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAY;IAErC,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAS;IAEzB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAQ;gBAEZ,OAAO,GAAE,aAAkB;IAiBvC,QAAQ,IAAI,OAAO;IAInB,SAAS,IAAI,OAAO;IAIpB,WAAW,IAAI,OAAO;IAItB,kEAAkE;IAClE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAYtC,4CAA4C;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGjD;AAED,eAAe,MAAM,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { EarningEventOptions } from '../types.js';
3
+ export declare class EarningEvent extends SmrtObject {
4
+ /** Tenant ID for multi-tenant isolation (nullable → global events). */
5
+ tenantId: string | null;
6
+ /**
7
+ * What kind of commercial event this is. Open string — see
8
+ * `EARNING_EVENT_KINDS` for the recommended vocabulary. Plan components
9
+ * match on this via their `trigger`.
10
+ */
11
+ eventKind: string;
12
+ /** When the commercial event occurred (not when it was ingested). */
13
+ occurredAt: Date;
14
+ /**
15
+ * Generic earning-source discriminator (`referral`, `opportunity`,
16
+ * `subscription`, `ad_event`, …). Free-form; this module attaches no
17
+ * semantics to it.
18
+ */
19
+ sourceKind: string;
20
+ /** Id of the source record named by {@link sourceKind}. */
21
+ sourceId: string;
22
+ /** Gross amount of the event in integer cents. */
23
+ grossAmountCents: number;
24
+ /**
25
+ * Net amount in integer cents, when the ingesting system defines one.
26
+ * `null` means "net is not defined for this event" — `net`-basis
27
+ * components then SKIP rather than falling back to gross (net is never
28
+ * derived).
29
+ */
30
+ netAmountCents: number | null;
31
+ /**
32
+ * Recognized margin in integer cents, when defined. `null` skips
33
+ * `margin`-basis components — margin is never derived.
34
+ */
35
+ marginCents: number | null;
36
+ /** ISO 4217 currency of the event's amounts. */
37
+ currency: string;
38
+ /**
39
+ * JSON map of `basisKey → integer cents` for `custom`-basis plan
40
+ * components. Use {@link getCustomBases}/{@link setCustomBases}.
41
+ */
42
+ customBases: string;
43
+ /**
44
+ * Idempotency natural key. Required. Callers embed tenant/source identity
45
+ * (see the class doc) — the framework does not synthesize it.
46
+ */
47
+ dedupeKey: string;
48
+ /** Additional metadata as a JSON string. */
49
+ metadata: string;
50
+ constructor(options?: EarningEventOptions);
51
+ /**
52
+ * Re-coerce {@link occurredAt} after the framework reapplies raw option /
53
+ * hydrated row values (SQLite hands back ISO strings), and capture the
54
+ * persisted state for the immutability guard when this instance hydrated
55
+ * an existing row.
56
+ */
57
+ initialize(): Promise<this>;
58
+ /**
59
+ * Save with the evidence-immutability guard. EarningEvents are immutable
60
+ * commercial evidence; three write vectors are closed:
61
+ *
62
+ * - a HYDRATED persisted row must serialize identically to its captured
63
+ * state (no-op re-saves pass, any change throws);
64
+ * - an instance carrying an existing id WITHOUT having hydrated it
65
+ * (`create({ id, _skipLoad: true })`) is rejected outright;
66
+ * - a NEW instance whose `dedupeKey` already belongs to another row is
67
+ * refused outright: the natural-key upsert would not only rewrite the
68
+ * evidence values but ROTATE the row's id (orphaning any Commission
69
+ * whose `earningEventId` points at it). Idempotent ingestion goes
70
+ * through `EarningEventCollection.getOrCreateByDedupeKey()`, which
71
+ * finds first and never upserts.
72
+ */
73
+ save(): Promise<this>;
74
+ private serializeState;
75
+ /**
76
+ * Parse {@link customBases} into a `basisKey → cents` map; non-numeric
77
+ * values are dropped. Returns `{}` on empty/invalid JSON.
78
+ */
79
+ getCustomBases(): Record<string, number>;
80
+ /** Serialize and store {@link customBases}. */
81
+ setCustomBases(bases: Record<string, number>): void;
82
+ /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */
83
+ getMetadata(): Record<string, unknown>;
84
+ /** Serialize and store {@link metadata}. */
85
+ setMetadata(data: Record<string, unknown>): void;
86
+ private static coerceDate;
87
+ }
88
+ export default EarningEvent;
89
+ //# sourceMappingURL=EarningEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EarningEvent.d.ts","sourceRoot":"","sources":["../../../src/commissions/models/EarningEvent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAS,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AASvD,qBAYa,YAAa,SAAQ,UAAU;IAC1C,uEAAuE;IAEvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;;;OAIG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB,qEAAqE;IACrE,UAAU,EAAE,IAAI,CAAc;IAE9B;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAM;IAEtB,kDAAkD;IAClD,gBAAgB,EAAE,MAAM,CAAK;IAE7B;;;;;OAKG;IAEH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC;;;OAGG;IAEH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElC,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAS;IAEzB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAQ;IAE3B;;;OAGG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAQ;gBAEZ,OAAO,GAAE,mBAAwB;IAsB7C;;;;;OAKG;IACY,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAS1C;;;;;;;;;;;;;;OAcG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDpC,OAAO,CAAC,cAAc;IAkBtB;;;OAGG;IACH,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAqBxC,+CAA+C;IAC/C,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAInD,kEAAkE;IAClE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAYtC,4CAA4C;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIhD,OAAO,CAAC,MAAM,CAAC,UAAU;CAS1B;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Model barrel for the commissions module.
3
+ * @packageDocumentation
4
+ */
5
+ export { Commission } from './Commission.js';
6
+ export { CommissionAdjustment } from './CommissionAdjustment.js';
7
+ export { CommissionPayout } from './CommissionPayout.js';
8
+ export { CommissionPlan, validateCommissionPlanComponents, } from './CommissionPlan.js';
9
+ export { Earner } from './Earner.js';
10
+ export { EarningEvent } from './EarningEvent.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commissions/models/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,cAAc,EACd,gCAAgC,GACjC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Integer-cents money helpers for the commissions module.
3
+ *
4
+ * Every monetary field in this module is stored as integer cents (`*Cents`
5
+ * suffix). Rounding happens exactly once per calculation step via
6
+ * {@link roundCents} using half-away-from-zero semantics, and every
7
+ * Commission persists a `calculationTrace` naming that rounding mode so
8
+ * amounts stay reproducible.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * Round to the nearest integer cent, half away from zero.
14
+ *
15
+ * `Math.round` alone rounds -2.5 to -2 (half toward +∞); financial
16
+ * conventions want symmetric behaviour, so the sign is factored out first:
17
+ * `Math.sign(v) * Math.round(Math.abs(v))` → `roundCents(2.5) === 3` and
18
+ * `roundCents(-2.5) === -3`.
19
+ */
20
+ export declare function roundCents(value: number): number;
21
+ /** Convert integer cents to a decimal major-unit amount (`/ 100`). */
22
+ export declare function centsToAmount(cents: number): number;
23
+ /**
24
+ * Convert a decimal major-unit amount to integer cents, rounding half away
25
+ * from zero (`roundCents(amount * 100)`).
26
+ */
27
+ export declare function amountToCents(amount: number): number;
28
+ /**
29
+ * Calculate a commission amount in integer cents:
30
+ * `roundCents(baseCents * rate * (shareFraction ?? 1))`.
31
+ *
32
+ * Rounding is applied once, on the final product, so split siblings each
33
+ * round independently and their sum can differ from the unsplit amount by at
34
+ * most one cent per sibling — the calculation trace records the inputs so any
35
+ * such drift is auditable.
36
+ *
37
+ * @param baseCents - Base amount in integer cents
38
+ * @param rate - Commission rate (0–1)
39
+ * @param shareFraction - Optional split share (0–1); defaults to 1
40
+ */
41
+ export declare function calculateCommissionAmountCents(baseCents: number, rate: number, shareFraction?: number): number;
42
+ //# sourceMappingURL=money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../src/commissions/money.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAER"}
@@ -0,0 +1,14 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { CommissionAdjustmentCollection } from '../collections/CommissionAdjustmentCollection.js';
3
+ import { CommissionCollection } from '../collections/CommissionCollection.js';
4
+ import { EarnerBalance } from '../types.js';
5
+ export declare class CommissionBalanceService {
6
+ private readonly commissions;
7
+ private readonly adjustments;
8
+ constructor(commissions: CommissionCollection, adjustments: CommissionAdjustmentCollection);
9
+ static create(classOptions?: SmrtClassOptions): Promise<CommissionBalanceService>;
10
+ /** Compute the {@link EarnerBalance} for one earner in one currency. */
11
+ getBalance(earnerId: string, currency: string): Promise<EarnerBalance>;
12
+ }
13
+ export default CommissionBalanceService;
14
+ //# sourceMappingURL=CommissionBalanceService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommissionBalanceService.d.ts","sourceRoot":"","sources":["../../../src/commissions/services/CommissionBalanceService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kDAAkD,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBADX,WAAW,EAAE,oBAAoB,EACjC,WAAW,EAAE,8BAA8B;WAGjD,MAAM,CACjB,YAAY,GAAE,gBAAqB,GAClC,OAAO,CAAC,wBAAwB,CAAC;IAOpC,wEAAwE;IAClE,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;CAkD7E;AAED,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { CommissionCollection } from '../collections/CommissionCollection.js';
3
+ import { EarnerCollection } from '../collections/EarnerCollection.js';
4
+ import { Commission } from '../models/Commission.js';
5
+ import { EarningEvent } from '../models/EarningEvent.js';
6
+ import { CommissionPlanComponent } from '../types.js';
7
+ /** Input for {@link CommissionCalculationService.calculateForEvent}. */
8
+ export interface CommissionCalculationInput {
9
+ /** The (persisted) earning event to calculate from. */
10
+ event: EarningEvent;
11
+ /** Snapshot reference recorded on every created Commission. */
12
+ planKey: string;
13
+ /** Snapshot reference recorded on every created Commission. */
14
+ planVersion: number;
15
+ /** The calculation terms to apply (typically from a frozen snapshot). */
16
+ components: CommissionPlanComponent[];
17
+ /** The earner the commissions belong to. */
18
+ earnerId: string;
19
+ /**
20
+ * Split share (0–1) this earner receives; defaults to 1. Callers running
21
+ * a split invoke the service once per earner with the shares and a shared
22
+ * `splitGroupId`.
23
+ */
24
+ shareFraction?: number;
25
+ /** Groups the sibling commissions of one split. */
26
+ splitGroupId?: string;
27
+ /** Generic polymorphic terms-snapshot reference (kind). */
28
+ termsSnapshotKind?: string;
29
+ /** Generic polymorphic terms-snapshot reference (id). */
30
+ termsSnapshotId?: string;
31
+ /**
32
+ * Clearing window in days: created commissions get
33
+ * `clearingEndsAt = event.occurredAt + clearingDays`. Omitted → no
34
+ * clearing (`clearingEndsAt: null`, immediately sweepable).
35
+ */
36
+ clearingDays?: number;
37
+ /**
38
+ * Currency the plan/terms are denominated in. When provided and different
39
+ * from `event.currency`, every matching component skips with reason
40
+ * `'currency_mismatch'` (this module performs no FX). Omitted → the event
41
+ * currency is taken as authoritative and no mismatch is possible.
42
+ */
43
+ currency?: string;
44
+ /**
45
+ * Resolves how many occurrences of a component this earner has already
46
+ * consumed under these terms (commissions from PRIOR events — the current
47
+ * event must not be counted). Drives `one_time` / `maxOccurrences` limits
48
+ * and the `occurrenceIndex` in the dedupe key and trace. Omitted →
49
+ * occurrence count `0`, i.e. recurrence limits are NOT enforced.
50
+ */
51
+ occurrenceCountResolver?: (componentKey: string) => Promise<number>;
52
+ /**
53
+ * Anchor for `windowMonths` recurrence checks (e.g. an agreement's
54
+ * effective date). Omitted → window checks are skipped.
55
+ */
56
+ anchorAt?: Date;
57
+ }
58
+ /** One component the calculation declined, and why. */
59
+ export interface CommissionComponentSkip {
60
+ componentKey: string;
61
+ /**
62
+ * `'net_basis_undefined'` | `'margin_basis_undefined'` |
63
+ * `'fixed_amount_missing'` | `'rate_missing'` | `'custom_basis_missing'`
64
+ * | `'currency_mismatch'` | `'occurrence_limit_reached'` |
65
+ * `'outside_recurrence_window'`
66
+ */
67
+ reason: string;
68
+ }
69
+ /** Result of {@link CommissionCalculationService.calculateForEvent}. */
70
+ export interface CommissionCalculationResult {
71
+ /** Commissions newly created by THIS call. */
72
+ created: Commission[];
73
+ /** Components that produced nothing, with reasons. */
74
+ skipped: CommissionComponentSkip[];
75
+ /**
76
+ * Idempotent replays: commissions that already existed for this
77
+ * (event, terms, component, earner) tuple. Never re-created, never
78
+ * mutated, and never in `created`.
79
+ */
80
+ existing: Commission[];
81
+ }
82
+ export declare class CommissionCalculationService {
83
+ private readonly commissions;
84
+ /**
85
+ * Optional earner lookup for the tenant-lane guard. When provided,
86
+ * `calculateForEvent` refuses an earner from a different tenant lane
87
+ * than the event (a cross-tenant `earnerId` would create a commission
88
+ * payable to another tenant's account). `static create()` always wires
89
+ * it; direct constructors may omit it for narrow test fixtures.
90
+ */
91
+ private readonly earners?;
92
+ constructor(commissions: CommissionCollection,
93
+ /**
94
+ * Optional earner lookup for the tenant-lane guard. When provided,
95
+ * `calculateForEvent` refuses an earner from a different tenant lane
96
+ * than the event (a cross-tenant `earnerId` would create a commission
97
+ * payable to another tenant's account). `static create()` always wires
98
+ * it; direct constructors may omit it for narrow test fixtures.
99
+ */
100
+ earners?: EarnerCollection | undefined);
101
+ static create(classOptions?: SmrtClassOptions): Promise<CommissionCalculationService>;
102
+ /**
103
+ * Calculate commissions for one event × one earner × a component set.
104
+ *
105
+ * For each component whose `trigger` matches `event.eventKind` (or `'*'`
106
+ * — non-matching components are silently filtered, not "skipped"):
107
+ *
108
+ * 1. **Idempotency** — if a Commission already exists for this
109
+ * (event, terms, component, earner) tuple, it is returned in
110
+ * `existing` and nothing else runs for the component.
111
+ * 2. **Currency** — `input.currency` (when given) must equal the event's;
112
+ * otherwise skip `'currency_mismatch'`.
113
+ * 3. **Recurrence** — `one_time` components skip
114
+ * `'occurrence_limit_reached'` once the resolver reports ≥ 1 prior
115
+ * occurrence; `recurring` components honor `maxOccurrences` and
116
+ * `windowMonths` (events after `anchorAt + windowMonths` skip
117
+ * `'outside_recurrence_window'`).
118
+ * 4. **Basis** — gross → `grossAmountCents`; net → `netAmountCents`
119
+ * (skip `'net_basis_undefined'` when null — net is explicit, NEVER
120
+ * derived from gross); margin → `marginCents` (skip
121
+ * `'margin_basis_undefined'` when null); fixed → `fixedAmountCents`;
122
+ * custom → `getCustomBases()[customBasisKey]` (skip
123
+ * `'custom_basis_missing'`).
124
+ * 5. **Amount** — `roundCents(base * rate * shareFraction)`; for `fixed`,
125
+ * `roundCents(fixedAmountCents * shareFraction)` with `rate` recorded
126
+ * as `0`. Rounding happens exactly once, on the final product.
127
+ *
128
+ * Every created Commission is persisted `pending`, carries the event's
129
+ * tenant/currency/source, a complete {@link CommissionCalculationTrace},
130
+ * `clearingEndsAt` when `clearingDays` was given, and the dedupe key
131
+ * `` `${event.dedupeKey}:${termsSnapshotId || planKey + '@' + planVersion}:${componentKey}:${earnerId}:${occurrenceIndex}` ``.
132
+ */
133
+ calculateForEvent(input: CommissionCalculationInput): Promise<CommissionCalculationResult>;
134
+ /**
135
+ * Calendar-month addition (UTC). JS `setUTCMonth` semantics: day-of-month
136
+ * overflow rolls into the next month (Jan 31 + 1 month → Mar 2/3), which
137
+ * is acceptable for coarse recurrence windows.
138
+ */
139
+ private static addMonths;
140
+ }
141
+ export default CommissionCalculationService;
142
+ //# sourceMappingURL=CommissionCalculationService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommissionCalculationService.d.ts","sourceRoot":"","sources":["../../../src/commissions/services/CommissionCalculationService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,KAAK,EAEV,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAIrB,wEAAwE;AACxE,MAAM,WAAW,0BAA0B;IACzC,uDAAuD;IACvD,KAAK,EAAE,YAAY,CAAC;IACpB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE;;;OAGG;IACH,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,WAAW,2BAA2B;IAC1C,8CAA8C;IAC9C,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sDAAsD;IACtD,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;;OAIG;IACH,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,qBAAa,4BAA4B;IAErC,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBARR,WAAW,EAAE,oBAAoB;IAClD;;;;;;OAMG;IACc,OAAO,CAAC,EAAE,gBAAgB,YAAA;WAGhC,MAAM,CACjB,YAAY,GAAE,gBAAqB,GAClC,OAAO,CAAC,4BAA4B,CAAC;IAOxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACG,iBAAiB,CACrB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC;IAoSvC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,SAAS;CAKzB;AAED,eAAe,4BAA4B,CAAC"}
@@ -0,0 +1,134 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { CommissionAdjustmentCollection } from '../collections/CommissionAdjustmentCollection.js';
3
+ import { CommissionCollection } from '../collections/CommissionCollection.js';
4
+ import { CommissionPayoutCollection } from '../collections/CommissionPayoutCollection.js';
5
+ import { EarnerCollection } from '../collections/EarnerCollection.js';
6
+ import { CommissionPayout } from '../models/CommissionPayout.js';
7
+ import { PayoutMethod } from '../types.js';
8
+ /** Collaborators for {@link CommissionPayoutService}. */
9
+ export interface CommissionPayoutServiceDeps {
10
+ earners: EarnerCollection;
11
+ commissions: CommissionCollection;
12
+ adjustments: CommissionAdjustmentCollection;
13
+ payouts: CommissionPayoutCollection;
14
+ }
15
+ /** Input for {@link CommissionPayoutService.createPayoutBatch}. */
16
+ export interface CreatePayoutBatchInput {
17
+ earnerId: string;
18
+ currency: string;
19
+ /** Informational period bounds recorded on the payout. */
20
+ periodStart?: Date;
21
+ periodEnd?: Date;
22
+ /**
23
+ * Idempotency natural key. Defaults to
24
+ * `` `${earnerId}:${currency}:${periodEnd ISO date}` `` where the date is
25
+ * the `YYYY-MM-DD` of `periodEnd` (falling back to `now`). Callers running
26
+ * more than one batch per earner/currency/day must supply their own key.
27
+ */
28
+ idempotencyKey?: string;
29
+ /** Overrides the earner's `payoutThresholdCents`. */
30
+ minimumThresholdCents?: number;
31
+ /** Overrides the earner's `payoutMethod`. */
32
+ payoutMethod?: PayoutMethod;
33
+ /** Clock override for deterministic tests. */
34
+ now?: Date;
35
+ }
36
+ /** Result of {@link CommissionPayoutService.createPayoutBatch}. */
37
+ export interface CreatePayoutBatchResult {
38
+ /** The created (or, on an idempotent replay, existing) payout — `null` on refusal. */
39
+ payout: CommissionPayout | null;
40
+ /** `true` only when THIS call minted the payout. */
41
+ created: boolean;
42
+ /** Why no payout was created, when refused. */
43
+ reason?: 'below_threshold' | 'nothing_payable';
44
+ /** Ids of the commissions THIS call stamped onto the payout. */
45
+ settledCommissionIds: string[];
46
+ /** Ids of the adjustments THIS call stamped onto the payout. */
47
+ settledAdjustmentIds: string[];
48
+ }
49
+ export declare class CommissionPayoutService {
50
+ private readonly deps;
51
+ constructor(deps: CommissionPayoutServiceDeps);
52
+ static create(classOptions?: SmrtClassOptions): Promise<CommissionPayoutService>;
53
+ /**
54
+ * Create a settlement batch for one earner in one currency.
55
+ *
56
+ * Flow:
57
+ * 1. **Idempotency + repair** — an existing payout with the (defaulted)
58
+ * key is returned as `{ payout, created: false }`. A clean replay
59
+ * touches nothing (new payable work is never swept into an existing
60
+ * batch). A PENDING payout whose stored totals disagree with the rows
61
+ * stamped with its id — the signature of an interrupted claim pass —
62
+ * is repaired: the claim pass re-runs and the totals are reconciled
63
+ * from the verified membership. Past `pending` the batch is frozen.
64
+ * 2. **Gather** — payable unsettled commissions for the earner/currency,
65
+ * plus unsettled adjustments whose parent commission is
66
+ * earned/approved/payable/paid (same eligibility as the balance
67
+ * service, so the batch settles exactly what the balance reports).
68
+ * 3. **Refuse** — `netTotal <= 0` → `'nothing_payable'`;
69
+ * `netTotal < threshold` (earner default, overridable) →
70
+ * `'below_threshold'`. Nothing is minted or stamped on refusal.
71
+ * 4. **Mint, claim, reconcile** — create the `pending` payout, then
72
+ * CLAIM the gathered rows through the collections' conditional
73
+ * `claimForPayout` (rows grabbed by another batch in the interim are
74
+ * skipped, never double-claimed), and finally store totals computed
75
+ * from the rows that were VERIFIABLY claimed — the payout's totals
76
+ * are always reproducible from its member rows.
77
+ *
78
+ * Concurrency: claims are conditional with post-save verification, which
79
+ * narrows but does not eliminate races between batches with different
80
+ * keys (the collection layer exposes no cross-row transaction — the same
81
+ * stance as commerce/ledgers compensation). Settlement runs are expected
82
+ * to be single-writer per earner; totals are correct-by-construction from
83
+ * claimed rows either way.
84
+ */
85
+ createPayoutBatch(input: CreatePayoutBatchInput): Promise<CreatePayoutBatchResult>;
86
+ /**
87
+ * Whether a payout's stored totals are reproducible from the rows
88
+ * actually stamped with its id — the invariant an interrupted claim pass
89
+ * breaks. Clean replays short-circuit on this; repair runs only when it
90
+ * fails.
91
+ */
92
+ private membershipConsistent;
93
+ /**
94
+ * Claim pass + totals reconciliation for a PENDING payout.
95
+ *
96
+ * The claim set is the union of rows already stamped with this payout
97
+ * (an interrupted earlier pass) and the currently gathered eligible
98
+ * rows. Claims go through the collections' conditional `claimForPayout`
99
+ * (rows owned by another batch are skipped); totals are then recomputed
100
+ * from the claimed rows and saved when they drift from what the payout
101
+ * carries. In the pathological all-rows-raced-away case the payout keeps
102
+ * zero totals and a note — auditable, never double-paid.
103
+ */
104
+ private claimAndReconcile;
105
+ /**
106
+ * Complete a payout: flip the batch's settled commissions
107
+ * `payable → paid` FIRST, then `payout.complete(paymentReference)`
108
+ * (requires status `processing`). Ordering matters for recoverability —
109
+ * if a member save fails mid-loop the payout is still `processing`, so a
110
+ * retry finishes the remaining members (already-paid ones are skipped)
111
+ * and then finalizes; the terminal transition never strands `payable`
112
+ * members behind a `completed` payout. Adjustments carry no status —
113
+ * stamping `payoutId` at batch time already settled them.
114
+ */
115
+ completePayout(payoutId: string, paymentReference: string, now?: Date): Promise<CommissionPayout>;
116
+ /**
117
+ * Fail a payout (`approved | processing → failed`). The batch's rows stay
118
+ * stamped — after `resetFromFailed()` the SAME payout retries the SAME
119
+ * rows; releasing the rows to a different batch would double-pay them if
120
+ * the failed remittance later settled.
121
+ */
122
+ failPayout(payoutId: string, reason: string): Promise<CommissionPayout>;
123
+ /**
124
+ * Unsettled adjustments for the earner/currency whose parent commission
125
+ * is earned/approved/payable/paid — the same eligibility rule the balance
126
+ * service applies, so batches settle exactly what balances report.
127
+ */
128
+ private findEligibleUnsettledAdjustments;
129
+ private requirePayout;
130
+ /** `${earnerId}:${currency}:${YYYY-MM-DD of periodEnd}` — see the input doc. */
131
+ private static defaultIdempotencyKey;
132
+ }
133
+ export default CommissionPayoutService;
134
+ //# sourceMappingURL=CommissionPayoutService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommissionPayoutService.d.ts","sourceRoot":"","sources":["../../../src/commissions/services/CommissionPayoutService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kDAAkD,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,8CAA8C,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAErB,yDAAyD;AACzD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,gBAAgB,CAAC;IAC1B,WAAW,EAAE,oBAAoB,CAAC;IAClC,WAAW,EAAE,8BAA8B,CAAC;IAC5C,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,8CAA8C;IAC9C,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAAC;IAC/C,gEAAgE;IAChE,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,2BAA2B;WAEjD,MAAM,CACjB,YAAY,GAAE,gBAAqB,GAClC,OAAO,CAAC,uBAAuB,CAAC;IASnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,uBAAuB,CAAC;IAqHnC;;;;;OAKG;YACW,oBAAoB;IAsBlC;;;;;;;;;;OAUG;YACW,iBAAiB;IA8E/B;;;;;;;;;OASG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,EACxB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,gBAAgB,CAAC;IA0B5B;;;;;OAKG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC;IAO5B;;;;OAIG;YACW,gCAAgC;YA6BhC,aAAa;IAU3B,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,qBAAqB;CAOrC;AAED,eAAe,uBAAuB,CAAC"}