@contractspec/example.crm-pipeline 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/AGENTS.md +51 -33
- package/CHANGELOG.md +16 -0
- package/README.md +66 -148
- package/dist/browser/events/contact.event.js +1 -1
- package/dist/browser/events/deal.event.js +1 -1
- package/dist/browser/events/index.js +3 -3
- package/dist/browser/events/task.event.js +1 -1
- package/dist/browser/index.js +293 -293
- package/dist/browser/ui/CrmDashboard.js +221 -221
- package/dist/browser/ui/CrmDealCard.js +5 -5
- package/dist/browser/ui/CrmPipelineBoard.js +13 -13
- package/dist/browser/ui/hooks/index.js +2 -2
- package/dist/browser/ui/hooks/useDealList.js +1 -1
- package/dist/browser/ui/hooks/useDealMutations.js +1 -1
- package/dist/browser/ui/index.js +290 -290
- package/dist/browser/ui/modals/CreateDealModal.js +12 -12
- package/dist/browser/ui/modals/DealActionsModal.js +21 -21
- package/dist/browser/ui/modals/index.js +33 -33
- package/dist/browser/ui/renderers/index.js +116 -116
- package/dist/browser/ui/renderers/pipeline.renderer.js +97 -97
- package/dist/deal/index.d.ts +2 -2
- package/dist/events/contact.event.js +1 -1
- package/dist/events/deal.event.js +1 -1
- package/dist/events/index.js +3 -3
- package/dist/events/task.event.js +1 -1
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +293 -293
- package/dist/node/events/contact.event.js +1 -1
- package/dist/node/events/deal.event.js +1 -1
- package/dist/node/events/index.js +3 -3
- package/dist/node/events/task.event.js +1 -1
- package/dist/node/index.js +293 -293
- package/dist/node/ui/CrmDashboard.js +221 -221
- package/dist/node/ui/CrmDealCard.js +5 -5
- package/dist/node/ui/CrmPipelineBoard.js +13 -13
- package/dist/node/ui/hooks/index.js +2 -2
- package/dist/node/ui/hooks/useDealList.js +1 -1
- package/dist/node/ui/hooks/useDealMutations.js +1 -1
- package/dist/node/ui/index.js +290 -290
- package/dist/node/ui/modals/CreateDealModal.js +12 -12
- package/dist/node/ui/modals/DealActionsModal.js +21 -21
- package/dist/node/ui/modals/index.js +33 -33
- package/dist/node/ui/renderers/index.js +116 -116
- package/dist/node/ui/renderers/pipeline.renderer.js +97 -97
- package/dist/operations/index.d.ts +1 -1
- package/dist/ui/CrmDashboard.js +221 -221
- package/dist/ui/CrmDealCard.js +5 -5
- package/dist/ui/CrmPipelineBoard.js +13 -13
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +2 -2
- package/dist/ui/hooks/useDealList.js +1 -1
- package/dist/ui/hooks/useDealMutations.d.ts +9 -0
- package/dist/ui/hooks/useDealMutations.js +1 -1
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +290 -290
- package/dist/ui/modals/CreateDealModal.js +12 -12
- package/dist/ui/modals/DealActionsModal.js +21 -21
- package/dist/ui/modals/index.js +33 -33
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +116 -116
- package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
- package/dist/ui/renderers/pipeline.renderer.js +97 -97
- package/package.json +14 -14
- package/src/crm-pipeline.feature.ts +86 -86
- package/src/deal/deal.enum.ts +8 -8
- package/src/deal/deal.operation.ts +255 -255
- package/src/deal/deal.schema.ts +92 -92
- package/src/deal/deal.test-spec.ts +48 -48
- package/src/deal/index.ts +17 -19
- package/src/docs/crm-pipeline.docblock.ts +43 -43
- package/src/entities/company.entity.ts +52 -52
- package/src/entities/contact.entity.ts +67 -67
- package/src/entities/deal.entity.ts +134 -134
- package/src/entities/index.ts +27 -27
- package/src/entities/task.entity.ts +105 -105
- package/src/events/contact.event.ts +22 -22
- package/src/events/deal.event.ts +77 -77
- package/src/events/task.event.ts +19 -19
- package/src/example.ts +32 -32
- package/src/handlers/crm.handlers.ts +358 -357
- package/src/handlers/deal.handlers.ts +179 -179
- package/src/handlers/index.ts +18 -19
- package/src/handlers/mock-data.ts +167 -167
- package/src/index.ts +11 -11
- package/src/operations/index.ts +16 -16
- package/src/presentations/dashboard.presentation.ts +45 -45
- package/src/presentations/pipeline.presentation.ts +90 -90
- package/src/seeders/index.ts +26 -26
- package/src/shared/overlay-types.ts +23 -23
- package/src/ui/CrmDashboard.tsx +256 -256
- package/src/ui/CrmDealCard.tsx +64 -64
- package/src/ui/CrmPipelineBoard.tsx +105 -105
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useDealList.ts +85 -85
- package/src/ui/hooks/useDealMutations.ts +151 -150
- package/src/ui/index.ts +5 -10
- package/src/ui/modals/CreateDealModal.tsx +217 -217
- package/src/ui/modals/DealActionsModal.tsx +390 -390
- package/src/ui/overlays/demo-overlays.ts +43 -43
- package/src/ui/renderers/index.ts +4 -3
- package/src/ui/renderers/pipeline.markdown.ts +165 -165
- package/src/ui/renderers/pipeline.renderer.tsx +17 -16
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
package/src/deal/deal.schema.ts
CHANGED
|
@@ -5,150 +5,150 @@ import { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
|
|
|
5
5
|
* A deal in the CRM pipeline.
|
|
6
6
|
*/
|
|
7
7
|
export const DealModel = defineSchemaModel({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
name: 'Deal',
|
|
9
|
+
description: 'A deal in the CRM pipeline',
|
|
10
|
+
fields: {
|
|
11
|
+
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
12
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
13
|
+
value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
14
|
+
currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
15
|
+
pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
16
|
+
stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
17
|
+
status: { type: DealStatusEnum, isOptional: false },
|
|
18
|
+
contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
19
|
+
companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
20
|
+
ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
21
|
+
expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true },
|
|
22
|
+
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
|
|
23
|
+
updatedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
|
|
24
|
+
},
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Input for creating a deal.
|
|
29
29
|
*/
|
|
30
30
|
export const CreateDealInputModel = defineSchemaModel({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
name: 'CreateDealInput',
|
|
32
|
+
description: 'Input for creating a deal',
|
|
33
|
+
fields: {
|
|
34
|
+
name: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
|
|
35
|
+
value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
36
|
+
currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
37
|
+
pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
38
|
+
stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
39
|
+
contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
40
|
+
companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
41
|
+
expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true },
|
|
42
|
+
},
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Input for moving a deal to another stage.
|
|
47
47
|
*/
|
|
48
48
|
export const MoveDealInputModel = defineSchemaModel({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
name: 'MoveDealInput',
|
|
50
|
+
description: 'Input for moving a deal to another stage',
|
|
51
|
+
fields: {
|
|
52
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
53
|
+
stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
54
|
+
position: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
55
|
+
},
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Payload for deal moved event.
|
|
60
60
|
*/
|
|
61
61
|
export const DealMovedPayloadModel = defineSchemaModel({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
name: 'DealMovedPayload',
|
|
63
|
+
fields: {
|
|
64
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
65
|
+
fromStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
66
|
+
toStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
67
|
+
},
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Input for marking a deal as won.
|
|
72
72
|
*/
|
|
73
73
|
export const WinDealInputModel = defineSchemaModel({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
name: 'WinDealInput',
|
|
75
|
+
description: 'Input for marking a deal as won',
|
|
76
|
+
fields: {
|
|
77
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
78
|
+
wonSource: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
79
|
+
notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
80
|
+
},
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* Payload for deal won event.
|
|
85
85
|
*/
|
|
86
86
|
export const DealWonPayloadModel = defineSchemaModel({
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
name: 'DealWonPayload',
|
|
88
|
+
fields: {
|
|
89
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
90
|
+
value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
91
|
+
},
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Input for marking a deal as lost.
|
|
96
96
|
*/
|
|
97
97
|
export const LoseDealInputModel = defineSchemaModel({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
name: 'LoseDealInput',
|
|
99
|
+
description: 'Input for marking a deal as lost',
|
|
100
|
+
fields: {
|
|
101
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
102
|
+
lostReason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
103
|
+
notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
104
|
+
},
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Payload for deal lost event.
|
|
109
109
|
*/
|
|
110
110
|
export const DealLostPayloadModel = defineSchemaModel({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
name: 'DealLostPayload',
|
|
112
|
+
fields: {
|
|
113
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
114
|
+
reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
115
|
+
},
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Input for listing deals.
|
|
120
120
|
*/
|
|
121
121
|
export const ListDealsInputModel = defineSchemaModel({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
122
|
+
name: 'ListDealsInput',
|
|
123
|
+
description: 'Input for listing deals',
|
|
124
|
+
fields: {
|
|
125
|
+
pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
126
|
+
stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
127
|
+
status: { type: DealStatusFilterEnum, isOptional: true },
|
|
128
|
+
ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
129
|
+
search: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
130
|
+
limit: {
|
|
131
|
+
type: ScalarTypeEnum.Int_unsecure(),
|
|
132
|
+
isOptional: true,
|
|
133
|
+
defaultValue: 20,
|
|
134
|
+
},
|
|
135
|
+
offset: {
|
|
136
|
+
type: ScalarTypeEnum.Int_unsecure(),
|
|
137
|
+
isOptional: true,
|
|
138
|
+
defaultValue: 0,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Output for listing deals.
|
|
145
145
|
*/
|
|
146
146
|
export const ListDealsOutputModel = defineSchemaModel({
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
name: 'ListDealsOutput',
|
|
148
|
+
description: 'Output for listing deals',
|
|
149
|
+
fields: {
|
|
150
|
+
deals: { type: DealModel, isArray: true, isOptional: false },
|
|
151
|
+
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
152
|
+
totalValue: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
153
|
+
},
|
|
154
154
|
});
|
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
import { defineTestSpec } from '@contractspec/lib.contracts-spec/tests';
|
|
2
2
|
|
|
3
3
|
export const dealListTest = defineTestSpec({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
4
|
+
meta: {
|
|
5
|
+
key: 'test.crm.deal.list',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
owners: ['@example.crm-pipeline'],
|
|
8
|
+
description: 'Test for listing deals',
|
|
9
|
+
stability: 'stable',
|
|
10
|
+
tags: ['test'],
|
|
11
|
+
},
|
|
12
|
+
target: {
|
|
13
|
+
type: 'operation',
|
|
14
|
+
operation: { key: 'crm.deal.list', version: '1.0.0' },
|
|
15
|
+
},
|
|
16
|
+
scenarios: [
|
|
17
|
+
{
|
|
18
|
+
key: 'success',
|
|
19
|
+
when: { operation: { key: 'crm.deal.list' } },
|
|
20
|
+
then: [{ type: 'expectOutput', match: {} }],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'error',
|
|
24
|
+
when: { operation: { key: 'crm.deal.list' } },
|
|
25
|
+
then: [{ type: 'expectError' }],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
export const dealMoveTest = defineTestSpec({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
meta: {
|
|
32
|
+
key: 'test.crm.deal.move',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
owners: ['@example.crm-pipeline'],
|
|
35
|
+
description: 'Test for moving deal',
|
|
36
|
+
stability: 'stable',
|
|
37
|
+
tags: ['test'],
|
|
38
|
+
},
|
|
39
|
+
target: {
|
|
40
|
+
type: 'operation',
|
|
41
|
+
operation: { key: 'crm.deal.move', version: '1.0.0' },
|
|
42
|
+
},
|
|
43
|
+
scenarios: [
|
|
44
|
+
{
|
|
45
|
+
key: 'success',
|
|
46
|
+
when: { operation: { key: 'crm.deal.move' } },
|
|
47
|
+
then: [{ type: 'expectOutput', match: {} }],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'error',
|
|
51
|
+
when: { operation: { key: 'crm.deal.move' } },
|
|
52
|
+
then: [{ type: 'expectError' }],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
55
|
});
|
package/src/deal/index.ts
CHANGED
|
@@ -3,24 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
DealModel,
|
|
9
|
-
CreateDealInputModel,
|
|
10
|
-
MoveDealInputModel,
|
|
11
|
-
DealMovedPayloadModel,
|
|
12
|
-
WinDealInputModel,
|
|
13
|
-
DealWonPayloadModel,
|
|
14
|
-
LoseDealInputModel,
|
|
15
|
-
DealLostPayloadModel,
|
|
16
|
-
ListDealsInputModel,
|
|
17
|
-
ListDealsOutputModel,
|
|
18
|
-
} from './deal.schema';
|
|
19
|
-
|
|
20
6
|
export {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
CreateDealContract,
|
|
8
|
+
ListDealsContract,
|
|
9
|
+
LoseDealContract,
|
|
10
|
+
MoveDealContract,
|
|
11
|
+
WinDealContract,
|
|
26
12
|
} from './deal.operation';
|
|
13
|
+
export {
|
|
14
|
+
CreateDealInputModel,
|
|
15
|
+
DealLostPayloadModel,
|
|
16
|
+
DealModel,
|
|
17
|
+
DealMovedPayloadModel,
|
|
18
|
+
DealWonPayloadModel,
|
|
19
|
+
ListDealsInputModel,
|
|
20
|
+
ListDealsOutputModel,
|
|
21
|
+
LoseDealInputModel,
|
|
22
|
+
MoveDealInputModel,
|
|
23
|
+
WinDealInputModel,
|
|
24
|
+
} from './deal.schema';
|
|
@@ -2,16 +2,16 @@ import type { DocBlock } from '@contractspec/lib.contracts-spec/docs';
|
|
|
2
2
|
import { registerDocBlocks } from '@contractspec/lib.contracts-spec/docs';
|
|
3
3
|
|
|
4
4
|
const crmPipelineDocBlocks: DocBlock[] = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
{
|
|
6
|
+
id: 'docs.examples.crm-pipeline.goal',
|
|
7
|
+
title: 'CRM Pipeline — Goal',
|
|
8
|
+
summary:
|
|
9
|
+
'Deals, stages, contacts, companies, and tasks with auditable stage movement.',
|
|
10
|
+
kind: 'goal',
|
|
11
|
+
visibility: 'public',
|
|
12
|
+
route: '/docs/examples/crm-pipeline/goal',
|
|
13
|
+
tags: ['crm', 'goal'],
|
|
14
|
+
body: `## Why it matters
|
|
15
15
|
- Regenerable CRM flow for deals/stages without code drift.
|
|
16
16
|
- Ensures stage movement, tasks, and contacts stay aligned across surfaces.
|
|
17
17
|
|
|
@@ -22,16 +22,16 @@ const crmPipelineDocBlocks: DocBlock[] = [
|
|
|
22
22
|
## Success criteria
|
|
23
23
|
- Stage/state changes emit events and remain declarative in spec.
|
|
24
24
|
- PII (contacts) is scoped/redacted in presentations.`,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'docs.examples.crm-pipeline.usage',
|
|
28
|
+
title: 'CRM Pipeline — Usage',
|
|
29
|
+
summary: 'How to seed, extend, and regenerate the CRM pipeline.',
|
|
30
|
+
kind: 'usage',
|
|
31
|
+
visibility: 'public',
|
|
32
|
+
route: '/docs/examples/crm-pipeline/usage',
|
|
33
|
+
tags: ['crm', 'usage'],
|
|
34
|
+
body: `## Setup
|
|
35
35
|
1) Seed (if available) or create pipeline stages, deals, contacts, companies, tasks.
|
|
36
36
|
2) Configure Notifications for stage changes/tasks; set policy.pii for contact data.
|
|
37
37
|
|
|
@@ -64,17 +64,17 @@ const crmPipelineDocBlocks: DocBlock[] = [
|
|
|
64
64
|
4) Wire the generated handler into your existing router.
|
|
65
65
|
5) Expand to events and presentations as you add surface areas.
|
|
66
66
|
`,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'docs.examples.crm-pipeline.reference',
|
|
70
|
+
title: 'CRM Pipeline — Reference',
|
|
71
|
+
summary:
|
|
72
|
+
'Entities, contracts, events, and presentations for the CRM template.',
|
|
73
|
+
kind: 'reference',
|
|
74
|
+
visibility: 'public',
|
|
75
|
+
route: '/docs/examples/crm-pipeline',
|
|
76
|
+
tags: ['crm', 'reference'],
|
|
77
|
+
body: `## Entities
|
|
78
78
|
- Contact, Company, Deal, Pipeline, Stage, Task.
|
|
79
79
|
|
|
80
80
|
## Contracts
|
|
@@ -89,17 +89,17 @@ const crmPipelineDocBlocks: DocBlock[] = [
|
|
|
89
89
|
## Notes
|
|
90
90
|
- Stage definitions should be declarative; enforce via spec and regeneration.
|
|
91
91
|
- Use Notifications for deal/task updates; Audit Trail for state changes.`,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'docs.examples.crm-pipeline.constraints',
|
|
95
|
+
title: 'CRM Pipeline — Constraints & Safety',
|
|
96
|
+
summary:
|
|
97
|
+
'Internal guardrails for stages, PII, and regeneration semantics in the CRM template.',
|
|
98
|
+
kind: 'reference',
|
|
99
|
+
visibility: 'internal',
|
|
100
|
+
route: '/docs/examples/crm-pipeline/constraints',
|
|
101
|
+
tags: ['crm', 'constraints', 'internal'],
|
|
102
|
+
body: `## Constraints
|
|
103
103
|
- Stage definitions/order must remain declarative; no imperative overrides in code.
|
|
104
104
|
- Events to emit: deal.created, stage.moved, task.completed, contact.updated (minimum).
|
|
105
105
|
- Regeneration should not alter stage semantics without explicit spec change.
|
|
@@ -112,7 +112,7 @@ const crmPipelineDocBlocks: DocBlock[] = [
|
|
|
112
112
|
- Add fixtures for stage move rules and SLA/task changes.
|
|
113
113
|
- Ensure Audit/Notifications remain wired for stage and task events.
|
|
114
114
|
- Use Feature Flags for experimental stages/SLAs; default safe/off.`,
|
|
115
|
-
|
|
115
|
+
},
|
|
116
116
|
];
|
|
117
117
|
|
|
118
118
|
registerDocBlocks(crmPipelineDocBlocks);
|
|
@@ -1,77 +1,77 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
defineEntity,
|
|
3
|
+
defineEntityEnum,
|
|
4
|
+
field,
|
|
5
|
+
index,
|
|
6
6
|
} from '@contractspec/lib.schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Company size enum.
|
|
10
10
|
*/
|
|
11
11
|
export const CompanySizeEnum = defineEntityEnum({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
name: 'CompanySize',
|
|
13
|
+
values: ['STARTUP', 'SMALL', 'MEDIUM', 'LARGE', 'ENTERPRISE'] as const,
|
|
14
|
+
schema: 'crm',
|
|
15
|
+
description: 'Size category of a company.',
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Company entity - organization/account.
|
|
20
20
|
*/
|
|
21
21
|
export const CompanyEntity = defineEntity({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
name: 'Company',
|
|
23
|
+
description: 'A company/organization in the CRM.',
|
|
24
|
+
schema: 'crm',
|
|
25
|
+
map: 'company',
|
|
26
|
+
fields: {
|
|
27
|
+
id: field.id({ description: 'Unique company ID' }),
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// Basic info
|
|
30
|
+
name: field.string({ description: 'Company name' }),
|
|
31
|
+
domain: field.string({ isOptional: true, description: 'Website domain' }),
|
|
32
|
+
website: field.url({ isOptional: true }),
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Industry
|
|
35
|
+
industry: field.string({ isOptional: true }),
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// Size
|
|
38
|
+
size: field.enum('CompanySize', { isOptional: true }),
|
|
39
|
+
employeeCount: field.int({ isOptional: true }),
|
|
40
|
+
annualRevenue: field.decimal({ isOptional: true }),
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// Ownership
|
|
43
|
+
organizationId: field.foreignKey(),
|
|
44
|
+
ownerId: field.foreignKey({ description: 'Account owner' }),
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Contact info
|
|
47
|
+
phone: field.string({ isOptional: true }),
|
|
48
|
+
email: field.email({ isOptional: true }),
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
// Address
|
|
51
|
+
address: field.string({ isOptional: true }),
|
|
52
|
+
city: field.string({ isOptional: true }),
|
|
53
|
+
state: field.string({ isOptional: true }),
|
|
54
|
+
country: field.string({ isOptional: true }),
|
|
55
|
+
postalCode: field.string({ isOptional: true }),
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// Social
|
|
58
|
+
linkedInUrl: field.url({ isOptional: true }),
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Notes
|
|
61
|
+
description: field.string({ isOptional: true }),
|
|
62
|
+
tags: field.string({ isArray: true }),
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Custom fields
|
|
65
|
+
customFields: field.json({ isOptional: true }),
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// Timestamps
|
|
68
|
+
createdAt: field.createdAt(),
|
|
69
|
+
updatedAt: field.updatedAt(),
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Relations
|
|
72
|
+
contacts: field.hasMany('Contact'),
|
|
73
|
+
deals: field.hasMany('Deal'),
|
|
74
|
+
},
|
|
75
|
+
indexes: [index.on(['organizationId', 'ownerId']), index.on(['domain'])],
|
|
76
|
+
enums: [CompanySizeEnum],
|
|
77
77
|
});
|