@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.
- package/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/chunks/__smrt-register__-kWdT7LOt.js +6 -0
- package/dist/chunks/__smrt-register__-kWdT7LOt.js.map +1 -0
- package/dist/chunks/commissions-BCxqOKVM.js +2181 -0
- package/dist/chunks/commissions-BCxqOKVM.js.map +1 -0
- package/dist/chunks/crm-DwEz7E2r.js +1428 -0
- package/dist/chunks/crm-DwEz7E2r.js.map +1 -0
- package/dist/chunks/referrals-BY47p70T.js +2880 -0
- package/dist/chunks/referrals-BY47p70T.js.map +1 -0
- package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts +26 -0
- package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts.map +1 -0
- package/dist/commissions/collections/CommissionCollection.d.ts +43 -0
- package/dist/commissions/collections/CommissionCollection.d.ts.map +1 -0
- package/dist/commissions/collections/CommissionPayoutCollection.d.ts +19 -0
- package/dist/commissions/collections/CommissionPayoutCollection.d.ts.map +1 -0
- package/dist/commissions/collections/CommissionPlanCollection.d.ts +46 -0
- package/dist/commissions/collections/CommissionPlanCollection.d.ts.map +1 -0
- package/dist/commissions/collections/EarnerCollection.d.ts +14 -0
- package/dist/commissions/collections/EarnerCollection.d.ts.map +1 -0
- package/dist/commissions/collections/EarningEventCollection.d.ts +27 -0
- package/dist/commissions/collections/EarningEventCollection.d.ts.map +1 -0
- package/dist/commissions/collections/index.d.ts +11 -0
- package/dist/commissions/collections/index.d.ts.map +1 -0
- package/dist/commissions/index.d.ts +20 -0
- package/dist/commissions/index.d.ts.map +1 -0
- package/dist/commissions/models/Commission.d.ts +150 -0
- package/dist/commissions/models/Commission.d.ts.map +1 -0
- package/dist/commissions/models/CommissionAdjustment.d.ts +62 -0
- package/dist/commissions/models/CommissionAdjustment.d.ts.map +1 -0
- package/dist/commissions/models/CommissionPayout.d.ts +119 -0
- package/dist/commissions/models/CommissionPayout.d.ts.map +1 -0
- package/dist/commissions/models/CommissionPlan.d.ts +117 -0
- package/dist/commissions/models/CommissionPlan.d.ts.map +1 -0
- package/dist/commissions/models/Earner.d.ts +49 -0
- package/dist/commissions/models/Earner.d.ts.map +1 -0
- package/dist/commissions/models/EarningEvent.d.ts +89 -0
- package/dist/commissions/models/EarningEvent.d.ts.map +1 -0
- package/dist/commissions/models/index.d.ts +11 -0
- package/dist/commissions/models/index.d.ts.map +1 -0
- package/dist/commissions/money.d.ts +42 -0
- package/dist/commissions/money.d.ts.map +1 -0
- package/dist/commissions/services/CommissionBalanceService.d.ts +14 -0
- package/dist/commissions/services/CommissionBalanceService.d.ts.map +1 -0
- package/dist/commissions/services/CommissionCalculationService.d.ts +142 -0
- package/dist/commissions/services/CommissionCalculationService.d.ts.map +1 -0
- package/dist/commissions/services/CommissionPayoutService.d.ts +134 -0
- package/dist/commissions/services/CommissionPayoutService.d.ts.map +1 -0
- package/dist/commissions/services/CommissionSettlementService.d.ts +42 -0
- package/dist/commissions/services/CommissionSettlementService.d.ts.map +1 -0
- package/dist/commissions/services/index.d.ts +9 -0
- package/dist/commissions/services/index.d.ts.map +1 -0
- package/dist/commissions/types.d.ts +243 -0
- package/dist/commissions/types.d.ts.map +1 -0
- package/dist/commissions.d.ts +2 -0
- package/dist/commissions.d.ts.map +1 -0
- package/dist/commissions.js +3 -0
- package/dist/crm/collections/LeadCollection.d.ts +79 -0
- package/dist/crm/collections/LeadCollection.d.ts.map +1 -0
- package/dist/crm/collections/OpportunityCollection.d.ts +42 -0
- package/dist/crm/collections/OpportunityCollection.d.ts.map +1 -0
- package/dist/crm/collections/OpportunityConversionCollection.d.ts +26 -0
- package/dist/crm/collections/OpportunityConversionCollection.d.ts.map +1 -0
- package/dist/crm/collections/PipelineDefinitionCollection.d.ts +46 -0
- package/dist/crm/collections/PipelineDefinitionCollection.d.ts.map +1 -0
- package/dist/crm/collections/PipelineStageCollection.d.ts +22 -0
- package/dist/crm/collections/PipelineStageCollection.d.ts.map +1 -0
- package/dist/crm/collections/SalesActivityCollection.d.ts +24 -0
- package/dist/crm/collections/SalesActivityCollection.d.ts.map +1 -0
- package/dist/crm/collections/SalesRepresentativeCollection.d.ts +19 -0
- package/dist/crm/collections/SalesRepresentativeCollection.d.ts.map +1 -0
- package/dist/crm/index.d.ts +27 -0
- package/dist/crm/index.d.ts.map +1 -0
- package/dist/crm/models/Lead.d.ts +135 -0
- package/dist/crm/models/Lead.d.ts.map +1 -0
- package/dist/crm/models/Opportunity.d.ts +111 -0
- package/dist/crm/models/Opportunity.d.ts.map +1 -0
- package/dist/crm/models/OpportunityConversion.d.ts +66 -0
- package/dist/crm/models/OpportunityConversion.d.ts.map +1 -0
- package/dist/crm/models/PipelineDefinition.d.ts +58 -0
- package/dist/crm/models/PipelineDefinition.d.ts.map +1 -0
- package/dist/crm/models/PipelineStage.d.ts +66 -0
- package/dist/crm/models/PipelineStage.d.ts.map +1 -0
- package/dist/crm/models/SalesActivity.d.ts +76 -0
- package/dist/crm/models/SalesActivity.d.ts.map +1 -0
- package/dist/crm/models/SalesRepresentative.d.ts +60 -0
- package/dist/crm/models/SalesRepresentative.d.ts.map +1 -0
- package/dist/crm/types.d.ts +228 -0
- package/dist/crm/types.d.ts.map +1 -0
- package/dist/crm.d.ts +2 -0
- package/dist/crm.d.ts.map +1 -0
- package/dist/crm.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/manifest.json +9340 -0
- package/dist/referrals/collections/AttributionExceptionCollection.d.ts +17 -0
- package/dist/referrals/collections/AttributionExceptionCollection.d.ts.map +1 -0
- package/dist/referrals/collections/AttributionPolicyCollection.d.ts +50 -0
- package/dist/referrals/collections/AttributionPolicyCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralAgreementCollection.d.ts +48 -0
- package/dist/referrals/collections/ReferralAgreementCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralCollection.d.ts +25 -0
- package/dist/referrals/collections/ReferralCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralLinkCollection.d.ts +96 -0
- package/dist/referrals/collections/ReferralLinkCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralProgramCollection.d.ts +18 -0
- package/dist/referrals/collections/ReferralProgramCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts +13 -0
- package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralTouchCollection.d.ts +30 -0
- package/dist/referrals/collections/ReferralTouchCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferrerCollection.d.ts +17 -0
- package/dist/referrals/collections/ReferrerCollection.d.ts.map +1 -0
- package/dist/referrals/index.d.ts +38 -0
- package/dist/referrals/index.d.ts.map +1 -0
- package/dist/referrals/models/AttributionException.d.ts +85 -0
- package/dist/referrals/models/AttributionException.d.ts.map +1 -0
- package/dist/referrals/models/AttributionPolicy.d.ts +128 -0
- package/dist/referrals/models/AttributionPolicy.d.ts.map +1 -0
- package/dist/referrals/models/Referral.d.ts +112 -0
- package/dist/referrals/models/Referral.d.ts.map +1 -0
- package/dist/referrals/models/ReferralAgreement.d.ts +125 -0
- package/dist/referrals/models/ReferralAgreement.d.ts.map +1 -0
- package/dist/referrals/models/ReferralLink.d.ts +71 -0
- package/dist/referrals/models/ReferralLink.d.ts.map +1 -0
- package/dist/referrals/models/ReferralProgram.d.ts +57 -0
- package/dist/referrals/models/ReferralProgram.d.ts.map +1 -0
- package/dist/referrals/models/ReferralTermSnapshot.d.ts +61 -0
- package/dist/referrals/models/ReferralTermSnapshot.d.ts.map +1 -0
- package/dist/referrals/models/ReferralTouch.d.ts +55 -0
- package/dist/referrals/models/ReferralTouch.d.ts.map +1 -0
- package/dist/referrals/models/Referrer.d.ts +69 -0
- package/dist/referrals/models/Referrer.d.ts.map +1 -0
- package/dist/referrals/services/AttributionService.d.ts +248 -0
- package/dist/referrals/services/AttributionService.d.ts.map +1 -0
- package/dist/referrals/services/ReferralCommissionService.d.ts +91 -0
- package/dist/referrals/services/ReferralCommissionService.d.ts.map +1 -0
- package/dist/referrals/services/ReferralQualificationService.d.ts +135 -0
- package/dist/referrals/services/ReferralQualificationService.d.ts.map +1 -0
- package/dist/referrals/types.d.ts +226 -0
- package/dist/referrals/types.d.ts.map +1 -0
- package/dist/referrals.d.ts +2 -0
- package/dist/referrals.d.ts.map +1 -0
- package/dist/referrals.js +3 -0
- package/dist/smrt-knowledge.json +5501 -0
- package/dist/svelte/__tests__/format.test.js +68 -0
- package/dist/svelte/__tests__/types.test.js +349 -0
- package/dist/svelte/components/AttributionConflictQueue.svelte +361 -0
- package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts +15 -0
- package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CommissionBreakdown.svelte +251 -0
- package/dist/svelte/components/CommissionBreakdown.svelte.d.ts +11 -0
- package/dist/svelte/components/CommissionBreakdown.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CommissionExpenseSummary.svelte +186 -0
- package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts +13 -0
- package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ExecutedAgreementsList.svelte +136 -0
- package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts +11 -0
- package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/LeadList.svelte +224 -0
- package/dist/svelte/components/LeadList.svelte.d.ts +19 -0
- package/dist/svelte/components/LeadList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OpportunityBoard.svelte +230 -0
- package/dist/svelte/components/OpportunityBoard.svelte.d.ts +17 -0
- package/dist/svelte/components/OpportunityBoard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OpportunityDetail.svelte +472 -0
- package/dist/svelte/components/OpportunityDetail.svelte.d.ts +27 -0
- package/dist/svelte/components/OpportunityDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PayoutBatchReview.svelte +285 -0
- package/dist/svelte/components/PayoutBatchReview.svelte.d.ts +21 -0
- package/dist/svelte/components/PayoutBatchReview.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PayoutHistoryList.svelte +245 -0
- package/dist/svelte/components/PayoutHistoryList.svelte.d.ts +11 -0
- package/dist/svelte/components/PayoutHistoryList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReferralLinkManager.svelte +216 -0
- package/dist/svelte/components/ReferralLinkManager.svelte.d.ts +17 -0
- package/dist/svelte/components/ReferralLinkManager.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReferralStatusList.svelte +118 -0
- package/dist/svelte/components/ReferralStatusList.svelte.d.ts +11 -0
- package/dist/svelte/components/ReferralStatusList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReferrerEarningsSummary.svelte +106 -0
- package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts +11 -0
- package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SalesDashboard.svelte +192 -0
- package/dist/svelte/components/SalesDashboard.svelte.d.ts +29 -0
- package/dist/svelte/components/SalesDashboard.svelte.d.ts.map +1 -0
- package/dist/svelte/format.d.ts +46 -0
- package/dist/svelte/format.d.ts.map +1 -0
- package/dist/svelte/format.js +75 -0
- package/dist/svelte/index.d.ts +50 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +37 -0
- package/dist/svelte/types.d.ts +422 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +376 -0
- package/package.json +89 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commissions-BCxqOKVM.js","names":[],"sources":["../../src/commissions/models/CommissionAdjustment.ts","../../src/commissions/collections/CommissionAdjustmentCollection.ts","../../src/commissions/models/Commission.ts","../../src/commissions/collections/CommissionCollection.ts","../../src/commissions/models/CommissionPayout.ts","../../src/commissions/collections/CommissionPayoutCollection.ts","../../src/commissions/types.ts","../../src/commissions/models/CommissionPlan.ts","../../src/commissions/collections/CommissionPlanCollection.ts","../../src/commissions/models/Earner.ts","../../src/commissions/collections/EarnerCollection.ts","../../src/commissions/models/EarningEvent.ts","../../src/commissions/collections/EarningEventCollection.ts","../../src/commissions/money.ts","../../src/commissions/services/CommissionBalanceService.ts","../../src/commissions/services/CommissionCalculationService.ts","../../src/commissions/services/CommissionPayoutService.ts","../../src/commissions/services/CommissionSettlementService.ts"],"sourcesContent":["/**\n * CommissionAdjustment — append-only correction against a Commission.\n *\n * Earned/paid Commissions are NEVER rewritten. A refund, credit,\n * chargeback, dispute outcome, or manual correction appends one of these\n * rows instead. `amountCents` is SIGNED — negative amounts claw earnings\n * back; positive amounts credit extra.\n *\n * Immutability contract: once persisted, an adjustment's substance\n * (`commissionId`, `earnerId`, `adjustmentKind`, `amountCents`, `currency`,\n * `reason`, `createdByProfileId`, `metadata`, `tenantId`) is frozen — the\n * save-time guard rejects any change via a WeakMap snapshot compare (the\n * commerce LicenseSale pattern). The ONLY post-create mutation allowed is\n * stamping/clearing `payoutId` when a settlement batch picks the row up.\n * A wrong adjustment is corrected by appending a counter-adjustment.\n *\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CommissionAdjustmentKind,\n CommissionAdjustmentOptions,\n} from '../types.js';\n\n/**\n * Module-scoped record of the frozen-fields snapshot each persisted\n * adjustment was loaded with (or first saved as). WeakMap keeps it out of\n * the schema and GCs with the instance — commerce LicenseSale pattern.\n */\nconst frozenAdjustmentSnapshot = new WeakMap<CommissionAdjustment, string>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Append-only audit rows: create/list/get only — no generated update or\n // delete on any surface.\n api: { include: ['create', 'list', 'get'] },\n mcp: { include: ['list', 'create'] },\n cli: false,\n})\nexport class CommissionAdjustment extends SmrtObject {\n /** Tenant ID for multi-tenant isolation (nullable → global rows). */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** The {@link Commission} this adjustment corrects. Required. */\n @foreignKey('Commission', { required: true })\n commissionId: string = '';\n\n /**\n * The {@link Earner} the adjustment applies to — denormalized from the\n * parent commission so balance queries never need a join. Required.\n */\n @foreignKey('Earner', { required: true })\n earnerId: string = '';\n\n /** What kind of correction this is. */\n adjustmentKind: CommissionAdjustmentKind = 'correction';\n\n /**\n * SIGNED amount in integer cents. Negative claws earnings back (refund,\n * chargeback); positive credits extra.\n */\n amountCents: number = 0;\n\n /** ISO 4217 currency — must match the parent commission's. */\n currency: string = 'USD';\n\n /** Human-readable justification. Required — audit rows explain themselves. */\n @field({ required: true })\n reason: string = '';\n\n /**\n * Profile of the operator/automation that created the adjustment\n * (cross-package string reference to smrt-profiles).\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n createdByProfileId: string = '';\n\n /**\n * The {@link CommissionPayout} batch that settled this adjustment. Empty\n * until stamped. This is the ONLY field mutable after creation.\n */\n @foreignKey('CommissionPayout')\n payoutId: string = '';\n\n /** Additional metadata as a JSON string. Frozen once persisted. */\n metadata: string = '{}';\n\n constructor(options: CommissionAdjustmentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.commissionId !== undefined)\n this.commissionId = options.commissionId;\n if (options.earnerId !== undefined) this.earnerId = options.earnerId;\n if (options.adjustmentKind !== undefined)\n this.adjustmentKind = options.adjustmentKind;\n if (options.amountCents !== undefined)\n this.amountCents = options.amountCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.reason !== undefined) this.reason = options.reason;\n if (options.createdByProfileId !== undefined)\n this.createdByProfileId = options.createdByProfileId;\n if (options.payoutId !== undefined) this.payoutId = options.payoutId;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Capture the frozen-fields snapshot when the row was loaded from the\n * database — from that moment on, only {@link payoutId} may change.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n if (await this.isSaved()) {\n frozenAdjustmentSnapshot.set(this, this.serializeFrozenSnapshot());\n }\n return this;\n }\n\n /** `true` once a payout batch has stamped {@link payoutId}. */\n isSettled(): boolean {\n return !!this.payoutId;\n }\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n\n /**\n * Save with the append-only guard: once the row has been persisted, every\n * field except `payoutId` must match the captured snapshot. Corrections\n * to a wrong adjustment are new counter-adjustments, never edits.\n */\n override async save(): Promise<this> {\n this.assertImmutableOncePersisted();\n const result = (await super.save()) as this;\n if (!frozenAdjustmentSnapshot.has(this)) {\n frozenAdjustmentSnapshot.set(this, this.serializeFrozenSnapshot());\n }\n return result;\n }\n\n private assertImmutableOncePersisted(): void {\n const captured = frozenAdjustmentSnapshot.get(this);\n if (!captured) return; // brand-new row — first save captures below\n const current = this.serializeFrozenSnapshot();\n if (captured !== current) {\n throw new Error(\n `CommissionAdjustment ${this.id ?? '<new>'}: adjustments are ` +\n 'append-only — only payoutId may change after creation. Append a ' +\n 'counter-adjustment instead of editing this one.',\n );\n }\n }\n\n /**\n * Serialize every field EXCEPT `payoutId` (the sole post-create mutable\n * field) with stable key ordering.\n */\n private serializeFrozenSnapshot(): string {\n return JSON.stringify({\n tenantId: this.tenantId,\n commissionId: this.commissionId,\n earnerId: this.earnerId,\n adjustmentKind: this.adjustmentKind,\n amountCents: this.amountCents,\n currency: this.currency,\n reason: this.reason,\n createdByProfileId: this.createdByProfileId,\n metadata: this.metadata,\n });\n }\n}\n\nexport default CommissionAdjustment;\n","/**\n * CommissionAdjustmentCollection — collection manager for\n * {@link CommissionAdjustment}.\n *\n * \"Unsettled\" means `payoutId` is empty (checked in memory so `''`/`NULL`\n * storage differences don't matter). NOTE: the collection-level queries do\n * NOT apply the parent-commission-status eligibility rule — that lives in\n * `CommissionBalanceService` / `CommissionPayoutService`, which filter\n * unsettled adjustments to those whose parent commission is\n * earned/approved/payable/paid.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CommissionAdjustment } from '../models/CommissionAdjustment.js';\n\nexport class CommissionAdjustmentCollection extends SmrtCollection<CommissionAdjustment> {\n static readonly _itemClass = CommissionAdjustment;\n\n /** All adjustments appended to one commission, oldest first. */\n async findByCommission(\n commissionId: string,\n ): Promise<CommissionAdjustment[]> {\n return await this.list({\n where: { commissionId },\n orderBy: 'created_at ASC',\n });\n }\n\n /** Unsettled adjustments for an earner+currency, oldest first. */\n async findUnsettledByEarner(\n earnerId: string,\n currency: string,\n ): Promise<CommissionAdjustment[]> {\n const rows = await this.list({\n where: { earnerId, currency },\n orderBy: 'created_at ASC',\n });\n return rows.filter((a) => !a.payoutId);\n }\n\n /**\n * Σ signed amountCents of {@link findUnsettledByEarner} rows (integer\n * cents; clawbacks make it negative).\n */\n async sumUnsettledByEarner(\n earnerId: string,\n currency: string,\n ): Promise<number> {\n const rows = await this.findUnsettledByEarner(earnerId, currency);\n return rows.reduce((sum, a) => sum + a.amountCents, 0);\n }\n\n /** Adjustments settled by one payout batch. */\n async findByPayout(payoutId: string): Promise<CommissionAdjustment[]> {\n return await this.list({\n where: { payoutId },\n orderBy: 'created_at ASC',\n });\n }\n\n /**\n * Conditionally claim adjustment rows for a payout batch — the\n * adjustment twin of `CommissionCollection.claimForPayout`. Rows already\n * claimed by a DIFFERENT payout are skipped; rows already claimed by THIS\n * payout pass through (idempotent retry / repair); every claim is\n * verified by a post-save re-read. Returns the claimed rows.\n */\n async claimForPayout(\n adjustmentIds: string[],\n payoutId: string,\n ): Promise<CommissionAdjustment[]> {\n const claimed: CommissionAdjustment[] = [];\n for (const id of adjustmentIds) {\n const row = await this.get({ id });\n if (!row) continue;\n if (row.payoutId && row.payoutId !== payoutId) continue; // other batch\n if (!row.payoutId) {\n row.payoutId = payoutId;\n await row.save();\n }\n const verified = await this.get({ id });\n if (verified && verified.payoutId === payoutId) {\n claimed.push(verified);\n }\n }\n return claimed;\n }\n}\n\nexport default CommissionAdjustmentCollection;\n","/**\n * Commission — one earning record for one earner, one plan component, one\n * earning-event occurrence.\n *\n * Amounts are integer cents; `rate`/`shareFraction` are decimals in 0–1.\n * Every row stores snapshot references (`planKey`/`planVersion` plus a\n * generic polymorphic `termsSnapshotKind`/`termsSnapshotId` — the referrals\n * module points the latter at its ReferralTermSnapshot) and a JSON\n * `calculationTrace` sufficient to reproduce `amountCents`, so earnings stay\n * auditable after plans are superseded.\n *\n * Lifecycle is a STRICT forward chain — `pending → earned → approved →\n * payable → paid` — enforced at save time against the AUTHORITATIVE prior\n * persisted status (re-read from the database, commerce pattern), so neither\n * raw mass-assignment nor a `create({ id, _skipLoad: true })` upsert can skip\n * steps or roll back. Use the transition methods ({@link markEarned} /\n * {@link approve} / {@link markPayable} / {@link markPaid}); they mutate and\n * stamp timestamps but DO NOT save — the caller saves (one explicit\n * persistence point per mutation, matching commerce's markSent/markConfirmed\n * convention).\n *\n * Commissions are audit rows: the generated surface has no update or delete.\n * Corrections append {@link CommissionAdjustment} rows instead of editing.\n *\n * @packageDocumentation\n */\n\nimport { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CommissionBasis,\n CommissionCalculationTrace,\n CommissionOptions,\n CommissionStatus,\n} from '../types.js';\n\n/**\n * Legal status transitions — the strict chain, keyed by prior persisted\n * status. No-op re-saves and brand-new rows are always permitted (imports /\n * fixtures may seed any status); this map governs *changes* to persisted\n * rows only.\n */\nconst COMMISSION_STATUS_TRANSITIONS: Record<\n CommissionStatus,\n CommissionStatus[]\n> = {\n pending: ['earned'],\n earned: ['approved'],\n approved: ['payable'],\n payable: ['paid'],\n paid: [],\n};\n\n/**\n * Module-scoped record of the status each Commission instance was loaded\n * with — fallback for the save-time guard when the DB re-read is\n * unavailable. WeakMap keeps it out of the schema (commerce pattern).\n */\nconst loadedCommissionStatus = new WeakMap<Commission, CommissionStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Idempotent creation: dedupeKey is the natural key so a retried\n // calculation upserts instead of duplicating.\n conflictColumns: ['dedupe_key'],\n // Audit rows: create/list/get only — no generated update or delete.\n // Lifecycle mutations happen through the guarded transition methods and\n // the settlement/payout services.\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get'] },\n // High volume and mutation-sensitive — no CLI surface.\n cli: false,\n})\nexport class Commission extends SmrtObject {\n /** Tenant ID for multi-tenant isolation (nullable → global rows). */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** The {@link Earner} this commission belongs to. Required. */\n @foreignKey('Earner', { required: true })\n earnerId: string = '';\n\n /** The {@link EarningEvent} evidence row this commission derives from. */\n @foreignKey('EarningEvent')\n earningEventId: string = '';\n\n /** Snapshot reference: plan key at calculation time. */\n planKey: string = '';\n\n /** Snapshot reference: plan version at calculation time. */\n planVersion: number = 0;\n\n /** Which plan component produced this commission. */\n componentKey: string = '';\n\n /**\n * Generic polymorphic reference to the terms snapshot that governed the\n * calculation (e.g. the referrals module sets\n * `('referral_term_snapshot', <id>)`). Free-form; this module attaches no\n * semantics beyond recording it in the dedupe key and trace.\n */\n termsSnapshotKind: string = '';\n\n /** Id of the terms snapshot named by {@link termsSnapshotKind}. */\n termsSnapshotId: string = '';\n\n /** How {@link baseAmountCents} was resolved from the event. */\n basis: CommissionBasis = 'gross';\n\n /** Base amount the rate was applied to, in integer cents. */\n baseAmountCents: number = 0;\n\n /** Rate applied (0–1). Recorded as `0` for `fixed`-basis commissions. */\n rate: number = 0.0;\n\n /** Split share applied (0–1). `1.0` for unsplit commissions. */\n shareFraction: number = 1.0;\n\n /**\n * Groups the sibling commissions of one split — every earner sharing an\n * event/component carries the same `splitGroupId`. Empty for unsplit rows.\n */\n splitGroupId: string = '';\n\n /** The earned amount in integer cents. */\n amountCents: number = 0;\n\n /** ISO 4217 currency (copied from the earning event). */\n currency: string = 'USD';\n\n /**\n * Lifecycle status — strict chain `pending → earned → approved → payable\n * → paid`. Mutate via the transition methods; the save-time guard rejects\n * illegal edges.\n */\n status: CommissionStatus = 'pending';\n\n /**\n * End of the clearing window (refund/chargeback holdback). `null` means\n * no clearing applies — the commission is immediately sweepable to\n * `earned` (see `CommissionSettlementService.sweepClearing`).\n */\n clearingEndsAt: Date | null = null;\n\n /** When the commission transitioned to `earned`. */\n earnedAt: Date | null = null;\n\n /** When the commission transitioned to `approved`. */\n approvedAt: Date | null = null;\n\n /** When the commission transitioned to `payable`. */\n payableAt: Date | null = null;\n\n /** When the commission transitioned to `paid`. */\n paidAt: Date | null = null;\n\n /**\n * The {@link CommissionPayout} batch that settled this commission. Empty\n * until a payout batch stamps it.\n */\n @foreignKey('CommissionPayout')\n payoutId: string = '';\n\n /** Copied from the earning event for reporting (generic source pair). */\n sourceKind: string = '';\n\n /** Copied from the earning event for reporting. */\n sourceId: string = '';\n\n /**\n * JSON-string {@link CommissionCalculationTrace} — everything needed to\n * reproduce {@link amountCents}. Use {@link getCalculationTrace} /\n * {@link setCalculationTrace}.\n */\n calculationTrace: string = '{}';\n\n /**\n * Idempotency natural key —\n * `` `${event.dedupeKey}:${terms}:${componentKey}:${earnerId}:${occurrenceIndex}` ``\n * (see `CommissionCalculationService`). Required.\n */\n @field({ required: true })\n dedupeKey: string = '';\n\n /** Additional metadata as a JSON string. */\n metadata: string = '{}';\n\n constructor(options: CommissionOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.earnerId !== undefined) this.earnerId = options.earnerId;\n if (options.earningEventId !== undefined)\n this.earningEventId = options.earningEventId;\n if (options.planKey !== undefined) this.planKey = options.planKey;\n if (options.planVersion !== undefined)\n this.planVersion = options.planVersion;\n if (options.componentKey !== undefined)\n this.componentKey = options.componentKey;\n if (options.termsSnapshotKind !== undefined)\n this.termsSnapshotKind = options.termsSnapshotKind;\n if (options.termsSnapshotId !== undefined)\n this.termsSnapshotId = options.termsSnapshotId;\n if (options.basis !== undefined) this.basis = options.basis;\n if (options.baseAmountCents !== undefined)\n this.baseAmountCents = options.baseAmountCents;\n if (options.rate !== undefined) this.rate = options.rate;\n if (options.shareFraction !== undefined)\n this.shareFraction = options.shareFraction;\n if (options.splitGroupId !== undefined)\n this.splitGroupId = options.splitGroupId;\n if (options.amountCents !== undefined)\n this.amountCents = options.amountCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.status !== undefined) this.status = options.status;\n if (options.clearingEndsAt !== undefined)\n this.clearingEndsAt = Commission.coerceDate(options.clearingEndsAt);\n if (options.earnedAt !== undefined)\n this.earnedAt = Commission.coerceDate(options.earnedAt);\n if (options.approvedAt !== undefined)\n this.approvedAt = Commission.coerceDate(options.approvedAt);\n if (options.payableAt !== undefined)\n this.payableAt = Commission.coerceDate(options.payableAt);\n if (options.paidAt !== undefined)\n this.paidAt = Commission.coerceDate(options.paidAt);\n if (options.payoutId !== undefined) this.payoutId = options.payoutId;\n if (options.sourceKind !== undefined) this.sourceKind = options.sourceKind;\n if (options.sourceId !== undefined) this.sourceId = options.sourceId;\n if (options.calculationTrace !== undefined)\n this.calculationTrace = options.calculationTrace;\n if (options.dedupeKey !== undefined) this.dedupeKey = options.dedupeKey;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Re-coerce timestamp fields after the framework reapplies raw option /\n * hydrated row values, and record the loaded status for the save guard.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.clearingEndsAt = Commission.coerceDate(this.clearingEndsAt);\n this.earnedAt = Commission.coerceDate(this.earnedAt);\n this.approvedAt = Commission.coerceDate(this.approvedAt);\n this.payableAt = Commission.coerceDate(this.payableAt);\n this.paidAt = Commission.coerceDate(this.paidAt);\n if (await this.isSaved()) {\n loadedCommissionStatus.set(this, this.status);\n }\n return this;\n }\n\n // -------- Status predicates --------\n\n isPending(): boolean {\n return this.status === 'pending';\n }\n\n isEarned(): boolean {\n return this.status === 'earned';\n }\n\n isApproved(): boolean {\n return this.status === 'approved';\n }\n\n isPayable(): boolean {\n return this.status === 'payable';\n }\n\n isPaid(): boolean {\n return this.status === 'paid';\n }\n\n /** `true` once a payout batch has stamped {@link payoutId}. */\n isSettled(): boolean {\n return !!this.payoutId;\n }\n\n // -------- Transition methods (mutate only — caller saves) --------\n\n /**\n * `pending → earned` (clearing window passed). Stamps {@link earnedAt}.\n * Does NOT save — the caller saves.\n */\n markEarned(now: Date = new Date()): void {\n this.assertTransitionFrom('pending', 'earned');\n this.status = 'earned';\n this.earnedAt = now;\n }\n\n /**\n * `earned → approved` (operator/automation approved the earning).\n * Stamps {@link approvedAt}. Does NOT save — the caller saves.\n */\n approve(now: Date = new Date()): void {\n this.assertTransitionFrom('earned', 'approved');\n this.status = 'approved';\n this.approvedAt = now;\n }\n\n /**\n * `approved → payable` (released for the next payout batch).\n * Stamps {@link payableAt}. Does NOT save — the caller saves.\n */\n markPayable(now: Date = new Date()): void {\n this.assertTransitionFrom('approved', 'payable');\n this.status = 'payable';\n this.payableAt = now;\n }\n\n /**\n * `payable → paid` (its payout batch completed). Stamps {@link paidAt}.\n * Does NOT save — the caller saves.\n */\n markPaid(now: Date = new Date()): void {\n this.assertTransitionFrom('payable', 'paid');\n this.status = 'paid';\n this.paidAt = now;\n }\n\n private assertTransitionFrom(\n expected: CommissionStatus,\n next: CommissionStatus,\n ): void {\n if (this.status !== expected) {\n throw new Error(\n `Commission ${this.id ?? '<new>'}: cannot transition to '${next}' ` +\n `from status '${this.status}' (chain is pending → earned → ` +\n 'approved → payable → paid)',\n );\n }\n }\n\n // -------- Trace / metadata helpers --------\n\n /** Parse {@link calculationTrace}; returns `null` on empty/invalid JSON. */\n getCalculationTrace(): CommissionCalculationTrace | null {\n if (!this.calculationTrace) return null;\n try {\n const parsed = JSON.parse(this.calculationTrace) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null;\n }\n const trace = parsed as CommissionCalculationTrace;\n return typeof trace.componentKey === 'string' &&\n typeof trace.baseAmountCents === 'number'\n ? trace\n : null;\n } catch {\n return null;\n }\n }\n\n /** Serialize and store {@link calculationTrace}. */\n setCalculationTrace(trace: CommissionCalculationTrace): void {\n this.calculationTrace = JSON.stringify(trace);\n }\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n\n // -------- Save-time guard --------\n\n /**\n * Save-time state-machine guard (commerce pattern). Validates the status\n * transition against the AUTHORITATIVE prior persisted status — re-read\n * from the database so a `create({ id: <existing>, _skipLoad: true })`\n * upsert is correctly treated as an update rather than a guard-free new\n * row. Brand-new rows may start in any status (fixtures/imports); a\n * persisted row may only advance one legal step.\n */\n override async save(): Promise<this> {\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n await this.assertDedupeKeyNotTaken();\n const result = (await super.save()) as this;\n loadedCommissionStatus.set(this, this.status);\n return result;\n }\n\n /**\n * Refuse a save whose `dedupeKey` already belongs to a DIFFERENT row —\n * commissions are audit rows, and the natural-key upsert would let a\n * fresh instance (generated `create`, or the loser of a calculation\n * race) overwrite the persisted amount/status and rotate the row id.\n * `CommissionCalculationService` treats this refusal as \"someone else\n * already earned it\" and returns the existing row.\n */\n private async assertDedupeKeyNotTaken(): Promise<void> {\n if (!this.dedupeKey) return;\n try {\n const res = await this.db.query(\n `SELECT id FROM ${this.tableName} WHERE dedupe_key = $1`,\n this.dedupeKey,\n );\n const rows = Array.isArray(res)\n ? (res as Record<string, unknown>[])\n : ((res as { rows?: Record<string, unknown>[] }).rows ?? []);\n const taken = rows.find((row) => row.id !== this.id);\n if (taken) {\n throw new Error(\n `Commission (dedupeKey '${this.dedupeKey}'): a commission with ` +\n 'this dedupe key already exists — commissions are immutable ' +\n 'audit rows; corrections append CommissionAdjustments.',\n );\n }\n } catch (error) {\n if (error instanceof Error && error.message.includes('immutable')) {\n throw error;\n }\n // DB not ready / table absent — nothing persisted to collide with.\n }\n }\n\n private async resolvePriorStatus(): Promise<CommissionStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as CommissionStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedCommissionStatus.get(this);\n }\n\n private assertStatusTransition(prior: CommissionStatus | undefined): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = COMMISSION_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Commission ${this.id}: illegal status transition '${prior}' → ` +\n `'${this.status}'. Use markEarned() / approve() / markPayable() ` +\n '/ markPaid().',\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default Commission;\n","/**\n * CommissionCollection — collection manager for {@link Commission}.\n *\n * \"Unsettled\" throughout means `payoutId` is empty — the row has not been\n * gathered into a {@link CommissionPayout} batch yet. The emptiness check is\n * applied in memory (`!c.payoutId`) rather than as a `WHERE payout_id = ''`\n * filter so the semantics hold whether an adapter stores the empty\n * reference as `''` or `NULL`.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Commission } from '../models/Commission.js';\nimport type { CommissionStatus } from '../types.js';\n\nexport class CommissionCollection extends SmrtCollection<Commission> {\n static readonly _itemClass = Commission;\n\n /** All commissions for an earner, newest first. */\n async findByEarner(earnerId: string): Promise<Commission[]> {\n return await this.list({\n where: { earnerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /** All commissions derived from one earning event. */\n async findByEvent(earningEventId: string): Promise<Commission[]> {\n return await this.list({\n where: { earningEventId },\n orderBy: 'created_at DESC',\n });\n }\n\n /** Commissions by lifecycle status, newest first. */\n async findByStatus(status: CommissionStatus): Promise<Commission[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /** Look up a commission by its idempotency natural key. */\n async findByDedupeKey(dedupeKey: string): Promise<Commission | null> {\n if (!dedupeKey) return null;\n const results = await this.list({ where: { dedupeKey }, limit: 1 });\n return results[0] ?? null;\n }\n\n /**\n * Payable commissions for an earner+currency that no payout batch has\n * settled yet — the rows `CommissionPayoutService.createPayoutBatch`\n * gathers.\n */\n async findPayableUnsettled(\n earnerId: string,\n currency: string,\n ): Promise<Commission[]> {\n const payable = await this.list({\n where: { earnerId, currency, status: 'payable' },\n orderBy: 'created_at ASC',\n });\n return payable.filter((c) => !c.payoutId);\n }\n\n /** Σ amountCents of {@link findPayableUnsettled} rows (integer cents). */\n async sumPayableByEarner(\n earnerId: string,\n currency: string,\n ): Promise<number> {\n const payable = await this.findPayableUnsettled(earnerId, currency);\n return payable.reduce((sum, c) => sum + c.amountCents, 0);\n }\n\n /** Commissions settled by one payout batch. */\n async findByPayout(payoutId: string): Promise<Commission[]> {\n return await this.list({\n where: { payoutId },\n orderBy: 'created_at ASC',\n });\n }\n\n /**\n * Conditionally claim rows for a payout batch: each row is re-loaded\n * fresh and stamped with `payoutId` only when it is still payable and\n * unclaimed (or already claimed by THIS payout — the idempotent-retry /\n * repair case). Rows claimed by a DIFFERENT payout are skipped, and every\n * claim is verified by a post-save re-read so a lost race never counts\n * toward the caller's totals.\n *\n * This is the single place claim semantics live. It narrows the\n * concurrent-batch window to the re-read granularity; true compare-and-set\n * needs DB transactions the collection layer doesn't expose (settlement\n * runs are expected to be single-writer per earner — see\n * `CommissionPayoutService`).\n *\n * Returns the claimed rows (freshly loaded, `payoutId` verified).\n */\n async claimForPayout(\n commissionIds: string[],\n payoutId: string,\n ): Promise<Commission[]> {\n const claimed: Commission[] = [];\n for (const id of commissionIds) {\n const row = await this.get({ id });\n if (!row) continue;\n if (row.payoutId && row.payoutId !== payoutId) continue; // other batch\n if (!row.payoutId) {\n if (!row.isPayable()) continue; // no longer eligible\n row.payoutId = payoutId;\n await row.save();\n }\n const verified = await this.get({ id });\n if (verified && verified.payoutId === payoutId) {\n claimed.push(verified);\n }\n }\n return claimed;\n }\n}\n\nexport default CommissionCollection;\n","/**\n * CommissionPayout — settlement batch for one earner in one currency.\n *\n * A payout gathers an earner's payable unsettled Commissions plus the\n * unsettled Adjustments of eligible commissions, stamps `payoutId` on those\n * EXACT rows, and records the totals it settled\n * (`totalAmountCents = commissionTotalCents + adjustmentTotalCents` —\n * enforced at save time). Batches are minted by\n * `CommissionPayoutService.createPayoutBatch`, idempotently via the\n * `idempotencyKey` natural key.\n *\n * The model is named CommissionPayout (table `commission_payouts`) to avoid\n * the global table-name collision with commerce `Payout` / legacy affiliates\n * `Payout` (`payouts`).\n *\n * Generated surface is FULLY read-only — list/get on api, mcp, AND cli.\n * Commerce Payout precedent (\"a payout has no safe generated write\"): the\n * status drives an outgoing remittance and the totals are the integrity\n * core, so the only legitimate writes are the service's creation path and\n * the guarded transition helpers below. The CLI is an independently\n * configured write surface — `cli: true` would regenerate the exact\n * create/update vector closed on api/mcp, so it is locked to list/get too.\n *\n * Lifecycle: `pending → approved → processing → completed | failed`, with\n * `failed` reachable from approved/processing and resettable to `pending`\n * only via {@link resetFromFailed}. Transition helpers mutate and stamp but\n * DO NOT save — the caller saves (same convention as Commission).\n *\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CommissionPayoutOptions,\n CommissionPayoutStatus,\n PayoutMethod,\n} from '../types.js';\n\n/**\n * Legal status transitions, keyed by the prior persisted status.\n * `failed → pending` exists only for {@link resetFromFailed}. `completed`\n * is terminal. No-op re-saves and brand-new rows are always permitted;\n * this map governs *changes* to persisted rows only (commerce pattern).\n */\nconst PAYOUT_STATUS_TRANSITIONS: Record<\n CommissionPayoutStatus,\n CommissionPayoutStatus[]\n> = {\n pending: ['approved'],\n approved: ['processing', 'failed'],\n processing: ['completed', 'failed'],\n completed: [],\n // FAILED is resettable to PENDING via resetFromFailed().\n failed: ['pending'],\n};\n\n/**\n * Module-scoped record of the status each payout instance was loaded with —\n * fallback for the save-time guard when the DB re-read is unavailable.\n */\nconst loadedPayoutStatus = new WeakMap<\n CommissionPayout,\n CommissionPayoutStatus\n>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Idempotent settlement: a retried batch with the same idempotencyKey\n // resolves to the existing payout instead of double-paying.\n conflictColumns: ['idempotency_key'],\n // Fully read-only generated surface on ALL THREE surfaces — see the\n // class doc. Writes go through CommissionPayoutService and the guarded\n // transition helpers only.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class CommissionPayout extends SmrtObject {\n /** Tenant ID for multi-tenant isolation (nullable → global payouts). */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** The {@link Earner} being paid. Required. */\n @foreignKey('Earner', { required: true })\n earnerId: string = '';\n\n /** Start of the settlement period this batch covers (informational). */\n periodStart: Date | null = null;\n\n /** End of the settlement period this batch covers (informational). */\n periodEnd: Date | null = null;\n\n /** Σ amountCents of the Commissions this batch settled (integer cents). */\n commissionTotalCents: number = 0;\n\n /**\n * Σ signed amountCents of the Adjustments this batch settled (integer\n * cents; clawbacks make it negative).\n */\n adjustmentTotalCents: number = 0;\n\n /**\n * Net amount remitted — must equal\n * `commissionTotalCents + adjustmentTotalCents` (enforced on save).\n */\n totalAmountCents: number = 0;\n\n /** ISO 4217 currency of the batch. */\n currency: string = 'USD';\n\n /** Delivery method for this batch (defaulted from the Earner). */\n payoutMethod: PayoutMethod = 'bank_transfer';\n\n /**\n * Lifecycle status — see the class doc. Mutate via {@link approve} /\n * {@link markProcessing} / {@link complete} / {@link fail} /\n * {@link resetFromFailed}.\n */\n status: CommissionPayoutStatus = 'pending';\n\n /**\n * Payment reference recorded at completion (check number, transfer id,\n * …). Cleared by {@link resetFromFailed}.\n */\n paymentReference: string = '';\n\n /**\n * Opaque payout-provider reference (processor batch id, remittance file\n * id, …). Retained across failure/reset for audit.\n */\n providerRef: string = '';\n\n /** When the payout completed. */\n paidAt: Date | null = null;\n\n /**\n * Optional link to the commerce Invoice that papers this payout\n * (cross-package string reference — never a DDL foreign key).\n */\n @crossPackageRef('@happyvertical/smrt-commerce:Invoice')\n invoiceId: string = '';\n\n /** Operator notes — approval memos, failure reasons (append-only). */\n notes: string = '';\n\n /**\n * Idempotency natural key. Required. The payout service defaults it to\n * `` `${earnerId}:${currency}:${periodEnd ISO date}` `` when the caller\n * doesn't supply one.\n */\n @field({ required: true })\n idempotencyKey: string = '';\n\n /** Additional metadata as a JSON string. */\n metadata: string = '{}';\n\n constructor(options: CommissionPayoutOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.earnerId !== undefined) this.earnerId = options.earnerId;\n if (options.periodStart !== undefined)\n this.periodStart = CommissionPayout.coerceDate(options.periodStart);\n if (options.periodEnd !== undefined)\n this.periodEnd = CommissionPayout.coerceDate(options.periodEnd);\n if (options.commissionTotalCents !== undefined)\n this.commissionTotalCents = options.commissionTotalCents;\n if (options.adjustmentTotalCents !== undefined)\n this.adjustmentTotalCents = options.adjustmentTotalCents;\n if (options.totalAmountCents !== undefined)\n this.totalAmountCents = options.totalAmountCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.payoutMethod !== undefined)\n this.payoutMethod = options.payoutMethod;\n if (options.status !== undefined) this.status = options.status;\n if (options.paymentReference !== undefined)\n this.paymentReference = options.paymentReference;\n if (options.providerRef !== undefined)\n this.providerRef = options.providerRef;\n if (options.paidAt !== undefined)\n this.paidAt = CommissionPayout.coerceDate(options.paidAt);\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.idempotencyKey !== undefined)\n this.idempotencyKey = options.idempotencyKey;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Re-coerce timestamp fields after the framework reapplies raw option /\n * hydrated row values, and record the loaded status for the save guard.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.periodStart = CommissionPayout.coerceDate(this.periodStart);\n this.periodEnd = CommissionPayout.coerceDate(this.periodEnd);\n this.paidAt = CommissionPayout.coerceDate(this.paidAt);\n if (await this.isSaved()) {\n loadedPayoutStatus.set(this, this.status);\n }\n return this;\n }\n\n // -------- Status predicates --------\n\n isPending(): boolean {\n return this.status === 'pending';\n }\n\n isApproved(): boolean {\n return this.status === 'approved';\n }\n\n isProcessing(): boolean {\n return this.status === 'processing';\n }\n\n isCompleted(): boolean {\n return this.status === 'completed';\n }\n\n isFailed(): boolean {\n return this.status === 'failed';\n }\n\n // -------- Transition methods (mutate only — caller saves) --------\n\n /** `pending → approved`. Does NOT save — the caller saves. */\n approve(): void {\n if (this.status !== 'pending') {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot approve from status '${this.status}'`,\n );\n }\n // A batch whose reconciled membership nets to nothing (or a clawback\n // surplus) must never move toward remittance — such payouts exist only\n // as audit artifacts of a raced/interrupted claim pass.\n if (this.totalAmountCents <= 0) {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot approve a batch with ` +\n `non-positive total (${this.totalAmountCents} cents)`,\n );\n }\n this.status = 'approved';\n }\n\n /** `approved → processing`. Does NOT save — the caller saves. */\n markProcessing(): void {\n if (this.status !== 'approved') {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot mark processing from status '${this.status}'`,\n );\n }\n this.status = 'processing';\n }\n\n /**\n * `processing → completed`. Requires a payment reference — a completed\n * payout with no reference is untraceable. Stamps {@link paidAt}.\n * Does NOT save — the caller saves.\n */\n complete(paymentReference: string, now: Date = new Date()): void {\n if (this.status !== 'processing') {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot complete from status '${this.status}'`,\n );\n }\n if (!paymentReference) {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: complete() requires a paymentReference`,\n );\n }\n this.status = 'completed';\n this.paymentReference = paymentReference;\n this.paidAt = now;\n }\n\n /**\n * `approved | processing → failed`. Appends the reason to {@link notes}.\n * Does NOT save — the caller saves.\n */\n fail(reason: string): void {\n if (this.status !== 'approved' && this.status !== 'processing') {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot fail from status '${this.status}'`,\n );\n }\n this.status = 'failed';\n const memo = `Failed: ${reason ?? ''}`;\n this.notes = this.notes ? `${this.notes}\\n${memo}` : memo;\n }\n\n /**\n * Operator-driven reset: `failed → pending` after fixing whatever broke.\n * Clears {@link paymentReference} and {@link paidAt} (the next attempt\n * gets fresh ones) but RETAINS {@link providerRef} and {@link notes} for\n * audit. The only path out of `failed`. Does NOT save — the caller saves.\n */\n resetFromFailed(): void {\n if (this.status !== 'failed') {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: cannot reset from status '${this.status}' — only failed payouts are resettable`,\n );\n }\n this.status = 'pending';\n this.paymentReference = '';\n this.paidAt = null;\n }\n\n // -------- Metadata helpers --------\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n\n // -------- Save-time guards --------\n\n /**\n * Save with two guards (commerce pattern):\n *\n * 1. **Totals invariant** — `totalAmountCents` must equal\n * `commissionTotalCents + adjustmentTotalCents` (exact integer\n * arithmetic, no epsilon).\n * 2. **Status transition** — validated against the AUTHORITATIVE prior\n * persisted status (re-read from the database so a\n * `create({ id, _skipLoad: true })` upsert can't sidestep the guard).\n * A `completed` payout additionally requires a payment reference,\n * matching {@link complete}'s invariant, regardless of how the status\n * was set.\n */\n override async save(): Promise<this> {\n this.validateTotals();\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n if (this.status === 'completed' && !this.paymentReference) {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: a completed payout requires a paymentReference (use complete()).`,\n );\n }\n const result = (await super.save()) as this;\n loadedPayoutStatus.set(this, this.status);\n return result;\n }\n\n /** Throws when the totals invariant doesn't hold. */\n validateTotals(): void {\n for (const [name, value] of [\n ['commissionTotalCents', this.commissionTotalCents],\n ['adjustmentTotalCents', this.adjustmentTotalCents],\n ['totalAmountCents', this.totalAmountCents],\n ] as const) {\n if (!Number.isInteger(value)) {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: ${name} must be integer cents (got ${value}).`,\n );\n }\n }\n const expected = this.commissionTotalCents + this.adjustmentTotalCents;\n if (this.totalAmountCents !== expected) {\n throw new Error(\n `CommissionPayout ${this.id ?? '<new>'}: totals invariant violated — ` +\n `commission=${this.commissionTotalCents} adjustment=${this.adjustmentTotalCents} ` +\n `total=${this.totalAmountCents} (expected total=${expected}).`,\n );\n }\n }\n\n private async resolvePriorStatus(): Promise<\n CommissionPayoutStatus | undefined\n > {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as CommissionPayoutStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedPayoutStatus.get(this);\n }\n\n private assertStatusTransition(\n prior: CommissionPayoutStatus | undefined,\n ): void {\n if (prior === undefined) return; // new row\n if (prior === this.status) return; // no-op re-save\n const allowed = PAYOUT_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `CommissionPayout ${this.id}: illegal status transition '${prior}' ` +\n `→ '${this.status}'. Use approve() / markProcessing() / ` +\n 'complete() / fail() / resetFromFailed().',\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default CommissionPayout;\n","/**\n * CommissionPayoutCollection — collection manager for\n * {@link CommissionPayout}.\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CommissionPayout } from '../models/CommissionPayout.js';\nimport type { CommissionPayoutStatus } from '../types.js';\n\nexport class CommissionPayoutCollection extends SmrtCollection<CommissionPayout> {\n static readonly _itemClass = CommissionPayout;\n\n /** All payout batches for an earner, newest first. */\n async findByEarner(earnerId: string): Promise<CommissionPayout[]> {\n return await this.list({\n where: { earnerId },\n orderBy: 'created_at DESC',\n });\n }\n\n /** Payout batches by status, newest first. */\n async findByStatus(\n status: CommissionPayoutStatus,\n ): Promise<CommissionPayout[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /** Look up a payout by its idempotency natural key. */\n async findByIdempotencyKey(\n idempotencyKey: string,\n ): Promise<CommissionPayout | null> {\n if (!idempotencyKey) return null;\n const results = await this.list({ where: { idempotencyKey }, limit: 1 });\n return results[0] ?? null;\n }\n\n /**\n * Σ totalAmountCents of COMPLETED payouts for an earner+currency —\n * lifetime settled earnings (integer cents).\n */\n async sumPaidByEarner(earnerId: string, currency: string): Promise<number> {\n const completed = await this.list({\n where: { earnerId, currency, status: 'completed' },\n });\n return completed.reduce((sum, p) => sum + p.totalAmountCents, 0);\n }\n}\n\nexport default CommissionPayoutCollection;\n","/**\n * Shared types for the neutral commissions financial core.\n *\n * Statuses are string-literal unions derived from `as const` arrays (no TS\n * enums) so downstream code can iterate the legal values and the types stay\n * erasable. All monetary fields across the module are integer cents with a\n * `*Cents` suffix; rates/fractions are decimals in the range 0–1.\n *\n * This module is the neutral financial core of `@happyvertical/smrt-sales`:\n * it never imports from the `crm` or `referrals` modules and never assumes\n * advertising, Referral, Lead, or Opportunity semantics. Earning sources are\n * generic `(sourceKind, sourceId)` string pairs.\n *\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\n\n// ---------------------------------------------------------------------------\n// Status / kind vocabularies\n// ---------------------------------------------------------------------------\n\n/** Lifecycle of an {@link Earner} payout account. */\nexport const EARNER_STATUSES = ['pending', 'active', 'suspended'] as const;\nexport type EarnerStatus = (typeof EARNER_STATUSES)[number];\n\n/**\n * How a payout is delivered. Shared between {@link Earner} (preference) and\n * {@link CommissionPayout} (what a specific batch will use).\n */\nexport const PAYOUT_METHODS = [\n 'bank_transfer',\n 'check',\n 'paypal',\n 'credit',\n 'other',\n] as const;\nexport type PayoutMethod = (typeof PAYOUT_METHODS)[number];\n\n/**\n * Lifecycle of a versioned {@link CommissionPlan} row.\n *\n * `draft → active | retired`; `active → superseded | retired`;\n * `superseded` / `retired` are terminal. Amendments never mutate an active\n * row — they insert a new `(planKey, version + 1)` draft.\n */\nexport const COMMISSION_PLAN_STATUSES = [\n 'draft',\n 'active',\n 'superseded',\n 'retired',\n] as const;\nexport type CommissionPlanStatus = (typeof COMMISSION_PLAN_STATUSES)[number];\n\n/**\n * Lifecycle of a {@link Commission} earning record. STRICT forward chain:\n * `pending → earned → approved → payable → paid` — no skips, no reversals.\n * Corrections to earned/paid commissions are appended as\n * {@link CommissionAdjustment} rows, never edits.\n */\nexport const COMMISSION_STATUSES = [\n 'pending',\n 'earned',\n 'approved',\n 'payable',\n 'paid',\n] as const;\nexport type CommissionStatus = (typeof COMMISSION_STATUSES)[number];\n\n/** How a commission amount is derived from its earning event. */\nexport const COMMISSION_BASES = [\n 'fixed',\n 'gross',\n 'net',\n 'margin',\n 'custom',\n] as const;\nexport type CommissionBasis = (typeof COMMISSION_BASES)[number];\n\n/** Kinds of append-only {@link CommissionAdjustment} corrections. */\nexport const COMMISSION_ADJUSTMENT_KINDS = [\n 'refund',\n 'credit',\n 'chargeback',\n 'dispute',\n 'correction',\n] as const;\nexport type CommissionAdjustmentKind =\n (typeof COMMISSION_ADJUSTMENT_KINDS)[number];\n\n/**\n * Lifecycle of a {@link CommissionPayout} settlement batch.\n *\n * `pending → approved → processing → completed | failed`, with `failed`\n * reachable from `approved`/`processing` and resettable to `pending` only via\n * the dedicated `resetFromFailed()` helper.\n */\nexport const COMMISSION_PAYOUT_STATUSES = [\n 'pending',\n 'approved',\n 'processing',\n 'completed',\n 'failed',\n] as const;\nexport type CommissionPayoutStatus =\n (typeof COMMISSION_PAYOUT_STATUSES)[number];\n\n/**\n * Recommended earning-event kinds. The `EarningEvent.eventKind` field stays an\n * open string so applications can define their own commercial vocabulary —\n * these are the kinds the framework's own modules emit and recognize.\n */\nexport const EARNING_EVENT_KINDS = [\n 'conversion',\n 'agreement_execution',\n 'invoice_payment',\n 'collected_revenue',\n 'recognized_margin',\n 'milestone',\n] as const;\nexport type EarningEventKind = (typeof EARNING_EVENT_KINDS)[number];\n\n/**\n * Commission statuses whose unsettled adjustments count toward an earner's\n * net payable balance (and are gathered into payout batches). Adjustments\n * against a still-`pending` commission stay out of settlement until the\n * underlying earning clears.\n */\nexport const ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES = [\n 'earned',\n 'approved',\n 'payable',\n 'paid',\n] as const satisfies readonly CommissionStatus[];\n\n// ---------------------------------------------------------------------------\n// Plan components\n// ---------------------------------------------------------------------------\n\n/** Recurrence contract for a {@link CommissionPlanComponent}. */\nexport interface CommissionPlanComponentRecurrence {\n /** `one_time` fires at most once per earner+terms; `recurring` repeats. */\n kind: 'one_time' | 'recurring';\n /** Maximum number of occurrences for `recurring` components. */\n maxOccurrences?: number;\n /**\n * Only events whose `occurredAt` falls within `anchorAt + windowMonths`\n * qualify (the anchor — e.g. an agreement's effective date — is supplied by\n * the caller at calculation time).\n */\n windowMonths?: number;\n}\n\n/**\n * One calculation term inside a {@link CommissionPlan}'s `components` JSON\n * array. Each earning event is matched against every component whose\n * `trigger` equals the event's `eventKind` (or `'*'`).\n */\nexport interface CommissionPlanComponent {\n /** Unique key within the plan (stable across versions by convention). */\n key: string;\n /** Earning-event kind this component fires on, or `'*'` for any kind. */\n trigger: string;\n /** How the commission base amount is resolved from the event. */\n basis: CommissionBasis;\n /** Rate in the range 0–1. Required for every basis except `fixed`. */\n rate?: number;\n /** Flat amount in integer cents. Required for basis `fixed`. */\n fixedAmountCents?: number;\n /** Optional recurrence limits; omitted means unlimited. */\n recurrence?: CommissionPlanComponentRecurrence;\n /**\n * For basis `custom`: the key into the earning event's `customBases`\n * JSON map (`basisKey → cents`) that supplies the base amount.\n */\n customBasisKey?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Calculation trace\n// ---------------------------------------------------------------------------\n\n/**\n * Everything needed to reproduce a Commission's `amountCents` from first\n * principles. Persisted as a JSON string on every Commission so amounts stay\n * auditable even after plans are superseded.\n */\nexport interface CommissionCalculationTrace {\n planKey: string;\n planVersion: number;\n componentKey: string;\n basis: CommissionBasis;\n /** Base amount the rate was applied to, in integer cents. */\n baseAmountCents: number;\n /** Rate applied (0–1). Recorded as `0` for `fixed`-basis components. */\n rate: number;\n /** Split share applied (0–1; `1` for unsplit commissions). */\n shareFraction: number;\n /** Zero-based occurrence index within the component's recurrence. */\n occurrenceIndex: number;\n /** Id of the {@link EarningEvent} evidence row. */\n earningEventId: string;\n /** Rounding contract used by `roundCents()`. */\n roundingMode: 'half_away_from_zero';\n}\n\n// ---------------------------------------------------------------------------\n// Balances\n// ---------------------------------------------------------------------------\n\n/**\n * Computed (never stored) per-earner, per-currency balance snapshot.\n * All figures in integer cents.\n */\nexport interface EarnerBalance {\n earnerId: string;\n currency: string;\n /** Σ unsettled `payable` commissions. */\n payableCents: number;\n /** Σ `pending` commissions (still clearing). */\n pendingCents: number;\n /** Σ `earned` commissions (cleared, awaiting approval). */\n earnedCents: number;\n /** Σ `approved` commissions (awaiting payable release). */\n approvedCents: number;\n /**\n * Σ unsettled adjustments whose parent commission is\n * earned/approved/payable/paid (signed — clawbacks are negative).\n */\n unsettledAdjustmentCents: number;\n /** `payableCents + unsettledAdjustmentCents`. May be negative. */\n netPayableCents: number;\n}\n\n// ---------------------------------------------------------------------------\n// Model constructor options\n// ---------------------------------------------------------------------------\n\n/** Options for constructing an {@link Earner}. */\nexport interface EarnerOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n profileId?: string;\n displayName?: string;\n status?: EarnerStatus;\n payoutMethod?: PayoutMethod;\n payoutThresholdCents?: number;\n payoutScheduleKey?: string;\n currency?: string;\n metadata?: string;\n}\n\n/** Options for constructing a {@link CommissionPlan}. */\nexport interface CommissionPlanOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n planKey?: string;\n version?: number;\n name?: string;\n description?: string;\n status?: CommissionPlanStatus;\n effectiveFrom?: Date | string | number | null;\n currency?: string;\n components?: string;\n metadata?: string;\n}\n\n/** Options for constructing an {@link EarningEvent}. */\nexport interface EarningEventOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n eventKind?: string;\n occurredAt?: Date | string | number;\n sourceKind?: string;\n sourceId?: string;\n grossAmountCents?: number;\n netAmountCents?: number | null;\n marginCents?: number | null;\n currency?: string;\n customBases?: string;\n dedupeKey?: string;\n metadata?: string;\n}\n\n/** Options for constructing a {@link Commission}. */\nexport interface CommissionOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n earnerId?: string;\n earningEventId?: string;\n planKey?: string;\n planVersion?: number;\n componentKey?: string;\n termsSnapshotKind?: string;\n termsSnapshotId?: string;\n basis?: CommissionBasis;\n baseAmountCents?: number;\n rate?: number;\n shareFraction?: number;\n splitGroupId?: string;\n amountCents?: number;\n currency?: string;\n status?: CommissionStatus;\n clearingEndsAt?: Date | string | number | null;\n earnedAt?: Date | string | number | null;\n approvedAt?: Date | string | number | null;\n payableAt?: Date | string | number | null;\n paidAt?: Date | string | number | null;\n payoutId?: string;\n sourceKind?: string;\n sourceId?: string;\n calculationTrace?: string;\n dedupeKey?: string;\n metadata?: string;\n}\n\n/** Options for constructing a {@link CommissionAdjustment}. */\nexport interface CommissionAdjustmentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n commissionId?: string;\n earnerId?: string;\n adjustmentKind?: CommissionAdjustmentKind;\n amountCents?: number;\n currency?: string;\n reason?: string;\n createdByProfileId?: string;\n payoutId?: string;\n metadata?: string;\n}\n\n/** Options for constructing a {@link CommissionPayout}. */\nexport interface CommissionPayoutOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n earnerId?: string;\n periodStart?: Date | string | number | null;\n periodEnd?: Date | string | number | null;\n commissionTotalCents?: number;\n adjustmentTotalCents?: number;\n totalAmountCents?: number;\n currency?: string;\n payoutMethod?: PayoutMethod;\n status?: CommissionPayoutStatus;\n paymentReference?: string;\n providerRef?: string;\n paidAt?: Date | string | number | null;\n invoiceId?: string;\n notes?: string;\n idempotencyKey?: string;\n metadata?: string;\n}\n","/**\n * CommissionPlan — versioned commission calculation terms.\n *\n * A plan is identified by its `(planKey, version)` natural key. Versions are\n * ROWS, not edits: amending a plan inserts a `version + 1` draft (see\n * `CommissionPlanCollection.createAmendment`) and the prior version is never\n * rewritten. Once a row has been saved with status `active`, its calculation\n * identity (`components`, `currency`, `planKey`, `version`, `effectiveFrom`)\n * is frozen — re-saving with any of them changed throws (same\n * WeakMap-serialize pattern as commerce's `LicenseSale` rights snapshot).\n * Status transitions remain allowed on frozen rows.\n *\n * Generated write surface is deliberately narrow: `create` only (drafts),\n * NO generated update route — amendments are new rows, and status moves go\n * through the guarded transition methods / legal save-time edges.\n *\n * @packageDocumentation\n */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n COMMISSION_BASES,\n type CommissionPlanComponent,\n type CommissionPlanOptions,\n type CommissionPlanStatus,\n} from '../types.js';\n\n/**\n * Legal status transitions, keyed by the prior persisted status.\n * `draft → active | retired`; `active → superseded | retired`;\n * `superseded` / `retired` are terminal. No-op re-saves and brand-new rows\n * are always permitted; this map governs *changes* to persisted rows only\n * (commerce pattern, S5 audit #1390 lineage).\n */\nconst PLAN_STATUS_TRANSITIONS: Record<\n CommissionPlanStatus,\n CommissionPlanStatus[]\n> = {\n draft: ['active', 'retired'],\n active: ['superseded', 'retired'],\n superseded: [],\n retired: [],\n};\n\n/**\n * Module-scoped record of the status each plan instance was loaded with —\n * fallback for the save-time transition guard when the DB re-read is\n * unavailable. WeakMap keeps it out of the schema and GCs with the instance.\n */\nconst loadedPlanStatus = new WeakMap<CommissionPlan, CommissionPlanStatus>();\n\n/**\n * Module-scoped record of the frozen calculation-identity snapshot captured\n * when a plan row is (or becomes) non-draft. Same rationale as commerce\n * `LicenseSale`: an instance field would become a persisted column, a\n * `Meta<T>` would round-trip and tautologically match — a WeakMap keyed by\n * the instance has no schema interaction and GCs with the instance.\n */\nconst frozenPlanSnapshot = new WeakMap<CommissionPlan, string>();\n\n/**\n * Validate a components array. Throws a descriptive error on the first\n * violation. Exported for reuse by the calculation service's input guards\n * and by referral-terms builders that assemble component arrays.\n *\n * Rules:\n * - component keys are non-empty and unique within the plan\n * - `trigger` is a non-empty string (`'*'` matches every event kind)\n * - `basis` is one of {@link COMMISSION_BASES}\n * - basis `fixed` requires an integer `fixedAmountCents`\n * - every other basis requires `rate` in `[0, 1]`\n * - basis `custom` additionally requires a non-empty `customBasisKey`\n * - `recurrence.kind` (when present) is `one_time` or `recurring`;\n * `maxOccurrences` / `windowMonths` (when present) are positive integers\n */\nexport function validateCommissionPlanComponents(\n components: CommissionPlanComponent[],\n): void {\n if (!Array.isArray(components)) {\n throw new Error('CommissionPlan components must be an array');\n }\n const seen = new Set<string>();\n for (const component of components) {\n const label = component?.key || '<missing key>';\n if (!component || typeof component !== 'object') {\n throw new Error('CommissionPlan component must be an object');\n }\n if (!component.key || typeof component.key !== 'string') {\n throw new Error('CommissionPlan component requires a non-empty key');\n }\n if (seen.has(component.key)) {\n throw new Error(\n `CommissionPlan component keys must be unique — duplicate '${component.key}'`,\n );\n }\n seen.add(component.key);\n if (!component.trigger || typeof component.trigger !== 'string') {\n throw new Error(\n `CommissionPlan component '${label}' requires a non-empty trigger ('*' matches all kinds)`,\n );\n }\n if (!COMMISSION_BASES.includes(component.basis)) {\n throw new Error(\n `CommissionPlan component '${label}' has invalid basis '${component.basis}'`,\n );\n }\n if (component.basis === 'fixed') {\n if (\n typeof component.fixedAmountCents !== 'number' ||\n !Number.isInteger(component.fixedAmountCents)\n ) {\n throw new Error(\n `CommissionPlan component '${label}' with basis 'fixed' requires an integer fixedAmountCents`,\n );\n }\n } else {\n if (\n typeof component.rate !== 'number' ||\n !Number.isFinite(component.rate) ||\n component.rate < 0 ||\n component.rate > 1\n ) {\n throw new Error(\n `CommissionPlan component '${label}' with basis '${component.basis}' requires a rate in [0, 1]`,\n );\n }\n }\n if (component.basis === 'custom' && !component.customBasisKey) {\n throw new Error(\n `CommissionPlan component '${label}' with basis 'custom' requires a customBasisKey`,\n );\n }\n const recurrence = component.recurrence;\n if (recurrence !== undefined) {\n if (recurrence.kind !== 'one_time' && recurrence.kind !== 'recurring') {\n throw new Error(\n `CommissionPlan component '${label}' recurrence.kind must be 'one_time' or 'recurring'`,\n );\n }\n for (const [name, value] of [\n ['maxOccurrences', recurrence.maxOccurrences],\n ['windowMonths', recurrence.windowMonths],\n ] as const) {\n if (\n value !== undefined &&\n (!Number.isInteger(value) || (value as number) <= 0)\n ) {\n throw new Error(\n `CommissionPlan component '${label}' recurrence.${name} must be a positive integer`,\n );\n }\n }\n }\n }\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // (tenantId, planKey, version) is the natural key — a retried create of\n // the same version upserts instead of duplicating, and two tenants can\n // both own a plan key like 'default' without colliding. NULL-tenant\n // (global) rows opt out of upsert dedup on adapters where NULLs compare\n // distinct — the PaymentIntent natural-key convention.\n conflictColumns: ['tenant_id', 'plan_key', 'version'],\n // NO generated update route: amendments are new rows\n // (CommissionPlanCollection.createAmendment) and status transitions go\n // through the guarded methods. The save-time guards below still protect\n // any server-side write path.\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class CommissionPlan extends SmrtObject {\n /** Tenant ID for multi-tenant isolation (nullable → global plans). */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Stable plan identity shared by every version of the plan. */\n @field({ required: true })\n planKey: string = '';\n\n /** Monotonic version within `planKey`. Amendments insert `max + 1`. */\n version: number = 1;\n\n /** Human-readable plan name. */\n name: string = '';\n\n /** Longer human-readable description of the terms. */\n description: string = '';\n\n /**\n * Lifecycle status — see {@link PLAN_STATUS_TRANSITIONS}. Mutate via\n * {@link activate} / {@link supersede} / {@link retire} (or a legal\n * single-step assignment; the save-time guard rejects illegal edges).\n */\n status: CommissionPlanStatus = 'draft';\n\n /** When this version takes effect. Frozen once the plan activates. */\n effectiveFrom: Date | null = null;\n\n /** ISO 4217 currency the plan's terms are denominated in. */\n currency: string = 'USD';\n\n /**\n * Calculation components as a JSON-string array — see\n * {@link CommissionPlanComponent}. Use {@link getComponents} /\n * {@link setComponents} (the setter validates).\n */\n components: string = '[]';\n\n /** Additional metadata as a JSON string. */\n metadata: string = '{}';\n\n constructor(options: CommissionPlanOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.planKey !== undefined) this.planKey = options.planKey;\n if (options.version !== undefined) this.version = options.version;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.status !== undefined) this.status = options.status;\n if (options.effectiveFrom !== undefined)\n this.effectiveFrom = CommissionPlan.coerceDate(options.effectiveFrom);\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.components !== undefined) this.components = options.components;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Re-coerce date fields after the framework reapplies raw option values,\n * record the loaded status for the transition guard, and capture the\n * frozen snapshot when the row arrived already activated. The snapshot is\n * captured for every non-draft status (not just `active`) so a superseded\n * or retired version — history — can't be rewritten either.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.effectiveFrom = CommissionPlan.coerceDate(this.effectiveFrom);\n if (await this.isSaved()) {\n loadedPlanStatus.set(this, this.status);\n if (this.status !== 'draft') {\n frozenPlanSnapshot.set(this, this.serializeFrozenSnapshot());\n }\n }\n return this;\n }\n\n // -------- Status predicates --------\n\n isDraft(): boolean {\n return this.status === 'draft';\n }\n\n isActive(): boolean {\n return this.status === 'active';\n }\n\n // -------- Components / metadata helpers --------\n\n /** Parse {@link components}; returns `[]` on empty/invalid JSON. */\n getComponents(): CommissionPlanComponent[] {\n if (!this.components) return [];\n try {\n const parsed = JSON.parse(this.components) as unknown;\n return Array.isArray(parsed) ? (parsed as CommissionPlanComponent[]) : [];\n } catch {\n return [];\n }\n }\n\n /**\n * Validate and store the components array. Throws on invalid components —\n * see {@link validateCommissionPlanComponents} for the rules.\n */\n setComponents(components: CommissionPlanComponent[]): void {\n validateCommissionPlanComponents(components);\n this.components = JSON.stringify(components);\n }\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n\n // -------- Status transitions --------\n\n /**\n * Transition `draft → active`. Validates components first so no active\n * plan can carry malformed terms.\n */\n activate(): void {\n if (this.status !== 'draft') {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: cannot activate from status '${this.status}'`,\n );\n }\n validateCommissionPlanComponents(this.getComponents());\n this.status = 'active';\n }\n\n /** Transition `active → superseded` (a newer version took over). */\n supersede(): void {\n if (this.status !== 'active') {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: cannot supersede from status '${this.status}'`,\n );\n }\n this.status = 'superseded';\n }\n\n /** Transition `draft | active → retired` (terminal). */\n retire(): void {\n if (this.status !== 'draft' && this.status !== 'active') {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: cannot retire from status '${this.status}'`,\n );\n }\n this.status = 'retired';\n }\n\n // -------- Save-time guards --------\n\n /**\n * Save with two guards:\n *\n * 1. **Status transition** — the about-to-be-written status must be a\n * legal edge from the authoritative prior persisted status (re-read\n * from the DB so a `create({ id: <existing>, _skipLoad: true })` upsert\n * can't sidestep the guard — commerce pattern).\n * 2. **Frozen calculation identity** — once the row has been saved\n * non-draft, `components` / `currency` / `planKey` / `version` /\n * `effectiveFrom` must match the captured snapshot. Amend by inserting\n * a new version instead.\n *\n * Activating saves also re-validate components, so an `active` row always\n * carries well-formed terms regardless of which write path set them.\n */\n override async save(): Promise<this> {\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n this.assertFrozenIdentityUnchanged();\n await this.assertNaturalKeyNotTaken();\n if (this.status === 'active') {\n validateCommissionPlanComponents(this.getComponents());\n }\n const result = (await super.save()) as this;\n loadedPlanStatus.set(this, this.status);\n if (this.status !== 'draft' && !frozenPlanSnapshot.has(this)) {\n frozenPlanSnapshot.set(this, this.serializeFrozenSnapshot());\n }\n return result;\n }\n\n /**\n * Refuse a save whose `(tenantId, planKey, version)` natural key already\n * belongs to a DIFFERENT row. The frozen-identity guard above is\n * instance-local (WeakMap), so a FRESH instance carrying an existing\n * natural key would otherwise sail through and the conflict-column\n * upsert would rewrite the persisted terms (and rotate the row id).\n * Edit drafts by hydrating them; change terms with\n * `CommissionPlanCollection.createAmendment()`.\n */\n private async assertNaturalKeyNotTaken(): Promise<void> {\n if (!this.planKey) return;\n try {\n const res = await this.db.query(\n `SELECT id, tenant_id FROM ${this.tableName} WHERE plan_key = $1 AND version = $2`,\n this.planKey,\n this.version,\n );\n const rows = Array.isArray(res)\n ? (res as Record<string, unknown>[])\n : ((res as { rows?: Record<string, unknown>[] }).rows ?? []);\n const taken = rows.find(\n (row) =>\n (row.tenant_id ?? null) === (this.tenantId ?? null) &&\n row.id !== this.id,\n );\n if (taken) {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: this version ` +\n 'already exists for the tenant — plan versions are immutable ' +\n 'records. Hydrate the existing row to edit a draft, or create ' +\n 'new terms with CommissionPlanCollection.createAmendment().',\n );\n }\n } catch (error) {\n if (error instanceof Error && error.message.includes('immutable')) {\n throw error;\n }\n // DB not ready / table absent — nothing persisted to collide with.\n }\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status from the database; fall back to\n * the loaded-status WeakMap only when the DB is unavailable. `undefined`\n * means no persisted row exists (genuinely new).\n */\n private async resolvePriorStatus(): Promise<\n CommissionPlanStatus | undefined\n > {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as CommissionPlanStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedPlanStatus.get(this);\n }\n\n private assertStatusTransition(\n prior: CommissionPlanStatus | undefined,\n ): void {\n if (prior === undefined) return; // new row — any starting status\n if (prior === this.status) return; // no-op re-save\n const allowed = PLAN_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: illegal status ` +\n `transition '${prior}' → '${this.status}'. Use activate() / ` +\n 'supersede() / retire().',\n );\n }\n }\n\n private assertFrozenIdentityUnchanged(): void {\n const captured = frozenPlanSnapshot.get(this);\n if (!captured) return;\n const current = this.serializeFrozenSnapshot();\n if (captured !== current) {\n throw new Error(\n `CommissionPlan ${this.planKey}@${this.version}: components, ` +\n 'currency, planKey, version, and effectiveFrom are immutable once ' +\n 'the plan has been active. Create an amendment ' +\n '(CommissionPlanCollection.createAmendment) instead of editing ' +\n 'this version.',\n );\n }\n }\n\n private serializeFrozenSnapshot(): string {\n // Stable key ordering so a no-op re-serialization matches.\n return JSON.stringify({\n planKey: this.planKey,\n version: this.version,\n currency: this.currency,\n components: this.components,\n effectiveFrom: this.effectiveFrom\n ? this.effectiveFrom.toISOString()\n : null,\n });\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default CommissionPlan;\n","/**\n * CommissionPlanCollection — collection manager for {@link CommissionPlan}.\n *\n * Plans are versioned rows: amendments insert `(planKey, maxVersion + 1)`\n * drafts via {@link createAmendment}; existing versions are never rewritten.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n CommissionPlan,\n validateCommissionPlanComponents,\n} from '../models/CommissionPlan.js';\nimport type {\n CommissionPlanComponent,\n CommissionPlanStatus,\n} from '../types.js';\n\n/**\n * Fields an amendment may change relative to the version it copies.\n * `planKey` is fixed (it identifies the plan), `version` is computed, and\n * `status` is always `draft` — a caller cannot mint a pre-activated\n * amendment.\n */\nexport interface CommissionPlanAmendmentChanges {\n name?: string;\n description?: string;\n components?: CommissionPlanComponent[];\n currency?: string;\n effectiveFrom?: Date | null;\n metadata?: Record<string, unknown>;\n}\n\nexport class CommissionPlanCollection extends SmrtCollection<CommissionPlan> {\n static readonly _itemClass = CommissionPlan;\n\n /** Every version of a plan, newest version first. */\n async findByPlanKey(planKey: string): Promise<CommissionPlan[]> {\n return await this.list({\n where: { planKey },\n orderBy: 'version DESC',\n });\n }\n\n /** Plans by status. */\n async findByStatus(status: CommissionPlanStatus): Promise<CommissionPlan[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * The highest ACTIVE version of a plan already IN EFFECT at `at`, or\n * `null` when none is. This is what calculation callers resolve terms\n * from when no frozen snapshot pins a specific version. A future-dated\n * amendment can be activated ahead of its effective date without\n * governing earlier qualifications (`effectiveFrom: null` = effective\n * immediately).\n */\n async latestActiveByKey(\n planKey: string,\n at: Date = new Date(),\n tenantId?: string | null,\n ): Promise<CommissionPlan | null> {\n const results = await this.list({\n where: { planKey, status: 'active' },\n orderBy: 'version DESC',\n });\n const inEffect = results.filter(\n (plan) => plan.effectiveFrom === null || plan.effectiveFrom <= at,\n );\n if (tenantId === undefined) {\n // No explicit scope: ambient tenant scoping (when present) applies.\n return inEffect[0] ?? null;\n }\n // Explicit scope (system/background paths run without ambient tenant\n // context): the tenant's own versions form their own key-space; global\n // (NULL-tenant) versions are the fallback. Never another tenant's.\n return (\n inEffect.find((plan) => plan.tenantId === tenantId) ??\n inEffect.find((plan) => plan.tenantId === null) ??\n null\n );\n }\n\n /**\n * Create an amendment: insert a new DRAFT row with\n * `version = max(existing versions) + 1`, copying the latest existing\n * version's fields and then applying `changes`. The source version is not\n * touched — activate the draft (and supersede the prior active version)\n * as a separate, explicit step.\n *\n * Throws when no version of `planKey` exists (nothing to amend — use\n * `create` for a brand-new plan).\n */\n async createAmendment(\n planKey: string,\n changes: CommissionPlanAmendmentChanges = {},\n ): Promise<CommissionPlan> {\n const versions = await this.findByPlanKey(planKey);\n const latest = versions[0];\n if (!latest) {\n throw new Error(\n `CommissionPlanCollection.createAmendment: no versions exist for plan key '${planKey}' — create the plan first`,\n );\n }\n // Validate amended components BEFORE persisting anything, so a bad\n // amendment fails cleanly instead of leaving a malformed draft row.\n if (changes.components !== undefined) {\n validateCommissionPlanComponents(changes.components);\n }\n\n const draft = await this.create({\n tenantId: latest.tenantId,\n planKey,\n version: latest.version + 1,\n status: 'draft',\n name: changes.name ?? latest.name,\n description: changes.description ?? latest.description,\n currency: changes.currency ?? latest.currency,\n effectiveFrom:\n changes.effectiveFrom !== undefined\n ? changes.effectiveFrom\n : latest.effectiveFrom,\n components:\n changes.components !== undefined\n ? JSON.stringify(changes.components)\n : latest.components,\n metadata:\n changes.metadata !== undefined\n ? JSON.stringify(changes.metadata)\n : latest.metadata,\n });\n return draft;\n }\n}\n\nexport default CommissionPlanCollection;\n","/**\n * Earner — neutral financial payout account.\n *\n * Replaces the financial half of legacy smrt-affiliates' `Partner`: it holds\n * everything money-related about a party that earns commissions (payout\n * method, threshold, schedule, currency, status) and NOTHING role-related.\n * Role models (a CRM `SalesRepresentative`, a referrals `Referrer`, or any\n * application-defined role) each hold their own `earnerId` pointing here, so\n * one person acting in several roles still settles through a single account.\n *\n * @packageDocumentation\n */\n\nimport { crossPackageRef, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { EarnerOptions, EarnerStatus, PayoutMethod } from '../types.js';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class Earner extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation. Nullable so global/operator-level\n * earners remain possible; unlike legacy affiliates, sales earners are\n * tenant-owned by default.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Identity link to a smrt-profiles Profile (cross-package string\n * reference — never a DDL foreign key).\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n profileId: string = '';\n\n /** Human-readable display name for portals and operator views. */\n displayName: string = '';\n\n /** Account lifecycle: `pending` (default) → `active` / `suspended`. */\n status: EarnerStatus = 'pending';\n\n /** Preferred payout delivery method. */\n payoutMethod: PayoutMethod = 'bank_transfer';\n\n /**\n * Minimum unsettled balance (integer cents) before a payout batch is\n * created. Default $50.00 = 5000 cents.\n */\n payoutThresholdCents: number = 5000;\n\n /**\n * Payout cadence key. Open string so applications can define their own\n * schedules (`manual`, `monthly`, `weekly`, `net_30`, …); `manual` means\n * an operator triggers batches explicitly.\n */\n payoutScheduleKey: string = 'manual';\n\n /** ISO 4217 currency all of this earner's balances settle in. */\n currency: string = 'USD';\n\n /**\n * Additional metadata as a JSON string (tax info, payout-rail details,\n * …). Use {@link getMetadata}/{@link setMetadata}.\n */\n metadata: string = '{}';\n\n constructor(options: EarnerOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.profileId !== undefined) this.profileId = options.profileId;\n if (options.displayName !== undefined)\n this.displayName = options.displayName;\n if (options.status !== undefined) this.status = options.status;\n if (options.payoutMethod !== undefined)\n this.payoutMethod = options.payoutMethod;\n if (options.payoutThresholdCents !== undefined)\n this.payoutThresholdCents = options.payoutThresholdCents;\n if (options.payoutScheduleKey !== undefined)\n this.payoutScheduleKey = options.payoutScheduleKey;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n isActive(): boolean {\n return this.status === 'active';\n }\n\n isPending(): boolean {\n return this.status === 'pending';\n }\n\n isSuspended(): boolean {\n return this.status === 'suspended';\n }\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n}\n\nexport default Earner;\n","/**\n * EarnerCollection — collection manager for {@link Earner}.\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Earner } from '../models/Earner.js';\nimport type { EarnerStatus } from '../types.js';\n\nexport class EarnerCollection extends SmrtCollection<Earner> {\n static readonly _itemClass = Earner;\n\n /** Earners linked to a smrt-profiles Profile. */\n async findByProfile(profileId: string): Promise<Earner[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /** Earners by status. */\n async findByStatus(status: EarnerStatus): Promise<Earner[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /** All active earners. */\n async findActive(): Promise<Earner[]> {\n return await this.findByStatus('active');\n }\n}\n\nexport default EarnerCollection;\n","/**\n * EarningEvent — immutable commercial-event evidence.\n *\n * An EarningEvent records that something commission-worthy happened: a\n * conversion, an agreement execution, an invoice payment, collected revenue,\n * recognized margin, a milestone — or any application-defined kind\n * (`eventKind` is an open string; see `EARNING_EVENT_KINDS` for the\n * recommended vocabulary). The source is a generic `(sourceKind, sourceId)`\n * string pair — this module never assumes advertising, Referral, Lead, or\n * Opportunity semantics.\n *\n * Immutability contract: events are evidence. The generated surface exposes\n * `create`/`list`/`get` only (no update, no delete), and application code\n * must treat persisted rows as append-only — commissions reference an\n * event's amounts in their calculation traces, so rewriting an event would\n * silently orphan the audit trail. Corrections are modelled as NEW events\n * (e.g. a `refund`-kind event) or as `CommissionAdjustment` rows.\n *\n * Idempotent ingestion: `dedupeKey` is the natural key\n * (`conflictColumns: ['dedupe_key']`). Callers embed tenant and source\n * identity in the key — e.g.\n * `` `${tenantId}:${sourceKind}:${sourceId}:${eventKind}:${occurrence}` `` —\n * so a retried ingest resolves to the existing row (see\n * `EarningEventCollection.getOrCreateByDedupeKey`).\n *\n * @packageDocumentation\n */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { EarningEventOptions } from '../types.js';\n\n/**\n * Serialized state of each persisted instance, for the save-time\n * immutability guard (WeakMap keeps it out of the schema and GCs with the\n * instance — the ReferralTermSnapshot pattern).\n */\nconst persistedEventState = new WeakMap<EarningEvent, string>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // Natural key for idempotent ingestion — a retried create with the same\n // dedupeKey upserts instead of duplicating.\n conflictColumns: ['dedupe_key'],\n // Immutable evidence: create/list/get only — no update or delete on any\n // generated surface.\n api: { include: ['create', 'list', 'get'] },\n mcp: { include: ['list', 'create'] },\n // High-volume evidence rows are not useful from the CLI.\n cli: false,\n})\nexport class EarningEvent extends SmrtObject {\n /** Tenant ID for multi-tenant isolation (nullable → global events). */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * What kind of commercial event this is. Open string — see\n * `EARNING_EVENT_KINDS` for the recommended vocabulary. Plan components\n * match on this via their `trigger`.\n */\n @field({ required: true })\n eventKind: string = '';\n\n /** When the commercial event occurred (not when it was ingested). */\n occurredAt: Date = new Date();\n\n /**\n * Generic earning-source discriminator (`referral`, `opportunity`,\n * `subscription`, `ad_event`, …). Free-form; this module attaches no\n * semantics to it.\n */\n sourceKind: string = '';\n\n /** Id of the source record named by {@link sourceKind}. */\n sourceId: string = '';\n\n /** Gross amount of the event in integer cents. */\n grossAmountCents: number = 0;\n\n /**\n * Net amount in integer cents, when the ingesting system defines one.\n * `null` means \"net is not defined for this event\" — `net`-basis\n * components then SKIP rather than falling back to gross (net is never\n * derived).\n */\n @field({ type: 'integer', nullable: true })\n netAmountCents: number | null = null;\n\n /**\n * Recognized margin in integer cents, when defined. `null` skips\n * `margin`-basis components — margin is never derived.\n */\n @field({ type: 'integer', nullable: true })\n marginCents: number | null = null;\n\n /** ISO 4217 currency of the event's amounts. */\n currency: string = 'USD';\n\n /**\n * JSON map of `basisKey → integer cents` for `custom`-basis plan\n * components. Use {@link getCustomBases}/{@link setCustomBases}.\n */\n customBases: string = '{}';\n\n /**\n * Idempotency natural key. Required. Callers embed tenant/source identity\n * (see the class doc) — the framework does not synthesize it.\n */\n @field({ required: true })\n dedupeKey: string = '';\n\n /** Additional metadata as a JSON string. */\n metadata: string = '{}';\n\n constructor(options: EarningEventOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.eventKind !== undefined) this.eventKind = options.eventKind;\n if (options.occurredAt !== undefined)\n this.occurredAt =\n EarningEvent.coerceDate(options.occurredAt) ?? new Date();\n if (options.sourceKind !== undefined) this.sourceKind = options.sourceKind;\n if (options.sourceId !== undefined) this.sourceId = options.sourceId;\n if (options.grossAmountCents !== undefined)\n this.grossAmountCents = options.grossAmountCents;\n if (options.netAmountCents !== undefined)\n this.netAmountCents = options.netAmountCents;\n if (options.marginCents !== undefined)\n this.marginCents = options.marginCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.customBases !== undefined)\n this.customBases = options.customBases;\n if (options.dedupeKey !== undefined) this.dedupeKey = options.dedupeKey;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Re-coerce {@link occurredAt} after the framework reapplies raw option /\n * hydrated row values (SQLite hands back ISO strings), and capture the\n * persisted state for the immutability guard when this instance hydrated\n * an existing row.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n this.occurredAt = EarningEvent.coerceDate(this.occurredAt) ?? new Date();\n if (await this.isSaved()) {\n persistedEventState.set(this, this.serializeState());\n }\n return this;\n }\n\n /**\n * Save with the evidence-immutability guard. EarningEvents are immutable\n * commercial evidence; three write vectors are closed:\n *\n * - a HYDRATED persisted row must serialize identically to its captured\n * state (no-op re-saves pass, any change throws);\n * - an instance carrying an existing id WITHOUT having hydrated it\n * (`create({ id, _skipLoad: true })`) is rejected outright;\n * - a NEW instance whose `dedupeKey` already belongs to another row is\n * refused outright: the natural-key upsert would not only rewrite the\n * evidence values but ROTATE the row's id (orphaning any Commission\n * whose `earningEventId` points at it). Idempotent ingestion goes\n * through `EarningEventCollection.getOrCreateByDedupeKey()`, which\n * finds first and never upserts.\n */\n override async save(): Promise<this> {\n const captured = persistedEventState.get(this);\n if (captured !== undefined) {\n if (captured !== this.serializeState()) {\n throw new Error(\n `EarningEvent ${this.id ?? '<new>'}: earning events are immutable ` +\n 'evidence — record a correcting event (or a CommissionAdjustment ' +\n 'downstream) instead of editing this row.',\n );\n }\n } else if (this.id && (await this.isSaved())) {\n throw new Error(\n `EarningEvent ${this.id}: refusing to overwrite an existing event ` +\n 'row from a non-hydrated instance — earning events are immutable ' +\n 'evidence.',\n );\n } else if (this.dedupeKey) {\n // Fresh instance (create() pre-assigns an id, so key off \"no captured\n // state and not a persisted id\" rather than a missing id): its\n // natural key may collide with existing evidence via the upsert.\n try {\n const row = await this.db.get(this.tableName, {\n dedupe_key: this.dedupeKey,\n });\n if (row && row.id !== this.id) {\n throw new Error(\n `EarningEvent (dedupeKey '${this.dedupeKey}'): an event with ` +\n 'this dedupe key already exists — earning events are ' +\n 'immutable evidence, and the natural-key upsert would rotate ' +\n \"the existing row's id (orphaning commissions that reference \" +\n 'it). Use EarningEventCollection.getOrCreateByDedupeKey() ' +\n 'for idempotent ingestion, or record a new event under its ' +\n 'own dedupe key.',\n );\n }\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('immutable evidence')\n ) {\n throw error;\n }\n // DB not ready / table absent — nothing persisted to protect yet.\n }\n }\n const result = (await super.save()) as this;\n persistedEventState.set(this, this.serializeState());\n return result;\n }\n\n private serializeState(): string {\n // Stable key ordering so a no-op re-serialization matches.\n return JSON.stringify({\n tenantId: this.tenantId,\n eventKind: this.eventKind,\n occurredAt: this.occurredAt.toISOString(),\n sourceKind: this.sourceKind,\n sourceId: this.sourceId,\n grossAmountCents: this.grossAmountCents,\n netAmountCents: this.netAmountCents,\n marginCents: this.marginCents,\n currency: this.currency,\n customBases: this.customBases,\n dedupeKey: this.dedupeKey,\n metadata: this.metadata,\n });\n }\n\n /**\n * Parse {@link customBases} into a `basisKey → cents` map; non-numeric\n * values are dropped. Returns `{}` on empty/invalid JSON.\n */\n getCustomBases(): Record<string, number> {\n if (!this.customBases) return {};\n try {\n const parsed = JSON.parse(this.customBases) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return {};\n }\n const out: Record<string, number> = {};\n for (const [key, value] of Object.entries(\n parsed as Record<string, unknown>,\n )) {\n if (typeof value === 'number' && Number.isFinite(value)) {\n out[key] = value;\n }\n }\n return out;\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link customBases}. */\n setCustomBases(bases: Record<string, number>): void {\n this.customBases = JSON.stringify(bases ?? {});\n }\n\n /** Parse {@link metadata}; returns `{}` on empty/invalid JSON. */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize and store {@link metadata}. */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data ?? {});\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n }\n}\n\nexport default EarningEvent;\n","/**\n * EarningEventCollection — collection manager for {@link EarningEvent}.\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { EarningEvent } from '../models/EarningEvent.js';\nimport type { EarningEventOptions } from '../types.js';\n\nexport class EarningEventCollection extends SmrtCollection<EarningEvent> {\n static readonly _itemClass = EarningEvent;\n\n /** Look up an event by its idempotency natural key. */\n async findByDedupeKey(dedupeKey: string): Promise<EarningEvent | null> {\n if (!dedupeKey) return null;\n const results = await this.list({ where: { dedupeKey }, limit: 1 });\n return results[0] ?? null;\n }\n\n /**\n * Idempotent ingestion: if an event with `options.dedupeKey` already\n * exists, return it untouched (`created: false`) — evidence is immutable,\n * so a replay never updates the stored row. Otherwise create the event.\n *\n * `dedupeKey` is required — callers embed tenant/source identity in it\n * (e.g. `` `${tenantId}:${sourceKind}:${sourceId}:${eventKind}` ``);\n * an empty key would silently disable idempotency, so it throws instead.\n */\n async getOrCreateByDedupeKey(\n options: EarningEventOptions,\n ): Promise<{ event: EarningEvent; created: boolean }> {\n const dedupeKey = options.dedupeKey ?? '';\n if (!dedupeKey) {\n throw new Error(\n 'EarningEventCollection.getOrCreateByDedupeKey requires a dedupeKey',\n );\n }\n const existing = await this.findByDedupeKey(dedupeKey);\n if (existing) {\n return { event: existing, created: false };\n }\n // Coerce the Date-ish option here so the create input is a real Date\n // (the model would coerce at initialize anyway; this keeps types exact).\n const { occurredAt, ...rest } = options;\n const event = await this.create({\n ...rest,\n ...(occurredAt !== undefined ? { occurredAt: new Date(occurredAt) } : {}),\n });\n return { event, created: true };\n }\n\n /** Events for one generic earning source, newest occurrence first. */\n async findBySource(\n sourceKind: string,\n sourceId: string,\n ): Promise<EarningEvent[]> {\n return await this.list({\n where: { sourceKind, sourceId },\n orderBy: 'occurred_at DESC',\n });\n }\n\n /** Events by kind, newest occurrence first. */\n async findByKind(eventKind: string): Promise<EarningEvent[]> {\n return await this.list({\n where: { eventKind },\n orderBy: 'occurred_at DESC',\n });\n }\n}\n\nexport default EarningEventCollection;\n","/**\n * Integer-cents money helpers for the commissions module.\n *\n * Every monetary field in this module is stored as integer cents (`*Cents`\n * suffix). Rounding happens exactly once per calculation step via\n * {@link roundCents} using half-away-from-zero semantics, and every\n * Commission persists a `calculationTrace` naming that rounding mode so\n * amounts stay reproducible.\n *\n * @packageDocumentation\n */\n\n/**\n * Round to the nearest integer cent, half away from zero.\n *\n * `Math.round` alone rounds -2.5 to -2 (half toward +∞); financial\n * conventions want symmetric behaviour, so the sign is factored out first:\n * `Math.sign(v) * Math.round(Math.abs(v))` → `roundCents(2.5) === 3` and\n * `roundCents(-2.5) === -3`.\n */\nexport function roundCents(value: number): number {\n const rounded = Math.sign(value) * Math.round(Math.abs(value));\n // Normalize -0 to 0 so strict equality (Object.is) comparisons behave.\n return rounded === 0 ? 0 : rounded;\n}\n\n/** Convert integer cents to a decimal major-unit amount (`/ 100`). */\nexport function centsToAmount(cents: number): number {\n return cents / 100;\n}\n\n/**\n * Convert a decimal major-unit amount to integer cents, rounding half away\n * from zero (`roundCents(amount * 100)`).\n */\nexport function amountToCents(amount: number): number {\n return roundCents(amount * 100);\n}\n\n/**\n * Calculate a commission amount in integer cents:\n * `roundCents(baseCents * rate * (shareFraction ?? 1))`.\n *\n * Rounding is applied once, on the final product, so split siblings each\n * round independently and their sum can differ from the unsplit amount by at\n * most one cent per sibling — the calculation trace records the inputs so any\n * such drift is auditable.\n *\n * @param baseCents - Base amount in integer cents\n * @param rate - Commission rate (0–1)\n * @param shareFraction - Optional split share (0–1); defaults to 1\n */\nexport function calculateCommissionAmountCents(\n baseCents: number,\n rate: number,\n shareFraction?: number,\n): number {\n return roundCents(baseCents * rate * (shareFraction ?? 1));\n}\n","/**\n * CommissionBalanceService — computed (never stored) per-earner balances.\n *\n * `payableCents` is the sum of unsettled `payable` commissions;\n * `unsettledAdjustmentCents` is the signed sum of unsettled adjustments\n * whose parent commission is earned/approved/payable/paid (adjustments\n * against still-`pending` commissions wait for the earning to clear);\n * `netPayableCents = payableCents + unsettledAdjustmentCents` and can go\n * NEGATIVE when clawbacks against already-paid commissions exceed what is\n * currently payable. The pending/earned/approved breakdowns feed portal\n * views.\n *\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CommissionAdjustmentCollection } from '../collections/CommissionAdjustmentCollection.js';\nimport { CommissionCollection } from '../collections/CommissionCollection.js';\nimport type { Commission } from '../models/Commission.js';\nimport {\n ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES,\n type CommissionStatus,\n type EarnerBalance,\n} from '../types.js';\n\nexport class CommissionBalanceService {\n constructor(\n private readonly commissions: CommissionCollection,\n private readonly adjustments: CommissionAdjustmentCollection,\n ) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CommissionBalanceService> {\n return new CommissionBalanceService(\n await CommissionCollection.create(classOptions),\n await CommissionAdjustmentCollection.create(classOptions),\n );\n }\n\n /** Compute the {@link EarnerBalance} for one earner in one currency. */\n async getBalance(earnerId: string, currency: string): Promise<EarnerBalance> {\n const rows = await this.commissions.list({\n where: { earnerId, currency },\n });\n\n const sumByStatus = (status: CommissionStatus): number =>\n rows\n .filter((c: Commission) => c.status === status)\n .reduce((sum: number, c: Commission) => sum + c.amountCents, 0);\n\n const pendingCents = sumByStatus('pending');\n const earnedCents = sumByStatus('earned');\n const approvedCents = sumByStatus('approved');\n const payableCents = rows\n .filter((c: Commission) => c.status === 'payable' && !c.payoutId)\n .reduce((sum: number, c: Commission) => sum + c.amountCents, 0);\n\n const statusById = new Map<string, CommissionStatus>();\n for (const c of rows) {\n if (c.id) statusById.set(c.id, c.status);\n }\n\n const unsettled = await this.adjustments.findUnsettledByEarner(\n earnerId,\n currency,\n );\n let unsettledAdjustmentCents = 0;\n for (const adjustment of unsettled) {\n const parentStatus = statusById.get(adjustment.commissionId);\n if (\n parentStatus !== undefined &&\n (\n ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES as readonly CommissionStatus[]\n ).includes(parentStatus)\n ) {\n unsettledAdjustmentCents += adjustment.amountCents;\n }\n }\n\n return {\n earnerId,\n currency,\n payableCents,\n pendingCents,\n earnedCents,\n approvedCents,\n unsettledAdjustmentCents,\n netPayableCents: payableCents + unsettledAdjustmentCents,\n };\n }\n}\n\nexport default CommissionBalanceService;\n","/**\n * CommissionCalculationService — turns one {@link EarningEvent} into\n * Commission rows for one earner, driven by a set of plan components.\n *\n * The service deliberately takes COMPONENTS (plus `planKey`/`planVersion`\n * snapshot refs), not a live plan: callers that calculate from frozen terms\n * — e.g. the referrals module's term snapshots — pass the components they\n * froze, so a later plan amendment can never leak into an already-agreed\n * calculation.\n *\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CommissionCollection } from '../collections/CommissionCollection.js';\nimport { EarnerCollection } from '../collections/EarnerCollection.js';\nimport type { Commission } from '../models/Commission.js';\nimport { validateCommissionPlanComponents } from '../models/CommissionPlan.js';\nimport type { EarningEvent } from '../models/EarningEvent.js';\nimport { calculateCommissionAmountCents, roundCents } from '../money.js';\nimport type {\n CommissionCalculationTrace,\n CommissionPlanComponent,\n} from '../types.js';\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/** Input for {@link CommissionCalculationService.calculateForEvent}. */\nexport interface CommissionCalculationInput {\n /** The (persisted) earning event to calculate from. */\n event: EarningEvent;\n /** Snapshot reference recorded on every created Commission. */\n planKey: string;\n /** Snapshot reference recorded on every created Commission. */\n planVersion: number;\n /** The calculation terms to apply (typically from a frozen snapshot). */\n components: CommissionPlanComponent[];\n /** The earner the commissions belong to. */\n earnerId: string;\n /**\n * Split share (0–1) this earner receives; defaults to 1. Callers running\n * a split invoke the service once per earner with the shares and a shared\n * `splitGroupId`.\n */\n shareFraction?: number;\n /** Groups the sibling commissions of one split. */\n splitGroupId?: string;\n /** Generic polymorphic terms-snapshot reference (kind). */\n termsSnapshotKind?: string;\n /** Generic polymorphic terms-snapshot reference (id). */\n termsSnapshotId?: string;\n /**\n * Clearing window in days: created commissions get\n * `clearingEndsAt = event.occurredAt + clearingDays`. Omitted → no\n * clearing (`clearingEndsAt: null`, immediately sweepable).\n */\n clearingDays?: number;\n /**\n * Currency the plan/terms are denominated in. When provided and different\n * from `event.currency`, every matching component skips with reason\n * `'currency_mismatch'` (this module performs no FX). Omitted → the event\n * currency is taken as authoritative and no mismatch is possible.\n */\n currency?: string;\n /**\n * Resolves how many occurrences of a component this earner has already\n * consumed under these terms (commissions from PRIOR events — the current\n * event must not be counted). Drives `one_time` / `maxOccurrences` limits\n * and the `occurrenceIndex` in the dedupe key and trace. Omitted →\n * occurrence count `0`, i.e. recurrence limits are NOT enforced.\n */\n occurrenceCountResolver?: (componentKey: string) => Promise<number>;\n /**\n * Anchor for `windowMonths` recurrence checks (e.g. an agreement's\n * effective date). Omitted → window checks are skipped.\n */\n anchorAt?: Date;\n}\n\n/** One component the calculation declined, and why. */\nexport interface CommissionComponentSkip {\n componentKey: string;\n /**\n * `'net_basis_undefined'` | `'margin_basis_undefined'` |\n * `'fixed_amount_missing'` | `'rate_missing'` | `'custom_basis_missing'`\n * | `'currency_mismatch'` | `'occurrence_limit_reached'` |\n * `'outside_recurrence_window'`\n */\n reason: string;\n}\n\n/** Result of {@link CommissionCalculationService.calculateForEvent}. */\nexport interface CommissionCalculationResult {\n /** Commissions newly created by THIS call. */\n created: Commission[];\n /** Components that produced nothing, with reasons. */\n skipped: CommissionComponentSkip[];\n /**\n * Idempotent replays: commissions that already existed for this\n * (event, terms, component, earner) tuple. Never re-created, never\n * mutated, and never in `created`.\n */\n existing: Commission[];\n}\n\nexport class CommissionCalculationService {\n constructor(\n private readonly commissions: CommissionCollection,\n /**\n * Optional earner lookup for the tenant-lane guard. When provided,\n * `calculateForEvent` refuses an earner from a different tenant lane\n * than the event (a cross-tenant `earnerId` would create a commission\n * payable to another tenant's account). `static create()` always wires\n * it; direct constructors may omit it for narrow test fixtures.\n */\n private readonly earners?: EarnerCollection,\n ) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CommissionCalculationService> {\n return new CommissionCalculationService(\n await CommissionCollection.create(classOptions),\n await EarnerCollection.create(classOptions),\n );\n }\n\n /**\n * Calculate commissions for one event × one earner × a component set.\n *\n * For each component whose `trigger` matches `event.eventKind` (or `'*'`\n * — non-matching components are silently filtered, not \"skipped\"):\n *\n * 1. **Idempotency** — if a Commission already exists for this\n * (event, terms, component, earner) tuple, it is returned in\n * `existing` and nothing else runs for the component.\n * 2. **Currency** — `input.currency` (when given) must equal the event's;\n * otherwise skip `'currency_mismatch'`.\n * 3. **Recurrence** — `one_time` components skip\n * `'occurrence_limit_reached'` once the resolver reports ≥ 1 prior\n * occurrence; `recurring` components honor `maxOccurrences` and\n * `windowMonths` (events after `anchorAt + windowMonths` skip\n * `'outside_recurrence_window'`).\n * 4. **Basis** — gross → `grossAmountCents`; net → `netAmountCents`\n * (skip `'net_basis_undefined'` when null — net is explicit, NEVER\n * derived from gross); margin → `marginCents` (skip\n * `'margin_basis_undefined'` when null); fixed → `fixedAmountCents`;\n * custom → `getCustomBases()[customBasisKey]` (skip\n * `'custom_basis_missing'`).\n * 5. **Amount** — `roundCents(base * rate * shareFraction)`; for `fixed`,\n * `roundCents(fixedAmountCents * shareFraction)` with `rate` recorded\n * as `0`. Rounding happens exactly once, on the final product.\n *\n * Every created Commission is persisted `pending`, carries the event's\n * tenant/currency/source, a complete {@link CommissionCalculationTrace},\n * `clearingEndsAt` when `clearingDays` was given, and the dedupe key\n * `` `${event.dedupeKey}:${termsSnapshotId || planKey + '@' + planVersion}:${componentKey}:${earnerId}:${occurrenceIndex}` ``.\n */\n async calculateForEvent(\n input: CommissionCalculationInput,\n ): Promise<CommissionCalculationResult> {\n const { event } = input;\n if (!event.id) {\n throw new Error(\n 'CommissionCalculationService.calculateForEvent requires a persisted event (missing id)',\n );\n }\n if (!input.earnerId) {\n throw new Error(\n 'CommissionCalculationService.calculateForEvent requires an earnerId',\n );\n }\n\n const shareFraction = input.shareFraction ?? 1;\n // Money guard: an out-of-range or non-finite fraction would persist\n // overpayment, negative, or NaN commissions — reject before any\n // component calculates.\n if (\n !Number.isFinite(shareFraction) ||\n shareFraction < 0 ||\n shareFraction > 1\n ) {\n throw new Error(\n `CommissionCalculationService.calculateForEvent: shareFraction must be a finite number in [0, 1], got ${String(shareFraction)}`,\n );\n }\n // Money guard: direct callers can hand in arbitrary component arrays\n // (snapshots and plans validate on write, but nothing forces callers\n // through them) — malformed rates/amounts must never reach the math.\n validateCommissionPlanComponents(input.components);\n\n // Tenant-lane guard: an earner from a different lane than the event\n // would receive a commission payable to another tenant's account\n // (reachable via any surface that lets an earnerId be assigned).\n if (this.earners) {\n const earner = await this.earners.get({ id: input.earnerId });\n if (\n earner &&\n earner.tenantId !== null &&\n (event.tenantId ?? null) !== null &&\n earner.tenantId !== event.tenantId\n ) {\n throw new Error(\n `CommissionCalculationService.calculateForEvent: earner '${input.earnerId}' ` +\n `belongs to tenant '${earner.tenantId}' but the event belongs to ` +\n `tenant '${event.tenantId}' — cross-tenant commissions are refused.`,\n );\n }\n }\n const termsRef =\n input.termsSnapshotId || `${input.planKey}@${input.planVersion}`;\n\n const created: Commission[] = [];\n const skipped: CommissionComponentSkip[] = [];\n const existing: Commission[] = [];\n\n for (const component of input.components) {\n // Trigger filter: only components listening for this event kind (or\n // everything via '*') participate at all.\n if (component.trigger !== '*' && component.trigger !== event.eventKind) {\n continue;\n }\n\n // Idempotent replay: the same event can only ever earn once per\n // component per earner under the same terms, regardless of what the\n // occurrence resolver would report on a re-run.\n const priorForEvent = await this.commissions.list({\n where: {\n earningEventId: event.id,\n earnerId: input.earnerId,\n componentKey: component.key,\n planKey: input.planKey,\n planVersion: input.planVersion,\n termsSnapshotId: input.termsSnapshotId ?? '',\n },\n limit: 1,\n });\n if (priorForEvent[0]) {\n existing.push(priorForEvent[0]);\n continue;\n }\n\n // Currency: terms and event must agree — this module performs no FX.\n if (input.currency !== undefined && input.currency !== event.currency) {\n skipped.push({\n componentKey: component.key,\n reason: 'currency_mismatch',\n });\n continue;\n }\n\n // Recurrence limits.\n const occurrenceCount = input.occurrenceCountResolver\n ? await input.occurrenceCountResolver(component.key)\n : 0;\n const recurrence = component.recurrence;\n if (recurrence) {\n if (recurrence.kind === 'one_time' && occurrenceCount >= 1) {\n skipped.push({\n componentKey: component.key,\n reason: 'occurrence_limit_reached',\n });\n continue;\n }\n if (\n recurrence.kind === 'recurring' &&\n recurrence.maxOccurrences !== undefined &&\n occurrenceCount >= recurrence.maxOccurrences\n ) {\n skipped.push({\n componentKey: component.key,\n reason: 'occurrence_limit_reached',\n });\n continue;\n }\n if (\n recurrence.windowMonths !== undefined &&\n input.anchorAt !== undefined &&\n event.occurredAt.getTime() >\n CommissionCalculationService.addMonths(\n input.anchorAt,\n recurrence.windowMonths,\n ).getTime()\n ) {\n skipped.push({\n componentKey: component.key,\n reason: 'outside_recurrence_window',\n });\n continue;\n }\n }\n\n // Resolve the base amount for the component's basis.\n let baseAmountCents: number;\n switch (component.basis) {\n case 'gross':\n baseAmountCents = event.grossAmountCents;\n break;\n case 'net':\n if (event.netAmountCents === null) {\n // Net must be explicitly defined by the ingesting system —\n // it is NEVER derived from gross.\n skipped.push({\n componentKey: component.key,\n reason: 'net_basis_undefined',\n });\n continue;\n }\n baseAmountCents = event.netAmountCents;\n break;\n case 'margin':\n if (event.marginCents === null) {\n skipped.push({\n componentKey: component.key,\n reason: 'margin_basis_undefined',\n });\n continue;\n }\n baseAmountCents = event.marginCents;\n break;\n case 'fixed':\n if (typeof component.fixedAmountCents !== 'number') {\n skipped.push({\n componentKey: component.key,\n reason: 'fixed_amount_missing',\n });\n continue;\n }\n baseAmountCents = component.fixedAmountCents;\n break;\n case 'custom': {\n const bases = event.getCustomBases();\n const key = component.customBasisKey ?? '';\n const value = key ? bases[key] : undefined;\n if (typeof value !== 'number') {\n skipped.push({\n componentKey: component.key,\n reason: 'custom_basis_missing',\n });\n continue;\n }\n baseAmountCents = value;\n break;\n }\n }\n\n // Resolve rate + amount. Fixed components record rate 0 and apply\n // only the share fraction; everything else applies rate × share.\n let rate: number;\n let amountCents: number;\n if (component.basis === 'fixed') {\n rate = 0;\n amountCents = roundCents(baseAmountCents * shareFraction);\n } else {\n if (typeof component.rate !== 'number') {\n skipped.push({\n componentKey: component.key,\n reason: 'rate_missing',\n });\n continue;\n }\n rate = component.rate;\n amountCents = calculateCommissionAmountCents(\n baseAmountCents,\n rate,\n shareFraction,\n );\n }\n\n const occurrenceIndex = occurrenceCount;\n const dedupeKey = `${event.dedupeKey}:${termsRef}:${component.key}:${input.earnerId}:${occurrenceIndex}`;\n\n // Second idempotency belt: an exact dedupe-key hit (however it came\n // to exist) is returned rather than re-created — creating through the\n // conflictColumns upsert would otherwise UPDATE the existing row.\n const priorByKey = await this.commissions.findByDedupeKey(dedupeKey);\n if (priorByKey) {\n existing.push(priorByKey);\n continue;\n }\n\n const trace: CommissionCalculationTrace = {\n planKey: input.planKey,\n planVersion: input.planVersion,\n componentKey: component.key,\n basis: component.basis,\n baseAmountCents,\n rate,\n shareFraction,\n occurrenceIndex,\n earningEventId: event.id,\n roundingMode: 'half_away_from_zero',\n };\n\n let commission: Commission;\n try {\n commission = await this.commissions.create({\n // Commissions inherit the event's tenancy so background\n // calculation (no active tenant context) still lands rows in the\n // right tenant.\n tenantId: event.tenantId,\n earnerId: input.earnerId,\n earningEventId: event.id,\n planKey: input.planKey,\n planVersion: input.planVersion,\n componentKey: component.key,\n termsSnapshotKind: input.termsSnapshotKind ?? '',\n termsSnapshotId: input.termsSnapshotId ?? '',\n basis: component.basis,\n baseAmountCents,\n rate,\n shareFraction,\n splitGroupId: input.splitGroupId ?? '',\n amountCents,\n currency: event.currency,\n status: 'pending',\n clearingEndsAt:\n input.clearingDays !== undefined\n ? new Date(\n event.occurredAt.getTime() + input.clearingDays * MS_PER_DAY,\n )\n : null,\n sourceKind: event.sourceKind,\n sourceId: event.sourceId,\n calculationTrace: JSON.stringify(trace),\n dedupeKey,\n });\n } catch (error) {\n // The Commission dedupe-key guard refuses to overwrite an existing\n // row — under a calculation race the loser lands here. That IS the\n // idempotent outcome: hand back the row that won.\n if (\n error instanceof Error &&\n error.message.includes('immutable audit rows')\n ) {\n const winner = await this.commissions.list({\n where: { dedupeKey },\n limit: 1,\n });\n if (winner[0]) {\n existing.push(winner[0]);\n continue;\n }\n }\n throw error;\n }\n created.push(commission);\n }\n\n return { created, skipped, existing };\n }\n\n /**\n * Calendar-month addition (UTC). JS `setUTCMonth` semantics: day-of-month\n * overflow rolls into the next month (Jan 31 + 1 month → Mar 2/3), which\n * is acceptable for coarse recurrence windows.\n */\n private static addMonths(date: Date, months: number): Date {\n const result = new Date(date.getTime());\n result.setUTCMonth(result.getUTCMonth() + months);\n return result;\n }\n}\n\nexport default CommissionCalculationService;\n","/**\n * CommissionPayoutService — mints and drives {@link CommissionPayout}\n * settlement batches.\n *\n * The service is the ONLY sanctioned creation path for payouts (their\n * generated surface is fully read-only): it gathers the exact payable\n * unsettled Commissions and eligible unsettled Adjustments, refuses\n * below-threshold / non-positive batches, stamps `payoutId` on the exact\n * gathered rows, and later flips the batch's commissions to `paid` when the\n * payout completes.\n *\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CommissionAdjustmentCollection } from '../collections/CommissionAdjustmentCollection.js';\nimport { CommissionCollection } from '../collections/CommissionCollection.js';\nimport { CommissionPayoutCollection } from '../collections/CommissionPayoutCollection.js';\nimport { EarnerCollection } from '../collections/EarnerCollection.js';\nimport type { CommissionPayout } from '../models/CommissionPayout.js';\nimport {\n ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES,\n type CommissionStatus,\n type PayoutMethod,\n} from '../types.js';\n\n/** Collaborators for {@link CommissionPayoutService}. */\nexport interface CommissionPayoutServiceDeps {\n earners: EarnerCollection;\n commissions: CommissionCollection;\n adjustments: CommissionAdjustmentCollection;\n payouts: CommissionPayoutCollection;\n}\n\n/** Input for {@link CommissionPayoutService.createPayoutBatch}. */\nexport interface CreatePayoutBatchInput {\n earnerId: string;\n currency: string;\n /** Informational period bounds recorded on the payout. */\n periodStart?: Date;\n periodEnd?: Date;\n /**\n * Idempotency natural key. Defaults to\n * `` `${earnerId}:${currency}:${periodEnd ISO date}` `` where the date is\n * the `YYYY-MM-DD` of `periodEnd` (falling back to `now`). Callers running\n * more than one batch per earner/currency/day must supply their own key.\n */\n idempotencyKey?: string;\n /** Overrides the earner's `payoutThresholdCents`. */\n minimumThresholdCents?: number;\n /** Overrides the earner's `payoutMethod`. */\n payoutMethod?: PayoutMethod;\n /** Clock override for deterministic tests. */\n now?: Date;\n}\n\n/** Result of {@link CommissionPayoutService.createPayoutBatch}. */\nexport interface CreatePayoutBatchResult {\n /** The created (or, on an idempotent replay, existing) payout — `null` on refusal. */\n payout: CommissionPayout | null;\n /** `true` only when THIS call minted the payout. */\n created: boolean;\n /** Why no payout was created, when refused. */\n reason?: 'below_threshold' | 'nothing_payable';\n /** Ids of the commissions THIS call stamped onto the payout. */\n settledCommissionIds: string[];\n /** Ids of the adjustments THIS call stamped onto the payout. */\n settledAdjustmentIds: string[];\n}\n\nexport class CommissionPayoutService {\n constructor(private readonly deps: CommissionPayoutServiceDeps) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CommissionPayoutService> {\n return new CommissionPayoutService({\n earners: await EarnerCollection.create(classOptions),\n commissions: await CommissionCollection.create(classOptions),\n adjustments: await CommissionAdjustmentCollection.create(classOptions),\n payouts: await CommissionPayoutCollection.create(classOptions),\n });\n }\n\n /**\n * Create a settlement batch for one earner in one currency.\n *\n * Flow:\n * 1. **Idempotency + repair** — an existing payout with the (defaulted)\n * key is returned as `{ payout, created: false }`. A clean replay\n * touches nothing (new payable work is never swept into an existing\n * batch). A PENDING payout whose stored totals disagree with the rows\n * stamped with its id — the signature of an interrupted claim pass —\n * is repaired: the claim pass re-runs and the totals are reconciled\n * from the verified membership. Past `pending` the batch is frozen.\n * 2. **Gather** — payable unsettled commissions for the earner/currency,\n * plus unsettled adjustments whose parent commission is\n * earned/approved/payable/paid (same eligibility as the balance\n * service, so the batch settles exactly what the balance reports).\n * 3. **Refuse** — `netTotal <= 0` → `'nothing_payable'`;\n * `netTotal < threshold` (earner default, overridable) →\n * `'below_threshold'`. Nothing is minted or stamped on refusal.\n * 4. **Mint, claim, reconcile** — create the `pending` payout, then\n * CLAIM the gathered rows through the collections' conditional\n * `claimForPayout` (rows grabbed by another batch in the interim are\n * skipped, never double-claimed), and finally store totals computed\n * from the rows that were VERIFIABLY claimed — the payout's totals\n * are always reproducible from its member rows.\n *\n * Concurrency: claims are conditional with post-save verification, which\n * narrows but does not eliminate races between batches with different\n * keys (the collection layer exposes no cross-row transaction — the same\n * stance as commerce/ledgers compensation). Settlement runs are expected\n * to be single-writer per earner; totals are correct-by-construction from\n * claimed rows either way.\n */\n async createPayoutBatch(\n input: CreatePayoutBatchInput,\n ): Promise<CreatePayoutBatchResult> {\n const now = input.now ?? new Date();\n const earner = await this.deps.earners.get({ id: input.earnerId });\n if (!earner) {\n throw new Error(\n `CommissionPayoutService: earner '${input.earnerId}' not found`,\n );\n }\n\n const idempotencyKey =\n input.idempotencyKey ??\n CommissionPayoutService.defaultIdempotencyKey(\n input.earnerId,\n input.currency,\n input.periodEnd ?? now,\n );\n\n // Idempotent replay: same key → same payout. A CLEAN replay (stored\n // totals match the stamped membership) returns without touching rows —\n // new payable work is never swept into an existing batch. A pending\n // payout whose stored totals DISAGREE with its membership is the\n // signature of an interrupted claim pass: repair it (re-claim +\n // reconcile totals) instead of returning totals its rows can't\n // reproduce. Anything past pending is frozen.\n const existingPayout =\n await this.deps.payouts.findByIdempotencyKey(idempotencyKey);\n if (existingPayout) {\n if (\n existingPayout.isPending() &&\n !(await this.membershipConsistent(existingPayout))\n ) {\n const repaired = await this.claimAndReconcile(existingPayout, input);\n return { ...repaired, created: false };\n }\n return {\n payout: existingPayout,\n created: false,\n settledCommissionIds: [],\n settledAdjustmentIds: [],\n };\n }\n\n // Gather the exact rows this batch would settle.\n const commissions = await this.deps.commissions.findPayableUnsettled(\n input.earnerId,\n input.currency,\n );\n const eligibleAdjustments = await this.findEligibleUnsettledAdjustments(\n input.earnerId,\n input.currency,\n );\n\n const commissionTotalCents = commissions.reduce(\n (sum, c) => sum + c.amountCents,\n 0,\n );\n const adjustmentTotalCents = eligibleAdjustments.reduce(\n (sum, a) => sum + a.amountCents,\n 0,\n );\n const netTotalCents = commissionTotalCents + adjustmentTotalCents;\n\n if (netTotalCents <= 0) {\n return {\n payout: null,\n created: false,\n reason: 'nothing_payable',\n settledCommissionIds: [],\n settledAdjustmentIds: [],\n };\n }\n\n const thresholdCents =\n input.minimumThresholdCents ?? earner.payoutThresholdCents;\n if (netTotalCents < thresholdCents) {\n return {\n payout: null,\n created: false,\n reason: 'below_threshold',\n settledCommissionIds: [],\n settledAdjustmentIds: [],\n };\n }\n\n const minted = await this.deps.payouts.create({\n // Payouts inherit the earner's tenancy so scheduled batch runs (no\n // active tenant context) still land in the right tenant.\n tenantId: earner.tenantId,\n earnerId: input.earnerId,\n currency: input.currency,\n periodStart: input.periodStart ?? null,\n periodEnd: input.periodEnd ?? null,\n payoutMethod: input.payoutMethod ?? earner.payoutMethod,\n status: 'pending',\n commissionTotalCents,\n adjustmentTotalCents,\n totalAmountCents: netTotalCents,\n idempotencyKey,\n });\n\n // Adopt the PERSISTED row for the idempotency key before claiming\n // anything: two workers racing past the earlier lookup both reach the\n // natural-key upsert, and the loser's in-memory instance carries an id\n // the database no longer holds. Claiming with that orphaned id would\n // stamp rows onto a payout that doesn't exist — so both workers\n // converge on whichever row actually won the key.\n const payout =\n (await this.deps.payouts.findByIdempotencyKey(idempotencyKey)) ?? minted;\n\n // Claim the gathered rows conditionally and reconcile the stored\n // totals from what was VERIFIABLY claimed — a row grabbed by another\n // batch between gather and claim is skipped, never double-claimed, and\n // never counted.\n const result = await this.claimAndReconcile(payout, input);\n return { ...result, created: true };\n }\n\n /**\n * Whether a payout's stored totals are reproducible from the rows\n * actually stamped with its id — the invariant an interrupted claim pass\n * breaks. Clean replays short-circuit on this; repair runs only when it\n * fails.\n */\n private async membershipConsistent(\n payout: CommissionPayout,\n ): Promise<boolean> {\n const payoutId = payout.id ?? '';\n const members = await this.deps.commissions.findByPayout(payoutId);\n const memberAdjustments =\n await this.deps.adjustments.findByPayout(payoutId);\n const commissionTotalCents = members.reduce(\n (sum, c) => sum + c.amountCents,\n 0,\n );\n const adjustmentTotalCents = memberAdjustments.reduce(\n (sum, a) => sum + a.amountCents,\n 0,\n );\n return (\n payout.commissionTotalCents === commissionTotalCents &&\n payout.adjustmentTotalCents === adjustmentTotalCents &&\n payout.totalAmountCents === commissionTotalCents + adjustmentTotalCents\n );\n }\n\n /**\n * Claim pass + totals reconciliation for a PENDING payout.\n *\n * The claim set is the union of rows already stamped with this payout\n * (an interrupted earlier pass) and the currently gathered eligible\n * rows. Claims go through the collections' conditional `claimForPayout`\n * (rows owned by another batch are skipped); totals are then recomputed\n * from the claimed rows and saved when they drift from what the payout\n * carries. In the pathological all-rows-raced-away case the payout keeps\n * zero totals and a note — auditable, never double-paid.\n */\n private async claimAndReconcile(\n payout: CommissionPayout,\n input: CreatePayoutBatchInput,\n ): Promise<Omit<CreatePayoutBatchResult, 'created'>> {\n const payoutId = payout.id ?? '';\n\n const previouslyClaimed =\n await this.deps.commissions.findByPayout(payoutId);\n const gathered = await this.deps.commissions.findPayableUnsettled(\n input.earnerId,\n input.currency,\n );\n const commissionIds = [\n ...new Set(\n [...previouslyClaimed, ...gathered]\n .map((c) => c.id)\n .filter((id): id is string => !!id),\n ),\n ];\n const claimedCommissions = await this.deps.commissions.claimForPayout(\n commissionIds,\n payoutId,\n );\n\n const previouslyClaimedAdjustments =\n await this.deps.adjustments.findByPayout(payoutId);\n const gatheredAdjustments = await this.findEligibleUnsettledAdjustments(\n input.earnerId,\n input.currency,\n );\n const adjustmentIds = [\n ...new Set(\n [...previouslyClaimedAdjustments, ...gatheredAdjustments]\n .map((a) => a.id)\n .filter((id): id is string => !!id),\n ),\n ];\n const claimedAdjustments = await this.deps.adjustments.claimForPayout(\n adjustmentIds,\n payoutId,\n );\n\n const commissionTotalCents = claimedCommissions.reduce(\n (sum, c) => sum + c.amountCents,\n 0,\n );\n const adjustmentTotalCents = claimedAdjustments.reduce(\n (sum, a) => sum + a.amountCents,\n 0,\n );\n const totalAmountCents = commissionTotalCents + adjustmentTotalCents;\n\n if (\n payout.commissionTotalCents !== commissionTotalCents ||\n payout.adjustmentTotalCents !== adjustmentTotalCents ||\n payout.totalAmountCents !== totalAmountCents\n ) {\n payout.commissionTotalCents = commissionTotalCents;\n payout.adjustmentTotalCents = adjustmentTotalCents;\n payout.totalAmountCents = totalAmountCents;\n if (claimedCommissions.length === 0 && claimedAdjustments.length === 0) {\n payout.notes =\n 'no rows claimed (raced by a concurrent batch); nothing will be paid';\n }\n await payout.save();\n }\n\n return {\n payout,\n settledCommissionIds: claimedCommissions\n .map((c) => c.id)\n .filter((id): id is string => !!id),\n settledAdjustmentIds: claimedAdjustments\n .map((a) => a.id)\n .filter((id): id is string => !!id),\n };\n }\n\n /**\n * Complete a payout: flip the batch's settled commissions\n * `payable → paid` FIRST, then `payout.complete(paymentReference)`\n * (requires status `processing`). Ordering matters for recoverability —\n * if a member save fails mid-loop the payout is still `processing`, so a\n * retry finishes the remaining members (already-paid ones are skipped)\n * and then finalizes; the terminal transition never strands `payable`\n * members behind a `completed` payout. Adjustments carry no status —\n * stamping `payoutId` at batch time already settled them.\n */\n async completePayout(\n payoutId: string,\n paymentReference: string,\n now: Date = new Date(),\n ): Promise<CommissionPayout> {\n const payout = await this.requirePayout(payoutId);\n if (!payout.isProcessing()) {\n throw new Error(\n `CommissionPayout ${payout.id ?? '<new>'}: cannot complete from status '${payout.status}'`,\n );\n }\n if (!paymentReference) {\n throw new Error(\n `CommissionPayout ${payout.id ?? '<new>'}: complete() requires a paymentReference`,\n );\n }\n\n const members = await this.deps.commissions.findByPayout(payoutId);\n for (const commission of members) {\n if (commission.isPayable()) {\n commission.markPaid(now);\n await commission.save();\n }\n }\n\n payout.complete(paymentReference, now);\n await payout.save();\n return payout;\n }\n\n /**\n * Fail a payout (`approved | processing → failed`). The batch's rows stay\n * stamped — after `resetFromFailed()` the SAME payout retries the SAME\n * rows; releasing the rows to a different batch would double-pay them if\n * the failed remittance later settled.\n */\n async failPayout(\n payoutId: string,\n reason: string,\n ): Promise<CommissionPayout> {\n const payout = await this.requirePayout(payoutId);\n payout.fail(reason);\n await payout.save();\n return payout;\n }\n\n /**\n * Unsettled adjustments for the earner/currency whose parent commission\n * is earned/approved/payable/paid — the same eligibility rule the balance\n * service applies, so batches settle exactly what balances report.\n */\n private async findEligibleUnsettledAdjustments(\n earnerId: string,\n currency: string,\n ) {\n const unsettled = await this.deps.adjustments.findUnsettledByEarner(\n earnerId,\n currency,\n );\n if (unsettled.length === 0) return unsettled;\n\n const parentIds = [\n ...new Set(unsettled.map((a) => a.commissionId).filter(Boolean)),\n ];\n const parents = await this.deps.commissions.listByIds(parentIds);\n const parentStatusById = new Map<string, CommissionStatus>();\n for (const parent of parents) {\n if (parent.id) parentStatusById.set(parent.id, parent.status);\n }\n return unsettled.filter((adjustment) => {\n const parentStatus = parentStatusById.get(adjustment.commissionId);\n return (\n parentStatus !== undefined &&\n (\n ADJUSTMENT_SETTLEABLE_COMMISSION_STATUSES as readonly CommissionStatus[]\n ).includes(parentStatus)\n );\n });\n }\n\n private async requirePayout(payoutId: string): Promise<CommissionPayout> {\n const payout = await this.deps.payouts.get({ id: payoutId });\n if (!payout) {\n throw new Error(\n `CommissionPayoutService: payout '${payoutId}' not found`,\n );\n }\n return payout;\n }\n\n /** `${earnerId}:${currency}:${YYYY-MM-DD of periodEnd}` — see the input doc. */\n private static defaultIdempotencyKey(\n earnerId: string,\n currency: string,\n periodEnd: Date,\n ): string {\n return `${earnerId}:${currency}:${periodEnd.toISOString().slice(0, 10)}`;\n }\n}\n\nexport default CommissionPayoutService;\n","/**\n * CommissionSettlementService — advances Commissions along the strict\n * `pending → earned → approved → payable` chain (the final `payable → paid`\n * step belongs to `CommissionPayoutService.completePayout`, which flips a\n * batch's rows when the money actually moves).\n *\n * All methods persist the rows they advance (transition method + save per\n * row) and return the updated instances.\n *\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CommissionCollection } from '../collections/CommissionCollection.js';\nimport type { Commission } from '../models/Commission.js';\n\nexport class CommissionSettlementService {\n constructor(private readonly commissions: CommissionCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CommissionSettlementService> {\n return new CommissionSettlementService(\n await CommissionCollection.create(classOptions),\n );\n }\n\n /**\n * Sweep the clearing window: every `pending` commission whose\n * `clearingEndsAt` is `<= now` — or whose `clearingEndsAt` is `null`\n * (null means NO clearing window applies, so the row is immediately\n * sweepable) — transitions to `earned` and is saved.\n *\n * @returns The commissions that were marked earned by this sweep.\n */\n async sweepClearing(now: Date = new Date()): Promise<Commission[]> {\n const pending = await this.commissions.findByStatus('pending');\n const swept: Commission[] = [];\n for (const commission of pending) {\n const clearingEndsAt = commission.clearingEndsAt;\n if (clearingEndsAt !== null && clearingEndsAt.getTime() > now.getTime()) {\n continue; // still clearing\n }\n commission.markEarned(now);\n await commission.save();\n swept.push(commission);\n }\n return swept;\n }\n\n /**\n * Approve `earned` commissions by id (`earned → approved`). Strict: a\n * missing id or a commission in any other status throws — the caller\n * names exact rows, so a mismatch is a bug worth surfacing, not skipping.\n */\n async approveCommissions(\n ids: string[],\n now: Date = new Date(),\n ): Promise<Commission[]> {\n return await this.transitionByIds(ids, (commission) => {\n commission.approve(now);\n });\n }\n\n /**\n * Release `approved` commissions to `payable` by id. Strict — see\n * {@link approveCommissions}.\n */\n async markPayable(\n ids: string[],\n now: Date = new Date(),\n ): Promise<Commission[]> {\n return await this.transitionByIds(ids, (commission) => {\n commission.markPayable(now);\n });\n }\n\n /**\n * Convenience chain: advance each commission from wherever it currently\n * sits up to `payable` (`pending → earned → approved → payable`), saving\n * after EACH step — the save-time guard only admits single-step edges, so\n * every intermediate state is persisted (each with its timestamp). Rows\n * already `payable` or `paid` are returned untouched (idempotent). Note\n * this deliberately bypasses the clearing window — it's the \"operator\n * says pay these now\" path.\n */\n async settleUpToPayable(\n ids: string[],\n now: Date = new Date(),\n ): Promise<Commission[]> {\n const updated: Commission[] = [];\n for (const id of ids) {\n const commission = await this.requireCommission(id);\n if (commission.isPending()) {\n commission.markEarned(now);\n await commission.save();\n }\n if (commission.isEarned()) {\n commission.approve(now);\n await commission.save();\n }\n if (commission.isApproved()) {\n commission.markPayable(now);\n await commission.save();\n }\n updated.push(commission);\n }\n return updated;\n }\n\n private async transitionByIds(\n ids: string[],\n transition: (commission: Commission) => void,\n ): Promise<Commission[]> {\n const updated: Commission[] = [];\n for (const id of ids) {\n const commission = await this.requireCommission(id);\n const statusBefore = commission.status;\n transition(commission);\n if (commission.status !== statusBefore) {\n await commission.save();\n }\n updated.push(commission);\n }\n return updated;\n }\n\n private async requireCommission(id: string): Promise<Commission> {\n const commission = await this.commissions.get({ id });\n if (!commission) {\n throw new Error(\n `CommissionSettlementService: commission '${id}' not found`,\n );\n }\n return commission;\n }\n}\n\nexport default CommissionSettlementService;\n"],"mappings":"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"}
|