business-as-code 2.1.1 → 2.3.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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/package.json +7 -4
- package/src/dollar.ts +5 -2
- package/src/entities/organization.ts +31 -18
- package/src/goals.ts +78 -12
- package/src/index.ts +48 -18
- package/src/kpis.ts +62 -8
- package/src/metrics.ts +92 -79
- package/src/okrs.ts +120 -20
- package/src/organization.ts +12 -15
- package/src/process.ts +11 -12
- package/src/product.ts +8 -9
- package/src/queries.ts +238 -75
- package/src/roles.ts +62 -61
- package/src/workflow.ts +22 -15
- package/test/business.test.ts +282 -0
- package/test/dollar.test.ts +270 -0
- package/test/entities.test.ts +628 -0
- package/test/financials.test.ts +539 -0
- package/test/goals.test.ts +451 -0
- package/{src → test}/index.test.ts +1 -1
- package/test/kpis.test.ts +440 -0
- package/test/metrics.test.ts +744 -0
- package/test/okrs.test.ts +741 -0
- package/test/organization.test.ts +548 -0
- package/test/process.test.ts +503 -0
- package/test/product.test.ts +430 -0
- package/test/queries.test.ts +556 -0
- package/test/roles.test.ts +546 -0
- package/test/service.test.ts +450 -0
- package/test/types.test.ts +1141 -0
- package/test/vision.test.ts +214 -0
- package/test/workflow.test.ts +501 -0
- package/vitest.config.ts +47 -0
- package/.turbo/turbo-build.log +0 -5
- package/dist/business.d.ts +0 -62
- package/dist/business.d.ts.map +0 -1
- package/dist/business.js +0 -109
- package/dist/business.js.map +0 -1
- package/dist/dollar.d.ts +0 -60
- package/dist/dollar.d.ts.map +0 -1
- package/dist/dollar.js +0 -107
- package/dist/dollar.js.map +0 -1
- package/dist/entities/assets.d.ts +0 -21
- package/dist/entities/assets.d.ts.map +0 -1
- package/dist/entities/assets.js +0 -323
- package/dist/entities/assets.js.map +0 -1
- package/dist/entities/business.d.ts +0 -36
- package/dist/entities/business.d.ts.map +0 -1
- package/dist/entities/business.js +0 -370
- package/dist/entities/business.js.map +0 -1
- package/dist/entities/communication.d.ts +0 -21
- package/dist/entities/communication.d.ts.map +0 -1
- package/dist/entities/communication.js +0 -255
- package/dist/entities/communication.js.map +0 -1
- package/dist/entities/customers.d.ts +0 -58
- package/dist/entities/customers.d.ts.map +0 -1
- package/dist/entities/customers.js +0 -989
- package/dist/entities/customers.js.map +0 -1
- package/dist/entities/financials.d.ts +0 -59
- package/dist/entities/financials.d.ts.map +0 -1
- package/dist/entities/financials.js +0 -932
- package/dist/entities/financials.js.map +0 -1
- package/dist/entities/goals.d.ts +0 -58
- package/dist/entities/goals.d.ts.map +0 -1
- package/dist/entities/goals.js +0 -800
- package/dist/entities/goals.js.map +0 -1
- package/dist/entities/index.d.ts +0 -299
- package/dist/entities/index.d.ts.map +0 -1
- package/dist/entities/index.js +0 -198
- package/dist/entities/index.js.map +0 -1
- package/dist/entities/legal.d.ts +0 -21
- package/dist/entities/legal.d.ts.map +0 -1
- package/dist/entities/legal.js +0 -301
- package/dist/entities/legal.js.map +0 -1
- package/dist/entities/market.d.ts +0 -21
- package/dist/entities/market.d.ts.map +0 -1
- package/dist/entities/market.js +0 -301
- package/dist/entities/market.js.map +0 -1
- package/dist/entities/marketing.d.ts +0 -67
- package/dist/entities/marketing.d.ts.map +0 -1
- package/dist/entities/marketing.js +0 -1157
- package/dist/entities/marketing.js.map +0 -1
- package/dist/entities/offerings.d.ts +0 -51
- package/dist/entities/offerings.d.ts.map +0 -1
- package/dist/entities/offerings.js +0 -727
- package/dist/entities/offerings.js.map +0 -1
- package/dist/entities/operations.d.ts +0 -58
- package/dist/entities/operations.d.ts.map +0 -1
- package/dist/entities/operations.js +0 -787
- package/dist/entities/operations.js.map +0 -1
- package/dist/entities/organization.d.ts +0 -57
- package/dist/entities/organization.d.ts.map +0 -1
- package/dist/entities/organization.js +0 -807
- package/dist/entities/organization.js.map +0 -1
- package/dist/entities/partnerships.d.ts +0 -21
- package/dist/entities/partnerships.d.ts.map +0 -1
- package/dist/entities/partnerships.js +0 -300
- package/dist/entities/partnerships.js.map +0 -1
- package/dist/entities/planning.d.ts +0 -87
- package/dist/entities/planning.d.ts.map +0 -1
- package/dist/entities/planning.js +0 -271
- package/dist/entities/planning.js.map +0 -1
- package/dist/entities/projects.d.ts +0 -25
- package/dist/entities/projects.d.ts.map +0 -1
- package/dist/entities/projects.js +0 -349
- package/dist/entities/projects.js.map +0 -1
- package/dist/entities/risk.d.ts +0 -21
- package/dist/entities/risk.d.ts.map +0 -1
- package/dist/entities/risk.js +0 -293
- package/dist/entities/risk.js.map +0 -1
- package/dist/entities/sales.d.ts +0 -72
- package/dist/entities/sales.d.ts.map +0 -1
- package/dist/entities/sales.js +0 -1248
- package/dist/entities/sales.js.map +0 -1
- package/dist/financials.d.ts +0 -130
- package/dist/financials.d.ts.map +0 -1
- package/dist/financials.js +0 -297
- package/dist/financials.js.map +0 -1
- package/dist/goals.d.ts +0 -87
- package/dist/goals.d.ts.map +0 -1
- package/dist/goals.js +0 -215
- package/dist/goals.js.map +0 -1
- package/dist/index.d.ts +0 -97
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -132
- package/dist/index.js.map +0 -1
- package/dist/kpis.d.ts +0 -118
- package/dist/kpis.d.ts.map +0 -1
- package/dist/kpis.js +0 -232
- package/dist/kpis.js.map +0 -1
- package/dist/metrics.d.ts +0 -448
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -325
- package/dist/metrics.js.map +0 -1
- package/dist/okrs.d.ts +0 -123
- package/dist/okrs.d.ts.map +0 -1
- package/dist/okrs.js +0 -269
- package/dist/okrs.js.map +0 -1
- package/dist/organization.d.ts +0 -585
- package/dist/organization.d.ts.map +0 -1
- package/dist/organization.js +0 -173
- package/dist/organization.js.map +0 -1
- package/dist/process.d.ts +0 -112
- package/dist/process.d.ts.map +0 -1
- package/dist/process.js +0 -241
- package/dist/process.js.map +0 -1
- package/dist/product.d.ts +0 -85
- package/dist/product.d.ts.map +0 -1
- package/dist/product.js +0 -145
- package/dist/product.js.map +0 -1
- package/dist/queries.d.ts +0 -304
- package/dist/queries.d.ts.map +0 -1
- package/dist/queries.js +0 -415
- package/dist/queries.js.map +0 -1
- package/dist/roles.d.ts +0 -340
- package/dist/roles.d.ts.map +0 -1
- package/dist/roles.js +0 -255
- package/dist/roles.js.map +0 -1
- package/dist/service.d.ts +0 -61
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -140
- package/dist/service.js.map +0 -1
- package/dist/types.d.ts +0 -459
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/vision.d.ts +0 -38
- package/dist/vision.d.ts.map +0 -1
- package/dist/vision.js +0 -68
- package/dist/vision.js.map +0 -1
- package/dist/workflow.d.ts +0 -115
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js +0 -247
- package/dist/workflow.js.map +0 -1
- package/src/business.js +0 -108
- package/src/dollar.js +0 -106
- package/src/entities/assets.js +0 -322
- package/src/entities/business.js +0 -369
- package/src/entities/communication.js +0 -254
- package/src/entities/customers.js +0 -988
- package/src/entities/financials.js +0 -931
- package/src/entities/goals.js +0 -799
- package/src/entities/index.js +0 -197
- package/src/entities/legal.js +0 -300
- package/src/entities/market.js +0 -300
- package/src/entities/marketing.js +0 -1156
- package/src/entities/offerings.js +0 -726
- package/src/entities/operations.js +0 -786
- package/src/entities/organization.js +0 -806
- package/src/entities/partnerships.js +0 -299
- package/src/entities/planning.js +0 -270
- package/src/entities/projects.js +0 -348
- package/src/entities/risk.js +0 -292
- package/src/entities/sales.js +0 -1247
- package/src/financials.js +0 -296
- package/src/goals.js +0 -214
- package/src/index.js +0 -131
- package/src/index.test.js +0 -274
- package/src/kpis.js +0 -231
- package/src/metrics.js +0 -324
- package/src/okrs.js +0 -268
- package/src/organization.js +0 -172
- package/src/process.js +0 -240
- package/src/product.js +0 -144
- package/src/queries.js +0 -414
- package/src/roles.js +0 -254
- package/src/service.js +0 -139
- package/src/types.js +0 -4
- package/src/vision.js +0 -67
- package/src/workflow.js +0 -246
package/src/roles.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
* @packageDocumentation
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { Role as OrgRole, RoleType as OrgRoleType, RoleWorkerType } from 'org.ai'
|
|
13
|
+
import type { Worker, WorkerRef } from 'digital-workers'
|
|
14
|
+
|
|
15
|
+
// Re-export for convenience
|
|
16
|
+
export type { Worker, WorkerRef } from 'digital-workers'
|
|
17
|
+
export type { OrgRole, OrgRoleType, RoleWorkerType }
|
|
18
|
+
|
|
12
19
|
// =============================================================================
|
|
13
20
|
// Business Role - Bridges Worker Role and Authorization
|
|
14
21
|
// =============================================================================
|
|
@@ -43,7 +50,10 @@ export type BusinessRoleType =
|
|
|
43
50
|
| string
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
* Business Role - extends
|
|
53
|
+
* Business Role - extends org.ai Role with authorization and task capabilities
|
|
54
|
+
*
|
|
55
|
+
* Composes with org.ai Role to provide additional business-specific properties
|
|
56
|
+
* like authorization permissions, task capabilities, and compensation.
|
|
47
57
|
*
|
|
48
58
|
* @example
|
|
49
59
|
* ```ts
|
|
@@ -54,6 +64,9 @@ export type BusinessRoleType =
|
|
|
54
64
|
* department: 'Engineering',
|
|
55
65
|
* description: 'Leads engineering team and makes technical decisions',
|
|
56
66
|
*
|
|
67
|
+
* // From org.ai Role
|
|
68
|
+
* skills: ['TypeScript', 'Architecture', 'Team Leadership'],
|
|
69
|
+
*
|
|
57
70
|
* // Business responsibilities
|
|
58
71
|
* responsibilities: [
|
|
59
72
|
* 'Lead engineering team',
|
|
@@ -78,28 +91,10 @@ export type BusinessRoleType =
|
|
|
78
91
|
* }
|
|
79
92
|
* ```
|
|
80
93
|
*/
|
|
81
|
-
export interface BusinessRole {
|
|
82
|
-
/**
|
|
83
|
-
id: string
|
|
84
|
-
|
|
85
|
-
/** Role display name */
|
|
86
|
-
name: string
|
|
87
|
-
|
|
88
|
-
/** Role type classification */
|
|
94
|
+
export interface BusinessRole extends Omit<OrgRole, 'permissions'> {
|
|
95
|
+
/** Role type classification (overrides OrgRole.type with business-specific types) */
|
|
89
96
|
type: BusinessRoleType
|
|
90
97
|
|
|
91
|
-
/** Department or team */
|
|
92
|
-
department?: string
|
|
93
|
-
|
|
94
|
-
/** Human-readable description */
|
|
95
|
-
description?: string
|
|
96
|
-
|
|
97
|
-
/** Key responsibilities */
|
|
98
|
-
responsibilities?: string[]
|
|
99
|
-
|
|
100
|
-
/** Required skills */
|
|
101
|
-
skills?: string[]
|
|
102
|
-
|
|
103
98
|
/**
|
|
104
99
|
* Authorization permissions by resource type
|
|
105
100
|
*
|
|
@@ -107,6 +102,8 @@ export interface BusinessRole {
|
|
|
107
102
|
* - 'read', 'edit', 'delete', 'manage' (standard)
|
|
108
103
|
* - 'act:*' or 'act:verb' (domain-specific verbs)
|
|
109
104
|
*
|
|
105
|
+
* Note: This differs from OrgRole.permissions which is string[]
|
|
106
|
+
*
|
|
110
107
|
* @example
|
|
111
108
|
* ```ts
|
|
112
109
|
* permissions: {
|
|
@@ -118,32 +115,8 @@ export interface BusinessRole {
|
|
|
118
115
|
*/
|
|
119
116
|
permissions?: Record<string, string[]>
|
|
120
117
|
|
|
121
|
-
/** Task types this role can handle */
|
|
122
|
-
canHandle?: string[]
|
|
123
|
-
|
|
124
|
-
/** Task types this role can delegate to others */
|
|
125
|
-
canDelegate?: string[]
|
|
126
|
-
|
|
127
|
-
/** Request types this role can approve */
|
|
128
|
-
canApprove?: string[]
|
|
129
|
-
|
|
130
|
-
/** Escalation path - role to escalate to */
|
|
131
|
-
escalateTo?: string
|
|
132
|
-
|
|
133
|
-
/** Reports to - manager role */
|
|
134
|
-
reportsTo?: string
|
|
135
|
-
|
|
136
|
-
/** Worker type preference: 'ai' | 'human' | 'hybrid' */
|
|
137
|
-
workerType?: 'ai' | 'human' | 'hybrid'
|
|
138
|
-
|
|
139
|
-
/** Level in hierarchy (1 = entry, higher = more senior) */
|
|
140
|
-
level?: number
|
|
141
|
-
|
|
142
118
|
/** Compensation band */
|
|
143
119
|
compensationBand?: string
|
|
144
|
-
|
|
145
|
-
/** Additional metadata */
|
|
146
|
-
metadata?: Record<string, unknown>
|
|
147
120
|
}
|
|
148
121
|
|
|
149
122
|
// =============================================================================
|
|
@@ -263,6 +236,8 @@ export interface TaskAssignment {
|
|
|
263
236
|
|
|
264
237
|
/**
|
|
265
238
|
* Reference to an assignee (worker, team, or role)
|
|
239
|
+
*
|
|
240
|
+
* Compatible with digital-workers WorkerRef for worker assignments.
|
|
266
241
|
*/
|
|
267
242
|
export interface AssigneeRef {
|
|
268
243
|
/** Type of assignee */
|
|
@@ -273,6 +248,34 @@ export interface AssigneeRef {
|
|
|
273
248
|
name?: string
|
|
274
249
|
}
|
|
275
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Convert a digital-workers WorkerRef to an AssigneeRef
|
|
253
|
+
*/
|
|
254
|
+
export function workerRefToAssignee(workerRef: WorkerRef): AssigneeRef {
|
|
255
|
+
const result: AssigneeRef = {
|
|
256
|
+
type: 'worker',
|
|
257
|
+
id: workerRef.id,
|
|
258
|
+
}
|
|
259
|
+
if (workerRef.name !== undefined) {
|
|
260
|
+
result.name = workerRef.name
|
|
261
|
+
}
|
|
262
|
+
return result
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Convert an AssigneeRef to a digital-workers WorkerRef (if type is 'worker')
|
|
267
|
+
*/
|
|
268
|
+
export function assigneeToWorkerRef(assignee: AssigneeRef): WorkerRef | null {
|
|
269
|
+
if (assignee.type !== 'worker') return null
|
|
270
|
+
const result: WorkerRef = {
|
|
271
|
+
id: assignee.id,
|
|
272
|
+
}
|
|
273
|
+
if (assignee.name !== undefined) {
|
|
274
|
+
result.name = assignee.name
|
|
275
|
+
}
|
|
276
|
+
return result
|
|
277
|
+
}
|
|
278
|
+
|
|
276
279
|
// =============================================================================
|
|
277
280
|
// Role-Based Task Routing
|
|
278
281
|
// =============================================================================
|
|
@@ -549,8 +552,8 @@ export function createBusinessRole(
|
|
|
549
552
|
|
|
550
553
|
return {
|
|
551
554
|
id,
|
|
552
|
-
name: standard
|
|
553
|
-
type: standard
|
|
555
|
+
name: standard['name'] || template,
|
|
556
|
+
type: standard['type'] || template,
|
|
554
557
|
...standard,
|
|
555
558
|
...overrides,
|
|
556
559
|
} as BusinessRole
|
|
@@ -559,11 +562,7 @@ export function createBusinessRole(
|
|
|
559
562
|
/**
|
|
560
563
|
* Check if a role has permission for an action on a resource type
|
|
561
564
|
*/
|
|
562
|
-
export function hasPermission(
|
|
563
|
-
role: BusinessRole,
|
|
564
|
-
resourceType: string,
|
|
565
|
-
action: string
|
|
566
|
-
): boolean {
|
|
565
|
+
export function hasPermission(role: BusinessRole, resourceType: string, action: string): boolean {
|
|
567
566
|
if (!role.permissions) return false
|
|
568
567
|
|
|
569
568
|
// Check wildcard permissions
|
|
@@ -595,24 +594,24 @@ export function hasPermission(
|
|
|
595
594
|
* Check if a role can handle a task type
|
|
596
595
|
*/
|
|
597
596
|
export function canHandleTask(role: BusinessRole, taskType: string): boolean {
|
|
598
|
-
if (!role
|
|
599
|
-
return role
|
|
597
|
+
if (!role['canHandle']) return false
|
|
598
|
+
return role['canHandle'].includes(taskType) || role['canHandle'].includes('*')
|
|
600
599
|
}
|
|
601
600
|
|
|
602
601
|
/**
|
|
603
602
|
* Check if a role can approve a request type
|
|
604
603
|
*/
|
|
605
604
|
export function canApproveRequest(role: BusinessRole, requestType: string): boolean {
|
|
606
|
-
if (!role
|
|
607
|
-
return role
|
|
605
|
+
if (!role['canApprove']) return false
|
|
606
|
+
return role['canApprove'].includes(requestType) || role['canApprove'].includes('*')
|
|
608
607
|
}
|
|
609
608
|
|
|
610
609
|
/**
|
|
611
610
|
* Check if a role can delegate a task type
|
|
612
611
|
*/
|
|
613
612
|
export function canDelegateTask(role: BusinessRole, taskType: string): boolean {
|
|
614
|
-
if (!role
|
|
615
|
-
return role
|
|
613
|
+
if (!role['canDelegate']) return false
|
|
614
|
+
return role['canDelegate'].includes(taskType) || role['canDelegate'].includes('*')
|
|
616
615
|
}
|
|
617
616
|
|
|
618
617
|
/**
|
|
@@ -623,7 +622,7 @@ export function findRoleForTask(
|
|
|
623
622
|
rules: TaskRoutingRule[],
|
|
624
623
|
context?: { amount?: number; skills?: string[] }
|
|
625
624
|
): TaskRoutingRule | undefined {
|
|
626
|
-
const matchingRules = rules.filter(rule => rule.taskType === taskType)
|
|
625
|
+
const matchingRules = rules.filter((rule) => rule.taskType === taskType)
|
|
627
626
|
|
|
628
627
|
if (matchingRules.length === 0) return undefined
|
|
629
628
|
|
|
@@ -633,7 +632,7 @@ export function findRoleForTask(
|
|
|
633
632
|
if (rule.escalateAbove && context.amount > rule.escalateAbove) {
|
|
634
633
|
// Find the escalated rule
|
|
635
634
|
const escalatedRule = rules.find(
|
|
636
|
-
r => r.taskType === taskType && r.requiredRole === rule.escalateTo
|
|
635
|
+
(r) => r.taskType === taskType && r.requiredRole === rule.escalateTo
|
|
637
636
|
)
|
|
638
637
|
if (escalatedRule) return escalatedRule
|
|
639
638
|
}
|
|
@@ -651,7 +650,9 @@ export function createTaskAssignment(
|
|
|
651
650
|
taskId: string,
|
|
652
651
|
taskType: string,
|
|
653
652
|
assignee: AssigneeRef,
|
|
654
|
-
options?: Partial<
|
|
653
|
+
options?: Partial<
|
|
654
|
+
Omit<TaskAssignment, 'id' | 'taskId' | 'taskType' | 'assignee' | 'status' | 'assignedAt'>
|
|
655
|
+
>
|
|
655
656
|
): TaskAssignment {
|
|
656
657
|
return {
|
|
657
658
|
id: `assign_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
package/src/workflow.ts
CHANGED
|
@@ -88,20 +88,23 @@ export function getActionsByType(
|
|
|
88
88
|
workflow: WorkflowDefinition,
|
|
89
89
|
type: WorkflowAction['type']
|
|
90
90
|
): WorkflowAction[] {
|
|
91
|
-
return workflow.actions?.filter(action => action.type === type) || []
|
|
91
|
+
return workflow.actions?.filter((action) => action.type === type) || []
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Get conditional actions
|
|
96
96
|
*/
|
|
97
97
|
export function getConditionalActions(workflow: WorkflowDefinition): WorkflowAction[] {
|
|
98
|
-
return workflow.actions?.filter(action => action.condition) || []
|
|
98
|
+
return workflow.actions?.filter((action) => action.condition) || []
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Add action to workflow
|
|
103
103
|
*/
|
|
104
|
-
export function addAction(
|
|
104
|
+
export function addAction(
|
|
105
|
+
workflow: WorkflowDefinition,
|
|
106
|
+
action: WorkflowAction
|
|
107
|
+
): WorkflowDefinition {
|
|
105
108
|
return {
|
|
106
109
|
...workflow,
|
|
107
110
|
actions: [...(workflow.actions || []), action],
|
|
@@ -112,10 +115,10 @@ export function addAction(workflow: WorkflowDefinition, action: WorkflowAction):
|
|
|
112
115
|
* Remove action from workflow
|
|
113
116
|
*/
|
|
114
117
|
export function removeAction(workflow: WorkflowDefinition, order: number): WorkflowDefinition {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
const actions = workflow.actions?.filter((a) => a.order !== order)
|
|
119
|
+
const result: WorkflowDefinition = { ...workflow }
|
|
120
|
+
if (actions !== undefined) result.actions = actions
|
|
121
|
+
return result
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
/**
|
|
@@ -126,14 +129,13 @@ export function updateAction(
|
|
|
126
129
|
order: number,
|
|
127
130
|
updates: Partial<WorkflowAction>
|
|
128
131
|
): WorkflowDefinition {
|
|
129
|
-
const actions = workflow.actions?.map(action =>
|
|
132
|
+
const actions = workflow.actions?.map((action) =>
|
|
130
133
|
action.order === order ? { ...action, ...updates } : action
|
|
131
134
|
)
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
136
|
+
const result: WorkflowDefinition = { ...workflow }
|
|
137
|
+
if (actions !== undefined) result.actions = actions
|
|
138
|
+
return result
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
/**
|
|
@@ -161,7 +163,9 @@ export function isWebhookTrigger(trigger: WorkflowTrigger): boolean {
|
|
|
161
163
|
* Parse wait duration to milliseconds
|
|
162
164
|
*/
|
|
163
165
|
export function parseWaitDuration(duration: string): number {
|
|
164
|
-
const match = duration.match(
|
|
166
|
+
const match = duration.match(
|
|
167
|
+
/(\d+)\s*(ms|millisecond|milliseconds|s|second|seconds|m|minute|minutes|h|hour|hours|d|day|days)/
|
|
168
|
+
)
|
|
165
169
|
if (!match) return 0
|
|
166
170
|
|
|
167
171
|
const value = parseInt(match[1] || '0', 10)
|
|
@@ -234,7 +238,10 @@ function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
|
234
238
|
/**
|
|
235
239
|
* Validate workflow definition
|
|
236
240
|
*/
|
|
237
|
-
export function validateWorkflow(workflow: WorkflowDefinition): {
|
|
241
|
+
export function validateWorkflow(workflow: WorkflowDefinition): {
|
|
242
|
+
valid: boolean
|
|
243
|
+
errors: string[]
|
|
244
|
+
} {
|
|
238
245
|
const errors: string[] = []
|
|
239
246
|
|
|
240
247
|
if (!workflow.name) {
|
|
@@ -267,7 +274,7 @@ export function validateWorkflow(workflow: WorkflowDefinition): { valid: boolean
|
|
|
267
274
|
orders.add(action.order)
|
|
268
275
|
|
|
269
276
|
// Validate action-specific requirements
|
|
270
|
-
if (action.type === 'wait' && !action.params?.duration) {
|
|
277
|
+
if (action.type === 'wait' && !action.params?.['duration']) {
|
|
271
278
|
errors.push(`Wait action at order ${action.order} must specify duration`)
|
|
272
279
|
}
|
|
273
280
|
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for business.ts - Business entity definition and management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
Business,
|
|
8
|
+
getTotalBudget,
|
|
9
|
+
getTotalTeamSize,
|
|
10
|
+
getDepartment,
|
|
11
|
+
getTeam,
|
|
12
|
+
validateBusiness,
|
|
13
|
+
} from '../src/business.js'
|
|
14
|
+
import type { BusinessDefinition } from '../src/types.js'
|
|
15
|
+
|
|
16
|
+
describe('Business', () => {
|
|
17
|
+
describe('Business()', () => {
|
|
18
|
+
it('should create a business entity with required fields', () => {
|
|
19
|
+
const business = Business({
|
|
20
|
+
name: 'Acme Corp',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(business.name).toBe('Acme Corp')
|
|
24
|
+
expect(business.values).toEqual([])
|
|
25
|
+
expect(business.teamSize).toBe(0)
|
|
26
|
+
expect(business.metadata).toEqual({})
|
|
27
|
+
expect(business.foundedAt).toBeInstanceOf(Date)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should create a business entity with all fields', () => {
|
|
31
|
+
const foundedDate = new Date('2020-01-01')
|
|
32
|
+
const business = Business({
|
|
33
|
+
name: 'Test Corp',
|
|
34
|
+
description: 'A test company',
|
|
35
|
+
industry: 'Technology',
|
|
36
|
+
mission: 'To innovate',
|
|
37
|
+
values: ['Innovation', 'Integrity'],
|
|
38
|
+
targetMarket: 'Enterprise',
|
|
39
|
+
foundedAt: foundedDate,
|
|
40
|
+
teamSize: 50,
|
|
41
|
+
structure: {
|
|
42
|
+
departments: [
|
|
43
|
+
{
|
|
44
|
+
name: 'Engineering',
|
|
45
|
+
head: 'Jane Smith',
|
|
46
|
+
members: ['Alice', 'Bob'],
|
|
47
|
+
budget: 1000000,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
metadata: { customField: 'value' },
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(business.name).toBe('Test Corp')
|
|
55
|
+
expect(business.description).toBe('A test company')
|
|
56
|
+
expect(business.industry).toBe('Technology')
|
|
57
|
+
expect(business.mission).toBe('To innovate')
|
|
58
|
+
expect(business.values).toEqual(['Innovation', 'Integrity'])
|
|
59
|
+
expect(business.targetMarket).toBe('Enterprise')
|
|
60
|
+
expect(business.foundedAt).toBe(foundedDate)
|
|
61
|
+
expect(business.teamSize).toBe(50)
|
|
62
|
+
expect(business.structure?.departments).toHaveLength(1)
|
|
63
|
+
expect(business.metadata).toEqual({ customField: 'value' })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should throw error if name is empty', () => {
|
|
67
|
+
expect(() => Business({ name: '' })).toThrow('Business name is required')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should preserve provided values and not override with defaults', () => {
|
|
71
|
+
const business = Business({
|
|
72
|
+
name: 'Test',
|
|
73
|
+
values: ['Custom'],
|
|
74
|
+
teamSize: 100,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
expect(business.values).toEqual(['Custom'])
|
|
78
|
+
expect(business.teamSize).toBe(100)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('getTotalBudget()', () => {
|
|
83
|
+
it('should return 0 if no departments', () => {
|
|
84
|
+
const business = Business({ name: 'Test' })
|
|
85
|
+
expect(getTotalBudget(business)).toBe(0)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should return 0 if structure has no departments', () => {
|
|
89
|
+
const business = Business({
|
|
90
|
+
name: 'Test',
|
|
91
|
+
structure: {},
|
|
92
|
+
})
|
|
93
|
+
expect(getTotalBudget(business)).toBe(0)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should calculate total budget across departments', () => {
|
|
97
|
+
const business = Business({
|
|
98
|
+
name: 'Test',
|
|
99
|
+
structure: {
|
|
100
|
+
departments: [
|
|
101
|
+
{ name: 'Engineering', budget: 1000000 },
|
|
102
|
+
{ name: 'Sales', budget: 500000 },
|
|
103
|
+
{ name: 'HR' }, // No budget specified
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expect(getTotalBudget(business)).toBe(1500000)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle departments with zero budget', () => {
|
|
112
|
+
const business = Business({
|
|
113
|
+
name: 'Test',
|
|
114
|
+
structure: {
|
|
115
|
+
departments: [
|
|
116
|
+
{ name: 'Engineering', budget: 1000000 },
|
|
117
|
+
{ name: 'Sales', budget: 0 },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(getTotalBudget(business)).toBe(1000000)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('getTotalTeamSize()', () => {
|
|
127
|
+
it('should return teamSize if no departments', () => {
|
|
128
|
+
const business = Business({ name: 'Test', teamSize: 50 })
|
|
129
|
+
expect(getTotalTeamSize(business)).toBe(50)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should return 0 if no teamSize and no departments', () => {
|
|
133
|
+
const business = Business({ name: 'Test' })
|
|
134
|
+
expect(getTotalTeamSize(business)).toBe(0)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should calculate total team size from department members', () => {
|
|
138
|
+
const business = Business({
|
|
139
|
+
name: 'Test',
|
|
140
|
+
structure: {
|
|
141
|
+
departments: [
|
|
142
|
+
{ name: 'Engineering', members: ['Alice', 'Bob', 'Charlie'] },
|
|
143
|
+
{ name: 'Sales', members: ['David', 'Eve'] },
|
|
144
|
+
{ name: 'HR' }, // No members
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
expect(getTotalTeamSize(business)).toBe(5)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('getDepartment()', () => {
|
|
154
|
+
const business = Business({
|
|
155
|
+
name: 'Test',
|
|
156
|
+
structure: {
|
|
157
|
+
departments: [
|
|
158
|
+
{ name: 'Engineering', head: 'Jane' },
|
|
159
|
+
{ name: 'Sales', head: 'John' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should find department by name', () => {
|
|
165
|
+
const dept = getDepartment(business, 'Engineering')
|
|
166
|
+
expect(dept?.name).toBe('Engineering')
|
|
167
|
+
expect(dept?.head).toBe('Jane')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should return undefined for non-existent department', () => {
|
|
171
|
+
const dept = getDepartment(business, 'Marketing')
|
|
172
|
+
expect(dept).toBeUndefined()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should return undefined if no structure', () => {
|
|
176
|
+
const simpleBusiness = Business({ name: 'Simple' })
|
|
177
|
+
const dept = getDepartment(simpleBusiness, 'Engineering')
|
|
178
|
+
expect(dept).toBeUndefined()
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('getTeam()', () => {
|
|
183
|
+
const business = Business({
|
|
184
|
+
name: 'Test',
|
|
185
|
+
structure: {
|
|
186
|
+
teams: [
|
|
187
|
+
{ name: 'Alpha', lead: 'Alice' },
|
|
188
|
+
{ name: 'Beta', lead: 'Bob' },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should find team by name', () => {
|
|
194
|
+
const team = getTeam(business, 'Alpha')
|
|
195
|
+
expect(team?.name).toBe('Alpha')
|
|
196
|
+
expect(team?.lead).toBe('Alice')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('should return undefined for non-existent team', () => {
|
|
200
|
+
const team = getTeam(business, 'Gamma')
|
|
201
|
+
expect(team).toBeUndefined()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should return undefined if no structure', () => {
|
|
205
|
+
const simpleBusiness = Business({ name: 'Simple' })
|
|
206
|
+
const team = getTeam(simpleBusiness, 'Alpha')
|
|
207
|
+
expect(team).toBeUndefined()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('validateBusiness()', () => {
|
|
212
|
+
it('should validate valid business', () => {
|
|
213
|
+
const business = Business({
|
|
214
|
+
name: 'Valid Corp',
|
|
215
|
+
teamSize: 50,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const result = validateBusiness(business)
|
|
219
|
+
expect(result.valid).toBe(true)
|
|
220
|
+
expect(result.errors).toHaveLength(0)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should fail validation if name is missing', () => {
|
|
224
|
+
const business: BusinessDefinition = { name: '' }
|
|
225
|
+
const result = validateBusiness(business)
|
|
226
|
+
|
|
227
|
+
expect(result.valid).toBe(false)
|
|
228
|
+
expect(result.errors).toContain('Business name is required')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should fail validation if teamSize is negative', () => {
|
|
232
|
+
const business: BusinessDefinition = { name: 'Test', teamSize: -5 }
|
|
233
|
+
const result = validateBusiness(business)
|
|
234
|
+
|
|
235
|
+
expect(result.valid).toBe(false)
|
|
236
|
+
expect(result.errors).toContain('Team size cannot be negative')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should fail validation if department has no name', () => {
|
|
240
|
+
const business: BusinessDefinition = {
|
|
241
|
+
name: 'Test',
|
|
242
|
+
structure: {
|
|
243
|
+
departments: [{ name: '', budget: 1000 }],
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
const result = validateBusiness(business)
|
|
247
|
+
|
|
248
|
+
expect(result.valid).toBe(false)
|
|
249
|
+
expect(result.errors).toContain('Department name is required')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('should fail validation if department budget is negative', () => {
|
|
253
|
+
const business: BusinessDefinition = {
|
|
254
|
+
name: 'Test',
|
|
255
|
+
structure: {
|
|
256
|
+
departments: [{ name: 'Engineering', budget: -1000 }],
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
const result = validateBusiness(business)
|
|
260
|
+
|
|
261
|
+
expect(result.valid).toBe(false)
|
|
262
|
+
expect(result.errors).toContain('Department Engineering budget cannot be negative')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should return multiple errors for multiple issues', () => {
|
|
266
|
+
const business: BusinessDefinition = {
|
|
267
|
+
name: '',
|
|
268
|
+
teamSize: -5,
|
|
269
|
+
structure: {
|
|
270
|
+
departments: [
|
|
271
|
+
{ name: '', budget: 1000 },
|
|
272
|
+
{ name: 'Sales', budget: -500 },
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
const result = validateBusiness(business)
|
|
277
|
+
|
|
278
|
+
expect(result.valid).toBe(false)
|
|
279
|
+
expect(result.errors.length).toBeGreaterThan(1)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
})
|