@happyvertical/smrt-sales 0.57.21 → 0.57.23
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 +8 -0
- package/README.md +17 -0
- package/agents/agreements.md +32 -0
- package/agents/crm.md +173 -0
- package/agents/svelte.md +7 -0
- package/dist/agreements/collections/AgreementCollection.d.ts +3 -2
- package/dist/agreements/collections/AgreementCollection.d.ts.map +1 -1
- package/dist/agreements/models/Agreement.d.ts.map +1 -1
- package/dist/agreements/services/AgreementExecutionService.d.ts +9 -0
- package/dist/agreements/services/AgreementExecutionService.d.ts.map +1 -1
- package/dist/agreements/types.d.ts +2 -0
- package/dist/agreements/types.d.ts.map +1 -1
- package/dist/agreements.js +2 -2
- package/dist/chunks/{__smrt-register__-Cg4G6dIN.js → __smrt-register__-BRrJGGpG.js} +2 -2
- package/dist/chunks/{__smrt-register__-Cg4G6dIN.js.map → __smrt-register__-BRrJGGpG.js.map} +1 -1
- package/dist/chunks/{agreements-jBiVmttD.js → agreements-RsyRLeDn.js} +35 -16
- package/dist/chunks/agreements-RsyRLeDn.js.map +1 -0
- package/dist/chunks/{crm-DaUkdNoG.js → crm-BENK-hHd.js} +976 -116
- package/dist/chunks/crm-BENK-hHd.js.map +1 -0
- package/dist/commissions.js +1 -1
- package/dist/crm/collections/SalesGovernanceEventCollection.d.ts +7 -0
- package/dist/crm/collections/SalesGovernanceEventCollection.d.ts.map +1 -0
- package/dist/crm/collections/SalesGovernanceStateCollection.d.ts +13 -0
- package/dist/crm/collections/SalesGovernanceStateCollection.d.ts.map +1 -0
- package/dist/crm/index.d.ts +9 -1
- package/dist/crm/index.d.ts.map +1 -1
- package/dist/crm/models/SalesGovernanceEvent.d.ts +19 -0
- package/dist/crm/models/SalesGovernanceEvent.d.ts.map +1 -0
- package/dist/crm/models/SalesGovernanceState.d.ts +17 -0
- package/dist/crm/models/SalesGovernanceState.d.ts.map +1 -0
- package/dist/crm/services/LeadConversionService.d.ts +49 -0
- package/dist/crm/services/LeadConversionService.d.ts.map +1 -0
- package/dist/crm/services/LeadWorkflowService.d.ts +11 -0
- package/dist/crm/services/LeadWorkflowService.d.ts.map +1 -1
- package/dist/crm/services/SalesGovernanceService.d.ts +68 -0
- package/dist/crm/services/SalesGovernanceService.d.ts.map +1 -0
- package/dist/crm/services/governance-write.d.ts +4 -0
- package/dist/crm/services/governance-write.d.ts.map +1 -0
- package/dist/crm/services/sales-governance-types.d.ts +108 -0
- package/dist/crm/services/sales-governance-types.d.ts.map +1 -0
- package/dist/crm.js +3 -3
- package/dist/index.js +4 -4
- package/dist/manifest.json +522 -1
- package/dist/referrals.js +1 -1
- package/dist/smrt-knowledge.json +274 -12
- package/dist/svelte/components/SellerGovernancePanel.svelte +65 -0
- package/dist/svelte/components/SellerGovernancePanel.svelte.d.ts +13 -0
- package/dist/svelte/components/SellerGovernancePanel.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +2 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/types.d.ts +19 -0
- package/dist/svelte/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/chunks/agreements-jBiVmttD.js.map +0 -1
- package/dist/chunks/crm-DaUkdNoG.js.map +0 -1
package/dist/smrt-knowledge.json
CHANGED
|
@@ -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.57.
|
|
6
|
+
"packageVersion": "0.57.23",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
13
|
-
"moduleDoc:agents/agreements.md": "
|
|
10
|
+
"manifest": "6ac9aa2863dc68114f69f3eb515501eccd996b7bedaa7c2651b0420023fb838e",
|
|
11
|
+
"packageJson": "d3fa1404c45031983aea881dd9b0953f7ef8335d9701519f9c64c7bdaa932ce4",
|
|
12
|
+
"agents": "561dfe5741f74b8f105617a907b5a17446d4546f6fdef7eb754956bfd09aedd9",
|
|
13
|
+
"moduleDoc:agents/agreements.md": "86609271b56633c1b6a3d9b1905a200cad1cb15de54adb8ca9f1200f6af8b9ce",
|
|
14
14
|
"moduleDoc:agents/commissions.md": "ba06d133f2cbee14ea9a5f18b632f155e3484c4cd721ed5ac993436397198cf8",
|
|
15
|
-
"moduleDoc:agents/crm.md": "
|
|
15
|
+
"moduleDoc:agents/crm.md": "074eff9e3ca7ae6d86e65a1d29ac7b9b555b719b3801918678b15df7538f99e0",
|
|
16
16
|
"moduleDoc:agents/referrals.md": "ef48c88e63155e3d08b9003f4643bc66ebb1942f1fb713252dc042ddcaad38e5",
|
|
17
|
-
"moduleDoc:agents/svelte.md": "
|
|
17
|
+
"moduleDoc:agents/svelte.md": "75e26f56185b5c2ce3895d9969522405131efcee4af4f5aaa66232a26fb27c8b"
|
|
18
18
|
},
|
|
19
19
|
"exports": [
|
|
20
20
|
".",
|
|
@@ -5736,6 +5736,95 @@
|
|
|
5736
5736
|
"tags": [],
|
|
5737
5737
|
"risks": []
|
|
5738
5738
|
},
|
|
5739
|
+
{
|
|
5740
|
+
"name": "SalesGovernanceEventCollection",
|
|
5741
|
+
"qualifiedName": "@happyvertical/smrt-sales:SalesGovernanceEventCollection",
|
|
5742
|
+
"collection": "salesgovernanceevents",
|
|
5743
|
+
"tableName": "sales_governance_events",
|
|
5744
|
+
"packageName": "@happyvertical/smrt-sales",
|
|
5745
|
+
"extends": "SmrtCollection",
|
|
5746
|
+
"fields": [],
|
|
5747
|
+
"relationships": [],
|
|
5748
|
+
"methods": [],
|
|
5749
|
+
"surfaces": [],
|
|
5750
|
+
"relationshipFeatures": [
|
|
5751
|
+
"uuidColumns"
|
|
5752
|
+
],
|
|
5753
|
+
"tags": [],
|
|
5754
|
+
"risks": []
|
|
5755
|
+
},
|
|
5756
|
+
{
|
|
5757
|
+
"name": "SalesGovernanceStateCollection",
|
|
5758
|
+
"qualifiedName": "@happyvertical/smrt-sales:SalesGovernanceStateCollection",
|
|
5759
|
+
"collection": "salesgovernancestates",
|
|
5760
|
+
"tableName": "sales_governance_states",
|
|
5761
|
+
"packageName": "@happyvertical/smrt-sales",
|
|
5762
|
+
"extends": "SmrtCollection",
|
|
5763
|
+
"fields": [],
|
|
5764
|
+
"relationships": [],
|
|
5765
|
+
"methods": [
|
|
5766
|
+
"compareAndSwap",
|
|
5767
|
+
"ensure",
|
|
5768
|
+
"lock"
|
|
5769
|
+
],
|
|
5770
|
+
"methodSignatures": [
|
|
5771
|
+
{
|
|
5772
|
+
"name": "compareAndSwap",
|
|
5773
|
+
"async": true,
|
|
5774
|
+
"params": [
|
|
5775
|
+
"id: string",
|
|
5776
|
+
"revision: number",
|
|
5777
|
+
"payload: string"
|
|
5778
|
+
],
|
|
5779
|
+
"returns": "Promise<boolean>"
|
|
5780
|
+
},
|
|
5781
|
+
{
|
|
5782
|
+
"name": "ensure",
|
|
5783
|
+
"async": true,
|
|
5784
|
+
"params": [
|
|
5785
|
+
"id: string"
|
|
5786
|
+
],
|
|
5787
|
+
"returns": "Promise<void>"
|
|
5788
|
+
},
|
|
5789
|
+
{
|
|
5790
|
+
"name": "lock",
|
|
5791
|
+
"async": true,
|
|
5792
|
+
"params": [
|
|
5793
|
+
"id: string"
|
|
5794
|
+
],
|
|
5795
|
+
"returns": "Promise<SalesGovernanceState>"
|
|
5796
|
+
}
|
|
5797
|
+
],
|
|
5798
|
+
"surfaces": [],
|
|
5799
|
+
"withheldSurfaces": [
|
|
5800
|
+
{
|
|
5801
|
+
"kind": "api",
|
|
5802
|
+
"operation": "compareAndSwap",
|
|
5803
|
+
"code": "api-disabled",
|
|
5804
|
+
"reason": "api is disabled",
|
|
5805
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceStateCollection"
|
|
5806
|
+
},
|
|
5807
|
+
{
|
|
5808
|
+
"kind": "api",
|
|
5809
|
+
"operation": "ensure",
|
|
5810
|
+
"code": "api-disabled",
|
|
5811
|
+
"reason": "api is disabled",
|
|
5812
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceStateCollection"
|
|
5813
|
+
},
|
|
5814
|
+
{
|
|
5815
|
+
"kind": "api",
|
|
5816
|
+
"operation": "lock",
|
|
5817
|
+
"code": "api-disabled",
|
|
5818
|
+
"reason": "api is disabled",
|
|
5819
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceStateCollection"
|
|
5820
|
+
}
|
|
5821
|
+
],
|
|
5822
|
+
"relationshipFeatures": [
|
|
5823
|
+
"uuidColumns"
|
|
5824
|
+
],
|
|
5825
|
+
"tags": [],
|
|
5826
|
+
"risks": []
|
|
5827
|
+
},
|
|
5739
5828
|
{
|
|
5740
5829
|
"name": "SalesRepresentativeCollection",
|
|
5741
5830
|
"qualifiedName": "@happyvertical/smrt-sales:SalesRepresentativeCollection",
|
|
@@ -7330,6 +7419,179 @@
|
|
|
7330
7419
|
"tags": [],
|
|
7331
7420
|
"risks": []
|
|
7332
7421
|
},
|
|
7422
|
+
{
|
|
7423
|
+
"name": "SalesGovernanceEvent",
|
|
7424
|
+
"qualifiedName": "@happyvertical/smrt-sales:SalesGovernanceEvent",
|
|
7425
|
+
"collection": "salesgovernanceevents",
|
|
7426
|
+
"description": "UUID replay fence and immutable audit; no generated mutation surface.",
|
|
7427
|
+
"tableName": "sales_governance_events",
|
|
7428
|
+
"packageName": "@happyvertical/smrt-sales",
|
|
7429
|
+
"extends": "SmrtObject",
|
|
7430
|
+
"fields": [
|
|
7431
|
+
{
|
|
7432
|
+
"name": "tenantId",
|
|
7433
|
+
"type": "text",
|
|
7434
|
+
"required": true,
|
|
7435
|
+
"columnType": "UUID"
|
|
7436
|
+
},
|
|
7437
|
+
{
|
|
7438
|
+
"name": "intent",
|
|
7439
|
+
"type": "text",
|
|
7440
|
+
"required": false,
|
|
7441
|
+
"columnType": "TEXT",
|
|
7442
|
+
"default": ""
|
|
7443
|
+
},
|
|
7444
|
+
{
|
|
7445
|
+
"name": "result",
|
|
7446
|
+
"type": "text",
|
|
7447
|
+
"required": false,
|
|
7448
|
+
"columnType": "TEXT",
|
|
7449
|
+
"default": ""
|
|
7450
|
+
}
|
|
7451
|
+
],
|
|
7452
|
+
"relationships": [],
|
|
7453
|
+
"methods": [
|
|
7454
|
+
"delete",
|
|
7455
|
+
"initialize",
|
|
7456
|
+
"save"
|
|
7457
|
+
],
|
|
7458
|
+
"methodSignatures": [
|
|
7459
|
+
{
|
|
7460
|
+
"name": "delete",
|
|
7461
|
+
"async": true,
|
|
7462
|
+
"returns": "Promise<void>"
|
|
7463
|
+
},
|
|
7464
|
+
{
|
|
7465
|
+
"name": "initialize",
|
|
7466
|
+
"async": true,
|
|
7467
|
+
"returns": "Promise<this>"
|
|
7468
|
+
},
|
|
7469
|
+
{
|
|
7470
|
+
"name": "save",
|
|
7471
|
+
"async": true,
|
|
7472
|
+
"returns": "Promise<this>"
|
|
7473
|
+
}
|
|
7474
|
+
],
|
|
7475
|
+
"tenant": {
|
|
7476
|
+
"scoped": true,
|
|
7477
|
+
"mode": "required",
|
|
7478
|
+
"field": "tenantId"
|
|
7479
|
+
},
|
|
7480
|
+
"conflictColumns": [
|
|
7481
|
+
"tenant_id",
|
|
7482
|
+
"slug",
|
|
7483
|
+
"context"
|
|
7484
|
+
],
|
|
7485
|
+
"surfaces": [],
|
|
7486
|
+
"withheldSurfaces": [
|
|
7487
|
+
{
|
|
7488
|
+
"kind": "api",
|
|
7489
|
+
"operation": "delete",
|
|
7490
|
+
"code": "api-disabled",
|
|
7491
|
+
"reason": "api is disabled",
|
|
7492
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceEvent"
|
|
7493
|
+
},
|
|
7494
|
+
{
|
|
7495
|
+
"kind": "api",
|
|
7496
|
+
"operation": "initialize",
|
|
7497
|
+
"code": "api-disabled",
|
|
7498
|
+
"reason": "api is disabled",
|
|
7499
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceEvent"
|
|
7500
|
+
},
|
|
7501
|
+
{
|
|
7502
|
+
"kind": "api",
|
|
7503
|
+
"operation": "save",
|
|
7504
|
+
"code": "api-disabled",
|
|
7505
|
+
"reason": "api is disabled",
|
|
7506
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceEvent"
|
|
7507
|
+
}
|
|
7508
|
+
],
|
|
7509
|
+
"relationshipFeatures": [
|
|
7510
|
+
"uuidColumns"
|
|
7511
|
+
],
|
|
7512
|
+
"tags": [],
|
|
7513
|
+
"risks": []
|
|
7514
|
+
},
|
|
7515
|
+
{
|
|
7516
|
+
"name": "SalesGovernanceState",
|
|
7517
|
+
"qualifiedName": "@happyvertical/smrt-sales:SalesGovernanceState",
|
|
7518
|
+
"collection": "salesgovernancestates",
|
|
7519
|
+
"description": "Service-owned compare-and-swap fence. Migrate through the SMRT manifest.",
|
|
7520
|
+
"tableName": "sales_governance_states",
|
|
7521
|
+
"packageName": "@happyvertical/smrt-sales",
|
|
7522
|
+
"extends": "SmrtObject",
|
|
7523
|
+
"fields": [
|
|
7524
|
+
{
|
|
7525
|
+
"name": "tenantId",
|
|
7526
|
+
"type": "text",
|
|
7527
|
+
"required": true,
|
|
7528
|
+
"columnType": "UUID"
|
|
7529
|
+
},
|
|
7530
|
+
{
|
|
7531
|
+
"name": "revision",
|
|
7532
|
+
"type": "integer",
|
|
7533
|
+
"required": false,
|
|
7534
|
+
"columnType": "INTEGER",
|
|
7535
|
+
"default": 0
|
|
7536
|
+
},
|
|
7537
|
+
{
|
|
7538
|
+
"name": "payload",
|
|
7539
|
+
"type": "text",
|
|
7540
|
+
"required": false,
|
|
7541
|
+
"columnType": "TEXT",
|
|
7542
|
+
"default": "{}"
|
|
7543
|
+
}
|
|
7544
|
+
],
|
|
7545
|
+
"relationships": [],
|
|
7546
|
+
"methods": [
|
|
7547
|
+
"delete",
|
|
7548
|
+
"save"
|
|
7549
|
+
],
|
|
7550
|
+
"methodSignatures": [
|
|
7551
|
+
{
|
|
7552
|
+
"name": "delete",
|
|
7553
|
+
"async": true,
|
|
7554
|
+
"returns": "Promise<void>"
|
|
7555
|
+
},
|
|
7556
|
+
{
|
|
7557
|
+
"name": "save",
|
|
7558
|
+
"async": true,
|
|
7559
|
+
"returns": "Promise<this>"
|
|
7560
|
+
}
|
|
7561
|
+
],
|
|
7562
|
+
"tenant": {
|
|
7563
|
+
"scoped": true,
|
|
7564
|
+
"mode": "required",
|
|
7565
|
+
"field": "tenantId"
|
|
7566
|
+
},
|
|
7567
|
+
"conflictColumns": [
|
|
7568
|
+
"tenant_id",
|
|
7569
|
+
"slug",
|
|
7570
|
+
"context"
|
|
7571
|
+
],
|
|
7572
|
+
"surfaces": [],
|
|
7573
|
+
"withheldSurfaces": [
|
|
7574
|
+
{
|
|
7575
|
+
"kind": "api",
|
|
7576
|
+
"operation": "delete",
|
|
7577
|
+
"code": "api-disabled",
|
|
7578
|
+
"reason": "api is disabled",
|
|
7579
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceState"
|
|
7580
|
+
},
|
|
7581
|
+
{
|
|
7582
|
+
"kind": "api",
|
|
7583
|
+
"operation": "save",
|
|
7584
|
+
"code": "api-disabled",
|
|
7585
|
+
"reason": "api is disabled",
|
|
7586
|
+
"objectName": "@happyvertical/smrt-sales:SalesGovernanceState"
|
|
7587
|
+
}
|
|
7588
|
+
],
|
|
7589
|
+
"relationshipFeatures": [
|
|
7590
|
+
"uuidColumns"
|
|
7591
|
+
],
|
|
7592
|
+
"tags": [],
|
|
7593
|
+
"risks": []
|
|
7594
|
+
},
|
|
7333
7595
|
{
|
|
7334
7596
|
"name": "SalesRepresentative",
|
|
7335
7597
|
"qualifiedName": "@happyvertical/smrt-sales:SalesRepresentative",
|
|
@@ -13600,14 +13862,14 @@
|
|
|
13600
13862
|
"junctionCollections": 0,
|
|
13601
13863
|
"hierarchicalObjects": 0,
|
|
13602
13864
|
"polymorphicAssociations": 0,
|
|
13603
|
-
"uuidColumns":
|
|
13865
|
+
"uuidColumns": 232
|
|
13604
13866
|
},
|
|
13605
|
-
"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, nonterminal Opportunity stage movement, and terminal closure with optional conversion links. Optional intake `profileId` is stored and fingerprinted but remains host-authorized. 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",
|
|
13867
|
+
"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, nonterminal Opportunity stage movement, and terminal closure with optional conversion links. Optional intake `profileId` is stored and fingerprinted but remains host-authorized. 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\n- **Governed sales use the owned service factory**: `SalesGovernanceService`\n accepts allowlisted PostgreSQL connection configuration only, owns its\n adapter and transaction executors, and guards private state/model writes.\n DuckDB support remains blocked on SDK #1452; SQLite is also rejected until\n same-process BUSY recovery is proven. Quote issue/sign always recheck\n current seller, host policy/inventory and exact finance approval; see\n [agents/crm.md](agents/crm.md#governed-sellers-quotes-and-canonical-advertiser-registration).\n",
|
|
13606
13868
|
"moduleDocs": [
|
|
13607
13869
|
{
|
|
13608
13870
|
"path": "agents/agreements.md",
|
|
13609
13871
|
"module": "agreements",
|
|
13610
|
-
"content": "# smrt-sales/agreements — verified execution evidence\n\nPer-module semantics for `@happyvertical/smrt-sales/agreements`. 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- **AgreementExecution**: private required-tenant orchestration state keyed by `(tenant_id, idempotency_key)`, with a collision-checked provider request/status, an expiring create-operation lease, source version/hash/size/Asset, intended signer identity/auth method, cancellation/expiry, reconciliation, exact staged artifact metadata, and secret-store references only.\n- **AgreementExecutionEvent**: private append-only evidence for verified webhooks and operator/provider operations. Provider events are deduped by tenant/provider replay key; explicit occurrence/first-receipt timestamps and the exact payload/hash are retained, while signature headers are never stored. A later receipt time for the same verified provider event is an idempotent replay. Stale, regressive, or conflicting terminal events are audited without regressing lifecycle state or creating executed evidence.\n- **ExecutedAgreement**: immutable read-only source version plus signed-document and audit-trail Asset ids/hashes/sizes/filenames/media types, completed signer evidence, acceptance/effective dates, and supersession reference. Amendments create new records.\n- **AgreementExecutionService**: accepts an SDK `SignatureProvider` and shared `AssetRuntimeLike`; enforces ambient tenant equality, expiring remote-attempt fencing, create idempotency, verified webhook ingestion/replay, tenant-fenced lifecycle compare-and-set with monotonic provider-event ordering, partial audit/failure updates that cannot overwrite lifecycle state, cancellation/expiry/reconciliation, exact artifact hashing, and immutable finalization. An expired create lease may be reclaimed only when the provider advertises atomic idempotency; otherwise a create with no confirmed request id must be reconciled/adopted before retry.\n"
|
|
13872
|
+
"content": "# smrt-sales/agreements — verified execution evidence\n\nPer-module semantics for `@happyvertical/smrt-sales/agreements`. 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- **AgreementExecution**: private required-tenant orchestration state keyed by `(tenant_id, idempotency_key)`, with a collision-checked provider request/status, an expiring create-operation lease, source version/hash/size/Asset, intended signer identity/auth method, cancellation/expiry, reconciliation, exact staged artifact metadata, and secret-store references only.\n- **AgreementExecutionEvent**: private append-only evidence for verified webhooks and operator/provider operations. Provider events are deduped by tenant/provider replay key; explicit occurrence/first-receipt timestamps and the exact payload/hash are retained, while signature headers are never stored. A later receipt time for the same verified provider event is an idempotent replay. Stale, regressive, or conflicting terminal events are audited without regressing lifecycle state or creating executed evidence.\n- **ExecutedAgreement**: immutable read-only source version plus signed-document and audit-trail Asset ids/hashes/sizes/filenames/media types, completed signer evidence, acceptance/effective dates, and supersession reference. Amendments create new records.\n- **AgreementExecutionService**: accepts an SDK `SignatureProvider` and shared `AssetRuntimeLike`; enforces ambient tenant equality, expiring remote-attempt fencing, create idempotency, verified webhook ingestion/replay, tenant-fenced lifecycle compare-and-set with monotonic provider-event ordering, partial audit/failure updates that cannot overwrite lifecycle state, cancellation/expiry/reconciliation, exact artifact hashing, and immutable finalization. An expired create lease may be reclaimed only when the provider advertises atomic idempotency; otherwise a create with no confirmed request id must be reconciled/adopted before retry.\n\n## Effective-dated agreement withdrawal\n\n`AgreementCollection.withdrawAmendment(id)` cancels any active version whose\n`effectiveFrom` is strictly later than the current time, including a scheduled\ninitial version and an amendment starting after an ended predecessor. Cancelled\nversions remain stored but never govern `activeFor` record-date lookups. Terms\nand execution evidence are retained unchanged.\n\nIf activation closed a predecessor, withdrawal restores only the exact persisted\npredecessor boundary and status. If activation closed nothing, withdrawal\nrestores nothing: an expired or terminated earlier version stays ended, and a\ngap remains a gap. Imported overlapping governing history is refused; disjoint\nlater versions remain unchanged. Tenant scope, revision checks, cancellation,\nand optional predecessor restoration share one transaction. Missing, draft,\nterminal, already-effective, repeated, and conflicting withdrawals fail closed.\n\n## Governed sales signing\n\n`sourceKind: 'sales-quote'` requires `signingActorProfileId` and an injected\n`authorizeSigning(CreateAgreementExecutionInput)` guard. Bind the exact tenant,\nsourceId (quoteId), sourceVersion (revision) and actor to\n`SalesGovernanceService.assertCanSign`, and validate the source document against\nthe frozen quote terms. The guard runs before source storage/existing-execution\nreplay and immediately before provider request creation. Actor identity is part\nof immutable retry intent. Other agreement kinds may use the same optional guard.\n\nLocal authority commits before external provider I/O; provider work cannot join\na SQL transaction. Seller or finance revocation denies fresh/retried signing but\ndoes not cancel already-dispatched provider requests. Hosts must explicitly cancel\noutstanding requests when their policy requires it. See the governed sales\nintegration and supported PostgreSQL-only boundary in [crm.md](crm.md).\n"
|
|
13611
13873
|
},
|
|
13612
13874
|
{
|
|
13613
13875
|
"path": "agents/commissions.md",
|
|
@@ -13617,7 +13879,7 @@
|
|
|
13617
13879
|
{
|
|
13618
13880
|
"path": "agents/crm.md",
|
|
13619
13881
|
"module": "crm",
|
|
13620
|
-
"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, nonterminal Opportunity stage movement, 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.\nThe optional `profileId` is a host-authorized cross-package identity link and is\npart of that immutable retry intent; Sales stores it but does not authorize or\nload the referenced Profile.\n\n`qualifyLead()` delegates to `LeadCollection.qualify()` on the same transaction\nexecutor and returns whether the Opportunity was newly created.\n`moveOpportunityToStage()` locks an open Opportunity and atomically moves it to\na nonterminal stage in the same tenant and pipeline. An exact stage/probability\nretry is a no-op; a conflicting same-stage probability is refused. Terminal\nstages remain exclusive to `closeOpportunity()`, which\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"
|
|
13882
|
+
"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, nonterminal Opportunity stage movement, 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.\nThe optional `profileId` is a host-authorized cross-package identity link and is\npart of that immutable retry intent; Sales stores it but does not authorize or\nload the referenced Profile.\n\n`qualifyLead()` delegates to `LeadCollection.qualify()` on the same transaction\nexecutor and returns whether the Opportunity was newly created.\n`moveOpportunityToStage()` locks an open Opportunity and atomically moves it to\na nonterminal stage in the same tenant and pipeline. An exact stage/probability\nretry is a no-op; a conflicting same-stage probability is refused. Terminal\nstages remain exclusive to `closeOpportunity()`, which\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\n`LeadConversionService.create({ db: tx, transaction: 'caller' })` records pending\nconversions before an Opportunity is won, using immutable `lead_conversion`\nSalesActivity receipts (no new table). Hosts authorize inputs and serialize the\nlead conversion scope, create downstream records, and record the receipt within\none caller-owned transaction. `getForLead({ tenantId, leadId, targetKind })` and\n`recordForLead({ ...scope, targetId, actorProfileId, metadata? })` require matching\nambient tenancy and a same-tenant Lead. A tenant/lead/targetKind has one target;\nexact target/metadata replay returns the original receipt and actor, conflicting\nintent is refused. The service never changes Lead/Opportunity state, invites\nusers, or starts a transaction. Only use returned plain receipts after commit.\n\n`findForTarget({ tenantId, targetKind, targetId })` reads canonical originating\nLead receipts for a downstream target in the active tenant, without requiring\nhosts to parse SalesActivity metadata.\n\n`LeadWorkflowService.updateLeadContact({ leadId, actorProfileId, name,\ncontactName, email, now? })` locks a same-tenant, non-merged Lead and corrects\neditable contact fields in its own transaction. Name is required, email is\nnormalized/validated, and at least email or existing phone is required. Exact\nretries are no-ops. Effective changes append one `contact_update` activity with\nbefore/after values and actor; audit failure rolls back the correction. Profile\nidentity, acquisition context/source, CRM status and downstream records stay intact.\n\n\n## Governed sellers, quotes and canonical advertiser registration\n\n`SalesGovernanceService` is the required mutation boundary for governed sales.\nReferral identity never grants seller authority. Every fresh seller mutation\nrequires a tenant context matching the canonical UUID `tenantId`, an actor Profile matching\nan active tenant-owned `SalesRepresentative`, host membership and suspension\nchecks, and `canSell`. There is no seller count limit. Hosts own newsroom or\neditorial classification through `canSell`; SMRT imports no application policy.\n\nCreate with `{ database: { type: 'postgres', url, ... }, host, now? }`.\nThe service owns connection creation; arbitrary adapter injection is not accepted.\n`service.database` is a read-only getter for migrations and host reads. The owned\nbackend declaration and adapter reference cannot be replaced through public API.\nThe factory validates an explicit connection-options allowlist before SQL sees\nit: no adapter-shaped objects or runtime schema definitions.\nConnections always use `cache: false`; requesting `cache: true` is refused before\nSQL. Each service owns a distinct adapter, so closing it cannot invalidate a peer.\nSQLite is rejected before opening: the required same-process recovery after\nBUSY invalidation could not be established on the published SQL adapter, even\nwith uncached owned connections, callback transactions, close and recreate.\nEarlier SQLite behavioral and separate-process race evidence does not establish\nthat recovery contract. This backend remains unavailable until a passing complete\nSales recovery proof supports enabling it. This is an unverified validation\ncontract, not an established raw SQL defect. Follow the acceptance in\n[SMRT #4269](https://github.com/happyvertical/smrt/issues/4269) before enabling SQLite.\n Every service\ntransaction and mutation rechecks its supported backend boundary. All host methods are mandatory and authorize\nonly on literal `true`. `host.policy` returns `{ version, maxDiscountBps,\nleaseDurationMs, approvalDurationMs }`: basis points and durations are safe\nintegers, durations positive, basis points between 0 and 10000. Unsupported or\nmalformed values, missing resources and thrown provider/inventory failures deny\nwithout partial writes. Host predicates receive the exact transaction executor;\nlock current authoritative membership, suspension, policy and inventory rows\nthrough that executor. A cached predicate or remote network check cannot promise\ntransactionally stable authority. `validateQuote` must validate the exact rate\ncard version, inventory version, advertiser resource validity and cents totals.\nSales itself enforces exclusive advertiser lease ownership on the same executor;\nhost quote validation cannot replace or disable that lease guard.\n\n`acquireLease` accepts a canonical tenant-local `advertiserId`, never free-text\norganization/email dedupe. `advertiserExists` validates this identity and tenant\nownership. Registration is exclusive while `released === false` and\n`expiresAtMs > now`; equality is expired. The shared deterministic resource UUID\nand database row lock/CAS arbitrate independent processes/connections. Database\nbusy/transaction-conflict failures are safe to retry with the original operation\nUUID. `renewLease` requires a live owner and matching `leaseId`, and starts a\nnew configured duration from current time. `releaseLease` requires the same\nowner token and does not transfer ownership; release at expiry is allowed.\nA newly acquired replacement has a new token. `getLease` returns the current\nregistration after host member/suspension and advertiser checks.\n\nAll tenant, actor/Profile, seller, advertiser, quote, lease, approval-request and\noperation identities must be valid UUIDs. The service lowercases them before\nresource-key hashing, host predicates, comparisons, persistence and replay intent\nbinding. UUID spelling is not a distinct identity: uppercase advertiser forms\ncannot acquire another lease, and uppercase maker forms cannot self-approve.\nThe ambient tenant context is normalized within the service while preserving its\nother metadata. Loaded owner/maker/checker identities are normalized before\ncomparisons; altered approval terms still invalidate their exact binding.\n\nEvery mutation accepts a globally unique UUID `operationId` (either case). An exact\nretry returns its immutable original outcome, including its original expiry,\nwithout extending a lease or repeating a transition. Case-only UUID changes\nare the same replay intent; changed identities or terms conflict.\nAn outcome replay is historical evidence, **never fresh authority to sign**.\nTo combine host writes and Sales writes, use:\n\n```ts\nawait sales.withTransaction(async (tx) => {\n const quote = await sales.signQuote(input, tx);\n await hostPersistSignIntent(quote, tx);\n});\n```\n\nOnly active executors issued by `withTransaction` are accepted; arbitrary pools\nand external callback executors are refused. This avoids guessing transaction\nidentity or nesting transactions on independent pooled sessions. Any throw rolls\nback Sales state, audit and host writes together. Do not retain the executor\nbeyond the callback. Result projections are plain detached values.\n\n`createQuote` freezes one commercial revision: seller, canonical advertiser,\nrate-card version, inventory version, subtotal/total integer cents, uppercase\ncurrency, maker Profile, tenant and exact policy. New revisions must increase;\nolder revisions cannot issue, approve or sign after a new revision is created.\nThe comparison `(subtotalCents - totalCents) * 10000 <= subtotalCents *\nmaxDiscountBps` uses BigInt, so the discount boundary has no floating rounding.\n`issueQuote` moves draft to issued; `signQuote` moves issued to signed. Both\nrevalidate current seller, host resources and exact policy version/values.\n`createQuote`, every fresh quote issue/sign transition, approval request/decision,\nand `assertCanSign` require the canonical advertiser's live, unreleased lease to\nbelong to the quote seller. The service locks that lease state in the **same\ntransaction** as the guarded action. Missing ownership, another seller, release,\nor expiry at `expiresAtMs <= now` refuses the action even when every host\npredicate returns true. Existing issued/signed records do not preserve ownership\nonce the lease expires, is released or is acquired by another seller. There is\nno policy flag that makes this guard optional. Historical operation outcomes\nremain audit evidence; provider preflight always rechecks current ownership.\n\nAbove-limit terms require `requestApproval` then `decideApproval`. Requests bind\nthe entire frozen revision and policy hash. A distinct current tenant member\nwith `canApprove` must decide: no self-approval or single-operator fallback.\nOnly pending, unexpired requests can approve/reject. Approved decisions can be\nrevoked, including after issue/sign. Sign/issue recheck current checker\nmembership, suspension and finance authorization. Pending, rejected, revoked,\nexpired or altered bindings fail closed. A new request after expiry/rejection\nuses a new quote revision. Decisions and reasons remain immutable audit events.\n\nFor provider execution use `sourceKind: 'sales-quote'`, `sourceId: quoteId`,\n`sourceVersion: revision`, and `signingActorProfileId`. Agreement execution\nrequires an injected `authorizeSigning(input)` guard for these sources; wire it\nto `sales.assertCanSign({ tenantId, actorProfileId: signingActorProfileId,\nquoteId: sourceId, revision: sourceVersion })` and\nvalidate that the source document describes those exact frozen terms. The guard\nruns before source storage/replay and immediately before provider create I/O.\nAn optional guard can cover other source kinds too. A host must not relabel a\nsales quote as an ungoverned source. Already-existing execution retries still\nrecheck the guard; seller revocation cannot reuse earlier signing authority.\nSQL commits local sign authority; external provider work cannot join or roll\nback with SQL. Revoking authority does not cancel already-dispatched provider\nrequests: hosts must explicitly cancel outstanding requests when their policy\nrequires it, and handle the provider result/reconciliation lifecycle.\n\nMigrate **both** manifest models `SalesGovernanceState` (resource/operation\nfences) and `SalesGovernanceEvent` (immutable intent/result history) before\ncalling the service. Runtime never creates application schema. Both models and\ncollections are root/CRM exports for generated registration but have no generated\nAPI/MCP/CLI writes. Direct generic collection/model create, update and delete are\nalso refused: the service owns an internal executor-bound write capability.\nDo not edit private state rows or delete audit rows. A privileged SQL owner can\nbypass domain models; never expose raw database access to application callers.\n`SellerGovernancePanel` and its exported `SellerGovernanceView` /\n`SellerGovernanceAction` are callback-only UI contracts; displayed eligibility\nnever substitutes for server authority checks. Hosts map canonical advertiser\nselection and current conflict/expiry state onto those props.\n\nThis delivery supports **PostgreSQL only**. SQLite, DuckDB, JSON, PGlite,\nmissing and unknown backend configurations are refused before any connection,\nSQL, policy callback or state mutation. PostgreSQL independent pooled-connection tests exercise the database fence.\nNative PostgreSQL acceptance cases run under `test:postgres`.\n\nThe complete #4264 request remains open and blocked on\n[happyvertical/sdk#1452](https://github.com/happyvertical/sdk/issues/1452): the\npublished SQL DuckDB adapter opens independent native instances for the same\nfile and a second handle can read stale state after another handle commits.\nIndependent writer authority cannot safely rely on that adapter. This explicit\nbackend boundary is a reduced supported contract, not a DuckDB SQL workaround.\nDuckDB support requires the upstream shared-instance visibility fix and a repeat\nof the full cross-connection lease/approval matrix before enabling it.\n"
|
|
13621
13883
|
},
|
|
13622
13884
|
{
|
|
13623
13885
|
"path": "agents/referrals.md",
|
|
@@ -13627,7 +13889,7 @@
|
|
|
13627
13889
|
{
|
|
13628
13890
|
"path": "agents/svelte.md",
|
|
13629
13891
|
"module": "svelte",
|
|
13630
|
-
"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"
|
|
13892
|
+
"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\n`SellerGovernancePanel` renders host-supplied seller eligibility, canonical\nadvertiser registration/expiry, quote revision/status and discount review. Its\n`SellerGovernanceAction` callback carries the exact quote revision, request and\nlease tokens plus review reason. Hosts invoke `SalesGovernanceService`; UI\nflags never grant seller or finance authority. No model or database imports run\nin the component. See the governed sales section in [crm.md](crm.md).\n"
|
|
13631
13893
|
}
|
|
13632
13894
|
],
|
|
13633
13895
|
"recipes": [
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** Props-only seller, advertiser registration and discount review surface. */
|
|
3
|
+
import { Form, FormGroup, Textarea } from '@happyvertical/smrt-ui/forms';
|
|
4
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
5
|
+
import { formatCents } from '../format.js';
|
|
6
|
+
import type { SellerGovernanceAction, SellerGovernanceView } from '../types.js';
|
|
7
|
+
export interface Props {
|
|
8
|
+
/** Host-authorized seller, advertiser lease and exact quote revision to display. */
|
|
9
|
+
view: SellerGovernanceView;
|
|
10
|
+
/** Prevents submitting another action while the host processes a request. */
|
|
11
|
+
busy?: boolean;
|
|
12
|
+
/** Sends an action intent to the host, which must authorize it through the service. */
|
|
13
|
+
onAction?: (action: SellerGovernanceAction) => void;
|
|
14
|
+
}
|
|
15
|
+
let { view, busy = false, onAction }: Props = $props();
|
|
16
|
+
let reason = $state('');
|
|
17
|
+
function act(action: SellerGovernanceAction['action']) {
|
|
18
|
+
onAction?.({
|
|
19
|
+
action,
|
|
20
|
+
quoteId: view.quote?.quoteId,
|
|
21
|
+
revision: view.quote?.revision,
|
|
22
|
+
requestId: view.quote?.approval?.requestId,
|
|
23
|
+
leaseId: view.lease?.leaseId,
|
|
24
|
+
reason: reason.trim(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<section data-smrt-component="SellerGovernancePanel" aria-label="Seller governance">
|
|
30
|
+
<h3>{view.sellerLabel}</h3>
|
|
31
|
+
<p>{view.authorized ? 'Authorized seller' : 'Seller authorization required'}</p>
|
|
32
|
+
<h4>{view.advertiserLabel}</h4>
|
|
33
|
+
{#if view.lease}
|
|
34
|
+
<p>Registration {view.lease.released ? 'released' : 'expires'} {new Date(view.lease.expiresAtMs).toISOString()}</p>
|
|
35
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('renew')}>Renew registration</Button>
|
|
36
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('release')}>Release registration</Button>
|
|
37
|
+
{:else}
|
|
38
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('acquire')}>Register advertiser</Button>
|
|
39
|
+
{/if}
|
|
40
|
+
{#if view.quote}
|
|
41
|
+
<p>Quote revision {view.quote.revision}: {formatCents(view.quote.totalCents, view.quote.currency)} ({view.quote.status})</p>
|
|
42
|
+
<p>Discount review: {view.quote.approval?.status ?? 'Not requested'}</p>
|
|
43
|
+
{#if !view.quote.approval && view.quote.status === 'draft'}
|
|
44
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('request')}>Request discount approval</Button>
|
|
45
|
+
{/if}
|
|
46
|
+
{#if view.canReview && view.quote.approval}
|
|
47
|
+
<Form onsubmit={() => {}}>
|
|
48
|
+
<FormGroup label="Review reason" required>
|
|
49
|
+
<Textarea name="discountReviewReason" value={reason} oninput={(event) => { reason = event.currentTarget.value; }} />
|
|
50
|
+
</FormGroup>
|
|
51
|
+
{#if view.quote.approval.status === 'pending'}
|
|
52
|
+
<Button disabled={busy || !reason.trim()} onclick={() => act('approve')}>Approve</Button>
|
|
53
|
+
<Button disabled={busy || !reason.trim()} onclick={() => act('reject')}>Reject</Button>
|
|
54
|
+
{:else if view.quote.approval.status === 'approved'}
|
|
55
|
+
<Button disabled={busy || !reason.trim()} onclick={() => act('revoke')}>Revoke approval</Button>
|
|
56
|
+
{/if}
|
|
57
|
+
</Form>
|
|
58
|
+
{/if}
|
|
59
|
+
{#if view.quote.status === 'draft'}
|
|
60
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('issue')}>Issue quote</Button>
|
|
61
|
+
{:else if view.quote.status === 'issued'}
|
|
62
|
+
<Button disabled={busy || !view.authorized} onclick={() => act('sign')}>Authorize signing</Button>
|
|
63
|
+
{/if}
|
|
64
|
+
{/if}
|
|
65
|
+
</section>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SellerGovernanceAction, SellerGovernanceView } from '../types.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** Host-authorized seller, advertiser lease and exact quote revision to display. */
|
|
4
|
+
view: SellerGovernanceView;
|
|
5
|
+
/** Prevents submitting another action while the host processes a request. */
|
|
6
|
+
busy?: boolean;
|
|
7
|
+
/** Sends an action intent to the host, which must authorize it through the service. */
|
|
8
|
+
onAction?: (action: SellerGovernanceAction) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const SellerGovernancePanel: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type SellerGovernancePanel = ReturnType<typeof SellerGovernancePanel>;
|
|
12
|
+
export default SellerGovernancePanel;
|
|
13
|
+
//# sourceMappingURL=SellerGovernancePanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SellerGovernancePanel.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/SellerGovernancePanel.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEhF,MAAM,WAAW,KAAK;IACpB,oFAAoF;IACpF,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uFAAuF;IACvF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACrD;AA8DD,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -48,7 +48,8 @@ export type ReferralLinkManagerProps = ComponentProps<typeof ReferralLinkManager
|
|
|
48
48
|
export type ReferralStatusListProps = ComponentProps<typeof ReferralStatusList>;
|
|
49
49
|
export type ReferrerEarningsSummaryProps = ComponentProps<typeof ReferrerEarningsSummary>;
|
|
50
50
|
export type SalesDashboardProps = ComponentProps<typeof SalesDashboard>;
|
|
51
|
+
export { default as SellerGovernancePanel } from './components/SellerGovernancePanel.svelte';
|
|
51
52
|
export { EMPTY_VALUE_PLACEHOLDER, formatCents, formatDate, formatPercent, } from './format.js';
|
|
52
|
-
export type { AgreementVersionView, AttributionAward, AttributionCandidateView, AttributionExceptionView, AwardValidation, BoardColumn, ClosedOpportunityOutcome, CommissionAdjustmentView, CommissionExpenseRowView, CommissionExpenseTotals, CommissionFormulaParts, CommissionRowView, CommissionTraceView, ConversionLinkView, CreateReferralLinkDraft, CurrencyAmount, DateInput, EarnerBalance, LeadCreateDraft, LeadDetailView, LeadHumanActivityDraft, LeadInboxFilter, LeadInboxSort, LeadLinkedOpportunityView, LeadListItemView, LeadNextActionDraft, LeadSourceOptionView, LeadWorkflowActions, NextActionView, OpportunityCardView, OpportunityDetailView, PayoutActions, PayoutBatchReviewItemView, PayoutTimelineStep, PayoutView, PipelineStageView, RecordActivityDraft, ReferralLinkView, ReferralStatusView, SalesActivityView, SalesRepOptionView, StagePipelineSummary, StatusBadgeVariant, } from './types.js';
|
|
53
|
+
export type { AgreementVersionView, AttributionAward, AttributionCandidateView, AttributionExceptionView, AwardValidation, BoardColumn, ClosedOpportunityOutcome, CommissionAdjustmentView, CommissionExpenseRowView, CommissionExpenseTotals, CommissionFormulaParts, CommissionRowView, CommissionTraceView, ConversionLinkView, CreateReferralLinkDraft, CurrencyAmount, DateInput, EarnerBalance, LeadCreateDraft, LeadDetailView, LeadHumanActivityDraft, LeadInboxFilter, LeadInboxSort, LeadLinkedOpportunityView, LeadListItemView, LeadNextActionDraft, LeadSourceOptionView, LeadWorkflowActions, NextActionView, OpportunityCardView, OpportunityDetailView, PayoutActions, PayoutBatchReviewItemView, PayoutTimelineStep, PayoutView, PipelineStageView, RecordActivityDraft, ReferralLinkView, ReferralStatusView, SalesActivityView, SalesRepOptionView, SellerGovernanceAction, SellerGovernanceView, StagePipelineSummary, StatusBadgeVariant, } from './types.js';
|
|
53
54
|
export { AWARD_FRACTION_TOLERANCE, adjacentStageIds, agreementStatusBadgeVariant, buildShareUrl, canQualifyLead, commissionStatusBadgeVariant, equalSplitAwards, formatCommissionFormula, formatPlanRef, groupOpportunitiesByStage, isHttpUrl, isOverdue, leadStatusBadgeVariant, leadWorkflowActionsFor, openOpportunityCount, openPipelineTotals, opportunityStatusBadgeVariant, payoutActionsFor, payoutStatusBadgeVariant, payoutStatusTimeline, pipelineValueByStage, referralLinkStatusBadgeVariant, referralStatusBadgeVariant, sumExpenseRowsByCurrency, uniqueCandidateReferrerIds, validateAwards, winRate, } from './types.js';
|
|
54
55
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,wBAAwB,MAAM,8CAA8C,CAAC;AACpF,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAC1E,OAAO,wBAAwB,MAAM,8CAA8C,CAAC;AACpF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,UAAU,MAAM,gCAAgC,CAAC;AACxD,OAAO,QAAQ,MAAM,8BAA8B,CAAC;AACpD,OAAO,gBAAgB,MAAM,sCAAsC,CAAC;AACpE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAC1E,OAAO,kBAAkB,MAAM,wCAAwC,CAAC;AACxE,OAAO,uBAAuB,MAAM,6CAA6C,CAAC;AAClF,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAGhE,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,cAAc,EACd,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,GACf,CAAC;AAGF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,CAAC;AAChE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5D,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,4BAA4B,GAAG,cAAc,CACvD,OAAO,uBAAuB,CAC/B,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,wBAAwB,MAAM,8CAA8C,CAAC;AACpF,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAC1E,OAAO,wBAAwB,MAAM,8CAA8C,CAAC;AACpF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,UAAU,MAAM,gCAAgC,CAAC;AACxD,OAAO,QAAQ,MAAM,8BAA8B,CAAC;AACpD,OAAO,gBAAgB,MAAM,sCAAsC,CAAC;AACpE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAC1E,OAAO,kBAAkB,MAAM,wCAAwC,CAAC;AACxE,OAAO,uBAAuB,MAAM,6CAA6C,CAAC;AAClF,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAGhE,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,cAAc,EACd,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,GACf,CAAC;AAGF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,CAAC;AAChE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5D,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,4BAA4B,GAAG,cAAc,CACvD,OAAO,uBAAuB,CAC/B,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AAExE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAE7F,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,aAAa,EACb,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,yBAAyB,EACzB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,aAAa,EACb,cAAc,EACd,4BAA4B,EAC5B,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,yBAAyB,EACzB,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,wBAAwB,EACxB,0BAA0B,EAC1B,cAAc,EACd,OAAO,GACR,MAAM,YAAY,CAAC"}
|
package/dist/svelte/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import ReferrerEarningsSummary from './components/ReferrerEarningsSummary.svelte
|
|
|
33
33
|
import SalesDashboard from './components/SalesDashboard.svelte';
|
|
34
34
|
// Components
|
|
35
35
|
export { AttributionConflictQueue, CommissionBreakdown, CommissionExpenseSummary, ExecutedAgreementsList, LeadCreateForm, LeadDetail, LeadList, OpportunityBoard, OpportunityDetail, PayoutBatchReview, PayoutHistoryList, ReferralLinkManager, ReferralStatusList, ReferrerEarningsSummary, SalesDashboard, };
|
|
36
|
+
export { default as SellerGovernancePanel } from './components/SellerGovernancePanel.svelte';
|
|
36
37
|
// Formatting helpers
|
|
37
38
|
export { EMPTY_VALUE_PLACEHOLDER, formatCents, formatDate, formatPercent, } from './format.js';
|
|
38
39
|
// View models and pure helpers
|
package/dist/svelte/types.d.ts
CHANGED
|
@@ -518,4 +518,23 @@ export interface CommissionExpenseTotals {
|
|
|
518
518
|
* are never summed together.
|
|
519
519
|
*/
|
|
520
520
|
export declare function sumExpenseRowsByCurrency(rows: CommissionExpenseRowView[]): CommissionExpenseTotals[];
|
|
521
|
+
/** Host-owned display state; eligibility is informational, server guards decide. */
|
|
522
|
+
export interface SellerGovernanceView {
|
|
523
|
+
sellerLabel: string;
|
|
524
|
+
authorized: boolean;
|
|
525
|
+
advertiserLabel: string;
|
|
526
|
+
lease: import('../crm/services/sales-governance-types.js').SalesAdvertiserLease | null;
|
|
527
|
+
quote: import('../crm/services/sales-governance-types.js').SalesQuoteSnapshot | null;
|
|
528
|
+
/** Hosts independently authorize finance actions. */
|
|
529
|
+
canReview: boolean;
|
|
530
|
+
}
|
|
531
|
+
/** Exact current revision/request identifiers are included in every UI action. */
|
|
532
|
+
export interface SellerGovernanceAction {
|
|
533
|
+
action: 'acquire' | 'renew' | 'release' | 'request' | 'approve' | 'reject' | 'revoke' | 'issue' | 'sign';
|
|
534
|
+
quoteId?: string;
|
|
535
|
+
revision?: number;
|
|
536
|
+
requestId?: string;
|
|
537
|
+
leaseId?: string;
|
|
538
|
+
reason?: string;
|
|
539
|
+
}
|
|
521
540
|
//# sourceMappingURL=types.d.ts.map
|