@elevasis/core 0.19.0 → 0.20.0
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/dist/index.js +62 -0
- package/dist/knowledge/index.d.ts +1 -1
- package/dist/organization-model/index.js +62 -0
- package/dist/test-utils/index.js +61 -0
- package/package.json +3 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +11 -4
- package/src/business/acquisition/api-schemas.test.ts +100 -14
- package/src/business/acquisition/api-schemas.ts +36 -9
- package/src/business/acquisition/derive-actions.test.ts +1 -1
- package/src/business/acquisition/types.ts +2 -2
- package/src/business/deals/api-schemas.ts +2 -2
- package/src/organization-model/__tests__/graph.test.ts +108 -2
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +6 -9
- package/src/organization-model/__tests__/schema.test.ts +122 -0
- package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
- package/src/organization-model/domains/prospecting.ts +91 -16
- package/src/organization-model/domains/sales.ts +8 -5
- package/src/organization-model/graph/build.ts +74 -0
- package/src/organization-model/graph/schema.ts +1 -0
- package/src/organization-model/graph/types.ts +1 -0
- package/src/organization-model/schema.ts +63 -0
- package/src/organization-model/surface-projection.ts +218 -0
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +11 -4
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OrganizationGraphNode,
|
|
8
8
|
OrganizationGraphNodeKind
|
|
9
9
|
} from './types'
|
|
10
|
+
import { CAPABILITY_REGISTRY } from '../domains/prospecting'
|
|
10
11
|
|
|
11
12
|
function nodeId(kind: OrganizationGraphNodeKind, sourceId?: string): string {
|
|
12
13
|
return kind === 'organization' ? 'organization-model' : `${kind}:${sourceId ?? ''}`
|
|
@@ -187,6 +188,79 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
const allStages = [
|
|
192
|
+
...organizationModel.prospecting.companyStages,
|
|
193
|
+
...organizationModel.prospecting.contactStages
|
|
194
|
+
].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
|
|
195
|
+
|
|
196
|
+
for (const stage of allStages) {
|
|
197
|
+
const id = nodeId('stage', stage.id)
|
|
198
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
199
|
+
id,
|
|
200
|
+
kind: 'stage',
|
|
201
|
+
label: stage.label,
|
|
202
|
+
sourceId: stage.id,
|
|
203
|
+
...(stage.description ? { description: stage.description } : {}),
|
|
204
|
+
...(stage.icon ? { icon: stage.icon } : {})
|
|
205
|
+
})
|
|
206
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
207
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
208
|
+
kind: 'contains',
|
|
209
|
+
sourceId: organizationNode.id,
|
|
210
|
+
targetId: id
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const cap of [...CAPABILITY_REGISTRY].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
215
|
+
const id = nodeId('capability', cap.id)
|
|
216
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
217
|
+
id,
|
|
218
|
+
kind: 'capability',
|
|
219
|
+
label: cap.label,
|
|
220
|
+
sourceId: cap.id,
|
|
221
|
+
description: cap.description
|
|
222
|
+
})
|
|
223
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
224
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
225
|
+
kind: 'contains',
|
|
226
|
+
sourceId: organizationNode.id,
|
|
227
|
+
targetId: id
|
|
228
|
+
})
|
|
229
|
+
const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, cap.resourceId)
|
|
230
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
231
|
+
id: edgeId('maps_to', id, resourceNode.id),
|
|
232
|
+
kind: 'maps_to',
|
|
233
|
+
sourceId: id,
|
|
234
|
+
targetId: resourceNode.id
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const template of [...organizationModel.prospecting.buildTemplates].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
239
|
+
const stepById = new Map(template.steps.map((s) => [s.id, s]))
|
|
240
|
+
for (const step of [...template.steps].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
241
|
+
const stageNodeId = nodeId('stage', step.stageKey)
|
|
242
|
+
const capNodeId = nodeId('capability', step.capabilityKey)
|
|
243
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
244
|
+
id: edgeId('uses', stageNodeId, capNodeId, step.id),
|
|
245
|
+
kind: 'uses',
|
|
246
|
+
sourceId: stageNodeId,
|
|
247
|
+
targetId: capNodeId
|
|
248
|
+
})
|
|
249
|
+
for (const depId of step.dependsOn ?? []) {
|
|
250
|
+
const depStep = stepById.get(depId)
|
|
251
|
+
if (depStep) {
|
|
252
|
+
const depStageNodeId = nodeId('stage', depStep.stageKey)
|
|
253
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
254
|
+
id: edgeId('references', stageNodeId, depStageNodeId, step.id),
|
|
255
|
+
kind: 'references',
|
|
256
|
+
sourceId: stageNodeId,
|
|
257
|
+
targetId: depStageNodeId
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
190
264
|
if (commandViewData) {
|
|
191
265
|
const commandViewResources = collectCommandViewResources(commandViewData).sort((a, b) =>
|
|
192
266
|
a.resourceId.localeCompare(b.resourceId)
|
|
@@ -70,8 +70,14 @@ function hasFeature(featuresById: Map<string, unknown>, featureId: string): bool
|
|
|
70
70
|
return featuresById.has(featureId) || featuresById.has(LEGACY_FEATURE_ALIASES.get(featureId) ?? '')
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function defaultFeaturePathFor(id: string): string {
|
|
74
|
+
return `/${id.replaceAll('.', '/')}`
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ctx) => {
|
|
74
78
|
const featuresById = collectIds(model.features, ctx, ['features'], 'Feature')
|
|
79
|
+
const featureIdsByEffectivePath = new Map<string, string>()
|
|
80
|
+
|
|
75
81
|
model.features.forEach((feature, featureIndex) => {
|
|
76
82
|
const segments = feature.id.split('.')
|
|
77
83
|
if (segments.length > 1) {
|
|
@@ -88,6 +94,21 @@ export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((
|
|
|
88
94
|
const hasChildren = model.features.some(
|
|
89
95
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.id !== feature.id
|
|
90
96
|
)
|
|
97
|
+
const contributesRoutePath = feature.path !== undefined || !hasChildren
|
|
98
|
+
if (contributesRoutePath) {
|
|
99
|
+
const effectivePath = feature.path ?? defaultFeaturePathFor(feature.id)
|
|
100
|
+
const existingFeatureId = featureIdsByEffectivePath.get(effectivePath)
|
|
101
|
+
if (existingFeatureId !== undefined) {
|
|
102
|
+
addIssue(
|
|
103
|
+
ctx,
|
|
104
|
+
['features', featureIndex, feature.path === undefined ? 'id' : 'path'],
|
|
105
|
+
`Feature "${feature.id}" effective path "${effectivePath}" duplicates feature "${existingFeatureId}"`
|
|
106
|
+
)
|
|
107
|
+
} else {
|
|
108
|
+
featureIdsByEffectivePath.set(effectivePath, feature.id)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
if (hasChildren && feature.enabled) {
|
|
92
113
|
const hasEnabledDescendant = model.features.some(
|
|
93
114
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.enabled
|
|
@@ -102,6 +123,48 @@ export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((
|
|
|
102
123
|
}
|
|
103
124
|
})
|
|
104
125
|
|
|
126
|
+
const surfacesById = collectIds(model.navigation.surfaces, ctx, ['navigation', 'surfaces'], 'Navigation surface')
|
|
127
|
+
|
|
128
|
+
if (model.navigation.defaultSurfaceId !== undefined && !surfacesById.has(model.navigation.defaultSurfaceId)) {
|
|
129
|
+
addIssue(
|
|
130
|
+
ctx,
|
|
131
|
+
['navigation', 'defaultSurfaceId'],
|
|
132
|
+
`Navigation defaultSurfaceId references unknown surface "${model.navigation.defaultSurfaceId}"`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
model.navigation.groups.forEach((group, groupIndex) => {
|
|
137
|
+
group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
|
|
138
|
+
if (!surfacesById.has(surfaceId)) {
|
|
139
|
+
addIssue(
|
|
140
|
+
ctx,
|
|
141
|
+
['navigation', 'groups', groupIndex, 'surfaceIds', surfaceIndex],
|
|
142
|
+
`Navigation group "${group.id}" references unknown surface "${surfaceId}"`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
model.navigation.surfaces.forEach((surface, surfaceIndex) => {
|
|
149
|
+
if (surface.featureId !== undefined && !hasFeature(featuresById, surface.featureId)) {
|
|
150
|
+
addIssue(
|
|
151
|
+
ctx,
|
|
152
|
+
['navigation', 'surfaces', surfaceIndex, 'featureId'],
|
|
153
|
+
`Navigation surface "${surface.id}" references unknown feature "${surface.featureId}"`
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
surface.featureIds.forEach((featureId, featureIndex) => {
|
|
158
|
+
if (!hasFeature(featuresById, featureId)) {
|
|
159
|
+
addIssue(
|
|
160
|
+
ctx,
|
|
161
|
+
['navigation', 'surfaces', surfaceIndex, 'featureIds', featureIndex],
|
|
162
|
+
`Navigation surface "${surface.id}" references unknown feature "${featureId}"`
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
105
168
|
// Offerings -> CustomerSegment cross-ref: targetSegmentIds must resolve
|
|
106
169
|
const segmentsById = new Map(model.customers.segments.map((seg) => [seg.id, seg]))
|
|
107
170
|
model.offerings.products.forEach((product, productIndex) => {
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { OrganizationModel, OrganizationModelFeature, OrganizationModelSurface } from './types'
|
|
2
|
+
|
|
3
|
+
export type OrganizationSurfaceProjectionIssueCode =
|
|
4
|
+
| 'duplicate-surface-id'
|
|
5
|
+
| 'duplicate-surface-path'
|
|
6
|
+
| 'unknown-default-surface'
|
|
7
|
+
| 'unknown-group-surface'
|
|
8
|
+
| 'unknown-surface-feature'
|
|
9
|
+
| 'unknown-surface-feature-reference'
|
|
10
|
+
|
|
11
|
+
export interface OrganizationSurfaceProjection {
|
|
12
|
+
id: string
|
|
13
|
+
label: string
|
|
14
|
+
path: string
|
|
15
|
+
surfaceType: OrganizationModelSurface['surfaceType']
|
|
16
|
+
featureId?: string
|
|
17
|
+
featureIds: string[]
|
|
18
|
+
entityIds: string[]
|
|
19
|
+
resourceIds: string[]
|
|
20
|
+
capabilityIds: string[]
|
|
21
|
+
enabled: boolean
|
|
22
|
+
devOnly?: boolean
|
|
23
|
+
requiresAdmin?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OrganizationSurfaceProjectionIssue {
|
|
27
|
+
code: OrganizationSurfaceProjectionIssueCode
|
|
28
|
+
message: string
|
|
29
|
+
path: Array<string | number>
|
|
30
|
+
surfaceId?: string
|
|
31
|
+
featureId?: string
|
|
32
|
+
groupId?: string
|
|
33
|
+
value?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const LEGACY_FEATURE_ALIASES = new Map<string, string>([
|
|
37
|
+
['crm', 'sales.crm'],
|
|
38
|
+
['lead-gen', 'sales.lead-gen'],
|
|
39
|
+
['submitted-requests', 'monitoring.submitted-requests']
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
function normalizePath(path: string): string {
|
|
43
|
+
return path.length > 1 ? path.replace(/\/+$/, '') : path
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectFeaturesById(model: OrganizationModel): Map<string, OrganizationModelFeature> {
|
|
47
|
+
return new Map(model.features.map((feature) => [feature.id, feature]))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveFeatureId(featuresById: Map<string, OrganizationModelFeature>, featureId: string): string | undefined {
|
|
51
|
+
if (featuresById.has(featureId)) {
|
|
52
|
+
return featureId
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const aliasTarget = LEGACY_FEATURE_ALIASES.get(featureId)
|
|
56
|
+
return aliasTarget !== undefined && featuresById.has(aliasTarget) ? aliasTarget : undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getFeatureWithAncestors(
|
|
60
|
+
featuresById: Map<string, OrganizationModelFeature>,
|
|
61
|
+
featureId: string
|
|
62
|
+
): OrganizationModelFeature[] {
|
|
63
|
+
const features: OrganizationModelFeature[] = []
|
|
64
|
+
const segments = featureId.split('.')
|
|
65
|
+
|
|
66
|
+
for (let index = 1; index <= segments.length; index += 1) {
|
|
67
|
+
const candidate = featuresById.get(segments.slice(0, index).join('.'))
|
|
68
|
+
if (candidate) {
|
|
69
|
+
features.push(candidate)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return features
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hasInheritedFlag(
|
|
77
|
+
featuresById: Map<string, OrganizationModelFeature>,
|
|
78
|
+
featureIds: string[],
|
|
79
|
+
flag: 'devOnly' | 'requiresAdmin'
|
|
80
|
+
): boolean {
|
|
81
|
+
return featureIds.some((featureId) =>
|
|
82
|
+
getFeatureWithAncestors(featuresById, featureId).some((feature) => feature[flag] === true)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isFeatureEnabled(featuresById: Map<string, OrganizationModelFeature>, featureId: string): boolean {
|
|
87
|
+
const featureLineage = getFeatureWithAncestors(featuresById, featureId)
|
|
88
|
+
return featureLineage.length > 0 && featureLineage.every((feature) => feature.enabled)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unique(values: string[]): string[] {
|
|
92
|
+
return [...new Set(values)]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function projectOrganizationSurfaces(model: OrganizationModel): OrganizationSurfaceProjection[] {
|
|
96
|
+
const featuresById = collectFeaturesById(model)
|
|
97
|
+
|
|
98
|
+
return model.navigation.surfaces.map((surface) => {
|
|
99
|
+
const featureId = surface.featureId ? resolveFeatureId(featuresById, surface.featureId) : undefined
|
|
100
|
+
const featureIds = unique(
|
|
101
|
+
[featureId, ...surface.featureIds.map((candidate) => resolveFeatureId(featuresById, candidate))].filter(
|
|
102
|
+
(candidate): candidate is string => candidate !== undefined
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
const enabled = surface.enabled && featureIds.every((candidate) => isFeatureEnabled(featuresById, candidate))
|
|
106
|
+
const devOnly = surface.devOnly === true || hasInheritedFlag(featuresById, featureIds, 'devOnly')
|
|
107
|
+
const requiresAdmin = hasInheritedFlag(featuresById, featureId ? [featureId] : featureIds, 'requiresAdmin')
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
id: surface.id,
|
|
111
|
+
label: surface.label,
|
|
112
|
+
path: surface.path,
|
|
113
|
+
surfaceType: surface.surfaceType,
|
|
114
|
+
featureId,
|
|
115
|
+
featureIds,
|
|
116
|
+
entityIds: [...surface.entityIds],
|
|
117
|
+
resourceIds: [...surface.resourceIds],
|
|
118
|
+
capabilityIds: [...surface.capabilityIds],
|
|
119
|
+
enabled,
|
|
120
|
+
...(devOnly ? { devOnly } : {}),
|
|
121
|
+
...(requiresAdmin ? { requiresAdmin } : {})
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function validateOrganizationSurfaceProjection(model: OrganizationModel): OrganizationSurfaceProjectionIssue[] {
|
|
127
|
+
const issues: OrganizationSurfaceProjectionIssue[] = []
|
|
128
|
+
const featuresById = collectFeaturesById(model)
|
|
129
|
+
const surfaceIds = new Set<string>()
|
|
130
|
+
const surfacePaths = new Map<string, string>()
|
|
131
|
+
|
|
132
|
+
function addIssue(issue: OrganizationSurfaceProjectionIssue): void {
|
|
133
|
+
issues.push(issue)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
model.navigation.surfaces.forEach((surface, surfaceIndex) => {
|
|
137
|
+
if (surfaceIds.has(surface.id)) {
|
|
138
|
+
addIssue({
|
|
139
|
+
code: 'duplicate-surface-id',
|
|
140
|
+
message: `Surface id "${surface.id}" must be unique`,
|
|
141
|
+
path: ['navigation', 'surfaces', surfaceIndex, 'id'],
|
|
142
|
+
surfaceId: surface.id,
|
|
143
|
+
value: surface.id
|
|
144
|
+
})
|
|
145
|
+
} else {
|
|
146
|
+
surfaceIds.add(surface.id)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const normalizedPath = normalizePath(surface.path)
|
|
150
|
+
const existingSurfaceId = surfacePaths.get(normalizedPath)
|
|
151
|
+
if (existingSurfaceId !== undefined) {
|
|
152
|
+
addIssue({
|
|
153
|
+
code: 'duplicate-surface-path',
|
|
154
|
+
message: `Surface path "${surface.path}" is already used by surface "${existingSurfaceId}"`,
|
|
155
|
+
path: ['navigation', 'surfaces', surfaceIndex, 'path'],
|
|
156
|
+
surfaceId: surface.id,
|
|
157
|
+
value: surface.path
|
|
158
|
+
})
|
|
159
|
+
} else {
|
|
160
|
+
surfacePaths.set(normalizedPath, surface.id)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (surface.featureId !== undefined && resolveFeatureId(featuresById, surface.featureId) === undefined) {
|
|
164
|
+
addIssue({
|
|
165
|
+
code: 'unknown-surface-feature',
|
|
166
|
+
message: `Surface "${surface.id}" references unknown feature "${surface.featureId}"`,
|
|
167
|
+
path: ['navigation', 'surfaces', surfaceIndex, 'featureId'],
|
|
168
|
+
surfaceId: surface.id,
|
|
169
|
+
featureId: surface.featureId,
|
|
170
|
+
value: surface.featureId
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
surface.featureIds.forEach((featureId, featureIndex) => {
|
|
175
|
+
if (resolveFeatureId(featuresById, featureId) !== undefined) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addIssue({
|
|
180
|
+
code: 'unknown-surface-feature-reference',
|
|
181
|
+
message: `Surface "${surface.id}" references unknown feature "${featureId}"`,
|
|
182
|
+
path: ['navigation', 'surfaces', surfaceIndex, 'featureIds', featureIndex],
|
|
183
|
+
surfaceId: surface.id,
|
|
184
|
+
featureId,
|
|
185
|
+
value: featureId
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (model.navigation.defaultSurfaceId !== undefined && !surfaceIds.has(model.navigation.defaultSurfaceId)) {
|
|
191
|
+
addIssue({
|
|
192
|
+
code: 'unknown-default-surface',
|
|
193
|
+
message: `Default surface "${model.navigation.defaultSurfaceId}" is not declared in navigation.surfaces`,
|
|
194
|
+
path: ['navigation', 'defaultSurfaceId'],
|
|
195
|
+
surfaceId: model.navigation.defaultSurfaceId,
|
|
196
|
+
value: model.navigation.defaultSurfaceId
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
model.navigation.groups.forEach((group, groupIndex) => {
|
|
201
|
+
group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
|
|
202
|
+
if (surfaceIds.has(surfaceId)) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
addIssue({
|
|
207
|
+
code: 'unknown-group-surface',
|
|
208
|
+
message: `Navigation group "${group.id}" references unknown surface "${surfaceId}"`,
|
|
209
|
+
path: ['navigation', 'groups', groupIndex, 'surfaceIds', surfaceIndex],
|
|
210
|
+
surfaceId,
|
|
211
|
+
groupId: group.id,
|
|
212
|
+
value: surfaceId
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return issues
|
|
218
|
+
}
|
|
@@ -1123,7 +1123,7 @@ export const DEFAULT_CRM_PRIORITY_RULE_CONFIG: CrmPriorityRuleConfig = {
|
|
|
1123
1123
|
### `DealStageSchema`
|
|
1124
1124
|
|
|
1125
1125
|
```typescript
|
|
1126
|
-
export const DealStageSchema =
|
|
1126
|
+
export const DealStageSchema = CrmStageKeySchema
|
|
1127
1127
|
```
|
|
1128
1128
|
|
|
1129
1129
|
### `AcqDealTaskKindSchema`
|
|
@@ -1215,7 +1215,7 @@ export const TransitionItemRequestSchema = z
|
|
|
1215
1215
|
.object({
|
|
1216
1216
|
pipelineKey: z.string().min(1),
|
|
1217
1217
|
stageKey: z.string().min(1),
|
|
1218
|
-
stateKey: z.string().nullable().optional(),
|
|
1218
|
+
stateKey: z.string().min(1).nullable().optional(),
|
|
1219
1219
|
reason: z.string().optional(),
|
|
1220
1220
|
expectedUpdatedAt: z.string().datetime().optional()
|
|
1221
1221
|
})
|
|
@@ -1409,6 +1409,11 @@ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
|
|
|
1409
1409
|
|
|
1410
1410
|
```typescript
|
|
1411
1411
|
export const DealSchemas = {
|
|
1412
|
+
// Primitives
|
|
1413
|
+
CrmStageKey: CrmStageKeySchema,
|
|
1414
|
+
CrmStateKey: CrmStateKeySchema,
|
|
1415
|
+
DealStage: DealStageSchema,
|
|
1416
|
+
|
|
1412
1417
|
// Params
|
|
1413
1418
|
DealIdParams: DealIdParamsSchema,
|
|
1414
1419
|
DealTaskIdParams: DealTaskIdParamsSchema,
|
|
@@ -1421,7 +1426,7 @@ export const DealSchemas = {
|
|
|
1421
1426
|
// Request bodies
|
|
1422
1427
|
CreateDealNoteRequest: CreateDealNoteRequestSchema,
|
|
1423
1428
|
CreateDealTaskRequest: CreateDealTaskRequestSchema,
|
|
1424
|
-
TransitionItemRequest:
|
|
1429
|
+
TransitionItemRequest: CrmTransitionItemRequestSchema,
|
|
1425
1430
|
TransitionDealStateRequest: TransitionDealStateRequestSchema,
|
|
1426
1431
|
ExecuteActionParams: ExecuteActionParamsSchema,
|
|
1427
1432
|
ExecuteActionRequest: ExecuteActionRequestSchema,
|
|
@@ -2552,7 +2557,7 @@ export const AcqSubstrateSchemas = {
|
|
|
2552
2557
|
ListCompanyIdParams: ListCompanyIdParamsSchema,
|
|
2553
2558
|
AcqListCompanyResponse: AcqListCompanyResponseSchema,
|
|
2554
2559
|
|
|
2555
|
-
// Transition (
|
|
2560
|
+
// Transition (generic stateful substrate)
|
|
2556
2561
|
TransitionItemRequest: TransitionItemRequestSchema
|
|
2557
2562
|
}
|
|
2558
2563
|
```
|
|
@@ -2620,6 +2625,8 @@ export interface StatefulStageDefinition {
|
|
|
2620
2625
|
/** Matches stage_key values written by workflow steps. */
|
|
2621
2626
|
stageKey: string
|
|
2622
2627
|
label: string
|
|
2628
|
+
/** UI color token. Consumers may map this to their design system. */
|
|
2629
|
+
color?: string
|
|
2623
2630
|
states: StatefulStateDefinition[]
|
|
2624
2631
|
}
|
|
2625
2632
|
```
|