@happyvertical/smrt-sales 0.40.8 → 0.40.10
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 +5 -3
- package/dist/agreements.js +1 -1
- package/dist/chunks/__smrt-register__-CZE4Gm8_.js +6 -0
- package/dist/chunks/{__smrt-register__-ITgv3u9E.js.map → __smrt-register__-CZE4Gm8_.js.map} +1 -1
- package/dist/chunks/{commissions-pI_MmBxN.js → commissions-CelWwvjZ.js} +354 -74
- package/dist/chunks/commissions-CelWwvjZ.js.map +1 -0
- package/dist/chunks/{referrals-cYYg4YZw.js → referrals-CJUOZxpa.js} +430 -75
- package/dist/chunks/referrals-CJUOZxpa.js.map +1 -0
- package/dist/commissions/collections/CommissionAdjustmentOperationCollection.d.ts +24 -0
- package/dist/commissions/collections/CommissionAdjustmentOperationCollection.d.ts.map +1 -0
- package/dist/commissions/models/CommissionAdjustment.d.ts.map +1 -1
- package/dist/commissions/models/CommissionAdjustmentOperation.d.ts +23 -0
- package/dist/commissions/models/CommissionAdjustmentOperation.d.ts.map +1 -0
- package/dist/commissions/services/CommissionAdjustmentService.d.ts +68 -0
- package/dist/commissions/services/CommissionAdjustmentService.d.ts.map +1 -0
- package/dist/commissions/services/index.d.ts +1 -0
- package/dist/commissions/services/index.d.ts.map +1 -1
- package/dist/commissions.js +3 -3
- package/dist/crm.js +1 -1
- package/dist/index.js +4 -4
- package/dist/manifest.json +548 -2
- package/dist/referrals/collections/ReferralClickOperationCollection.d.ts +29 -0
- package/dist/referrals/collections/ReferralClickOperationCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralLinkCollection.d.ts +56 -5
- package/dist/referrals/collections/ReferralLinkCollection.d.ts.map +1 -1
- package/dist/referrals/index.d.ts +1 -1
- package/dist/referrals/index.d.ts.map +1 -1
- package/dist/referrals/models/ReferralClickOperation.d.ts +39 -0
- package/dist/referrals/models/ReferralClickOperation.d.ts.map +1 -0
- package/dist/referrals/models/ReferralLink.d.ts +3 -2
- package/dist/referrals/models/ReferralLink.d.ts.map +1 -1
- package/dist/referrals.js +3 -3
- package/dist/smrt-knowledge.json +139 -7
- package/package.json +6 -6
- package/dist/chunks/__smrt-register__-ITgv3u9E.js +0 -6
- package/dist/chunks/commissions-pI_MmBxN.js.map +0 -1
- package/dist/chunks/referrals-cYYg4YZw.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ Referrers and Sales Representatives are **distinct roles** and stay that way. Bo
|
|
|
37
37
|
- **CommissionPlan**: versioned calculation terms. Natural key `(tenant_id, plan_key, version)` (per-tenant keys; the adapters' null-aware upsert dedups NULL-tenant keys through the model layer, though duplicate global rows minted via raw SQL remain representable); `status` (`draft|active|superseded|retired`); `effectiveFrom`. Components are a JSON-string array (`getComponents()`): each component has `key`, `trigger` (earning-event kind or `*`), `basis` (`fixed|gross|net|margin|custom`), `rate` or `fixedAmountCents`, `recurrence` (`one_time` or `recurring` with optional `maxOccurrences`/`windowMonths`). **Immutable once active** — amendments create a new row with `version + 1`; prior versions are never rewritten. `latestActiveByKey(key, at?)` resolves the highest active version **already in effect at `at`** — a future-dated amendment can be activated ahead of time without governing earlier qualifications (same rule on `AttributionPolicyCollection`).
|
|
38
38
|
- **EarningEvent**: immutable commercial-event evidence (`conversion`, `agreement_execution`, `invoice_payment`, `collected_revenue`, `recognized_margin`, or any extensible kind). Carries `sourceKind`/`sourceId` (generic earning source), `grossAmountCents`/`netAmountCents`/`marginCents`, `currency`, `occurredAt`, and a `dedupeKey` natural key for idempotent ingestion. No update/delete surface, plus a save-time immutability guard: hydrated edits, blind id overwrites, and fresh creates onto an existing dedupe key all throw (the upsert would rotate the row id and orphan referencing Commissions) — idempotent ingestion goes through `getOrCreateByDedupeKey()`.
|
|
39
39
|
- **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).
|
|
40
|
-
- **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.
|
|
40
|
+
- **CommissionAdjustment**: append-only corrections (`refund|credit|chargeback|dispute|correction`), signed `amountCents`, required `reason`, immutable once created. `CommissionAdjustmentService.createAdjustment()` is the tenant-required idempotent creation door: callers provide a stable operation UUID; the database creates exactly one row, exact retries return it, and changed tenant/commission/earner/kind/cents/currency/reason/operator/metadata fail with a typed replay conflict. Parent Commission plus denormalized tenant/Earner/currency and operator UUID are validated before insert. The financial row deliberately exposes no operation field. A private, required-tenant `CommissionAdjustmentOperation` table uses the globally unique operation UUID as its primary id, maps it to the adjustment UUID, and is inserted in the same transaction as the adjustment. Existing adjustment schemas and legacy direct creates remain unchanged. Earned/paid Commissions are never rewritten — adjustments append to them.
|
|
41
41
|
- **Payable balance**: computed, not stored — `CommissionBalanceService` sums payable Commissions plus unsettled Adjustments per Earner/currency.
|
|
42
42
|
- **CommissionPayout**: settlement batch per Earner. `pending → approved → processing → completed | failed` (`resetFromFailed()` is the only exit from failed), plus the terminal operator decline `rejected` reachable from pending/approved (#1987 — reject through `transitionPayoutForSource`, which also RELEASES the batch's membership so the rows settle through a future batch; `reject()` on the model mutates status only). 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 (the claim pass re-checks the payout is still pending first). Each batch/repair pass also derives the payout's **single-source stamp** (`sourceKind`/`sourceId`, indexed): set when every claimed commission — and every claimed adjustment through its parent commission — shares exactly one non-empty source; empty for mixed/unknown/empty membership. `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, intersecting id sets, or a batch replay racing a lifecycle transition of the same payout) 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.
|
|
43
43
|
- **Source-scoped payout history** (#1985): `CommissionPayoutService.getSourcePayoutHistory({ sourceKind, sourceId, limit?, offset? })` — one page of the payouts belonging to ONE earning source, newest first (`created_at DESC, id DESC`), offset contract (`nextOffset` advances by the scanned count; `null` when exhausted). Candidates come from the indexed stamp, so work is bounded by page size (a sparse source never scans the global history); each page is re-verified against actual membership in three batched queries (no per-payout N+1) — every member commission and every adjustment's parent must carry exactly the requested source and agree on earner/currency/tenant; unprovable rows (mixed-source, missing parents, memberless artifacts, released/rejected batches) are excluded fail-closed and reported in `excluded` with reasons. Adjustment-only payouts prove ownership through parent commissions and list normally. Payouts minted before the stamp existed are invisible until backfilled with `restampPayoutSource(payoutId)` (idempotent, any status).
|
|
@@ -57,7 +57,7 @@ Referrers and Sales Representatives are **distinct roles** and stay that way. Bo
|
|
|
57
57
|
- **Referrer**: role model (`profileId`, `earnerId`, `status`).
|
|
58
58
|
- **ReferralProgram**: program defaults — default commission plan key, default attribution policy key, eligibility defaults.
|
|
59
59
|
- **AttributionPolicy**: versioned `(tenant_id, policy_key, version)` policy (per-tenant keys) — attribution `windowDays`, credit mode (`first_touch|last_touch|assigned|split`), split shares, self-referral/existing-client eligibility, eligible services/campaigns/regions, conflict behavior. Immutable once active; amendments bump `version`.
|
|
60
|
-
- **ReferralLink**: shareable link/code per Referrer+Program. Codes are crypto-random
|
|
60
|
+
- **ReferralLink**: shareable link/code per Referrer+Program. Codes are crypto-random and uniqueness-checked. `ReferralLinkCollection.recordClick()` transactionally creates one immutable touch plus one counter increment behind a caller replay key. Exact retries return the original touch; changed link/subject/time/evidence intent raises `ReferralClickReplayConflictError`. Replay comparison uses the immutable operation/touch snapshot, so later mutable link edits do not invalidate the retry; the result carries the current link and original touch evidence. The exact canonical persisted evidence JSON, after Sales overwrites `code`, `linkId`, and `targetUrl`, is bounded to 4096 UTF-8 bytes by default (`maxEvidenceBytes` customizes it).
|
|
61
61
|
- **ReferralTouch**: immutable attribution evidence (`click|code_entry|manual_assignment|partner_entry`) with subject hints and evidence JSON.
|
|
62
62
|
- **Referral**: the introduction — generic qualifying target (`targetKind`/`targetId`: lead, opportunity, client, project, subscription, …), resolved policy version, credit fraction (splits create sibling Referrals sharing `splitGroupId`), lifecycle `pending → attributed → qualified | disqualified | expired | under_review`.
|
|
63
63
|
- **AttributionException**: conflict review queue. Conclusive policy resolves automatically; ambiguity creates an exception; overrides require a `resolutionReason` and are audited.
|
|
@@ -75,7 +75,7 @@ Props-driven presentational components (no data fetching, no model-class imports
|
|
|
75
75
|
|
|
76
76
|
## Tenancy
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Business/domain models are generally `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable: true })`. This deliberately departs from legacy smrt-affiliates (which was cross-tenant by design for ad networks): sales programs, earners, and payouts are tenant-owned, while `tenantId: null` remains available for intentional global/operator-level rows. Immutable tenant-bound execution evidence (`AgreementExecution`, `AgreementExecutionEvent`, `ExecutedAgreement`) and private orchestration fences (`CommissionAdjustmentOperation`) instead use required tenancy with a non-null owner; never weaken those rows to optional tenancy.
|
|
79
79
|
|
|
80
80
|
## Cross-package references (plain strings)
|
|
81
81
|
|
|
@@ -91,6 +91,7 @@ Every model is `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable:
|
|
|
91
91
|
- **Versioned terms are rows, not edits**: CommissionPlan / AttributionPolicy / ReferralAgreement amendments insert `version + 1`; active versions are save-guarded immutable.
|
|
92
92
|
- **Idempotency is dedupeKey-based**: EarningEvent, Commission, and CommissionPayout carry natural keys (`conflictColumns`) — retried ingestion/settlement upserts instead of duplicating.
|
|
93
93
|
- **Adjustments never rewrite**: correcting an earned/paid Commission means appending a CommissionAdjustment, not editing the Commission.
|
|
94
|
+
- **Adjustment retries use the service**: CommissionAdjustment has no public operation field and rejects an untyped `operationId` constructor option; ordinary legacy creates without one remain compatible. `CommissionAdjustmentService` transactionally claims the private operation fence with insert-on-conflict-no-op, creates the explicit mapped adjustment id only for the winner, then verifies the persisted intent; the same globally unique operation UUID cannot be reused by another tenant.
|
|
94
95
|
- **CommissionPayout, not Payout**: avoids the pre-existing global table-name collision between commerce `Payout` and legacy affiliates `Payout` (`payouts`).
|
|
95
96
|
- **Table names are global**: new models were named to avoid collisions across packages (`commission_payouts`, `sales_activities`, `attribution_policies`, …).
|
|
96
97
|
- **Svelte module is svelte-check-gated**: no runtime component tests; `typecheck` runs `svelte-check` via `scripts/svelte-check-a11y.mjs`. Ship raw `.svelte` via `svelte-package`.
|
|
@@ -101,4 +102,5 @@ Every model is `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable:
|
|
|
101
102
|
- **Agreement freeze scope**: activating a ReferralAgreement requires and freezes its execution/evidence refs along with its commercial terms. `effectiveTo` remains writable for explicit end-dating; signed bytes, hashes, signer evidence, and audit trail exist only on immutable ExecutedAgreement/Asset records.
|
|
102
103
|
- **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.
|
|
103
104
|
- **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()`.
|
|
105
|
+
- **Click retries reuse one key**: pass the exact same non-empty, well-formed-Unicode `recordClick.idempotencyKey` (maximum 256 UTF-8 bytes) on transport/database retry. Omitting it remains source-compatible but creates and returns a one-shot UUID; it cannot deduplicate a later independent invocation. `maxEvidenceBytes` guards creation; changing it cannot turn an already-committed exact replay into failure. Replay validation is against the immutable operation/touch snapshot, not mutable current link fields: later link edits preserve exact replay, the result link is current, and the touch evidence is the original snapshot. The private optional-tenant `ReferralClickOperation` table is the serialization fence and must be included in schema migration. Never pre-bound caller evidence: Sales owns the authoritative final-envelope byte check.
|
|
104
106
|
- **`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.
|
package/dist/agreements.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./chunks/__smrt-register__-
|
|
1
|
+
import "./chunks/__smrt-register__-CZE4Gm8_.js";
|
|
2
2
|
import { a as AgreementExecutionEvent, c as AGREEMENT_EXECUTION_STATUSES, d as sanitizeSignerIntent, i as AgreementExecutionEventCollection, l as coerceAgreementDate, n as ExecutedAgreementCollection, o as AgreementExecutionCollection, r as ExecutedAgreement, s as AgreementExecution, t as AgreementExecutionService, u as sanitizeSignerEvidence } from "./chunks/agreements-DbzW1Pcq.js";
|
|
3
3
|
export { AGREEMENT_EXECUTION_STATUSES, AgreementExecution, AgreementExecutionCollection, AgreementExecutionEvent, AgreementExecutionEventCollection, AgreementExecutionService, ExecutedAgreement, ExecutedAgreementCollection, coerceAgreementDate, sanitizeSignerEvidence, sanitizeSignerIntent };
|