@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
package/dist/index.js
CHANGED
|
@@ -386,6 +386,12 @@ var ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
|
|
|
386
386
|
id: ModelIdSchema,
|
|
387
387
|
steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
|
|
388
388
|
});
|
|
389
|
+
z.object({
|
|
390
|
+
id: ModelIdSchema,
|
|
391
|
+
label: z.string(),
|
|
392
|
+
description: z.string(),
|
|
393
|
+
resourceId: ModelIdSchema
|
|
394
|
+
});
|
|
389
395
|
var PROSPECTING_STEPS = {
|
|
390
396
|
localServices: {
|
|
391
397
|
sourceCompanies: {
|
|
@@ -1124,8 +1130,12 @@ var LEGACY_FEATURE_ALIASES = /* @__PURE__ */ new Map([
|
|
|
1124
1130
|
function hasFeature(featuresById, featureId) {
|
|
1125
1131
|
return featuresById.has(featureId) || featuresById.has(LEGACY_FEATURE_ALIASES.get(featureId) ?? "");
|
|
1126
1132
|
}
|
|
1133
|
+
function defaultFeaturePathFor(id) {
|
|
1134
|
+
return `/${id.replaceAll(".", "/")}`;
|
|
1135
|
+
}
|
|
1127
1136
|
var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ctx) => {
|
|
1128
1137
|
const featuresById = collectIds(model.features, ctx, ["features"], "Feature");
|
|
1138
|
+
const featureIdsByEffectivePath = /* @__PURE__ */ new Map();
|
|
1129
1139
|
model.features.forEach((feature, featureIndex) => {
|
|
1130
1140
|
const segments = feature.id.split(".");
|
|
1131
1141
|
if (segments.length > 1) {
|
|
@@ -1141,6 +1151,20 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
1141
1151
|
const hasChildren = model.features.some(
|
|
1142
1152
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.id !== feature.id
|
|
1143
1153
|
);
|
|
1154
|
+
const contributesRoutePath = feature.path !== void 0 || !hasChildren;
|
|
1155
|
+
if (contributesRoutePath) {
|
|
1156
|
+
const effectivePath = feature.path ?? defaultFeaturePathFor(feature.id);
|
|
1157
|
+
const existingFeatureId = featureIdsByEffectivePath.get(effectivePath);
|
|
1158
|
+
if (existingFeatureId !== void 0) {
|
|
1159
|
+
addIssue(
|
|
1160
|
+
ctx,
|
|
1161
|
+
["features", featureIndex, feature.path === void 0 ? "id" : "path"],
|
|
1162
|
+
`Feature "${feature.id}" effective path "${effectivePath}" duplicates feature "${existingFeatureId}"`
|
|
1163
|
+
);
|
|
1164
|
+
} else {
|
|
1165
|
+
featureIdsByEffectivePath.set(effectivePath, feature.id);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1144
1168
|
if (hasChildren && feature.enabled) {
|
|
1145
1169
|
const hasEnabledDescendant = model.features.some(
|
|
1146
1170
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.enabled
|
|
@@ -1154,6 +1178,43 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
1154
1178
|
}
|
|
1155
1179
|
}
|
|
1156
1180
|
});
|
|
1181
|
+
const surfacesById = collectIds(model.navigation.surfaces, ctx, ["navigation", "surfaces"], "Navigation surface");
|
|
1182
|
+
if (model.navigation.defaultSurfaceId !== void 0 && !surfacesById.has(model.navigation.defaultSurfaceId)) {
|
|
1183
|
+
addIssue(
|
|
1184
|
+
ctx,
|
|
1185
|
+
["navigation", "defaultSurfaceId"],
|
|
1186
|
+
`Navigation defaultSurfaceId references unknown surface "${model.navigation.defaultSurfaceId}"`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
model.navigation.groups.forEach((group, groupIndex) => {
|
|
1190
|
+
group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
|
|
1191
|
+
if (!surfacesById.has(surfaceId)) {
|
|
1192
|
+
addIssue(
|
|
1193
|
+
ctx,
|
|
1194
|
+
["navigation", "groups", groupIndex, "surfaceIds", surfaceIndex],
|
|
1195
|
+
`Navigation group "${group.id}" references unknown surface "${surfaceId}"`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
model.navigation.surfaces.forEach((surface, surfaceIndex) => {
|
|
1201
|
+
if (surface.featureId !== void 0 && !hasFeature(featuresById, surface.featureId)) {
|
|
1202
|
+
addIssue(
|
|
1203
|
+
ctx,
|
|
1204
|
+
["navigation", "surfaces", surfaceIndex, "featureId"],
|
|
1205
|
+
`Navigation surface "${surface.id}" references unknown feature "${surface.featureId}"`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
surface.featureIds.forEach((featureId, featureIndex) => {
|
|
1209
|
+
if (!hasFeature(featuresById, featureId)) {
|
|
1210
|
+
addIssue(
|
|
1211
|
+
ctx,
|
|
1212
|
+
["navigation", "surfaces", surfaceIndex, "featureIds", featureIndex],
|
|
1213
|
+
`Navigation surface "${surface.id}" references unknown feature "${featureId}"`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1157
1218
|
const segmentsById = new Map(model.customers.segments.map((seg) => [seg.id, seg]));
|
|
1158
1219
|
model.offerings.products.forEach((product, productIndex) => {
|
|
1159
1220
|
product.targetSegmentIds.forEach((segmentId, segmentIndex) => {
|
|
@@ -1199,6 +1260,7 @@ var OrganizationGraphNodeKindSchema = z.enum([
|
|
|
1199
1260
|
"surface",
|
|
1200
1261
|
"entity",
|
|
1201
1262
|
"capability",
|
|
1263
|
+
"stage",
|
|
1202
1264
|
"resource",
|
|
1203
1265
|
"knowledge"
|
|
1204
1266
|
]);
|
|
@@ -1216,7 +1216,7 @@ declare const OrganizationModelSchema: z.ZodObject<{
|
|
|
1216
1216
|
|
|
1217
1217
|
type OrganizationModel = z.infer<typeof OrganizationModelSchema>;
|
|
1218
1218
|
|
|
1219
|
-
type OrganizationGraphNodeKind = 'organization' | 'feature' | 'surface' | 'entity' | 'capability' | 'resource' | 'knowledge';
|
|
1219
|
+
type OrganizationGraphNodeKind = 'organization' | 'feature' | 'surface' | 'entity' | 'capability' | 'stage' | 'resource' | 'knowledge';
|
|
1220
1220
|
type OrganizationGraphEdgeKind = 'contains' | 'references' | 'exposes' | 'maps_to' | 'operates-on' | 'uses' | 'governs';
|
|
1221
1221
|
|
|
1222
1222
|
interface OrganizationGraphNode {
|
|
@@ -386,6 +386,12 @@ var ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
|
|
|
386
386
|
id: ModelIdSchema,
|
|
387
387
|
steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
|
|
388
388
|
});
|
|
389
|
+
z.object({
|
|
390
|
+
id: ModelIdSchema,
|
|
391
|
+
label: z.string(),
|
|
392
|
+
description: z.string(),
|
|
393
|
+
resourceId: ModelIdSchema
|
|
394
|
+
});
|
|
389
395
|
var PROSPECTING_STEPS = {
|
|
390
396
|
localServices: {
|
|
391
397
|
sourceCompanies: {
|
|
@@ -1124,8 +1130,12 @@ var LEGACY_FEATURE_ALIASES = /* @__PURE__ */ new Map([
|
|
|
1124
1130
|
function hasFeature(featuresById, featureId) {
|
|
1125
1131
|
return featuresById.has(featureId) || featuresById.has(LEGACY_FEATURE_ALIASES.get(featureId) ?? "");
|
|
1126
1132
|
}
|
|
1133
|
+
function defaultFeaturePathFor(id) {
|
|
1134
|
+
return `/${id.replaceAll(".", "/")}`;
|
|
1135
|
+
}
|
|
1127
1136
|
var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ctx) => {
|
|
1128
1137
|
const featuresById = collectIds(model.features, ctx, ["features"], "Feature");
|
|
1138
|
+
const featureIdsByEffectivePath = /* @__PURE__ */ new Map();
|
|
1129
1139
|
model.features.forEach((feature, featureIndex) => {
|
|
1130
1140
|
const segments = feature.id.split(".");
|
|
1131
1141
|
if (segments.length > 1) {
|
|
@@ -1141,6 +1151,20 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
1141
1151
|
const hasChildren = model.features.some(
|
|
1142
1152
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.id !== feature.id
|
|
1143
1153
|
);
|
|
1154
|
+
const contributesRoutePath = feature.path !== void 0 || !hasChildren;
|
|
1155
|
+
if (contributesRoutePath) {
|
|
1156
|
+
const effectivePath = feature.path ?? defaultFeaturePathFor(feature.id);
|
|
1157
|
+
const existingFeatureId = featureIdsByEffectivePath.get(effectivePath);
|
|
1158
|
+
if (existingFeatureId !== void 0) {
|
|
1159
|
+
addIssue(
|
|
1160
|
+
ctx,
|
|
1161
|
+
["features", featureIndex, feature.path === void 0 ? "id" : "path"],
|
|
1162
|
+
`Feature "${feature.id}" effective path "${effectivePath}" duplicates feature "${existingFeatureId}"`
|
|
1163
|
+
);
|
|
1164
|
+
} else {
|
|
1165
|
+
featureIdsByEffectivePath.set(effectivePath, feature.id);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1144
1168
|
if (hasChildren && feature.enabled) {
|
|
1145
1169
|
const hasEnabledDescendant = model.features.some(
|
|
1146
1170
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.enabled
|
|
@@ -1154,6 +1178,43 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
1154
1178
|
}
|
|
1155
1179
|
}
|
|
1156
1180
|
});
|
|
1181
|
+
const surfacesById = collectIds(model.navigation.surfaces, ctx, ["navigation", "surfaces"], "Navigation surface");
|
|
1182
|
+
if (model.navigation.defaultSurfaceId !== void 0 && !surfacesById.has(model.navigation.defaultSurfaceId)) {
|
|
1183
|
+
addIssue(
|
|
1184
|
+
ctx,
|
|
1185
|
+
["navigation", "defaultSurfaceId"],
|
|
1186
|
+
`Navigation defaultSurfaceId references unknown surface "${model.navigation.defaultSurfaceId}"`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
model.navigation.groups.forEach((group, groupIndex) => {
|
|
1190
|
+
group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
|
|
1191
|
+
if (!surfacesById.has(surfaceId)) {
|
|
1192
|
+
addIssue(
|
|
1193
|
+
ctx,
|
|
1194
|
+
["navigation", "groups", groupIndex, "surfaceIds", surfaceIndex],
|
|
1195
|
+
`Navigation group "${group.id}" references unknown surface "${surfaceId}"`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
model.navigation.surfaces.forEach((surface, surfaceIndex) => {
|
|
1201
|
+
if (surface.featureId !== void 0 && !hasFeature(featuresById, surface.featureId)) {
|
|
1202
|
+
addIssue(
|
|
1203
|
+
ctx,
|
|
1204
|
+
["navigation", "surfaces", surfaceIndex, "featureId"],
|
|
1205
|
+
`Navigation surface "${surface.id}" references unknown feature "${surface.featureId}"`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
surface.featureIds.forEach((featureId, featureIndex) => {
|
|
1209
|
+
if (!hasFeature(featuresById, featureId)) {
|
|
1210
|
+
addIssue(
|
|
1211
|
+
ctx,
|
|
1212
|
+
["navigation", "surfaces", surfaceIndex, "featureIds", featureIndex],
|
|
1213
|
+
`Navigation surface "${surface.id}" references unknown feature "${featureId}"`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1157
1218
|
const segmentsById = new Map(model.customers.segments.map((seg) => [seg.id, seg]));
|
|
1158
1219
|
model.offerings.products.forEach((product, productIndex) => {
|
|
1159
1220
|
product.targetSegmentIds.forEach((segmentId, segmentIndex) => {
|
|
@@ -1199,6 +1260,7 @@ var OrganizationGraphNodeKindSchema = z.enum([
|
|
|
1199
1260
|
"surface",
|
|
1200
1261
|
"entity",
|
|
1201
1262
|
"capability",
|
|
1263
|
+
"stage",
|
|
1202
1264
|
"resource",
|
|
1203
1265
|
"knowledge"
|
|
1204
1266
|
]);
|
package/dist/test-utils/index.js
CHANGED
|
@@ -19801,6 +19801,12 @@ var ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
|
|
|
19801
19801
|
id: ModelIdSchema,
|
|
19802
19802
|
steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
|
|
19803
19803
|
});
|
|
19804
|
+
z.object({
|
|
19805
|
+
id: ModelIdSchema,
|
|
19806
|
+
label: z.string(),
|
|
19807
|
+
description: z.string(),
|
|
19808
|
+
resourceId: ModelIdSchema
|
|
19809
|
+
});
|
|
19804
19810
|
var PROSPECTING_STEPS = {
|
|
19805
19811
|
localServices: {
|
|
19806
19812
|
sourceCompanies: {
|
|
@@ -20521,8 +20527,12 @@ var LEGACY_FEATURE_ALIASES = /* @__PURE__ */ new Map([
|
|
|
20521
20527
|
function hasFeature(featuresById, featureId) {
|
|
20522
20528
|
return featuresById.has(featureId) || featuresById.has(LEGACY_FEATURE_ALIASES.get(featureId) ?? "");
|
|
20523
20529
|
}
|
|
20530
|
+
function defaultFeaturePathFor(id) {
|
|
20531
|
+
return `/${id.replaceAll(".", "/")}`;
|
|
20532
|
+
}
|
|
20524
20533
|
var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ctx) => {
|
|
20525
20534
|
const featuresById = collectIds(model.features, ctx, ["features"], "Feature");
|
|
20535
|
+
const featureIdsByEffectivePath = /* @__PURE__ */ new Map();
|
|
20526
20536
|
model.features.forEach((feature, featureIndex) => {
|
|
20527
20537
|
const segments = feature.id.split(".");
|
|
20528
20538
|
if (segments.length > 1) {
|
|
@@ -20538,6 +20548,20 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
20538
20548
|
const hasChildren = model.features.some(
|
|
20539
20549
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.id !== feature.id
|
|
20540
20550
|
);
|
|
20551
|
+
const contributesRoutePath = feature.path !== void 0 || !hasChildren;
|
|
20552
|
+
if (contributesRoutePath) {
|
|
20553
|
+
const effectivePath = feature.path ?? defaultFeaturePathFor(feature.id);
|
|
20554
|
+
const existingFeatureId = featureIdsByEffectivePath.get(effectivePath);
|
|
20555
|
+
if (existingFeatureId !== void 0) {
|
|
20556
|
+
addIssue(
|
|
20557
|
+
ctx,
|
|
20558
|
+
["features", featureIndex, feature.path === void 0 ? "id" : "path"],
|
|
20559
|
+
`Feature "${feature.id}" effective path "${effectivePath}" duplicates feature "${existingFeatureId}"`
|
|
20560
|
+
);
|
|
20561
|
+
} else {
|
|
20562
|
+
featureIdsByEffectivePath.set(effectivePath, feature.id);
|
|
20563
|
+
}
|
|
20564
|
+
}
|
|
20541
20565
|
if (hasChildren && feature.enabled) {
|
|
20542
20566
|
const hasEnabledDescendant = model.features.some(
|
|
20543
20567
|
(candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.enabled
|
|
@@ -20551,6 +20575,43 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
|
|
|
20551
20575
|
}
|
|
20552
20576
|
}
|
|
20553
20577
|
});
|
|
20578
|
+
const surfacesById = collectIds(model.navigation.surfaces, ctx, ["navigation", "surfaces"], "Navigation surface");
|
|
20579
|
+
if (model.navigation.defaultSurfaceId !== void 0 && !surfacesById.has(model.navigation.defaultSurfaceId)) {
|
|
20580
|
+
addIssue(
|
|
20581
|
+
ctx,
|
|
20582
|
+
["navigation", "defaultSurfaceId"],
|
|
20583
|
+
`Navigation defaultSurfaceId references unknown surface "${model.navigation.defaultSurfaceId}"`
|
|
20584
|
+
);
|
|
20585
|
+
}
|
|
20586
|
+
model.navigation.groups.forEach((group, groupIndex) => {
|
|
20587
|
+
group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
|
|
20588
|
+
if (!surfacesById.has(surfaceId)) {
|
|
20589
|
+
addIssue(
|
|
20590
|
+
ctx,
|
|
20591
|
+
["navigation", "groups", groupIndex, "surfaceIds", surfaceIndex],
|
|
20592
|
+
`Navigation group "${group.id}" references unknown surface "${surfaceId}"`
|
|
20593
|
+
);
|
|
20594
|
+
}
|
|
20595
|
+
});
|
|
20596
|
+
});
|
|
20597
|
+
model.navigation.surfaces.forEach((surface, surfaceIndex) => {
|
|
20598
|
+
if (surface.featureId !== void 0 && !hasFeature(featuresById, surface.featureId)) {
|
|
20599
|
+
addIssue(
|
|
20600
|
+
ctx,
|
|
20601
|
+
["navigation", "surfaces", surfaceIndex, "featureId"],
|
|
20602
|
+
`Navigation surface "${surface.id}" references unknown feature "${surface.featureId}"`
|
|
20603
|
+
);
|
|
20604
|
+
}
|
|
20605
|
+
surface.featureIds.forEach((featureId, featureIndex) => {
|
|
20606
|
+
if (!hasFeature(featuresById, featureId)) {
|
|
20607
|
+
addIssue(
|
|
20608
|
+
ctx,
|
|
20609
|
+
["navigation", "surfaces", surfaceIndex, "featureIds", featureIndex],
|
|
20610
|
+
`Navigation surface "${surface.id}" references unknown feature "${featureId}"`
|
|
20611
|
+
);
|
|
20612
|
+
}
|
|
20613
|
+
});
|
|
20614
|
+
});
|
|
20554
20615
|
const segmentsById = new Map(model.customers.segments.map((seg) => [seg.id, seg]));
|
|
20555
20616
|
model.offerings.products.forEach((product, productIndex) => {
|
|
20556
20617
|
product.targetSegmentIds.forEach((segmentId, segmentIndex) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elevasis/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Minimal shared constants across Elevasis monorepo",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"rollup-plugin-dts": "^6.3.0",
|
|
42
42
|
"tsup": "^8.0.0",
|
|
43
43
|
"typescript": "5.9.2",
|
|
44
|
-
"@repo/
|
|
45
|
-
"@repo/
|
|
44
|
+
"@repo/typescript-config": "0.0.0",
|
|
45
|
+
"@repo/eslint-config": "0.0.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@anthropic-ai/sdk": "^0.62.0",
|
|
@@ -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
|
```
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
CRM_PIPELINE_DEFINITION,
|
|
3
4
|
DEFAULT_CRM_PRIORITY_RULE_CONFIG,
|
|
4
5
|
LEAD_GEN_PIPELINE_DEFINITIONS,
|
|
5
6
|
LEAD_GEN_STAGE_CATALOG
|
|
@@ -19,6 +20,9 @@ import {
|
|
|
19
20
|
CreateContactRequestSchema,
|
|
20
21
|
CreateDealNoteRequestSchema,
|
|
21
22
|
CreateDealTaskRequestSchema,
|
|
23
|
+
CrmStageKeySchema,
|
|
24
|
+
CrmStateKeySchema,
|
|
25
|
+
CrmTransitionItemRequestSchema,
|
|
22
26
|
CreateListRequestSchema,
|
|
23
27
|
DealDetailResponseSchema,
|
|
24
28
|
DealListItemSchema,
|
|
@@ -37,6 +41,7 @@ import {
|
|
|
37
41
|
ListStatusSchema,
|
|
38
42
|
PipelineStageSchema,
|
|
39
43
|
ScrapingConfigSchema,
|
|
44
|
+
TransitionDealStateRequestSchema,
|
|
40
45
|
TransitionItemRequestSchema,
|
|
41
46
|
UpdateCompanyRequestSchema,
|
|
42
47
|
UpdateContactRequestSchema,
|
|
@@ -67,17 +72,31 @@ const PRIORITY = {
|
|
|
67
72
|
// ---------------------------------------------------------------------------
|
|
68
73
|
|
|
69
74
|
describe('DealStageSchema', () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)
|
|
76
|
+
const crmStateKeys = CRM_PIPELINE_DEFINITION.stages.flatMap((stage) => stage.states.map((state) => state.stateKey))
|
|
77
|
+
|
|
78
|
+
it('derives CRM stage keys from CRM_PIPELINE_DEFINITION', () => {
|
|
79
|
+
expect(CrmStageKeySchema.options).toEqual(crmStageKeys)
|
|
80
|
+
expect(DealStageSchema.options).toEqual(crmStageKeys)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('derives CRM state keys from CRM_PIPELINE_DEFINITION', () => {
|
|
84
|
+
expect(CrmStateKeySchema.options).toEqual(crmStateKeys)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it.each(crmStageKeys)('accepts canonical stage "%s"', (stage) => {
|
|
88
|
+
expect(DealStageSchema.safeParse(stage).success).toBe(true)
|
|
89
|
+
})
|
|
76
90
|
|
|
77
91
|
it('rejects an unknown stage value', () => {
|
|
78
92
|
expect(DealStageSchema.safeParse('open').success).toBe(false)
|
|
79
93
|
expect(DealStageSchema.safeParse('').success).toBe(false)
|
|
80
94
|
})
|
|
95
|
+
|
|
96
|
+
it('rejects unknown CRM state values', () => {
|
|
97
|
+
expect(CrmStateKeySchema.safeParse('custom_state').success).toBe(false)
|
|
98
|
+
expect(CrmStateKeySchema.safeParse('').success).toBe(false)
|
|
99
|
+
})
|
|
81
100
|
})
|
|
82
101
|
|
|
83
102
|
// ---------------------------------------------------------------------------
|
|
@@ -86,7 +105,7 @@ describe('DealStageSchema', () => {
|
|
|
86
105
|
|
|
87
106
|
describe('TransitionItemRequestSchema', () => {
|
|
88
107
|
const valid = {
|
|
89
|
-
pipelineKey: '
|
|
108
|
+
pipelineKey: 'lead-gen',
|
|
90
109
|
stageKey: 'interested',
|
|
91
110
|
stateKey: null
|
|
92
111
|
}
|
|
@@ -122,11 +141,16 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
122
141
|
})
|
|
123
142
|
|
|
124
143
|
it('accepts all canonical CRM deal stages', () => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
144
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
145
|
+
expect(TransitionItemRequestSchema.safeParse({ pipelineKey: 'crm', stageKey, stateKey: null }).success).toBe(true)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
150
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
151
|
+
stage.states.map((state) => state.stateKey)
|
|
152
|
+
)) {
|
|
153
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
130
154
|
}
|
|
131
155
|
})
|
|
132
156
|
|
|
@@ -158,6 +182,11 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
158
182
|
expect(result.success).toBe(false)
|
|
159
183
|
})
|
|
160
184
|
|
|
185
|
+
it('accepts unknown non-empty stage and state keys for generic substrate transitions', () => {
|
|
186
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'custom_stage' }).success).toBe(true)
|
|
187
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'custom_state' }).success).toBe(true)
|
|
188
|
+
})
|
|
189
|
+
|
|
161
190
|
it('rejects unknown top-level fields (strict mode)', () => {
|
|
162
191
|
const result = TransitionItemRequestSchema.safeParse({ ...valid, unknownField: 'x' })
|
|
163
192
|
expect(result.success).toBe(false)
|
|
@@ -176,6 +205,64 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
176
205
|
})
|
|
177
206
|
})
|
|
178
207
|
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// CrmTransitionItemRequestSchema
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe('CrmTransitionItemRequestSchema', () => {
|
|
213
|
+
const valid = {
|
|
214
|
+
pipelineKey: 'crm',
|
|
215
|
+
stageKey: 'interested',
|
|
216
|
+
stateKey: null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
it('accepts catalog-derived CRM stage and state keys', () => {
|
|
220
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
221
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey }).success).toBe(true)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
225
|
+
stage.states.map((state) => state.stateKey)
|
|
226
|
+
)) {
|
|
227
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('rejects non-CRM pipeline keys and unknown CRM keys', () => {
|
|
232
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(false)
|
|
233
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(false)
|
|
234
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(false)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// TransitionDealStateRequestSchema
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
describe('TransitionDealStateRequestSchema', () => {
|
|
243
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
244
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
245
|
+
stage.states.map((state) => state.stateKey)
|
|
246
|
+
)) {
|
|
247
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey }).success).toBe(true)
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('rejects unknown state values', () => {
|
|
252
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: 'unknown_state' }).success).toBe(false)
|
|
253
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: '' }).success).toBe(false)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('preserves strict request schema behavior', () => {
|
|
257
|
+
expect(
|
|
258
|
+
TransitionDealStateRequestSchema.safeParse({
|
|
259
|
+
stateKey: CRM_PIPELINE_DEFINITION.stages[0]?.states[0]?.stateKey,
|
|
260
|
+
extra: 'x'
|
|
261
|
+
}).success
|
|
262
|
+
).toBe(false)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
179
266
|
// ---------------------------------------------------------------------------
|
|
180
267
|
// ExecuteActionRequestSchema
|
|
181
268
|
// ---------------------------------------------------------------------------
|
|
@@ -698,7 +785,7 @@ describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
|
698
785
|
organization_id: VALID_UUID,
|
|
699
786
|
contact_id: null,
|
|
700
787
|
contact_email: 'test@example.com',
|
|
701
|
-
pipeline_key: '
|
|
788
|
+
pipeline_key: 'crm',
|
|
702
789
|
stage_key: null,
|
|
703
790
|
state_key: null,
|
|
704
791
|
activity_log: [],
|
|
@@ -1585,5 +1672,4 @@ describe('AcqContactResponseSchema (forward-compat)', () => {
|
|
|
1585
1672
|
it('rejects an invalid emailValid value', () => {
|
|
1586
1673
|
expect(AcqContactResponseSchema.safeParse({ ...baseContact, emailValid: 'BAD' }).success).toBe(false)
|
|
1587
1674
|
})
|
|
1588
|
-
|
|
1589
1675
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
|
|
3
|
-
import { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
|
|
3
|
+
import { CRM_PIPELINE_DEFINITION, LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
|
|
4
4
|
import { CAPABILITY_REGISTRY } from '../../organization-model/domains/prospecting'
|
|
5
5
|
import { isProspectingBuildTemplateId } from './build-templates'
|
|
6
6
|
export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
|
|
@@ -16,10 +16,19 @@ export const LeadGenStageKeySchema = z
|
|
|
16
16
|
|
|
17
17
|
export const LeadGenCapabilityKeySchema = z
|
|
18
18
|
.string()
|
|
19
|
-
.refine((value) =>
|
|
19
|
+
.refine((value) => CAPABILITY_REGISTRY.some((c) => c.id === value), {
|
|
20
20
|
message: 'capabilityKey must match CAPABILITY_REGISTRY'
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
+
const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey) as [string, ...string[]]
|
|
24
|
+
const crmStateKeys = CRM_PIPELINE_DEFINITION.stages.flatMap((stage) => stage.states.map((state) => state.stateKey)) as [
|
|
25
|
+
string,
|
|
26
|
+
...string[]
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
export const CrmStageKeySchema = z.enum(crmStageKeys)
|
|
30
|
+
export const CrmStateKeySchema = z.enum(crmStateKeys)
|
|
31
|
+
|
|
23
32
|
export const ProcessingStateEntrySchema = z
|
|
24
33
|
.object({
|
|
25
34
|
status: ProcessingStageStatusSchema,
|
|
@@ -47,7 +56,7 @@ export const ContactProcessingStateSchema = ProcessingStateSchema
|
|
|
47
56
|
// Enum literals (must match DB CHECK constraints exactly)
|
|
48
57
|
// ---------------------------------------------------------------------------
|
|
49
58
|
|
|
50
|
-
export const DealStageSchema =
|
|
59
|
+
export const DealStageSchema = CrmStageKeySchema
|
|
51
60
|
|
|
52
61
|
export const AcqDealTaskKindSchema = z.enum(['call', 'email', 'meeting', 'other'])
|
|
53
62
|
|
|
@@ -115,7 +124,17 @@ export const TransitionItemRequestSchema = z
|
|
|
115
124
|
.object({
|
|
116
125
|
pipelineKey: z.string().min(1),
|
|
117
126
|
stageKey: z.string().min(1),
|
|
118
|
-
stateKey: z.string().nullable().optional(),
|
|
127
|
+
stateKey: z.string().min(1).nullable().optional(),
|
|
128
|
+
reason: z.string().optional(),
|
|
129
|
+
expectedUpdatedAt: z.string().datetime().optional()
|
|
130
|
+
})
|
|
131
|
+
.strict()
|
|
132
|
+
|
|
133
|
+
export const CrmTransitionItemRequestSchema = z
|
|
134
|
+
.object({
|
|
135
|
+
pipelineKey: z.literal(CRM_PIPELINE_DEFINITION.pipelineKey),
|
|
136
|
+
stageKey: CrmStageKeySchema,
|
|
137
|
+
stateKey: CrmStateKeySchema.nullable().optional(),
|
|
119
138
|
reason: z.string().optional(),
|
|
120
139
|
expectedUpdatedAt: z.string().datetime().optional()
|
|
121
140
|
})
|
|
@@ -123,7 +142,7 @@ export const TransitionItemRequestSchema = z
|
|
|
123
142
|
|
|
124
143
|
export const TransitionDealStateRequestSchema = z
|
|
125
144
|
.object({
|
|
126
|
-
stateKey:
|
|
145
|
+
stateKey: CrmStateKeySchema,
|
|
127
146
|
reason: z.string().optional(),
|
|
128
147
|
expectedUpdatedAt: z.string().datetime().optional()
|
|
129
148
|
})
|
|
@@ -331,6 +350,11 @@ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
|
|
|
331
350
|
// ---------------------------------------------------------------------------
|
|
332
351
|
|
|
333
352
|
export const DealSchemas = {
|
|
353
|
+
// Primitives
|
|
354
|
+
CrmStageKey: CrmStageKeySchema,
|
|
355
|
+
CrmStateKey: CrmStateKeySchema,
|
|
356
|
+
DealStage: DealStageSchema,
|
|
357
|
+
|
|
334
358
|
// Params
|
|
335
359
|
DealIdParams: DealIdParamsSchema,
|
|
336
360
|
DealTaskIdParams: DealTaskIdParamsSchema,
|
|
@@ -343,7 +367,7 @@ export const DealSchemas = {
|
|
|
343
367
|
// Request bodies
|
|
344
368
|
CreateDealNoteRequest: CreateDealNoteRequestSchema,
|
|
345
369
|
CreateDealTaskRequest: CreateDealTaskRequestSchema,
|
|
346
|
-
TransitionItemRequest:
|
|
370
|
+
TransitionItemRequest: CrmTransitionItemRequestSchema,
|
|
347
371
|
TransitionDealStateRequest: TransitionDealStateRequestSchema,
|
|
348
372
|
ExecuteActionParams: ExecuteActionParamsSchema,
|
|
349
373
|
ExecuteActionRequest: ExecuteActionRequestSchema,
|
|
@@ -366,6 +390,8 @@ export const DealSchemas = {
|
|
|
366
390
|
// ---------------------------------------------------------------------------
|
|
367
391
|
|
|
368
392
|
export type DealStage = z.infer<typeof DealStageSchema>
|
|
393
|
+
export type CrmStageKey = z.infer<typeof CrmStageKeySchema>
|
|
394
|
+
export type CrmStateKey = z.infer<typeof CrmStateKeySchema>
|
|
369
395
|
export type AcqDealTaskKind = z.infer<typeof AcqDealTaskKindSchema>
|
|
370
396
|
export type DealIdParams = z.infer<typeof DealIdParamsSchema>
|
|
371
397
|
export type DealTaskIdParams = z.infer<typeof DealTaskIdParamsSchema>
|
|
@@ -375,6 +401,7 @@ export type ListDealTasksDueQuery = z.infer<typeof ListDealTasksDueQuerySchema>
|
|
|
375
401
|
export type CreateDealNoteRequest = z.infer<typeof CreateDealNoteRequestSchema>
|
|
376
402
|
export type CreateDealTaskRequest = z.infer<typeof CreateDealTaskRequestSchema>
|
|
377
403
|
export type TransitionItemRequest = z.infer<typeof TransitionItemRequestSchema>
|
|
404
|
+
export type CrmTransitionItemRequest = z.infer<typeof CrmTransitionItemRequestSchema>
|
|
378
405
|
export type TransitionDealStateRequest = z.infer<typeof TransitionDealStateRequestSchema>
|
|
379
406
|
export type ExecuteActionParams = z.infer<typeof ExecuteActionParamsSchema>
|
|
380
407
|
export type ExecuteActionRequest = z.infer<typeof ExecuteActionRequestSchema>
|
|
@@ -1090,8 +1117,8 @@ export const AcqListCompanyResponseSchema = z.object({
|
|
|
1090
1117
|
})
|
|
1091
1118
|
|
|
1092
1119
|
// ---------------------------------------------------------------------------
|
|
1093
|
-
// Track B: Transition
|
|
1094
|
-
//
|
|
1120
|
+
// Track B: Transition request for list, list-member, and list-company substrate routes.
|
|
1121
|
+
// CRM deals use DealSchemas.TransitionItemRequest, which is catalog-backed.
|
|
1095
1122
|
// ---------------------------------------------------------------------------
|
|
1096
1123
|
|
|
1097
1124
|
export const AcqCompanySchemas = {
|
|
@@ -1197,7 +1224,7 @@ export const AcqSubstrateSchemas = {
|
|
|
1197
1224
|
ListCompanyIdParams: ListCompanyIdParamsSchema,
|
|
1198
1225
|
AcqListCompanyResponse: AcqListCompanyResponseSchema,
|
|
1199
1226
|
|
|
1200
|
-
// Transition (
|
|
1227
|
+
// Transition (generic stateful substrate)
|
|
1201
1228
|
TransitionItemRequest: TransitionItemRequestSchema
|
|
1202
1229
|
}
|
|
1203
1230
|
|