@contractspec/example.crm-pipeline 0.0.0-canary-20260113170453
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$colon$bundle.log +172 -0
- package/.turbo/turbo-build.log +173 -0
- package/CHANGELOG.md +436 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/crm-pipeline.feature.d.ts +12 -0
- package/dist/crm-pipeline.feature.d.ts.map +1 -0
- package/dist/crm-pipeline.feature.js +166 -0
- package/dist/crm-pipeline.feature.js.map +1 -0
- package/dist/deal/deal.enum.d.ts +14 -0
- package/dist/deal/deal.enum.d.ts.map +1 -0
- package/dist/deal/deal.enum.js +25 -0
- package/dist/deal/deal.enum.js.map +1 -0
- package/dist/deal/deal.operation.d.ts +513 -0
- package/dist/deal/deal.operation.d.ts.map +1 -0
- package/dist/deal/deal.operation.js +270 -0
- package/dist/deal/deal.operation.js.map +1 -0
- package/dist/deal/deal.schema.d.ts +300 -0
- package/dist/deal/deal.schema.d.ts.map +1 -0
- package/dist/deal/deal.schema.js +286 -0
- package/dist/deal/deal.schema.js.map +1 -0
- package/dist/deal/deal.test-spec.d.ts +8 -0
- package/dist/deal/deal.test-spec.d.ts.map +1 -0
- package/dist/deal/deal.test-spec.js +65 -0
- package/dist/deal/deal.test-spec.js.map +1 -0
- package/dist/deal/index.d.ts +4 -0
- package/dist/deal/index.js +5 -0
- package/dist/docs/crm-pipeline.docblock.d.ts +1 -0
- package/dist/docs/crm-pipeline.docblock.js +100 -0
- package/dist/docs/crm-pipeline.docblock.js.map +1 -0
- package/dist/docs/index.d.ts +1 -0
- package/dist/docs/index.js +1 -0
- package/dist/entities/company.entity.d.ts +40 -0
- package/dist/entities/company.entity.d.ts.map +1 -0
- package/dist/entities/company.entity.js +63 -0
- package/dist/entities/company.entity.js.map +1 -0
- package/dist/entities/contact.entity.d.ts +44 -0
- package/dist/entities/contact.entity.d.ts.map +1 -0
- package/dist/entities/contact.entity.js +78 -0
- package/dist/entities/contact.entity.js.map +1 -0
- package/dist/entities/deal.entity.d.ts +73 -0
- package/dist/entities/deal.entity.d.ts.map +1 -0
- package/dist/entities/deal.entity.js +120 -0
- package/dist/entities/deal.entity.js.map +1 -0
- package/dist/entities/index.d.ts +15 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +33 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/task.entity.d.ts +65 -0
- package/dist/entities/task.entity.d.ts.map +1 -0
- package/dist/entities/task.entity.js +129 -0
- package/dist/entities/task.entity.js.map +1 -0
- package/dist/events/contact.event.d.ts +29 -0
- package/dist/events/contact.event.d.ts.map +1 -0
- package/dist/events/contact.event.js +45 -0
- package/dist/events/contact.event.js.map +1 -0
- package/dist/events/deal.event.d.ts +111 -0
- package/dist/events/deal.event.d.ts.map +1 -0
- package/dist/events/deal.event.js +172 -0
- package/dist/events/deal.event.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.js +5 -0
- package/dist/events/task.event.d.ts +29 -0
- package/dist/events/task.event.d.ts.map +1 -0
- package/dist/events/task.event.js +45 -0
- package/dist/events/task.event.js.map +1 -0
- package/dist/example.d.ts +7 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +53 -0
- package/dist/example.js.map +1 -0
- package/dist/handlers/crm.handlers.d.ts +89 -0
- package/dist/handlers/crm.handlers.d.ts.map +1 -0
- package/dist/handlers/crm.handlers.js +172 -0
- package/dist/handlers/crm.handlers.js.map +1 -0
- package/dist/handlers/deal.handlers.d.ts +94 -0
- package/dist/handlers/deal.handlers.d.ts.map +1 -0
- package/dist/handlers/deal.handlers.js +120 -0
- package/dist/handlers/deal.handlers.js.map +1 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.js +5 -0
- package/dist/handlers/mock-data.d.ts +49 -0
- package/dist/handlers/mock-data.d.ts.map +1 -0
- package/dist/handlers/mock-data.js +188 -0
- package/dist/handlers/mock-data.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/index.d.ts +5 -0
- package/dist/operations/index.js +6 -0
- package/dist/presentations/dashboard.presentation.d.ts +14 -0
- package/dist/presentations/dashboard.presentation.d.ts.map +1 -0
- package/dist/presentations/dashboard.presentation.js +62 -0
- package/dist/presentations/dashboard.presentation.js.map +1 -0
- package/dist/presentations/index.d.ts +3 -0
- package/dist/presentations/index.js +4 -0
- package/dist/presentations/pipeline.presentation.d.ts +22 -0
- package/dist/presentations/pipeline.presentation.d.ts.map +1 -0
- package/dist/presentations/pipeline.presentation.js +122 -0
- package/dist/presentations/pipeline.presentation.js.map +1 -0
- package/dist/seeders/index.d.ts +10 -0
- package/dist/seeders/index.d.ts.map +1 -0
- package/dist/seeders/index.js +47 -0
- package/dist/seeders/index.js.map +1 -0
- package/dist/shared/overlay-types.d.ts +34 -0
- package/dist/shared/overlay-types.d.ts.map +1 -0
- package/dist/shared/overlay-types.js +0 -0
- package/dist/ui/CrmDashboard.d.ts +7 -0
- package/dist/ui/CrmDashboard.d.ts.map +1 -0
- package/dist/ui/CrmDashboard.js +304 -0
- package/dist/ui/CrmDashboard.js.map +1 -0
- package/dist/ui/CrmDealCard.d.ts +15 -0
- package/dist/ui/CrmDealCard.d.ts.map +1 -0
- package/dist/ui/CrmDealCard.js +49 -0
- package/dist/ui/CrmDealCard.js.map +1 -0
- package/dist/ui/CrmPipelineBoard.d.ts +23 -0
- package/dist/ui/CrmPipelineBoard.d.ts.map +1 -0
- package/dist/ui/CrmPipelineBoard.js +98 -0
- package/dist/ui/CrmPipelineBoard.js.map +1 -0
- package/dist/ui/hooks/index.d.ts +3 -0
- package/dist/ui/hooks/index.js +6 -0
- package/dist/ui/hooks/useDealList.d.ts +35 -0
- package/dist/ui/hooks/useDealList.d.ts.map +1 -0
- package/dist/ui/hooks/useDealList.js +94 -0
- package/dist/ui/hooks/useDealList.js.map +1 -0
- package/dist/ui/hooks/useDealMutations.d.ts +26 -0
- package/dist/ui/hooks/useDealMutations.d.ts.map +1 -0
- package/dist/ui/hooks/useDealMutations.js +159 -0
- package/dist/ui/hooks/useDealMutations.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/modals/CreateDealModal.d.ts +33 -0
- package/dist/ui/modals/CreateDealModal.d.ts.map +1 -0
- package/dist/ui/modals/CreateDealModal.js +183 -0
- package/dist/ui/modals/CreateDealModal.js.map +1 -0
- package/dist/ui/modals/DealActionsModal.d.ts +51 -0
- package/dist/ui/modals/DealActionsModal.d.ts.map +1 -0
- package/dist/ui/modals/DealActionsModal.js +372 -0
- package/dist/ui/modals/DealActionsModal.js.map +1 -0
- package/dist/ui/modals/index.d.ts +3 -0
- package/dist/ui/modals/index.js +4 -0
- package/dist/ui/overlays/demo-overlays.d.ts +19 -0
- package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
- package/dist/ui/overlays/demo-overlays.js +68 -0
- package/dist/ui/overlays/demo-overlays.js.map +1 -0
- package/dist/ui/overlays/index.d.ts +2 -0
- package/dist/ui/overlays/index.js +3 -0
- package/dist/ui/renderers/index.d.ts +3 -0
- package/dist/ui/renderers/index.js +4 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts +23 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.markdown.js +118 -0
- package/dist/ui/renderers/pipeline.markdown.js.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts +9 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.js +28 -0
- package/dist/ui/renderers/pipeline.renderer.js.map +1 -0
- package/example.ts +1 -0
- package/package.json +127 -0
- package/src/crm-pipeline.feature.ts +100 -0
- package/src/deal/deal.enum.ts +21 -0
- package/src/deal/deal.operation.ts +291 -0
- package/src/deal/deal.schema.ts +154 -0
- package/src/deal/deal.test-spec.ts +55 -0
- package/src/deal/index.ts +26 -0
- package/src/docs/crm-pipeline.docblock.ts +98 -0
- package/src/docs/index.ts +1 -0
- package/src/entities/company.entity.ts +77 -0
- package/src/entities/contact.entity.ts +93 -0
- package/src/entities/deal.entity.ts +160 -0
- package/src/entities/index.ts +45 -0
- package/src/entities/task.entity.ts +137 -0
- package/src/events/contact.event.ts +31 -0
- package/src/events/deal.event.ts +104 -0
- package/src/events/index.ts +3 -0
- package/src/events/task.event.ts +28 -0
- package/src/example.ts +38 -0
- package/src/handlers/crm.handlers.ts +415 -0
- package/src/handlers/deal.handlers.ts +253 -0
- package/src/handlers/index.ts +30 -0
- package/src/handlers/mock-data.ts +198 -0
- package/src/index.ts +32 -0
- package/src/operations/index.ts +20 -0
- package/src/presentations/dashboard.presentation.ts +59 -0
- package/src/presentations/index.ts +2 -0
- package/src/presentations/pipeline.presentation.ts +117 -0
- package/src/seeders/index.ts +35 -0
- package/src/shared/overlay-types.ts +39 -0
- package/src/ui/CrmDashboard.tsx +311 -0
- package/src/ui/CrmDealCard.tsx +83 -0
- package/src/ui/CrmPipelineBoard.tsx +136 -0
- package/src/ui/hooks/index.ts +10 -0
- package/src/ui/hooks/useDealList.ts +113 -0
- package/src/ui/hooks/useDealMutations.ts +174 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/modals/CreateDealModal.tsx +239 -0
- package/src/ui/modals/DealActionsModal.tsx +424 -0
- package/src/ui/modals/index.ts +2 -0
- package/src/ui/overlays/demo-overlays.ts +68 -0
- package/src/ui/overlays/index.ts +1 -0
- package/src/ui/renderers/index.ts +6 -0
- package/src/ui/renderers/pipeline.markdown.ts +198 -0
- package/src/ui/renderers/pipeline.renderer.tsx +35 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.js +7 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCommand,
|
|
3
|
+
defineQuery,
|
|
4
|
+
} from '@contractspec/lib.contracts/operations';
|
|
5
|
+
import {
|
|
6
|
+
DealModel,
|
|
7
|
+
CreateDealInputModel,
|
|
8
|
+
MoveDealInputModel,
|
|
9
|
+
DealMovedPayloadModel,
|
|
10
|
+
WinDealInputModel,
|
|
11
|
+
DealWonPayloadModel,
|
|
12
|
+
LoseDealInputModel,
|
|
13
|
+
DealLostPayloadModel,
|
|
14
|
+
ListDealsInputModel,
|
|
15
|
+
ListDealsOutputModel,
|
|
16
|
+
} from './deal.schema';
|
|
17
|
+
|
|
18
|
+
const OWNERS = ['@example.crm-pipeline'] as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a new deal.
|
|
22
|
+
*/
|
|
23
|
+
export const CreateDealContract = defineCommand({
|
|
24
|
+
meta: {
|
|
25
|
+
key: 'crm.deal.create',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
stability: 'stable',
|
|
28
|
+
owners: [...OWNERS],
|
|
29
|
+
tags: ['crm', 'deal', 'create'],
|
|
30
|
+
description: 'Create a new deal in the pipeline.',
|
|
31
|
+
goal: 'Allow sales reps to create new opportunities.',
|
|
32
|
+
context: 'Deal creation UI, quick add.',
|
|
33
|
+
},
|
|
34
|
+
io: {
|
|
35
|
+
input: CreateDealInputModel,
|
|
36
|
+
output: DealModel,
|
|
37
|
+
},
|
|
38
|
+
policy: {
|
|
39
|
+
auth: 'user',
|
|
40
|
+
},
|
|
41
|
+
sideEffects: {
|
|
42
|
+
emits: [
|
|
43
|
+
{
|
|
44
|
+
key: 'deal.created',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
when: 'Deal is created',
|
|
47
|
+
payload: DealModel,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
audit: ['deal.created'],
|
|
51
|
+
},
|
|
52
|
+
acceptance: {
|
|
53
|
+
scenarios: [
|
|
54
|
+
{
|
|
55
|
+
key: 'create-deal-happy-path',
|
|
56
|
+
given: ['User is authenticated'],
|
|
57
|
+
when: ['User creates a deal with valid data'],
|
|
58
|
+
then: ['Deal is created', 'DealCreated event is emitted'],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
examples: [
|
|
62
|
+
{
|
|
63
|
+
key: 'create-basic-deal',
|
|
64
|
+
input: {
|
|
65
|
+
title: 'Big Corp Q3 License',
|
|
66
|
+
stageId: 'stage-lead',
|
|
67
|
+
value: 50000,
|
|
68
|
+
companyId: 'comp-123',
|
|
69
|
+
},
|
|
70
|
+
output: {
|
|
71
|
+
id: 'deal-789',
|
|
72
|
+
title: 'Big Corp Q3 License',
|
|
73
|
+
status: 'open',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Move deal to a different stage.
|
|
82
|
+
*/
|
|
83
|
+
export const MoveDealContract = defineCommand({
|
|
84
|
+
meta: {
|
|
85
|
+
key: 'crm.deal.move',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
stability: 'stable',
|
|
88
|
+
owners: [...OWNERS],
|
|
89
|
+
tags: ['crm', 'deal', 'move', 'kanban'],
|
|
90
|
+
description: 'Move a deal to a different stage.',
|
|
91
|
+
goal: 'Allow drag-and-drop stage movement in Kanban.',
|
|
92
|
+
context: 'Pipeline Kanban view.',
|
|
93
|
+
},
|
|
94
|
+
io: {
|
|
95
|
+
input: MoveDealInputModel,
|
|
96
|
+
output: DealModel,
|
|
97
|
+
},
|
|
98
|
+
policy: {
|
|
99
|
+
auth: 'user',
|
|
100
|
+
},
|
|
101
|
+
sideEffects: {
|
|
102
|
+
emits: [
|
|
103
|
+
{
|
|
104
|
+
key: 'deal.moved',
|
|
105
|
+
version: '1.0.0',
|
|
106
|
+
when: 'Deal stage changed',
|
|
107
|
+
payload: DealMovedPayloadModel,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
audit: ['deal.moved'],
|
|
111
|
+
},
|
|
112
|
+
acceptance: {
|
|
113
|
+
scenarios: [
|
|
114
|
+
{
|
|
115
|
+
key: 'move-deal-happy-path',
|
|
116
|
+
given: ['Deal exists in stage A'],
|
|
117
|
+
when: ['User moves deal to stage B'],
|
|
118
|
+
then: ['Deal stage is updated', 'DealMoved event is emitted'],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
examples: [
|
|
122
|
+
{
|
|
123
|
+
key: 'move-to-negotiation',
|
|
124
|
+
input: { dealId: 'deal-789', targetStageId: 'stage-negotiation' },
|
|
125
|
+
output: {
|
|
126
|
+
id: 'deal-789',
|
|
127
|
+
stageId: 'stage-negotiation',
|
|
128
|
+
movedAt: '2025-01-15T10:00:00Z',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Mark deal as won.
|
|
137
|
+
*/
|
|
138
|
+
export const WinDealContract = defineCommand({
|
|
139
|
+
meta: {
|
|
140
|
+
key: 'crm.deal.win',
|
|
141
|
+
version: '1.0.0',
|
|
142
|
+
stability: 'stable',
|
|
143
|
+
owners: [...OWNERS],
|
|
144
|
+
tags: ['crm', 'deal', 'won'],
|
|
145
|
+
description: 'Mark a deal as won.',
|
|
146
|
+
goal: 'Close a deal as successful.',
|
|
147
|
+
context: 'Deal closing flow.',
|
|
148
|
+
},
|
|
149
|
+
io: {
|
|
150
|
+
input: WinDealInputModel,
|
|
151
|
+
output: DealModel,
|
|
152
|
+
},
|
|
153
|
+
policy: {
|
|
154
|
+
auth: 'user',
|
|
155
|
+
},
|
|
156
|
+
sideEffects: {
|
|
157
|
+
emits: [
|
|
158
|
+
{
|
|
159
|
+
key: 'deal.won',
|
|
160
|
+
version: '1.0.0',
|
|
161
|
+
when: 'Deal is won',
|
|
162
|
+
payload: DealWonPayloadModel,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
audit: ['deal.won'],
|
|
166
|
+
},
|
|
167
|
+
acceptance: {
|
|
168
|
+
scenarios: [
|
|
169
|
+
{
|
|
170
|
+
key: 'win-deal-happy-path',
|
|
171
|
+
given: ['Deal is open'],
|
|
172
|
+
when: ['User marks deal as won'],
|
|
173
|
+
then: ['Deal status becomes WON', 'DealWon event is emitted'],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
examples: [
|
|
177
|
+
{
|
|
178
|
+
key: 'mark-won',
|
|
179
|
+
input: {
|
|
180
|
+
dealId: 'deal-789',
|
|
181
|
+
actualValue: 52000,
|
|
182
|
+
note: 'Signed contract attached',
|
|
183
|
+
},
|
|
184
|
+
output: {
|
|
185
|
+
id: 'deal-789',
|
|
186
|
+
status: 'won',
|
|
187
|
+
closedAt: '2025-01-20T14:30:00Z',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Mark deal as lost.
|
|
196
|
+
*/
|
|
197
|
+
export const LoseDealContract = defineCommand({
|
|
198
|
+
meta: {
|
|
199
|
+
key: 'crm.deal.lose',
|
|
200
|
+
version: '1.0.0',
|
|
201
|
+
stability: 'stable',
|
|
202
|
+
owners: [...OWNERS],
|
|
203
|
+
tags: ['crm', 'deal', 'lost'],
|
|
204
|
+
description: 'Mark a deal as lost.',
|
|
205
|
+
goal: 'Close a deal as unsuccessful.',
|
|
206
|
+
context: 'Deal closing flow.',
|
|
207
|
+
},
|
|
208
|
+
io: {
|
|
209
|
+
input: LoseDealInputModel,
|
|
210
|
+
output: DealModel,
|
|
211
|
+
},
|
|
212
|
+
policy: {
|
|
213
|
+
auth: 'user',
|
|
214
|
+
},
|
|
215
|
+
sideEffects: {
|
|
216
|
+
emits: [
|
|
217
|
+
{
|
|
218
|
+
key: 'deal.lost',
|
|
219
|
+
version: '1.0.0',
|
|
220
|
+
when: 'Deal is lost',
|
|
221
|
+
payload: DealLostPayloadModel,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
audit: ['deal.lost'],
|
|
225
|
+
},
|
|
226
|
+
acceptance: {
|
|
227
|
+
scenarios: [
|
|
228
|
+
{
|
|
229
|
+
key: 'lose-deal-happy-path',
|
|
230
|
+
given: ['Deal is open'],
|
|
231
|
+
when: ['User marks deal as lost'],
|
|
232
|
+
then: ['Deal status becomes LOST', 'DealLost event is emitted'],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
examples: [
|
|
236
|
+
{
|
|
237
|
+
key: 'mark-lost',
|
|
238
|
+
input: {
|
|
239
|
+
dealId: 'deal-789',
|
|
240
|
+
reason: 'competitor',
|
|
241
|
+
note: 'Went with cheaper option',
|
|
242
|
+
},
|
|
243
|
+
output: {
|
|
244
|
+
id: 'deal-789',
|
|
245
|
+
status: 'lost',
|
|
246
|
+
closedAt: '2025-01-21T09:00:00Z',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* List deals in pipeline.
|
|
255
|
+
*/
|
|
256
|
+
export const ListDealsContract = defineQuery({
|
|
257
|
+
meta: {
|
|
258
|
+
key: 'crm.deal.list',
|
|
259
|
+
version: '1.0.0',
|
|
260
|
+
stability: 'stable',
|
|
261
|
+
owners: [...OWNERS],
|
|
262
|
+
tags: ['crm', 'deal', 'list'],
|
|
263
|
+
description: 'List deals with filters.',
|
|
264
|
+
goal: 'Show pipeline, deal lists, dashboards.',
|
|
265
|
+
context: 'Pipeline view, deal list.',
|
|
266
|
+
},
|
|
267
|
+
io: {
|
|
268
|
+
input: ListDealsInputModel,
|
|
269
|
+
output: ListDealsOutputModel,
|
|
270
|
+
},
|
|
271
|
+
policy: {
|
|
272
|
+
auth: 'user',
|
|
273
|
+
},
|
|
274
|
+
acceptance: {
|
|
275
|
+
scenarios: [
|
|
276
|
+
{
|
|
277
|
+
key: 'list-deals-happy-path',
|
|
278
|
+
given: ['User has access to deals'],
|
|
279
|
+
when: ['User lists deals'],
|
|
280
|
+
then: ['List of deals is returned'],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
examples: [
|
|
284
|
+
{
|
|
285
|
+
key: 'list-filter-stage',
|
|
286
|
+
input: { stageId: 'stage-lead', limit: 20 },
|
|
287
|
+
output: { items: [], total: 5, hasMore: false },
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
|
|
2
|
+
import { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A deal in the CRM pipeline.
|
|
6
|
+
*/
|
|
7
|
+
export const DealModel = defineSchemaModel({
|
|
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
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Input for creating a deal.
|
|
29
|
+
*/
|
|
30
|
+
export const CreateDealInputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Input for moving a deal to another stage.
|
|
47
|
+
*/
|
|
48
|
+
export const MoveDealInputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Payload for deal moved event.
|
|
60
|
+
*/
|
|
61
|
+
export const DealMovedPayloadModel = defineSchemaModel({
|
|
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
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Input for marking a deal as won.
|
|
72
|
+
*/
|
|
73
|
+
export const WinDealInputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Payload for deal won event.
|
|
85
|
+
*/
|
|
86
|
+
export const DealWonPayloadModel = defineSchemaModel({
|
|
87
|
+
name: 'DealWonPayload',
|
|
88
|
+
fields: {
|
|
89
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
90
|
+
value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Input for marking a deal as lost.
|
|
96
|
+
*/
|
|
97
|
+
export const LoseDealInputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Payload for deal lost event.
|
|
109
|
+
*/
|
|
110
|
+
export const DealLostPayloadModel = defineSchemaModel({
|
|
111
|
+
name: 'DealLostPayload',
|
|
112
|
+
fields: {
|
|
113
|
+
dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
114
|
+
reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Input for listing deals.
|
|
120
|
+
*/
|
|
121
|
+
export const ListDealsInputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Output for listing deals.
|
|
145
|
+
*/
|
|
146
|
+
export const ListDealsOutputModel = defineSchemaModel({
|
|
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
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineTestSpec } from '@contractspec/lib.contracts';
|
|
2
|
+
|
|
3
|
+
export const dealListTest = defineTestSpec({
|
|
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
|
+
});
|
|
29
|
+
|
|
30
|
+
export const dealMoveTest = defineTestSpec({
|
|
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
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deal domain - Deal management in CRM pipeline.
|
|
3
|
+
*/
|
|
4
|
+
|
|
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
|
+
export {
|
|
21
|
+
CreateDealContract,
|
|
22
|
+
MoveDealContract,
|
|
23
|
+
WinDealContract,
|
|
24
|
+
LoseDealContract,
|
|
25
|
+
ListDealsContract,
|
|
26
|
+
} from './deal.operation';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { DocBlock } from '@contractspec/lib.contracts/docs';
|
|
2
|
+
import { registerDocBlocks } from '@contractspec/lib.contracts/docs';
|
|
3
|
+
|
|
4
|
+
const crmPipelineDocBlocks: DocBlock[] = [
|
|
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
|
+
- Regenerable CRM flow for deals/stages without code drift.
|
|
16
|
+
- Ensures stage movement, tasks, and contacts stay aligned across surfaces.
|
|
17
|
+
|
|
18
|
+
## Business/Product goal
|
|
19
|
+
- Give sales teams a governed pipeline with auditable moves and notifications.
|
|
20
|
+
- Allow experimentation (feature flags) on stage definitions and task flows.
|
|
21
|
+
|
|
22
|
+
## Success criteria
|
|
23
|
+
- Stage/state changes emit events and remain declarative in spec.
|
|
24
|
+
- PII (contacts) is scoped/redacted in presentations.`,
|
|
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
|
+
1) Seed (if available) or create pipeline stages, deals, contacts, companies, tasks.
|
|
36
|
+
2) Configure Notifications for stage changes/tasks; set policy.pii for contact data.
|
|
37
|
+
|
|
38
|
+
## Extend & regenerate
|
|
39
|
+
1) Adjust stage schema/order, deal fields, task fields in the spec.
|
|
40
|
+
2) Regenerate to sync UI/API/events; ensure kanban/action buttons update.
|
|
41
|
+
3) Use Feature Flags to trial new stages or SLA rules.
|
|
42
|
+
|
|
43
|
+
## Guardrails
|
|
44
|
+
- Emit events for stage moves and task completions; log to Audit Trail.
|
|
45
|
+
- Keep required fields enforced in contracts; avoid freeform state.
|
|
46
|
+
- Redact contact PII in markdown/JSON outputs.`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'docs.examples.crm-pipeline.reference',
|
|
50
|
+
title: 'CRM Pipeline — Reference',
|
|
51
|
+
summary:
|
|
52
|
+
'Entities, contracts, events, and presentations for the CRM template.',
|
|
53
|
+
kind: 'reference',
|
|
54
|
+
visibility: 'public',
|
|
55
|
+
route: '/docs/examples/crm-pipeline',
|
|
56
|
+
tags: ['crm', 'reference'],
|
|
57
|
+
body: `## Entities
|
|
58
|
+
- Contact, Company, Deal, Pipeline, Stage, Task.
|
|
59
|
+
|
|
60
|
+
## Contracts
|
|
61
|
+
- deal/create, stage/move, contact/company CRUD, task create/complete.
|
|
62
|
+
|
|
63
|
+
## Events
|
|
64
|
+
- deal.created, stage.moved, task.completed, contact.updated.
|
|
65
|
+
|
|
66
|
+
## Presentations
|
|
67
|
+
- Pipelines/kanban, deal detail, contact/company profiles, task lists.
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
- Stage definitions should be declarative; enforce via spec and regeneration.
|
|
71
|
+
- Use Notifications for deal/task updates; Audit Trail for state changes.`,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'docs.examples.crm-pipeline.constraints',
|
|
75
|
+
title: 'CRM Pipeline — Constraints & Safety',
|
|
76
|
+
summary:
|
|
77
|
+
'Internal guardrails for stages, PII, and regeneration semantics in the CRM template.',
|
|
78
|
+
kind: 'reference',
|
|
79
|
+
visibility: 'internal',
|
|
80
|
+
route: '/docs/examples/crm-pipeline/constraints',
|
|
81
|
+
tags: ['crm', 'constraints', 'internal'],
|
|
82
|
+
body: `## Constraints
|
|
83
|
+
- Stage definitions/order must remain declarative; no imperative overrides in code.
|
|
84
|
+
- Events to emit: deal.created, stage.moved, task.completed, contact.updated (minimum).
|
|
85
|
+
- Regeneration should not alter stage semantics without explicit spec change.
|
|
86
|
+
|
|
87
|
+
## PII
|
|
88
|
+
- Mark contact/company PII (emails, phones) for redaction in presentations.
|
|
89
|
+
- Ensure MCP/web outputs avoid raw PII when not needed.
|
|
90
|
+
|
|
91
|
+
## Verification
|
|
92
|
+
- Add fixtures for stage move rules and SLA/task changes.
|
|
93
|
+
- Ensure Audit/Notifications remain wired for stage and task events.
|
|
94
|
+
- Use Feature Flags for experimental stages/SLAs; default safe/off.`,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
registerDocBlocks(crmPipelineDocBlocks);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './crm-pipeline.docblock';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineEntity,
|
|
3
|
+
defineEntityEnum,
|
|
4
|
+
field,
|
|
5
|
+
index,
|
|
6
|
+
} from '@contractspec/lib.schema';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Company size enum.
|
|
10
|
+
*/
|
|
11
|
+
export const CompanySizeEnum = defineEntityEnum({
|
|
12
|
+
name: 'CompanySize',
|
|
13
|
+
values: ['STARTUP', 'SMALL', 'MEDIUM', 'LARGE', 'ENTERPRISE'] as const,
|
|
14
|
+
schema: 'crm',
|
|
15
|
+
description: 'Size category of a company.',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Company entity - organization/account.
|
|
20
|
+
*/
|
|
21
|
+
export const CompanyEntity = defineEntity({
|
|
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
|
+
|
|
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
|
+
|
|
34
|
+
// Industry
|
|
35
|
+
industry: field.string({ isOptional: true }),
|
|
36
|
+
|
|
37
|
+
// Size
|
|
38
|
+
size: field.enum('CompanySize', { isOptional: true }),
|
|
39
|
+
employeeCount: field.int({ isOptional: true }),
|
|
40
|
+
annualRevenue: field.decimal({ isOptional: true }),
|
|
41
|
+
|
|
42
|
+
// Ownership
|
|
43
|
+
organizationId: field.foreignKey(),
|
|
44
|
+
ownerId: field.foreignKey({ description: 'Account owner' }),
|
|
45
|
+
|
|
46
|
+
// Contact info
|
|
47
|
+
phone: field.string({ isOptional: true }),
|
|
48
|
+
email: field.email({ isOptional: true }),
|
|
49
|
+
|
|
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
|
+
|
|
57
|
+
// Social
|
|
58
|
+
linkedInUrl: field.url({ isOptional: true }),
|
|
59
|
+
|
|
60
|
+
// Notes
|
|
61
|
+
description: field.string({ isOptional: true }),
|
|
62
|
+
tags: field.string({ isArray: true }),
|
|
63
|
+
|
|
64
|
+
// Custom fields
|
|
65
|
+
customFields: field.json({ isOptional: true }),
|
|
66
|
+
|
|
67
|
+
// Timestamps
|
|
68
|
+
createdAt: field.createdAt(),
|
|
69
|
+
updatedAt: field.updatedAt(),
|
|
70
|
+
|
|
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
|
+
});
|