@happyvertical/smrt-sales 0.39.4 → 0.39.7

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 CHANGED
@@ -26,7 +26,7 @@ Referrers and Sales Representatives are **distinct roles** and stay that way. Bo
26
26
  - **Commission**: one earning record per earner per plan component per event occurrence. Integer-cents amounts (`baseAmountCents`, `amountCents`), decimal `rate`, `basis`, snapshot references (`planKey`/`planVersion`, generic `termsSnapshotKind`/`termsSnapshotId`), a JSON `calculationTrace` sufficient to reproduce the amount, split support (`splitGroupId`, `shareFraction`), and a `dedupeKey` for idempotent creation. Lifecycle `pending → earned → approved → payable → paid`, enforced by a save-time transition guard with an authoritative prior-status re-read (commerce pattern).
27
27
  - **CommissionAdjustment**: append-only corrections (`refund|credit|chargeback|dispute|correction`), signed `amountCents`, required `reason`, immutable once created. Earned/paid Commissions are never rewritten — adjustments append to them.
28
28
  - **Payable balance**: computed, not stored — `CommissionBalanceService` sums payable Commissions plus unsettled Adjustments per Earner/currency.
29
- - **CommissionPayout**: settlement batch per Earner. `pending → approved → processing → completed | failed` (`resetFromFailed()` is the only exit from failed); settles rows via the collections' **conditional `claimForPayout`** (a row owned by another batch is never re-claimed) and stores totals recomputed from the VERIFIED claimed membership; idempotent via `idempotencyKey` natural key (default `${earnerId}:${currency}:${YYYY-MM-DD}`) — a clean replay touches nothing, while a pending payout whose totals disagree with its stamped rows (interrupted claim pass) is repaired on replay. `completePayout` flips member commissions to `paid` BEFORE the terminal transition, so a mid-loop failure stays retryable. Enforces `totalAmountCents = commissionTotalCents + adjustmentTotalCents` at save; retains `paymentReference`/`providerRef`; optional `invoiceId` cross-package string ref to a commerce Invoice. Generated surface is read-only on ALL doors (api/mcp/cli `list`/`get`) — writes go through `CommissionPayoutService` (settlement is single-writer per earner by expectation; the collection layer has no cross-row transactions).
29
+ - **CommissionPayout**: settlement batch per Earner. `pending → approved → processing → completed | failed` (`resetFromFailed()` is the only exit from failed); settles rows via the collections' **conditional `claimForPayout`** (model-layer get/save/re-read — tenancy- and dialect-safe; a row owned by another batch is never re-claimed) and stores totals recomputed from the VERIFIED claimed membership; idempotent via `idempotencyKey` natural key (default `${earnerId}:${currency}:${YYYY-MM-DD}`) — a clean replay touches nothing, while a pending payout whose totals disagree with its stamped rows (interrupted claim pass) is repaired on replay. `completePayout` flips member commissions to `paid` BEFORE the terminal transition, so a mid-loop failure stays retryable. Enforces `totalAmountCents = commissionTotalCents + adjustmentTotalCents` at save; retains `paymentReference`/`providerRef`; optional `invoiceId` cross-package string ref to a commerce Invoice. Generated surface is read-only on ALL doors (api/mcp/cli `list`/`get`) — writes go through `CommissionPayoutService`. `createPayoutBatch` settles the whole earner+currency by default, or a **scoped** subset: pass `sourceKind`+`sourceId` to settle just one earning source (e.g. one ad network), or `commissionIds` for an explicit set (each validated payable+unsettled+earner+currency; requires its own `idempotencyKey`) — adjustments are narrowed to the same scope, and the default idempotency key folds the source in. Scoping is how concurrent per-source batches settle safely: batches scoped to different sources (or disjoint `commissionIds`) gather **disjoint** row sets and never contend. The collection layer has no cross-row transaction, so OVERLAPPING concurrent scopes (a source batch + the earner-wide batch, or intersecting id sets) must be serialized by the caller — `claimForPayout` still won't double-own a row, but a shared commission and its adjustment could split across the two batches.
30
30
 
31
31
  ### crm
32
32
 
@@ -83,5 +83,5 @@ Every model is `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable:
83
83
  - **`sweepClearing` treats `clearingEndsAt: null` as immediately sweepable** (no clearing configured ⇒ nothing to wait for).
84
84
  - **Agreement freeze scope**: activating a ReferralAgreement freezes referrer/program/version/plan refs/clearingDays/approvalMode/effectiveFrom; `effectiveTo` (end-dating) and the evidence fields (`contractRef`, artifact url/hash, `acceptanceEvidence`) stay writable — e-signature completes downstream.
85
85
  - **Circular FK pair by design**: `Referral.snapshotId ↔ ReferralTermSnapshot.referralId`. Fine on SQLite (and no cross-table DDL constraints are emitted for these string FKs); keep an eye on strict-DDL environments.
86
- - **Attribution resolution is single-writer per (target, program)**: `resolve()` is idempotent against PERSISTED state, but two workers resolving the same target concurrently can both pass the existence check and create duplicate credit — the collection layer has no cross-row transactions (the same stance as payout batching). Run intake resolution serially per target (it naturally is, in a request handler); duplicates are visible in the portal and correctable via `override()`.
86
+ - **Attribution resolution is single-writer per (target, program)**: `resolve()` is idempotent against PERSISTED state, but two workers resolving the same target concurrently can both pass the existence check and create duplicate credit — the collection layer has no cross-row transaction (the same stance as payout batching, which relies on disjoint scopes for concurrency). Run intake resolution serially per target (it naturally is, in a request handler); duplicates are visible in the portal and correctable via `override()`.
87
87
  - **`ObjectRegistry.getConfig(X)` needs the class module imported** (decorator side effects) — manifest-only registration doesn't carry api/mcp/conflictColumns; tests asserting surface configs need a side-effect import of the module barrel.