@happyvertical/smrt-sales 0.54.2 → 0.54.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +21 -7
  3. package/agents/crm.md +41 -2
  4. package/agents/svelte.md +2 -2
  5. package/dist/agreements.js +2 -2
  6. package/dist/chunks/__smrt-register__-12v_iN-t.js +6 -0
  7. package/dist/chunks/__smrt-register__-12v_iN-t.js.map +1 -0
  8. package/dist/chunks/{agreements-CxHyIbZB.js → agreements-BB_tNU0U.js} +25 -1
  9. package/dist/chunks/agreements-BB_tNU0U.js.map +1 -0
  10. package/dist/chunks/{commissions-BlJpPXFC.js → commissions-Cs4MsxVg.js} +65 -1
  11. package/dist/chunks/commissions-Cs4MsxVg.js.map +1 -0
  12. package/dist/chunks/{crm-C2bD25yv.js → crm-DfRKKvWV.js} +850 -185
  13. package/dist/chunks/crm-DfRKKvWV.js.map +1 -0
  14. package/dist/chunks/{referrals-BsGHbnpW.js → referrals-CrFDsdq-.js} +82 -2
  15. package/dist/chunks/referrals-CrFDsdq-.js.map +1 -0
  16. package/dist/commissions.js +2 -2
  17. package/dist/crm/collections/LeadCollection.d.ts +20 -0
  18. package/dist/crm/collections/LeadCollection.d.ts.map +1 -1
  19. package/dist/crm/collections/OpportunityCollection.d.ts.map +1 -1
  20. package/dist/crm/collections/OpportunityConversionCollection.d.ts.map +1 -1
  21. package/dist/crm/index.d.ts +2 -1
  22. package/dist/crm/index.d.ts.map +1 -1
  23. package/dist/crm/services/LeadWorkflowService.d.ts +79 -5
  24. package/dist/crm/services/LeadWorkflowService.d.ts.map +1 -1
  25. package/dist/crm/services/lead-inbox.d.ts +54 -0
  26. package/dist/crm/services/lead-inbox.d.ts.map +1 -0
  27. package/dist/crm/types.d.ts +2 -0
  28. package/dist/crm/types.d.ts.map +1 -1
  29. package/dist/crm.js +2 -2
  30. package/dist/index.js +5 -5
  31. package/dist/manifest.json +17 -2
  32. package/dist/referrals.js +2 -2
  33. package/dist/smrt-knowledge.json +58 -9
  34. package/dist/svelte/components/LeadCreateForm.svelte +146 -0
  35. package/dist/svelte/components/LeadCreateForm.svelte.d.ts +22 -0
  36. package/dist/svelte/components/LeadCreateForm.svelte.d.ts.map +1 -0
  37. package/dist/svelte/components/LeadDetail.svelte +28 -0
  38. package/dist/svelte/components/LeadDetail.svelte.d.ts +6 -1
  39. package/dist/svelte/components/LeadDetail.svelte.d.ts.map +1 -1
  40. package/dist/svelte/components/LeadList.svelte +129 -3
  41. package/dist/svelte/components/LeadList.svelte.d.ts +29 -1
  42. package/dist/svelte/components/LeadList.svelte.d.ts.map +1 -1
  43. package/dist/svelte/index.d.ts +4 -2
  44. package/dist/svelte/index.d.ts.map +1 -1
  45. package/dist/svelte/index.js +2 -1
  46. package/dist/svelte/types.d.ts +49 -0
  47. package/dist/svelte/types.d.ts.map +1 -1
  48. package/package.json +10 -9
  49. package/dist/chunks/__smrt-register__-RRPKjeCe.js +0 -6
  50. package/dist/chunks/__smrt-register__-RRPKjeCe.js.map +0 -1
  51. package/dist/chunks/agreements-CxHyIbZB.js.map +0 -1
  52. package/dist/chunks/commissions-BlJpPXFC.js.map +0 -1
  53. package/dist/chunks/crm-C2bD25yv.js.map +0 -1
  54. package/dist/chunks/referrals-BsGHbnpW.js.map +0 -1
@@ -3,18 +3,18 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-sales",
6
- "packageVersion": "0.54.2",
6
+ "packageVersion": "0.54.3",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "0b9d7d72d3b1825b94b88cd2ffff366669e6b3327c70d2d23e38738853850085",
11
- "packageJson": "823a98a1bd482bdbd69872aff2624f1e02d45e2c8347dce4fc9ba18840d96c74",
12
- "agents": "70ca45be28b73fa742d3ebebe4c7cc25b524ba3684757d7066056f707efc3b22",
10
+ "manifest": "88b5ddf99b6cb795a25bc8ebc6cc855692ef45fb2fca1adce14b469bd1ad4887",
11
+ "packageJson": "ca6b9ae36d49a8334476bda3b3168a7406c98dd162e0d8c8a0a94477f3f7f1b8",
12
+ "agents": "5f8df148793d29cea2d9fba8122b347028ea691302956f3885a8a086138c82f1",
13
13
  "moduleDoc:agents/agreements.md": "eb6ff147a0f60ee627d3337e8e9394d06fda955b4a4fff228537a9bd2803f886",
14
14
  "moduleDoc:agents/commissions.md": "e047024aafe5edcf072c1a6b7ea253c24cb15f2fa1beb2edd13651e735921f73",
15
- "moduleDoc:agents/crm.md": "e90f813e857782bbfbd4122c9f67b61914e3ef49284a58e976a5507391851b0c",
15
+ "moduleDoc:agents/crm.md": "b5b8d092a29c23c838adb74855123f6d1f2589d919708e418fb753817dd2dd13",
16
16
  "moduleDoc:agents/referrals.md": "e64b64aad4b3feea08f172a18e3992539d761b5f05ac4e5107338a700c0cc4ce",
17
- "moduleDoc:agents/svelte.md": "e03d8efce0a3c5571648ec16858715ccf4377c9840acca403beed4e554205164"
17
+ "moduleDoc:agents/svelte.md": "819f690b91cf93980f6f79e89da8d9629ca6e5c565494e3fd2ca3be7587ad6de"
18
18
  },
19
19
  "exports": [
20
20
  ".",
@@ -4684,6 +4684,7 @@
4684
4684
  "activitiesIncludingMerged",
4685
4685
  "findByOwner",
4686
4686
  "findByStatus",
4687
+ "listInbox",
4687
4688
  "mergeLeads",
4688
4689
  "qualify"
4689
4690
  ],
@@ -4712,6 +4713,14 @@
4712
4713
  ],
4713
4714
  "returns": "Promise<Lead[]>"
4714
4715
  },
4716
+ {
4717
+ "name": "listInbox",
4718
+ "async": true,
4719
+ "params": [
4720
+ "options: LeadInboxOptions"
4721
+ ],
4722
+ "returns": "Promise<LeadInboxResult>"
4723
+ },
4715
4724
  {
4716
4725
  "name": "mergeLeads",
4717
4726
  "async": true,
@@ -4746,6 +4755,14 @@
4746
4755
  "path": "/leads/findByOwner",
4747
4756
  "method": "POST"
4748
4757
  },
4758
+ {
4759
+ "kind": "api",
4760
+ "name": "leads.listInbox",
4761
+ "operation": "listInbox",
4762
+ "objectName": "@happyvertical/smrt-sales:LeadCollection",
4763
+ "path": "/leads/listInbox",
4764
+ "method": "POST"
4765
+ },
4749
4766
  {
4750
4767
  "kind": "api",
4751
4768
  "name": "leads.qualify",
@@ -4782,6 +4799,12 @@
4782
4799
  "operation": "findByOwner",
4783
4800
  "objectName": "@happyvertical/smrt-sales:LeadCollection"
4784
4801
  },
4802
+ {
4803
+ "kind": "cli",
4804
+ "name": "leadcollection_listInbox",
4805
+ "operation": "listInbox",
4806
+ "objectName": "@happyvertical/smrt-sales:LeadCollection"
4807
+ },
4785
4808
  {
4786
4809
  "kind": "cli",
4787
4810
  "name": "leadcollection_qualify",
@@ -4812,6 +4835,12 @@
4812
4835
  "operation": "findByOwner",
4813
4836
  "objectName": "@happyvertical/smrt-sales:LeadCollection"
4814
4837
  },
4838
+ {
4839
+ "kind": "mcp",
4840
+ "name": "leadcollection_listinbox",
4841
+ "operation": "listInbox",
4842
+ "objectName": "@happyvertical/smrt-sales:LeadCollection"
4843
+ },
4815
4844
  {
4816
4845
  "kind": "mcp",
4817
4846
  "name": "leadcollection_qualify",
@@ -12128,6 +12157,14 @@
12128
12157
  "path": "/leads/findByOwner",
12129
12158
  "method": "POST"
12130
12159
  },
12160
+ {
12161
+ "kind": "api",
12162
+ "name": "leads.listInbox",
12163
+ "operation": "listInbox",
12164
+ "objectName": "@happyvertical/smrt-sales:LeadCollection",
12165
+ "path": "/leads/listInbox",
12166
+ "method": "POST"
12167
+ },
12131
12168
  {
12132
12169
  "kind": "api",
12133
12170
  "name": "leads.qualify",
@@ -12164,6 +12201,12 @@
12164
12201
  "operation": "findByOwner",
12165
12202
  "objectName": "@happyvertical/smrt-sales:LeadCollection"
12166
12203
  },
12204
+ {
12205
+ "kind": "cli",
12206
+ "name": "leadcollection_listInbox",
12207
+ "operation": "listInbox",
12208
+ "objectName": "@happyvertical/smrt-sales:LeadCollection"
12209
+ },
12167
12210
  {
12168
12211
  "kind": "cli",
12169
12212
  "name": "leadcollection_qualify",
@@ -12194,6 +12237,12 @@
12194
12237
  "operation": "findByOwner",
12195
12238
  "objectName": "@happyvertical/smrt-sales:LeadCollection"
12196
12239
  },
12240
+ {
12241
+ "kind": "mcp",
12242
+ "name": "leadcollection_listinbox",
12243
+ "operation": "listInbox",
12244
+ "objectName": "@happyvertical/smrt-sales:LeadCollection"
12245
+ },
12197
12246
  {
12198
12247
  "kind": "mcp",
12199
12248
  "name": "leadcollection_qualify",
@@ -14306,7 +14355,7 @@
14306
14355
  "polymorphicAssociations": 0,
14307
14356
  "uuidColumns": 216
14308
14357
  },
14309
- "agentDoc": "# @happyvertical/smrt-sales\n\nModular sales: provider-neutral agreement execution, CRM, referral intake, a neutral commissions financial core, and reusable Svelte surfaces. One installable package with distinct subpath exports:\n\n- `@happyvertical/smrt-sales/agreements`\n- `@happyvertical/smrt-sales/crm`\n- `@happyvertical/smrt-sales/referrals`\n- `@happyvertical/smrt-sales/commissions`\n- `@happyvertical/smrt-sales/svelte`\n\nThe root export re-exports every TS module. `agreements` depends on the provider-neutral `@happyvertical/signatures` contract and `smrt-assets`; provider credentials stay in the injected SDK adapter/secret store. `referrals` binds its versioned terms to `agreements`.\n\n## Validation\n\nRun `pnpm --filter @happyvertical/smrt-sales test` and `pnpm --filter @happyvertical/smrt-sales typecheck` for package changes. PostgreSQL-sensitive changes must also run `pnpm --filter @happyvertical/smrt-sales test:postgres`; the command uses the repository's disposable PostgreSQL harness and is registered in the PostgreSQL CI shard.\n\n## Roles vs. money\n\nReferrers and Sales Representatives are **distinct roles** and stay that way. Both connect to money through one neutral financial account:\n\n- **Earner** (commissions): payout identity — method, threshold, currency, status. Referenced by every Commission and CommissionPayout.\n- **SalesRepresentative** (crm) and **Referrer** (referrals): role models, each holding `profileId` (cross-package string ref to smrt-profiles) and `earnerId`.\n\n## Modules\n\nPer-module semantics live in sibling module docs — read the one for the module\nyou are editing. This file keeps what holds in every module.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `agreements` | verified execution evidence — private orchestration state, append-only provider events, immutable executed agreements, and the provider-neutral execution service | [agents/agreements.md](agents/agreements.md) |\n| `commissions` | the neutral financial core — earners, versioned plans, earning events, commissions, adjustments, balances, and payout settlement | [agents/commissions.md](agents/commissions.md) |\n| `crm` | leads, configurable pipelines, opportunities, tenant-safe Lead follow-up, activity trail, and idempotent conversion links | [agents/crm.md](agents/crm.md) |\n| `referrals` | referrers, programs, versioned attribution policies, links/touches, referrals, exception review, and term snapshots | [agents/referrals.md](agents/referrals.md) |\n| `svelte` | props-driven presentational surfaces for CRM, the referrer portal, and operator review | [agents/svelte.md](agents/svelte.md) |\n\n## Currency\n\n**All monetary fields are integer cents** with `*Cents` suffixes; that scale is a Sales contract, not an implicit framework currency rule. Fresh PostgreSQL/DuckDB INTEGER columns are BIGINT, while JavaScript hydration rejects values outside the safe-integer range. Rates are decimal (`0`–`1`). Rounding happens once per calculation step via `roundCents()` (half-away-from-zero) and every Commission stores its `calculationTrace` so amounts are reproducible. Convert at display/commerce boundaries with `centsToAmount()`/`amountToCents()`.\n\n## Tenancy\n\nBusiness/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.\n\n## Cross-package references (plain strings)\n\n`profileId` → smrt-profiles Profile; `invoiceId` (CommissionPayout) → smrt-commerce Invoice; agreement artifacts → smrt-assets Asset ids. No static imports of unrelated sibling domain packages.\n\n## smrt-affiliates migration\n\n`@happyvertical/smrt-affiliates` is now a deprecated compatibility package that re-exports this package's commissions core under the legacy names (`Partner` → `Earner`, `Payout` → `CommissionPayout`, `Commission`, collections, enums) with `@deprecated` guidance and **no duplicate persistence model**. The data/API mapping lives in `packages/affiliates/MIGRATION.md`.\n\n## Gotchas\n\n- **Cents vs. rates**: `*Cents` fields are INTEGER (`= 0` defaults); `rate`/`probability`/`shareFraction` are DECIMAL (`= 0.0` defaults). Don't mix the two conventions on one field.\n- **Optional same-package foreign keys use null**: execution, supersession, earning-event, and payout references that are absent persist as `null`, never `''`; their physical database constraints remain enabled when a real id is present.\n- **Versioned terms are rows, not edits**: CommissionPlan / AttributionPolicy / ReferralAgreement amendments insert `version + 1`; active versions are save-guarded immutable.\n- **Idempotency is dedupeKey-based**: EarningEvent, Commission, and CommissionPayout carry natural keys (`conflictColumns`) — retried ingestion/settlement upserts instead of duplicating.\n- **Adjustments never rewrite**: correcting an earned/paid Commission means appending a CommissionAdjustment, not editing the Commission.\n- **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.\n- **Manifest objects must remain root-importable**: generated consumer registration imports every manifest-advertised model and collection from `@happyvertical/smrt-sales`. The `CommissionAdjustmentOperation` and `ReferralClickOperation` model/collection values are therefore root and owning-subpath exports for runtime loading, while their `api: false`, `mcp: false`, and `cli: false` decorators keep them off generated application surfaces. Publish-pack validation imports the actual CLI-generated register against the packed tarball.\n- **CommissionPayout, not Payout**: avoids the pre-existing global table-name collision between commerce `Payout` and legacy affiliates `Payout` (`payouts`).\n- **Table names are global**: new models were named to avoid collisions across packages (`commission_payouts`, `sales_activities`, `attribution_policies`, …).\n- **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`.\n- **Lead follow-up stays generic**: call `LeadWorkflowService` for audited Lead assignment/status/activity/task mutations under ambient tenancy; it locks rows, rejects cross-tenant identifiers, and has no authorization, SLA, automatic-owner, reminder, website-contact, or conversion policy. `LeadDetail` is props/callback only; host applications map state and call the service.\n- **Model transition methods don't save**: `markEarned()/approve()/markPayable()/markPaid()` (Commission) and the payout transitions mutate + stamp timestamps only — callers save; the settlement/payout services do both.\n- **`reject()` alone strands rows**: the model method only flips status — always reject through `transitionPayoutForSource`, which releases the batch's membership in the same transaction; stamped rows on a rejected payout would be unsettleable forever.\n- **The payout source stamp is derived data, never authorization**: `sourceKind`/`sourceId` on CommissionPayout index the history listing; both the listing and the lifecycle service re-verify actual membership and fail closed when the stamp cannot be proven.\n- **`sweepClearing` treats `clearingEndsAt: null` as immediately sweepable** (no clearing configured ⇒ nothing to wait for).\n- **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.\n- **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.\n- **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()`.\n- **recordClick inside your transaction participates, never nests**: a nested adapter `transaction()` takes an independent pooled connection (happyvertical/sdk#1108) — it deadlocks undetectably on locks the caller's transaction holds (PostgreSQL sees a promise-wait, not a lock-wait) and refuses caller-created uncommitted links as `unknown_code`. Pass the caller's transaction database as `RecordClickInput.transaction` or bind the collection to it (`{ db: tx, _reuseInitializedDb: true, _deferRuntimeInitialization: true }` — detected and honored automatically); a pool-level database passed as `transaction` is refused with typed reason `invalid_transaction`. Participating results are bound to the caller's transaction — carry ids across the commit boundary; the pool-bound default still self-transacts and rehydrates after commit.\n- **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.\n- **`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.\n",
14358
+ "agentDoc": "# @happyvertical/smrt-sales\n\nModular sales: provider-neutral agreement execution, CRM, referral intake, a neutral commissions financial core, and reusable Svelte surfaces. One installable package with distinct subpath exports:\n\n- `@happyvertical/smrt-sales/agreements`\n- `@happyvertical/smrt-sales/crm`\n- `@happyvertical/smrt-sales/referrals`\n- `@happyvertical/smrt-sales/commissions`\n- `@happyvertical/smrt-sales/svelte`\n\nThe root export re-exports every TS module. `agreements` depends on the provider-neutral `@happyvertical/signatures` contract and `smrt-assets`; provider credentials stay in the injected SDK adapter/secret store. `referrals` binds its versioned terms to `agreements`.\n\n## Validation\n\nRun `pnpm --filter @happyvertical/smrt-sales test` and `pnpm --filter @happyvertical/smrt-sales typecheck` for package changes. PostgreSQL-sensitive changes must also run `pnpm --filter @happyvertical/smrt-sales test:postgres`; the command uses the repository's disposable PostgreSQL harness and is registered in the PostgreSQL CI shard.\n\n## Roles vs. money\n\nReferrers and Sales Representatives are **distinct roles** and stay that way. Both connect to money through one neutral financial account:\n\n- **Earner** (commissions): payout identity — method, threshold, currency, status. Referenced by every Commission and CommissionPayout.\n- **SalesRepresentative** (crm) and **Referrer** (referrals): role models, each holding `profileId` (cross-package string ref to smrt-profiles) and `earnerId`.\n\n## Modules\n\nPer-module semantics live in sibling module docs — read the one for the module\nyou are editing. This file keeps what holds in every module.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `agreements` | verified execution evidence — private orchestration state, append-only provider events, immutable executed agreements, and the provider-neutral execution service | [agents/agreements.md](agents/agreements.md) |\n| `commissions` | the neutral financial core — earners, versioned plans, earning events, commissions, adjustments, balances, and payout settlement | [agents/commissions.md](agents/commissions.md) |\n| `crm` | leads, configurable pipelines, opportunities, tenant-safe Lead follow-up, activity trail, and idempotent conversion links | [agents/crm.md](agents/crm.md) |\n| `referrals` | referrers, programs, versioned attribution policies, links/touches, referrals, exception review, and term snapshots | [agents/referrals.md](agents/referrals.md) |\n| `svelte` | props-driven presentational surfaces for CRM, the referrer portal, and operator review | [agents/svelte.md](agents/svelte.md) |\n\n## Currency\n\n**All monetary fields are integer cents** with `*Cents` suffixes; that scale is a Sales contract, not an implicit framework currency rule. Fresh PostgreSQL/DuckDB INTEGER columns are BIGINT, while JavaScript hydration rejects values outside the safe-integer range. Rates are decimal (`0`–`1`). Rounding happens once per calculation step via `roundCents()` (half-away-from-zero) and every Commission stores its `calculationTrace` so amounts are reproducible. Convert at display/commerce boundaries with `centsToAmount()`/`amountToCents()`.\n\n## Tenancy\n\nBusiness/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.\n\n## Cross-package references (plain strings)\n\n`profileId` → smrt-profiles Profile; `invoiceId` (CommissionPayout) → smrt-commerce Invoice; agreement artifacts → smrt-assets Asset ids. No static imports of unrelated sibling domain packages.\n\n## smrt-affiliates migration\n\n`@happyvertical/smrt-affiliates` is now a deprecated compatibility package that re-exports this package's commissions core under the legacy names (`Partner` → `Earner`, `Payout` → `CommissionPayout`, `Commission`, collections, enums) with `@deprecated` guidance and **no duplicate persistence model**. The data/API mapping lives in `packages/affiliates/MIGRATION.md`.\n\n## Gotchas\n\n- **Cents vs. rates**: `*Cents` fields are INTEGER (`= 0` defaults); `rate`/`probability`/`shareFraction` are DECIMAL (`= 0.0` defaults). Don't mix the two conventions on one field.\n- **Optional same-package foreign keys use null**: execution, supersession, earning-event, and payout references that are absent persist as `null`, never `''`; their physical database constraints remain enabled when a real id is present.\n- **Versioned terms are rows, not edits**: CommissionPlan / AttributionPolicy / ReferralAgreement amendments insert `version + 1`; active versions are save-guarded immutable.\n- **Idempotency is dedupeKey-based**: EarningEvent, Commission, and CommissionPayout carry natural keys (`conflictColumns`) — retried ingestion/settlement upserts instead of duplicating.\n- **Adjustments never rewrite**: correcting an earned/paid Commission means appending a CommissionAdjustment, not editing the Commission.\n- **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.\n- **Manifest objects must remain root-importable**: generated consumer registration imports every manifest-advertised model and collection from `@happyvertical/smrt-sales`. The `CommissionAdjustmentOperation` and `ReferralClickOperation` model/collection values are therefore root and owning-subpath exports for runtime loading, while their `api: false`, `mcp: false`, and `cli: false` decorators keep them off generated application surfaces. Publish-pack validation imports the actual CLI-generated register against the packed tarball.\n- **CommissionPayout, not Payout**: avoids the pre-existing global table-name collision between commerce `Payout` and legacy affiliates `Payout` (`payouts`).\n- **Table names are global**: new models were named to avoid collisions across packages (`commission_payouts`, `sales_activities`, `attribution_policies`, …).\n- **Svelte validation includes interaction contracts**: intake and inbox components have runtime component tests; `typecheck` also runs `svelte-check` via `scripts/svelte-check-a11y.mjs`. Ship raw `.svelte` via `svelte-package`.\n- **Lead workflows stay generic**: call `LeadWorkflowService` for tenant-safe intake, audited follow-up, qualification, and opportunity closure with optional conversion links. The service delegates CRM lifecycles to their owning collections; hosts supply authorization and create downstream records. `LeadCreateForm`, `LeadList`, and `LeadDetail` are props/callback only. See [agents/crm.md](agents/crm.md) for dedupe, retry, and inbox contracts.\n- **Model transition methods don't save**: `markEarned()/approve()/markPayable()/markPaid()` (Commission) and the payout transitions mutate + stamp timestamps only — callers save; the settlement/payout services do both.\n- **`reject()` alone strands rows**: the model method only flips status — always reject through `transitionPayoutForSource`, which releases the batch's membership in the same transaction; stamped rows on a rejected payout would be unsettleable forever.\n- **The payout source stamp is derived data, never authorization**: `sourceKind`/`sourceId` on CommissionPayout index the history listing; both the listing and the lifecycle service re-verify actual membership and fail closed when the stamp cannot be proven.\n- **`sweepClearing` treats `clearingEndsAt: null` as immediately sweepable** (no clearing configured ⇒ nothing to wait for).\n- **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.\n- **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.\n- **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()`.\n- **recordClick inside your transaction participates, never nests**: a nested adapter `transaction()` takes an independent pooled connection (happyvertical/sdk#1108) — it deadlocks undetectably on locks the caller's transaction holds (PostgreSQL sees a promise-wait, not a lock-wait) and refuses caller-created uncommitted links as `unknown_code`. Pass the caller's transaction database as `RecordClickInput.transaction` or bind the collection to it (`{ db: tx, _reuseInitializedDb: true, _deferRuntimeInitialization: true }` — detected and honored automatically); a pool-level database passed as `transaction` is refused with typed reason `invalid_transaction`. Participating results are bound to the caller's transaction — carry ids across the commit boundary; the pool-bound default still self-transacts and rehydrates after commit.\n- **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.\n- **`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.\n",
14310
14359
  "moduleDocs": [
14311
14360
  {
14312
14361
  "path": "agents/agreements.md",
@@ -14321,7 +14370,7 @@
14321
14370
  {
14322
14371
  "path": "agents/crm.md",
14323
14372
  "module": "crm",
14324
- "content": "# smrt-sales/crm\n\nPer-module semantics for `@happyvertical/smrt-sales/crm`. Package orientation, the\ncross-module invariants (currency, tenancy, cross-package refs, roles vs.\nmoney), and the Gotchas that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\n- **SalesRepresentative**: role model (`profileId`, `earnerId`, `status`).\n- **Lead**: identified prospect with owner assignment, generic acquisition source (`sourceKind`/`sourceId`), preserved `acquisitionContext` JSON, and audited merge (`mergedIntoId`; merges preserve activity + acquisition history on both sides).\n- **PipelineDefinition / PipelineStage**: configurable ordered stages with default `new → qualified → discovery → proposal → negotiation → closed_won | closed_lost` (seeded via `ensureDefaultPipeline()`); stages carry `probability` and `isWon`/`isLost` terminal flags.\n- **Opportunity**: qualified engagement — owner, pipeline + stage, `expectedValueCents`, `probability`, `expectedCloseAt`, outcome. Stage movement validated against the pipeline; terminal stages set `won|lost` status.\n- **SalesActivity**: activity/next-action trail for Leads and Opportunities (`subjectKind`/`subjectId`), also the audit trail for assignment, qualification, merges, and stage movement.\n- **LeadWorkflowService**: the required tenant-safe pre-qualification mutation seam. It transactionally locks the Lead (and completion task on PostgreSQL), accepts only active same-tenant representatives, writes assignment/status/completion audits alongside their mutations, and returns merge-aware timeline/work-state reads. It owns `new | disqualified → working`, `new | working → disqualified`, human follow-up (`note | call | email | meeting`), and task scheduling/completion; qualification and merging remain collection lifecycles.\n- **OpportunityConversion**: idempotent conversion links (`targetKind`/`targetId` — client, project, contract, subscription, …) with a composite natural key. CRM never creates downstream records itself and never mutates referral or commission state.\n\nWorkflow calls require ambient tenant context and an actor profile id. Fail closed for foreign/missing Lead, representative, and task ids without revealing their existence. Human metadata is plain JSON-object data; framework audit metadata is generated separately. Keep queue projection pure (`now`/optional timezone are injected) and do not add assignment, reminder, SLA, authorization, or conversion policy.\n"
14373
+ "content": "# smrt-sales/crm\n\nPer-module semantics for `@happyvertical/smrt-sales/crm`. Package orientation, the\ncross-module invariants (currency, tenancy, cross-package refs, roles vs.\nmoney), and the Gotchas that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\n- **SalesRepresentative**: role model (`profileId`, `earnerId`, `status`).\n- **Lead**: identified prospect with owner assignment, generic acquisition source (`sourceKind`/`sourceId`), preserved `acquisitionContext` JSON, and audited merge (`mergedIntoId`; merges preserve activity + acquisition history on both sides).\n- **PipelineDefinition / PipelineStage**: configurable ordered stages with default `new → qualified → discovery → proposal → negotiation → closed_won | closed_lost` (seeded via `ensureDefaultPipeline()`); stages carry `probability` and `isWon`/`isLost` terminal flags.\n- **Opportunity**: qualified engagement — owner, pipeline + stage, `expectedValueCents`, `probability`, `expectedCloseAt`, outcome. Stage movement validated against the pipeline; terminal stages set `won|lost` status.\n- **SalesActivity**: activity/next-action trail for Leads and Opportunities (`subjectKind`/`subjectId`), also the audit trail for assignment, qualification, merges, and stage movement.\n- **LeadWorkflowService**: the required tenant-safe Lead mutation seam. It transactionally locks the affected Lead/Opportunity (and completion task on PostgreSQL), accepts only active same-tenant representatives, writes actor-attributed audits alongside mutations, and returns merge-aware timeline/work-state reads. It owns validated `createLead()` intake, `new | disqualified → working`, `new | working → disqualified`, human follow-up (`note | call | email | meeting`), task scheduling/completion, delegated qualification, and terminal Opportunity closure/conversion. Merging remains a collection lifecycle.\n- **OpportunityConversion**: idempotent conversion links (`targetKind`/`targetId` — client, project, contract, subscription, …) with a composite natural key. CRM never creates downstream records itself and never mutates referral or commission state.\n\nWorkflow calls require ambient tenant context. Mutations require an actor profile\nid except `createLead()`, whose actor is optional for automated intake. Fail\nclosed for foreign/missing Lead, Opportunity, pipeline, representative, and task\nids without revealing their existence. Human metadata is plain JSON-object data;\nframework audit metadata is generated separately. Keep queue projection pure\n(`now`/optional timezone are injected) and do not add assignment, reminder, SLA,\nauthorization, or conversion policy.\n\n`createLead()` requires a name and at least one of email/phone, normalizes email,\nand supports `none | email | email_or_org` tenant-local dedupe. Active duplicates\nreturn the existing Lead and append one `inbound` activity per novel normalized\nintent; terminal matches return `{ created: false, duplicateOf }` without mutation\nso the caller must explicitly retry with `dedupe: 'none'` to create another Lead.\nCaller idempotency keys are persisted through deterministic immutable activity\nids, including when intake resolves to an existing active Lead; reusing a key\nwith changed intent is rejected. PostgreSQL uses transaction advisory/row locks,\nwhile single-connection adapters serialize the entire transaction.\n\n`qualifyLead()` delegates to `LeadCollection.qualify()` on the same transaction\nexecutor and returns whether the Opportunity was newly created. `closeOpportunity()`\nresolves the configured won/lost terminal stage, delegates stage movement, and\noptionally records a won conversion in the same transaction. Exact retries add\nno stage activity or conversion. `getLeadWorkState()` includes the canonical\nlinked Opportunity when present. Transaction-bound result models are reloaded\non the service database after commit before being returned.\n\n`LeadCollection.listInbox({ status?, ownerRepId?, unassigned?, overdue?,\nsearch?, sort?, limit, offset, now? })` requires ambient tenancy and returns\n`{ leads, items, total, statusCounts }`. `leads` are hydrated Lead rows; aligned\n`items` add the hydrated owner and earliest open Lead task as `nextAction`\nwithout per-row queries. Search trims and performs case-insensitive literal\nsubstring matching across name, contact name, email, phone, and organization.\nSort values are `created | next_action | name`, all with stable id tie-breaks;\nmissing next actions sort last. `limit` is required from 1–100 and `offset` is\nrequired and nonnegative.\n\n`total` applies every selected filter. `statusCounts` contains `all`, every Lead\nstatus, `unassigned`, and `overdue`; counts retain owner/search scope but ignore\nthe selected status/unassigned/overdue tab. Overdue means the earliest open task\nis due strictly before injected `now` (default current time).\n"
14325
14374
  },
14326
14375
  {
14327
14376
  "path": "agents/referrals.md",
@@ -14331,7 +14380,7 @@
14331
14380
  {
14332
14381
  "path": "agents/svelte.md",
14333
14382
  "module": "svelte",
14334
- "content": "# smrt-sales/svelte\n\nPer-module semantics for `@happyvertical/smrt-sales/svelte`. Package orientation, the\ncross-module invariants (currency, tenancy, cross-package refs, roles vs.\nmoney), and the Gotchas that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\nProps-driven presentational components (no data fetching, no model-class imports, Provider-free smrt-ui primitives, `--smrt-*` tokens only): CRM — `SalesDashboard`, `LeadList`, `LeadDetail`, `OpportunityBoard`, `OpportunityDetail`; referrer portal — `ReferralLinkManager`, `ReferralStatusList`, `ReferrerEarningsSummary`, `CommissionBreakdown` (trace-explained amounts), `PayoutHistoryList`, `ExecutedAgreementsList`; operator — `AttributionConflictQueue` (award editor + required resolution reason), `PayoutBatchReview`, `CommissionExpenseSummary` (explicitly distinct from client invoices). Monetary props stay integer cents; `format.ts` converts at render. View-model prop types are exported interfaces (never inline intersected generics in `$props()`); pure helpers (dashboard math, Lead workflow action gating, award validation mirroring the service, payout action gating) are unit-tested while components are svelte-check-gated.\n\n`LeadDetail` renders a complete host-supplied timeline and callback-only assignment, start/reopen, disqualification, human-activity, next-action, completion, and optional qualification controls. Its `busy` prop disables every mutation affordance; the host owns tenancy, authorization, service invocation, refresh, and any policy around due dates or reminders.\n"
14383
+ "content": "# smrt-sales/svelte\n\nPer-module semantics for `@happyvertical/smrt-sales/svelte`. Package orientation, the\ncross-module invariants (currency, tenancy, cross-package refs, roles vs.\nmoney), and the Gotchas that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\nProps-driven presentational components (no data fetching, no model-class imports, Provider-free smrt-ui primitives, `--smrt-*` tokens only): CRM — `SalesDashboard`, `LeadCreateForm`, `LeadList`, `LeadDetail`, `OpportunityBoard`, `OpportunityDetail`; referrer portal — `ReferralLinkManager`, `ReferralStatusList`, `ReferrerEarningsSummary`, `CommissionBreakdown` (trace-explained amounts), `PayoutHistoryList`, `ExecutedAgreementsList`; operator — `AttributionConflictQueue` (award editor + required resolution reason), `PayoutBatchReview`, `CommissionExpenseSummary` (explicitly distinct from client invoices). Monetary props stay integer cents; `format.ts` converts at render. View-model prop types are exported interfaces (never inline intersected generics in `$props()`). Pure helpers are unit-tested and Lead intake/inbox/detail components have mounted runtime tests through `vitest.ui.config.ts`.\n\n`LeadCreateForm` validates the required name and email-or-phone contact rule, normalizes email, and emits a host-mapped `LeadCreateDraft`; source choices, duplicate results, errors, and owner choices come from the host. `LeadList` accepts controlled filter/count, sort, page, selection/link, and row-action props; the host owns inbox fetching and callback mutations. `LeadDetail` renders a complete host-supplied timeline, an optional linked-opportunity summary snippet, and callback-only assignment, start/reopen, disqualification, human-activity, next-action, completion, and qualification controls. Its `busy` prop disables every mutation affordance; the host owns tenancy, authorization, service invocation, refresh, and any policy around due dates or reminders.\n"
14335
14384
  }
14336
14385
  ]
14337
14386
  }
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ /**
3
+ * LeadCreateForm — host-driven manual Lead intake surface.
4
+ *
5
+ * The host supplies source and owner choices, handles persistence and dedupe,
6
+ * and reports any duplicate or service error back through props.
7
+ */
8
+ import { Form, FormGroup, Input, Select } from '@happyvertical/smrt-ui/forms';
9
+ import { Button } from '@happyvertical/smrt-ui/ui';
10
+ import type {
11
+ LeadCreateDraft,
12
+ LeadListItemView,
13
+ LeadSourceOptionView,
14
+ SalesRepOptionView,
15
+ } from '../types.js';
16
+
17
+ /** Props for {@link LeadCreateForm}. */
18
+ export interface Props {
19
+ /** Representatives available for initial assignment. */
20
+ reps?: SalesRepOptionView[];
21
+ /** Open-string acquisition source choices supplied by the host. */
22
+ sourceOptions?: LeadSourceOptionView[];
23
+ /** Disable submission and editing during a host mutation. */
24
+ busy?: boolean;
25
+ /** Host-reported create failure. */
26
+ error?: string;
27
+ /** Possible duplicate returned by host dedupe. */
28
+ duplicate?: LeadListItemView;
29
+ /** Create a lead using the validated form draft. */
30
+ onSubmit?: (draft: LeadCreateDraft) => void;
31
+ /** Cancel intake. */
32
+ onCancel?: () => void;
33
+ }
34
+
35
+ let {
36
+ reps = [],
37
+ sourceOptions = [],
38
+ busy = false,
39
+ error,
40
+ duplicate,
41
+ onSubmit,
42
+ onCancel,
43
+ }: Props = $props();
44
+
45
+ let name = $state('');
46
+ let email = $state('');
47
+ let contactName = $state('');
48
+ let phone = $state('');
49
+ let organizationName = $state('');
50
+ let sourceKind = $state('');
51
+ let sourceId = $state('');
52
+ let ownerRepId = $state('');
53
+ let validationError = $state('');
54
+
55
+ const validEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
56
+
57
+ function submit() {
58
+ if (busy) return;
59
+ const normalizedName = name.trim();
60
+ const normalizedEmail = email.trim().toLowerCase();
61
+ if (!normalizedName) {
62
+ validationError = 'Name is required.';
63
+ return;
64
+ }
65
+ if (!normalizedEmail && !phone.trim()) {
66
+ validationError = 'Enter an email address or phone number.';
67
+ return;
68
+ }
69
+ if (normalizedEmail && !validEmail(normalizedEmail)) {
70
+ validationError = 'Enter a valid email address.';
71
+ return;
72
+ }
73
+ validationError = '';
74
+ onSubmit?.({
75
+ name: normalizedName,
76
+ email: normalizedEmail || undefined,
77
+ contactName: contactName.trim() || undefined,
78
+ phone: phone.trim() || undefined,
79
+ organizationName: organizationName.trim() || undefined,
80
+ sourceKind: sourceKind || sourceOptions[0]?.value || 'manual',
81
+ sourceId: sourceId.trim() || undefined,
82
+ ownerRepId: ownerRepId || undefined,
83
+ });
84
+ }
85
+ </script>
86
+
87
+ <section class="sales-lead-create" aria-label="Create lead">
88
+ <Form class="sales-lead-create__form" onsubmit={submit}>
89
+ <FormGroup label="Lead name" required>
90
+ <Input value={name} required disabled={busy} oninput={(event) => (name = event.currentTarget.value)} />
91
+ </FormGroup>
92
+ <FormGroup label="Email">
93
+ <Input type="email" value={email} disabled={busy} oninput={(event) => (email = event.currentTarget.value)} />
94
+ </FormGroup>
95
+ <FormGroup label="Phone">
96
+ <Input type="tel" value={phone} disabled={busy} oninput={(event) => (phone = event.currentTarget.value)} />
97
+ </FormGroup>
98
+ <FormGroup label="Contact name">
99
+ <Input value={contactName} disabled={busy} oninput={(event) => (contactName = event.currentTarget.value)} />
100
+ </FormGroup>
101
+ <FormGroup label="Organization">
102
+ <Input value={organizationName} disabled={busy} oninput={(event) => (organizationName = event.currentTarget.value)} />
103
+ </FormGroup>
104
+ {#if sourceOptions.length > 0}
105
+ <FormGroup label="Source">
106
+ <Select value={sourceKind || sourceOptions[0].value} disabled={busy} onchange={(event) => (sourceKind = event.currentTarget.value)}>
107
+ {#each sourceOptions as option (option.value)}
108
+ <option value={option.value}>{option.label}</option>
109
+ {/each}
110
+ </Select>
111
+ </FormGroup>
112
+ {/if}
113
+ <FormGroup label="Source reference">
114
+ <Input value={sourceId} disabled={busy} oninput={(event) => (sourceId = event.currentTarget.value)} />
115
+ </FormGroup>
116
+ {#if reps.length > 0}
117
+ <FormGroup label="Owner">
118
+ <Select value={ownerRepId} disabled={busy} onchange={(event) => (ownerRepId = event.currentTarget.value)}>
119
+ <option value="">Unassigned</option>
120
+ {#each reps as rep (rep.id)}
121
+ <option value={rep.id}>{rep.name}</option>
122
+ {/each}
123
+ </Select>
124
+ </FormGroup>
125
+ {/if}
126
+ {#if validationError || error}
127
+ <p class="sales-lead-create__error" role="alert">{validationError || error}</p>
128
+ {/if}
129
+ {#if duplicate}
130
+ <p class="sales-lead-create__duplicate" role="status">Possible duplicate: {duplicate.name}</p>
131
+ {/if}
132
+ <div class="sales-lead-create__actions">
133
+ {#if onCancel}
134
+ <Button type="button" variant="secondary" disabled={busy} onclick={() => onCancel?.()}>Cancel</Button>
135
+ {/if}
136
+ <Button type="submit" variant="primary" disabled={busy}>Create lead</Button>
137
+ </div>
138
+ </Form>
139
+ </section>
140
+
141
+ <style>
142
+ :global(.sales-lead-create__form) { display: grid; gap: var(--smrt-spacing-3, 0.75rem); }
143
+ .sales-lead-create__actions { display: flex; justify-content: flex-end; gap: var(--smrt-spacing-2, 0.5rem); }
144
+ .sales-lead-create__error { color: var(--smrt-color-error, #dc2626); margin: 0; }
145
+ .sales-lead-create__duplicate { color: var(--smrt-color-warning, #a16207); margin: 0; }
146
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { LeadCreateDraft, LeadListItemView, LeadSourceOptionView, SalesRepOptionView } from '../types.js';
2
+ /** Props for {@link LeadCreateForm}. */
3
+ export interface Props {
4
+ /** Representatives available for initial assignment. */
5
+ reps?: SalesRepOptionView[];
6
+ /** Open-string acquisition source choices supplied by the host. */
7
+ sourceOptions?: LeadSourceOptionView[];
8
+ /** Disable submission and editing during a host mutation. */
9
+ busy?: boolean;
10
+ /** Host-reported create failure. */
11
+ error?: string;
12
+ /** Possible duplicate returned by host dedupe. */
13
+ duplicate?: LeadListItemView;
14
+ /** Create a lead using the validated form draft. */
15
+ onSubmit?: (draft: LeadCreateDraft) => void;
16
+ /** Cancel intake. */
17
+ onCancel?: () => void;
18
+ }
19
+ declare const LeadCreateForm: import("svelte").Component<Props, {}, "">;
20
+ type LeadCreateForm = ReturnType<typeof LeadCreateForm>;
21
+ export default LeadCreateForm;
22
+ //# sourceMappingURL=LeadCreateForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeadCreateForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/LeadCreateForm.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAGrB,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,wDAAwD;IACxD,IAAI,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5B,mEAAmE;IACnE,aAAa,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACvC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAsHD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -15,10 +15,12 @@ import {
15
15
  Textarea,
16
16
  } from '@happyvertical/smrt-ui/forms';
17
17
  import { Badge, Button } from '@happyvertical/smrt-ui/ui';
18
+ import type { Snippet } from 'svelte';
18
19
  import { formatDate } from '../format.js';
19
20
  import type {
20
21
  LeadDetailView,
21
22
  LeadHumanActivityDraft,
23
+ LeadLinkedOpportunityView,
22
24
  LeadNextActionDraft,
23
25
  SalesActivityView,
24
26
  SalesRepOptionView,
@@ -54,6 +56,10 @@ export interface Props {
54
56
  onCompleteNextAction?: (leadId: string, taskId: string) => void;
55
57
  /** Delegate qualification to the host's existing LeadCollection lifecycle. */
56
58
  onQualify?: (leadId: string) => void;
59
+ /** Linked opportunity supplied by the host for qualified leads. */
60
+ opportunity?: LeadLinkedOpportunityView;
61
+ /** Optional host-rendered linked-opportunity summary. */
62
+ opportunitySummary?: Snippet<[LeadLinkedOpportunityView]>;
57
63
  }
58
64
 
59
65
  let {
@@ -69,6 +75,8 @@ let {
69
75
  onScheduleNextAction,
70
76
  onCompleteNextAction,
71
77
  onQualify,
78
+ opportunity,
79
+ opportunitySummary,
72
80
  }: Props = $props();
73
81
 
74
82
  const actions = $derived(leadWorkflowActionsFor(lead.status));
@@ -166,6 +174,23 @@ function isOpenTask(activity: SalesActivityView): boolean {
166
174
  <p class="merged-note">This lead was merged into <code>{lead.mergedIntoId}</code>.</p>
167
175
  {/if}
168
176
 
177
+ {#if opportunity}
178
+ <section class="linked-opportunity" aria-label="Linked opportunity">
179
+ {#if opportunitySummary}
180
+ {@render opportunitySummary(opportunity)}
181
+ {:else}
182
+ <h3>Opportunity</h3>
183
+ {#if opportunity.href}
184
+ <a href={opportunity.href}>{opportunity.name}</a>
185
+ {:else}
186
+ <span>{opportunity.name}</span>
187
+ {/if}
188
+ <Badge variant={opportunity.status === 'won' ? 'success' : opportunity.status === 'lost' ? 'error' : 'default'} size="sm">{opportunity.status}</Badge>
189
+ {#if opportunity.stageName}<span class="secondary">{opportunity.stageName}</span>{/if}
190
+ {/if}
191
+ </section>
192
+ {/if}
193
+
169
194
  {#if actions.canAssign || actions.canStartWorking || actions.canDisqualify || actions.canQualify}
170
195
  <section class="controls" aria-label="Lead workflow actions">
171
196
  {#if actions.canAssign && onAssign && reps.length > 0}
@@ -407,6 +432,9 @@ function isOpenTask(activity: SalesActivityView): boolean {
407
432
  overflow-wrap: anywhere;
408
433
  }
409
434
 
435
+ .linked-opportunity { display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-2, 0.5rem); padding: var(--smrt-spacing-3, 0.75rem); border: 1px solid var(--smrt-color-outline-variant, #d8dde6); border-radius: var(--smrt-radius-md, 0.5rem); }
436
+ .linked-opportunity h3 { flex-basis: 100%; font-size: var(--smrt-typography-title-small-size, 1rem); }
437
+
410
438
  .controls,
411
439
  .sales-lead-detail :global(.composer__form) {
412
440
  display: flex;
@@ -1,4 +1,5 @@
1
- import type { LeadDetailView, LeadHumanActivityDraft, LeadNextActionDraft, SalesActivityView, SalesRepOptionView } from '../types.js';
1
+ import type { Snippet } from 'svelte';
2
+ import type { LeadDetailView, LeadHumanActivityDraft, LeadLinkedOpportunityView, LeadNextActionDraft, SalesActivityView, SalesRepOptionView } from '../types.js';
2
3
  export interface Props {
3
4
  /** Lead header and contact data. */
4
5
  lead: LeadDetailView;
@@ -24,6 +25,10 @@ export interface Props {
24
25
  onCompleteNextAction?: (leadId: string, taskId: string) => void;
25
26
  /** Delegate qualification to the host's existing LeadCollection lifecycle. */
26
27
  onQualify?: (leadId: string) => void;
28
+ /** Linked opportunity supplied by the host for qualified leads. */
29
+ opportunity?: LeadLinkedOpportunityView;
30
+ /** Optional host-rendered linked-opportunity summary. */
31
+ opportunitySummary?: Snippet<[LeadLinkedOpportunityView]>;
27
32
  }
28
33
  declare const LeadDetail: import("svelte").Component<Props, {}, "">;
29
34
  type LeadDetail = ReturnType<typeof LeadDetail>;
@@ -1 +1 @@
1
- {"version":3,"file":"LeadDetail.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/LeadDetail.svelte.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAQrB,MAAM,WAAW,KAAK;IACpB,oCAAoC;IACpC,IAAI,EAAE,cAAc,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACjC,wCAAwC;IACxC,IAAI,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5B,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,sDAAsD;IACtD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC3E,oCAAoC;IACpC,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5E,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAmQD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"LeadDetail.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/LeadDetail.svelte.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAQrB,MAAM,WAAW,KAAK;IACpB,oCAAoC;IACpC,IAAI,EAAE,cAAc,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACjC,wCAAwC;IACxC,IAAI,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5B,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,sDAAsD;IACtD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC3E,oCAAoC;IACpC,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5E,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,mEAAmE;IACnE,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,OAAO,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;CAC3D;AAuRD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}