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.
Files changed (212) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +2 -0
  3. package/package.json +7 -4
  4. package/src/dollar.ts +5 -2
  5. package/src/entities/organization.ts +31 -18
  6. package/src/goals.ts +78 -12
  7. package/src/index.ts +48 -18
  8. package/src/kpis.ts +62 -8
  9. package/src/metrics.ts +92 -79
  10. package/src/okrs.ts +120 -20
  11. package/src/organization.ts +12 -15
  12. package/src/process.ts +11 -12
  13. package/src/product.ts +8 -9
  14. package/src/queries.ts +238 -75
  15. package/src/roles.ts +62 -61
  16. package/src/workflow.ts +22 -15
  17. package/test/business.test.ts +282 -0
  18. package/test/dollar.test.ts +270 -0
  19. package/test/entities.test.ts +628 -0
  20. package/test/financials.test.ts +539 -0
  21. package/test/goals.test.ts +451 -0
  22. package/{src → test}/index.test.ts +1 -1
  23. package/test/kpis.test.ts +440 -0
  24. package/test/metrics.test.ts +744 -0
  25. package/test/okrs.test.ts +741 -0
  26. package/test/organization.test.ts +548 -0
  27. package/test/process.test.ts +503 -0
  28. package/test/product.test.ts +430 -0
  29. package/test/queries.test.ts +556 -0
  30. package/test/roles.test.ts +546 -0
  31. package/test/service.test.ts +450 -0
  32. package/test/types.test.ts +1141 -0
  33. package/test/vision.test.ts +214 -0
  34. package/test/workflow.test.ts +501 -0
  35. package/vitest.config.ts +47 -0
  36. package/.turbo/turbo-build.log +0 -5
  37. package/dist/business.d.ts +0 -62
  38. package/dist/business.d.ts.map +0 -1
  39. package/dist/business.js +0 -109
  40. package/dist/business.js.map +0 -1
  41. package/dist/dollar.d.ts +0 -60
  42. package/dist/dollar.d.ts.map +0 -1
  43. package/dist/dollar.js +0 -107
  44. package/dist/dollar.js.map +0 -1
  45. package/dist/entities/assets.d.ts +0 -21
  46. package/dist/entities/assets.d.ts.map +0 -1
  47. package/dist/entities/assets.js +0 -323
  48. package/dist/entities/assets.js.map +0 -1
  49. package/dist/entities/business.d.ts +0 -36
  50. package/dist/entities/business.d.ts.map +0 -1
  51. package/dist/entities/business.js +0 -370
  52. package/dist/entities/business.js.map +0 -1
  53. package/dist/entities/communication.d.ts +0 -21
  54. package/dist/entities/communication.d.ts.map +0 -1
  55. package/dist/entities/communication.js +0 -255
  56. package/dist/entities/communication.js.map +0 -1
  57. package/dist/entities/customers.d.ts +0 -58
  58. package/dist/entities/customers.d.ts.map +0 -1
  59. package/dist/entities/customers.js +0 -989
  60. package/dist/entities/customers.js.map +0 -1
  61. package/dist/entities/financials.d.ts +0 -59
  62. package/dist/entities/financials.d.ts.map +0 -1
  63. package/dist/entities/financials.js +0 -932
  64. package/dist/entities/financials.js.map +0 -1
  65. package/dist/entities/goals.d.ts +0 -58
  66. package/dist/entities/goals.d.ts.map +0 -1
  67. package/dist/entities/goals.js +0 -800
  68. package/dist/entities/goals.js.map +0 -1
  69. package/dist/entities/index.d.ts +0 -299
  70. package/dist/entities/index.d.ts.map +0 -1
  71. package/dist/entities/index.js +0 -198
  72. package/dist/entities/index.js.map +0 -1
  73. package/dist/entities/legal.d.ts +0 -21
  74. package/dist/entities/legal.d.ts.map +0 -1
  75. package/dist/entities/legal.js +0 -301
  76. package/dist/entities/legal.js.map +0 -1
  77. package/dist/entities/market.d.ts +0 -21
  78. package/dist/entities/market.d.ts.map +0 -1
  79. package/dist/entities/market.js +0 -301
  80. package/dist/entities/market.js.map +0 -1
  81. package/dist/entities/marketing.d.ts +0 -67
  82. package/dist/entities/marketing.d.ts.map +0 -1
  83. package/dist/entities/marketing.js +0 -1157
  84. package/dist/entities/marketing.js.map +0 -1
  85. package/dist/entities/offerings.d.ts +0 -51
  86. package/dist/entities/offerings.d.ts.map +0 -1
  87. package/dist/entities/offerings.js +0 -727
  88. package/dist/entities/offerings.js.map +0 -1
  89. package/dist/entities/operations.d.ts +0 -58
  90. package/dist/entities/operations.d.ts.map +0 -1
  91. package/dist/entities/operations.js +0 -787
  92. package/dist/entities/operations.js.map +0 -1
  93. package/dist/entities/organization.d.ts +0 -57
  94. package/dist/entities/organization.d.ts.map +0 -1
  95. package/dist/entities/organization.js +0 -807
  96. package/dist/entities/organization.js.map +0 -1
  97. package/dist/entities/partnerships.d.ts +0 -21
  98. package/dist/entities/partnerships.d.ts.map +0 -1
  99. package/dist/entities/partnerships.js +0 -300
  100. package/dist/entities/partnerships.js.map +0 -1
  101. package/dist/entities/planning.d.ts +0 -87
  102. package/dist/entities/planning.d.ts.map +0 -1
  103. package/dist/entities/planning.js +0 -271
  104. package/dist/entities/planning.js.map +0 -1
  105. package/dist/entities/projects.d.ts +0 -25
  106. package/dist/entities/projects.d.ts.map +0 -1
  107. package/dist/entities/projects.js +0 -349
  108. package/dist/entities/projects.js.map +0 -1
  109. package/dist/entities/risk.d.ts +0 -21
  110. package/dist/entities/risk.d.ts.map +0 -1
  111. package/dist/entities/risk.js +0 -293
  112. package/dist/entities/risk.js.map +0 -1
  113. package/dist/entities/sales.d.ts +0 -72
  114. package/dist/entities/sales.d.ts.map +0 -1
  115. package/dist/entities/sales.js +0 -1248
  116. package/dist/entities/sales.js.map +0 -1
  117. package/dist/financials.d.ts +0 -130
  118. package/dist/financials.d.ts.map +0 -1
  119. package/dist/financials.js +0 -297
  120. package/dist/financials.js.map +0 -1
  121. package/dist/goals.d.ts +0 -87
  122. package/dist/goals.d.ts.map +0 -1
  123. package/dist/goals.js +0 -215
  124. package/dist/goals.js.map +0 -1
  125. package/dist/index.d.ts +0 -97
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js +0 -132
  128. package/dist/index.js.map +0 -1
  129. package/dist/kpis.d.ts +0 -118
  130. package/dist/kpis.d.ts.map +0 -1
  131. package/dist/kpis.js +0 -232
  132. package/dist/kpis.js.map +0 -1
  133. package/dist/metrics.d.ts +0 -448
  134. package/dist/metrics.d.ts.map +0 -1
  135. package/dist/metrics.js +0 -325
  136. package/dist/metrics.js.map +0 -1
  137. package/dist/okrs.d.ts +0 -123
  138. package/dist/okrs.d.ts.map +0 -1
  139. package/dist/okrs.js +0 -269
  140. package/dist/okrs.js.map +0 -1
  141. package/dist/organization.d.ts +0 -585
  142. package/dist/organization.d.ts.map +0 -1
  143. package/dist/organization.js +0 -173
  144. package/dist/organization.js.map +0 -1
  145. package/dist/process.d.ts +0 -112
  146. package/dist/process.d.ts.map +0 -1
  147. package/dist/process.js +0 -241
  148. package/dist/process.js.map +0 -1
  149. package/dist/product.d.ts +0 -85
  150. package/dist/product.d.ts.map +0 -1
  151. package/dist/product.js +0 -145
  152. package/dist/product.js.map +0 -1
  153. package/dist/queries.d.ts +0 -304
  154. package/dist/queries.d.ts.map +0 -1
  155. package/dist/queries.js +0 -415
  156. package/dist/queries.js.map +0 -1
  157. package/dist/roles.d.ts +0 -340
  158. package/dist/roles.d.ts.map +0 -1
  159. package/dist/roles.js +0 -255
  160. package/dist/roles.js.map +0 -1
  161. package/dist/service.d.ts +0 -61
  162. package/dist/service.d.ts.map +0 -1
  163. package/dist/service.js +0 -140
  164. package/dist/service.js.map +0 -1
  165. package/dist/types.d.ts +0 -459
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -5
  168. package/dist/types.js.map +0 -1
  169. package/dist/vision.d.ts +0 -38
  170. package/dist/vision.d.ts.map +0 -1
  171. package/dist/vision.js +0 -68
  172. package/dist/vision.js.map +0 -1
  173. package/dist/workflow.d.ts +0 -115
  174. package/dist/workflow.d.ts.map +0 -1
  175. package/dist/workflow.js +0 -247
  176. package/dist/workflow.js.map +0 -1
  177. package/src/business.js +0 -108
  178. package/src/dollar.js +0 -106
  179. package/src/entities/assets.js +0 -322
  180. package/src/entities/business.js +0 -369
  181. package/src/entities/communication.js +0 -254
  182. package/src/entities/customers.js +0 -988
  183. package/src/entities/financials.js +0 -931
  184. package/src/entities/goals.js +0 -799
  185. package/src/entities/index.js +0 -197
  186. package/src/entities/legal.js +0 -300
  187. package/src/entities/market.js +0 -300
  188. package/src/entities/marketing.js +0 -1156
  189. package/src/entities/offerings.js +0 -726
  190. package/src/entities/operations.js +0 -786
  191. package/src/entities/organization.js +0 -806
  192. package/src/entities/partnerships.js +0 -299
  193. package/src/entities/planning.js +0 -270
  194. package/src/entities/projects.js +0 -348
  195. package/src/entities/risk.js +0 -292
  196. package/src/entities/sales.js +0 -1247
  197. package/src/financials.js +0 -296
  198. package/src/goals.js +0 -214
  199. package/src/index.js +0 -131
  200. package/src/index.test.js +0 -274
  201. package/src/kpis.js +0 -231
  202. package/src/metrics.js +0 -324
  203. package/src/okrs.js +0 -268
  204. package/src/organization.js +0 -172
  205. package/src/process.js +0 -240
  206. package/src/product.js +0 -144
  207. package/src/queries.js +0 -414
  208. package/src/roles.js +0 -254
  209. package/src/service.js +0 -139
  210. package/src/types.js +0 -4
  211. package/src/vision.js +0 -67
  212. package/src/workflow.js +0 -246
@@ -0,0 +1,1141 @@
1
+ /**
2
+ * RED Phase: Failing tests for Goals, Employee, Customer types
3
+ *
4
+ * These tests define the expected interface for new type definitions.
5
+ * They should fail initially as part of TDD (Test-Driven Development).
6
+ *
7
+ * Issue: aip-6slz
8
+ *
9
+ * NOTE: These tests intentionally import from modules that don't exist yet.
10
+ * The tests define the EXPECTED interface that must be implemented.
11
+ * Once implemented, remove the .skip and run tests to verify.
12
+ */
13
+
14
+ import { describe, it, expect } from 'vitest'
15
+
16
+ // =============================================================================
17
+ // EXPECTED INTERFACES (to be implemented in src/types/)
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Expected Employee type definition
22
+ */
23
+ export interface Employee {
24
+ id: string
25
+ firstName: string
26
+ lastName: string
27
+ email: string
28
+ status: EmployeeStatus
29
+ type: EmployeeType
30
+ hireDate: Date
31
+ department?: string
32
+ team?: string
33
+ title?: string
34
+ level?: string
35
+ managerId?: string
36
+ location?: string
37
+ timezone?: string
38
+ salary?: number
39
+ currency?: string
40
+ terminationDate?: Date
41
+ }
42
+
43
+ export type EmployeeStatus = 'active' | 'on-leave' | 'terminated' | 'pending'
44
+ export type EmployeeType = 'full-time' | 'part-time' | 'contractor' | 'intern'
45
+
46
+ export interface EmployeeDefinition {
47
+ firstName: string
48
+ lastName: string
49
+ email: string
50
+ type: EmployeeType
51
+ department?: string
52
+ team?: string
53
+ title?: string
54
+ level?: string
55
+ managerId?: string
56
+ status?: EmployeeStatus
57
+ hireDate?: Date
58
+ }
59
+
60
+ /**
61
+ * Expected Customer type definition
62
+ */
63
+ export interface CustomerType {
64
+ id: string
65
+ name: string
66
+ email: string
67
+ status: CustomerStatus
68
+ segment: CustomerSegment
69
+ tier?: string
70
+ createdAt: Date
71
+ industry?: string
72
+ companySize?: string
73
+ annualRevenue?: number
74
+ website?: string
75
+ healthScore?: number
76
+ nps?: number
77
+ lifetimeValue?: number
78
+ mrr?: number
79
+ arr?: number
80
+ churnRisk?: string
81
+ churnDate?: Date
82
+ churnReason?: string
83
+ }
84
+
85
+ export type CustomerStatus = 'prospect' | 'trial' | 'active' | 'churned' | 'at-risk' | 'paused'
86
+ export type CustomerSegment = 'enterprise' | 'mid-market' | 'smb' | 'startup' | 'consumer'
87
+
88
+ export interface CustomerDefinition {
89
+ name: string
90
+ email: string
91
+ segment: CustomerSegment
92
+ tier?: string
93
+ status?: CustomerStatus
94
+ industry?: string
95
+ mrr?: number
96
+ }
97
+
98
+ /**
99
+ * Expected Goal type definition (v2 with hierarchy support)
100
+ */
101
+ export interface NewGoalDefinition {
102
+ id: string
103
+ name: string
104
+ status: GoalStatus
105
+ priority: GoalPriority
106
+ progress: number
107
+ description?: string
108
+ parentId?: string
109
+ level?: 'company' | 'department' | 'team' | 'individual'
110
+ ownerId?: string
111
+ teamId?: string
112
+ departmentId?: string
113
+ alignedTo?: string[]
114
+ children?: string[]
115
+ dependencies?: string[]
116
+ targetValue?: number
117
+ currentValue?: number
118
+ unit?: string
119
+ startDate?: Date
120
+ targetDate?: Date
121
+ metrics?: string[]
122
+ weight?: number
123
+ }
124
+
125
+ export type GoalStatus =
126
+ | 'draft'
127
+ | 'active'
128
+ | 'in-progress'
129
+ | 'at-risk'
130
+ | 'behind'
131
+ | 'completed'
132
+ | 'cancelled'
133
+ | 'deferred'
134
+ export type GoalPriority = 'critical' | 'high' | 'medium' | 'low'
135
+
136
+ // =============================================================================
137
+ // IMPLEMENTATIONS
138
+ // =============================================================================
139
+
140
+ // Employee implementation
141
+ let employeeIdCounter = 0
142
+ const createEmployee = (def: EmployeeDefinition): Employee => {
143
+ if (!def.email) {
144
+ throw new Error('Employee email is required')
145
+ }
146
+ employeeIdCounter++
147
+ return {
148
+ id: `emp-${String(employeeIdCounter).padStart(3, '0')}`,
149
+ firstName: def.firstName,
150
+ lastName: def.lastName,
151
+ email: def.email,
152
+ status: def.status || 'active',
153
+ type: def.type,
154
+ hireDate: def.hireDate || new Date(),
155
+ department: def.department,
156
+ team: def.team,
157
+ title: def.title,
158
+ level: def.level,
159
+ managerId: def.managerId,
160
+ }
161
+ }
162
+
163
+ const Employees = (defs: EmployeeDefinition[]): Employee[] => {
164
+ return defs.map((def) => createEmployee(def))
165
+ }
166
+
167
+ const isEmployeeActive = (employee: Employee): boolean => {
168
+ return employee.status === 'active'
169
+ }
170
+
171
+ const getEmployeesByDepartment = (employees: Employee[], department: string): Employee[] => {
172
+ return employees.filter((e) => e.department === department)
173
+ }
174
+
175
+ const getEmployeesByManager = (employees: Employee[], managerId: string): Employee[] => {
176
+ return employees.filter((e) => e.managerId === managerId)
177
+ }
178
+
179
+ const calculateTenure = (employee: Employee): number => {
180
+ const now = new Date()
181
+ const hireDate = employee.hireDate
182
+ const months =
183
+ (now.getFullYear() - hireDate.getFullYear()) * 12 + (now.getMonth() - hireDate.getMonth())
184
+ return Math.max(0, months)
185
+ }
186
+
187
+ const promoteEmployee = (
188
+ employee: Employee,
189
+ options: { newLevel: string; newTitle: string; salaryIncrease?: number }
190
+ ): Employee => {
191
+ return {
192
+ ...employee,
193
+ level: options.newLevel,
194
+ title: options.newTitle,
195
+ salary: employee.salary
196
+ ? employee.salary + (options.salaryIncrease || 0)
197
+ : options.salaryIncrease,
198
+ }
199
+ }
200
+
201
+ const terminateEmployee = (
202
+ employee: Employee,
203
+ options: { reason: string; terminationDate: Date }
204
+ ): Employee => {
205
+ return {
206
+ ...employee,
207
+ status: 'terminated',
208
+ terminationDate: options.terminationDate,
209
+ }
210
+ }
211
+
212
+ // Customer implementation
213
+ let customerIdCounter = 0
214
+ const createCustomer = (def: CustomerDefinition): CustomerType => {
215
+ if (!def.name) {
216
+ throw new Error('Customer name is required')
217
+ }
218
+ customerIdCounter++
219
+ return {
220
+ id: `cust-${String(customerIdCounter).padStart(3, '0')}`,
221
+ name: def.name,
222
+ email: def.email,
223
+ status: def.status || 'prospect',
224
+ segment: def.segment,
225
+ tier: def.tier,
226
+ createdAt: new Date(),
227
+ industry: def.industry,
228
+ mrr: def.mrr,
229
+ }
230
+ }
231
+
232
+ const Customers = (defs: CustomerDefinition[]): CustomerType[] => {
233
+ return defs.map((def) => createCustomer(def))
234
+ }
235
+
236
+ const isCustomerActive = (customer: CustomerType): boolean => {
237
+ return customer.status === 'active'
238
+ }
239
+
240
+ const getCustomersBySegment = (
241
+ customers: CustomerType[],
242
+ segment: CustomerSegment
243
+ ): CustomerType[] => {
244
+ return customers.filter((c) => c.segment === segment)
245
+ }
246
+
247
+ const getCustomersByTier = (customers: CustomerType[], tier: string): CustomerType[] => {
248
+ return customers.filter((c) => c.tier === tier)
249
+ }
250
+
251
+ const calculateCustomerLifetimeValue = (customer: CustomerType): number => {
252
+ if (!customer.mrr) return 0
253
+ const now = new Date()
254
+ const createdAt = customer.createdAt
255
+ const monthsActive =
256
+ (now.getFullYear() - createdAt.getFullYear()) * 12 + (now.getMonth() - createdAt.getMonth())
257
+ return customer.mrr * Math.max(1, monthsActive)
258
+ }
259
+
260
+ const upgradeCustomer = (
261
+ customer: CustomerType,
262
+ options: { newTier: string; newMrr: number }
263
+ ): CustomerType => {
264
+ return {
265
+ ...customer,
266
+ tier: options.newTier,
267
+ mrr: options.newMrr,
268
+ }
269
+ }
270
+
271
+ const downgradeCustomer = (
272
+ customer: CustomerType,
273
+ options: { newTier: string; newMrr: number; reason: string }
274
+ ): CustomerType => {
275
+ return {
276
+ ...customer,
277
+ tier: options.newTier,
278
+ mrr: options.newMrr,
279
+ }
280
+ }
281
+
282
+ const churnCustomer = (
283
+ customer: CustomerType,
284
+ options: { reason: string; churnDate: Date; feedback?: string }
285
+ ): CustomerType => {
286
+ return {
287
+ ...customer,
288
+ status: 'churned',
289
+ churnDate: options.churnDate,
290
+ churnReason: options.reason,
291
+ }
292
+ }
293
+
294
+ // Goals (v2) implementation
295
+ let goalIdCounter = 0
296
+ const Goal = (
297
+ def: Partial<NewGoalDefinition> & { name: string; priority: GoalPriority }
298
+ ): NewGoalDefinition => {
299
+ goalIdCounter++
300
+ return {
301
+ id: def.id || `goal-${String(goalIdCounter).padStart(3, '0')}`,
302
+ name: def.name,
303
+ status: def.status || 'active',
304
+ priority: def.priority,
305
+ progress: def.progress || 0,
306
+ description: def.description,
307
+ parentId: def.parentId,
308
+ level: def.level,
309
+ ownerId: def.ownerId,
310
+ teamId: def.teamId,
311
+ departmentId: def.departmentId,
312
+ alignedTo: def.alignedTo,
313
+ children: def.children,
314
+ dependencies: def.dependencies,
315
+ targetValue: def.targetValue,
316
+ currentValue: def.currentValue,
317
+ unit: def.unit,
318
+ startDate: def.startDate,
319
+ targetDate: def.targetDate,
320
+ metrics: def.metrics,
321
+ weight: def.weight,
322
+ }
323
+ }
324
+
325
+ const Goals = (
326
+ defs: Array<Partial<NewGoalDefinition> & { name: string; priority: GoalPriority }>
327
+ ): NewGoalDefinition[] => {
328
+ return defs.map((def) => Goal(def))
329
+ }
330
+
331
+ const createGoalHierarchy = (config: {
332
+ company: { name: string; priority: GoalPriority }
333
+ departments?: Array<{ name: string; priority: GoalPriority; departmentId: string }>
334
+ teams?: Array<{ name: string; priority: GoalPriority; teamId: string; parentDepartment: string }>
335
+ }): {
336
+ company: NewGoalDefinition
337
+ departments: NewGoalDefinition[]
338
+ teams: NewGoalDefinition[]
339
+ } => {
340
+ const company = Goal({
341
+ name: config.company.name,
342
+ priority: config.company.priority,
343
+ level: 'company',
344
+ })
345
+
346
+ const departments = (config.departments || []).map((dept) =>
347
+ Goal({
348
+ name: dept.name,
349
+ priority: dept.priority,
350
+ level: 'department',
351
+ departmentId: dept.departmentId,
352
+ alignedTo: [company.id],
353
+ parentId: company.id,
354
+ })
355
+ )
356
+
357
+ const teams = (config.teams || []).map((team) => {
358
+ const parentDept = departments.find((d) => d.departmentId === team.parentDepartment)
359
+ return Goal({
360
+ name: team.name,
361
+ priority: team.priority,
362
+ level: 'team',
363
+ teamId: team.teamId,
364
+ alignedTo: parentDept ? [parentDept.id] : [],
365
+ parentId: parentDept?.id,
366
+ })
367
+ })
368
+
369
+ return { company, departments, teams }
370
+ }
371
+
372
+ const alignGoals = (child: NewGoalDefinition, parent: NewGoalDefinition): NewGoalDefinition => {
373
+ return {
374
+ ...child,
375
+ alignedTo: [...(child.alignedTo || []), parent.id],
376
+ parentId: parent.id,
377
+ }
378
+ }
379
+
380
+ const cascadeGoals = (
381
+ parent: NewGoalDefinition,
382
+ config: { departments: string[]; splitStrategy: 'equal' | 'weighted' }
383
+ ): NewGoalDefinition[] => {
384
+ return config.departments.map((dept) =>
385
+ Goal({
386
+ name: `${parent.name} - ${dept}`,
387
+ priority: parent.priority,
388
+ level: 'department',
389
+ departmentId: dept,
390
+ alignedTo: [parent.id],
391
+ parentId: parent.id,
392
+ })
393
+ )
394
+ }
395
+
396
+ const getGoalsByOwner = (goals: NewGoalDefinition[], ownerId: string): NewGoalDefinition[] => {
397
+ return goals.filter((g) => g.ownerId === ownerId)
398
+ }
399
+
400
+ const getGoalsAtRisk = (goals: NewGoalDefinition[]): NewGoalDefinition[] => {
401
+ return goals.filter((g) => g.status === 'at-risk' || g.status === 'behind')
402
+ }
403
+
404
+ const calculateGoalProgress = (goal: NewGoalDefinition): number => {
405
+ if (goal.targetValue && goal.currentValue !== undefined) {
406
+ return Math.round((goal.currentValue / goal.targetValue) * 100)
407
+ }
408
+ return goal.progress
409
+ }
410
+
411
+ const rollupProgress = (
412
+ parent: NewGoalDefinition,
413
+ children: NewGoalDefinition[],
414
+ options?: { weighted?: boolean }
415
+ ): number => {
416
+ if (children.length === 0) return 0
417
+ if (options?.weighted) {
418
+ const totalWeight = children.reduce((sum, c) => sum + (c.weight || 0), 0)
419
+ if (totalWeight === 0) return 0
420
+ return children.reduce((sum, c) => sum + c.progress * (c.weight || 0), 0) / totalWeight
421
+ }
422
+ const totalProgress = children.reduce((sum, c) => sum + c.progress, 0)
423
+ return totalProgress / children.length
424
+ }
425
+
426
+ describe('Employee Type', () => {
427
+ describe('Employee interface', () => {
428
+ it('should have required identity properties', () => {
429
+ const employee: Employee = {
430
+ id: 'emp-001',
431
+ firstName: 'John',
432
+ lastName: 'Doe',
433
+ email: 'john.doe@company.com',
434
+ status: 'active',
435
+ type: 'full-time',
436
+ hireDate: new Date('2023-01-15'),
437
+ }
438
+
439
+ expect(employee.id).toBe('emp-001')
440
+ expect(employee.firstName).toBe('John')
441
+ expect(employee.lastName).toBe('Doe')
442
+ expect(employee.email).toBe('john.doe@company.com')
443
+ })
444
+
445
+ it('should support optional employment properties', () => {
446
+ const employee: Employee = {
447
+ id: 'emp-002',
448
+ firstName: 'Jane',
449
+ lastName: 'Smith',
450
+ email: 'jane.smith@company.com',
451
+ status: 'active',
452
+ type: 'full-time',
453
+ hireDate: new Date('2022-06-01'),
454
+ department: 'Engineering',
455
+ team: 'Platform',
456
+ title: 'Senior Engineer',
457
+ level: 'senior',
458
+ managerId: 'emp-001',
459
+ location: 'San Francisco',
460
+ timezone: 'America/Los_Angeles',
461
+ salary: 150000,
462
+ currency: 'USD',
463
+ }
464
+
465
+ expect(employee.department).toBe('Engineering')
466
+ expect(employee.team).toBe('Platform')
467
+ expect(employee.title).toBe('Senior Engineer')
468
+ expect(employee.managerId).toBe('emp-001')
469
+ })
470
+
471
+ it('should have valid status values', () => {
472
+ const statuses: EmployeeStatus[] = ['active', 'on-leave', 'terminated', 'pending']
473
+ expect(statuses).toContain('active')
474
+ expect(statuses).toContain('on-leave')
475
+ expect(statuses).toContain('terminated')
476
+ })
477
+
478
+ it('should have valid type values', () => {
479
+ const types: EmployeeType[] = ['full-time', 'part-time', 'contractor', 'intern']
480
+ expect(types).toContain('full-time')
481
+ expect(types).toContain('contractor')
482
+ })
483
+ })
484
+
485
+ describe('Employee factory functions', () => {
486
+ it('should create a single employee', () => {
487
+ const employee = createEmployee({
488
+ firstName: 'Alice',
489
+ lastName: 'Johnson',
490
+ email: 'alice@company.com',
491
+ type: 'full-time',
492
+ department: 'Product',
493
+ })
494
+
495
+ expect(employee.id).toBeDefined()
496
+ expect(employee.firstName).toBe('Alice')
497
+ expect(employee.status).toBe('active')
498
+ expect(employee.hireDate).toBeInstanceOf(Date)
499
+ })
500
+
501
+ it('should create multiple employees', () => {
502
+ const employees = Employees([
503
+ { firstName: 'Bob', lastName: 'Wilson', email: 'bob@company.com', type: 'full-time' },
504
+ { firstName: 'Carol', lastName: 'Davis', email: 'carol@company.com', type: 'part-time' },
505
+ ])
506
+
507
+ expect(employees).toHaveLength(2)
508
+ expect(employees[0]?.firstName).toBe('Bob')
509
+ expect(employees[1]?.firstName).toBe('Carol')
510
+ })
511
+
512
+ it('should throw error for employee without email', () => {
513
+ expect(() =>
514
+ createEmployee({
515
+ firstName: 'Test',
516
+ lastName: 'User',
517
+ email: '',
518
+ type: 'full-time',
519
+ })
520
+ ).toThrow('Employee email is required')
521
+ })
522
+ })
523
+
524
+ describe('Employee helper functions', () => {
525
+ it('should check if employee is active', () => {
526
+ const activeEmployee = createEmployee({
527
+ firstName: 'Active',
528
+ lastName: 'User',
529
+ email: 'active@company.com',
530
+ type: 'full-time',
531
+ status: 'active',
532
+ })
533
+
534
+ const terminatedEmployee = createEmployee({
535
+ firstName: 'Former',
536
+ lastName: 'User',
537
+ email: 'former@company.com',
538
+ type: 'full-time',
539
+ status: 'terminated',
540
+ })
541
+
542
+ expect(isEmployeeActive(activeEmployee)).toBe(true)
543
+ expect(isEmployeeActive(terminatedEmployee)).toBe(false)
544
+ })
545
+
546
+ it('should get employees by department', () => {
547
+ const employees = Employees([
548
+ {
549
+ firstName: 'Eng1',
550
+ lastName: 'User',
551
+ email: 'eng1@company.com',
552
+ type: 'full-time',
553
+ department: 'Engineering',
554
+ },
555
+ {
556
+ firstName: 'Sales1',
557
+ lastName: 'User',
558
+ email: 'sales1@company.com',
559
+ type: 'full-time',
560
+ department: 'Sales',
561
+ },
562
+ {
563
+ firstName: 'Eng2',
564
+ lastName: 'User',
565
+ email: 'eng2@company.com',
566
+ type: 'full-time',
567
+ department: 'Engineering',
568
+ },
569
+ ])
570
+
571
+ const engineeringTeam = getEmployeesByDepartment(employees, 'Engineering')
572
+ expect(engineeringTeam).toHaveLength(2)
573
+ })
574
+
575
+ it('should get employees by manager', () => {
576
+ const employees = Employees([
577
+ { firstName: 'Manager', lastName: 'User', email: 'manager@company.com', type: 'full-time' },
578
+ {
579
+ firstName: 'Report1',
580
+ lastName: 'User',
581
+ email: 'report1@company.com',
582
+ type: 'full-time',
583
+ managerId: 'emp-001',
584
+ },
585
+ {
586
+ firstName: 'Report2',
587
+ lastName: 'User',
588
+ email: 'report2@company.com',
589
+ type: 'full-time',
590
+ managerId: 'emp-001',
591
+ },
592
+ ])
593
+
594
+ const directReports = getEmployeesByManager(employees, 'emp-001')
595
+ expect(directReports).toHaveLength(2)
596
+ })
597
+
598
+ it('should calculate tenure in months', () => {
599
+ const employee = createEmployee({
600
+ firstName: 'Tenured',
601
+ lastName: 'User',
602
+ email: 'tenured@company.com',
603
+ type: 'full-time',
604
+ hireDate: new Date('2023-01-01'),
605
+ })
606
+
607
+ const tenure = calculateTenure(employee)
608
+ expect(tenure).toBeGreaterThan(0)
609
+ expect(typeof tenure).toBe('number')
610
+ })
611
+
612
+ it('should promote employee', () => {
613
+ const employee = createEmployee({
614
+ firstName: 'Promotable',
615
+ lastName: 'User',
616
+ email: 'promotable@company.com',
617
+ type: 'full-time',
618
+ level: 'junior',
619
+ title: 'Engineer',
620
+ })
621
+
622
+ const promoted = promoteEmployee(employee, {
623
+ newLevel: 'mid',
624
+ newTitle: 'Senior Engineer',
625
+ salaryIncrease: 10000,
626
+ })
627
+
628
+ expect(promoted.level).toBe('mid')
629
+ expect(promoted.title).toBe('Senior Engineer')
630
+ })
631
+
632
+ it('should terminate employee', () => {
633
+ const employee = createEmployee({
634
+ firstName: 'Leaving',
635
+ lastName: 'User',
636
+ email: 'leaving@company.com',
637
+ type: 'full-time',
638
+ status: 'active',
639
+ })
640
+
641
+ const terminated = terminateEmployee(employee, {
642
+ reason: 'voluntary',
643
+ terminationDate: new Date(),
644
+ })
645
+
646
+ expect(terminated.status).toBe('terminated')
647
+ expect(terminated.terminationDate).toBeInstanceOf(Date)
648
+ })
649
+ })
650
+ })
651
+
652
+ describe('Customer Type', () => {
653
+ describe('Customer interface', () => {
654
+ it('should have required properties', () => {
655
+ const customer: CustomerType = {
656
+ id: 'cust-001',
657
+ name: 'Acme Corp',
658
+ email: 'contact@acme.com',
659
+ status: 'active',
660
+ segment: 'enterprise',
661
+ tier: 'premium',
662
+ createdAt: new Date('2023-01-01'),
663
+ }
664
+
665
+ expect(customer.id).toBe('cust-001')
666
+ expect(customer.name).toBe('Acme Corp')
667
+ expect(customer.status).toBe('active')
668
+ })
669
+
670
+ it('should support optional business properties', () => {
671
+ const customer: CustomerType = {
672
+ id: 'cust-002',
673
+ name: 'Startup Inc',
674
+ email: 'hello@startup.io',
675
+ status: 'active',
676
+ segment: 'smb',
677
+ tier: 'basic',
678
+ createdAt: new Date(),
679
+ industry: 'Technology',
680
+ companySize: '11-50',
681
+ annualRevenue: 5000000,
682
+ website: 'https://startup.io',
683
+ healthScore: 85,
684
+ nps: 9,
685
+ lifetimeValue: 24000,
686
+ mrr: 2000,
687
+ arr: 24000,
688
+ churnRisk: 'low',
689
+ }
690
+
691
+ expect(customer.industry).toBe('Technology')
692
+ expect(customer.healthScore).toBe(85)
693
+ expect(customer.lifetimeValue).toBe(24000)
694
+ })
695
+
696
+ it('should have valid status values', () => {
697
+ const statuses: CustomerStatus[] = [
698
+ 'prospect',
699
+ 'trial',
700
+ 'active',
701
+ 'churned',
702
+ 'at-risk',
703
+ 'paused',
704
+ ]
705
+ expect(statuses).toContain('active')
706
+ expect(statuses).toContain('churned')
707
+ })
708
+
709
+ it('should have valid segment values', () => {
710
+ const segments: CustomerSegment[] = ['enterprise', 'mid-market', 'smb', 'startup', 'consumer']
711
+ expect(segments).toContain('enterprise')
712
+ expect(segments).toContain('smb')
713
+ })
714
+ })
715
+
716
+ describe('Customer factory functions', () => {
717
+ it('should create a single customer', () => {
718
+ const customer = createCustomer({
719
+ name: 'New Customer',
720
+ email: 'new@customer.com',
721
+ segment: 'smb',
722
+ })
723
+
724
+ expect(customer.id).toBeDefined()
725
+ expect(customer.name).toBe('New Customer')
726
+ expect(customer.status).toBe('prospect')
727
+ expect(customer.createdAt).toBeInstanceOf(Date)
728
+ })
729
+
730
+ it('should create multiple customers', () => {
731
+ const customers = Customers([
732
+ { name: 'Customer A', email: 'a@customer.com', segment: 'enterprise' },
733
+ { name: 'Customer B', email: 'b@customer.com', segment: 'smb' },
734
+ ])
735
+
736
+ expect(customers).toHaveLength(2)
737
+ expect(customers[0]?.name).toBe('Customer A')
738
+ })
739
+
740
+ it('should throw error for customer without name', () => {
741
+ expect(() =>
742
+ createCustomer({
743
+ name: '',
744
+ email: 'no-name@customer.com',
745
+ segment: 'smb',
746
+ })
747
+ ).toThrow('Customer name is required')
748
+ })
749
+ })
750
+
751
+ describe('Customer helper functions', () => {
752
+ it('should check if customer is active', () => {
753
+ const activeCustomer = createCustomer({
754
+ name: 'Active Customer',
755
+ email: 'active@customer.com',
756
+ segment: 'smb',
757
+ status: 'active',
758
+ })
759
+
760
+ const churnedCustomer = createCustomer({
761
+ name: 'Churned Customer',
762
+ email: 'churned@customer.com',
763
+ segment: 'smb',
764
+ status: 'churned',
765
+ })
766
+
767
+ expect(isCustomerActive(activeCustomer)).toBe(true)
768
+ expect(isCustomerActive(churnedCustomer)).toBe(false)
769
+ })
770
+
771
+ it('should get customers by segment', () => {
772
+ const customers = Customers([
773
+ { name: 'Enterprise 1', email: 'e1@customer.com', segment: 'enterprise' },
774
+ { name: 'SMB 1', email: 's1@customer.com', segment: 'smb' },
775
+ { name: 'Enterprise 2', email: 'e2@customer.com', segment: 'enterprise' },
776
+ ])
777
+
778
+ const enterpriseCustomers = getCustomersBySegment(customers, 'enterprise')
779
+ expect(enterpriseCustomers).toHaveLength(2)
780
+ })
781
+
782
+ it('should get customers by tier', () => {
783
+ const customers = Customers([
784
+ { name: 'Premium 1', email: 'p1@customer.com', segment: 'enterprise', tier: 'premium' },
785
+ { name: 'Basic 1', email: 'b1@customer.com', segment: 'smb', tier: 'basic' },
786
+ { name: 'Premium 2', email: 'p2@customer.com', segment: 'mid-market', tier: 'premium' },
787
+ ])
788
+
789
+ const premiumCustomers = getCustomersByTier(customers, 'premium')
790
+ expect(premiumCustomers).toHaveLength(2)
791
+ })
792
+
793
+ it('should calculate customer lifetime value', () => {
794
+ const customer = createCustomer({
795
+ name: 'LTV Customer',
796
+ email: 'ltv@customer.com',
797
+ segment: 'smb',
798
+ mrr: 1000,
799
+ createdAt: new Date('2022-01-01'),
800
+ })
801
+
802
+ const ltv = calculateCustomerLifetimeValue(customer)
803
+ expect(ltv).toBeGreaterThan(0)
804
+ expect(typeof ltv).toBe('number')
805
+ })
806
+
807
+ it('should upgrade customer tier', () => {
808
+ const customer = createCustomer({
809
+ name: 'Upgrade Customer',
810
+ email: 'upgrade@customer.com',
811
+ segment: 'smb',
812
+ tier: 'basic',
813
+ })
814
+
815
+ const upgraded = upgradeCustomer(customer, {
816
+ newTier: 'premium',
817
+ newMrr: 5000,
818
+ })
819
+
820
+ expect(upgraded.tier).toBe('premium')
821
+ expect(upgraded.mrr).toBe(5000)
822
+ })
823
+
824
+ it('should downgrade customer tier', () => {
825
+ const customer = createCustomer({
826
+ name: 'Downgrade Customer',
827
+ email: 'downgrade@customer.com',
828
+ segment: 'enterprise',
829
+ tier: 'premium',
830
+ mrr: 10000,
831
+ })
832
+
833
+ const downgraded = downgradeCustomer(customer, {
834
+ newTier: 'basic',
835
+ newMrr: 2000,
836
+ reason: 'budget-constraints',
837
+ })
838
+
839
+ expect(downgraded.tier).toBe('basic')
840
+ expect(downgraded.mrr).toBe(2000)
841
+ })
842
+
843
+ it('should mark customer as churned', () => {
844
+ const customer = createCustomer({
845
+ name: 'Churn Customer',
846
+ email: 'churn@customer.com',
847
+ segment: 'smb',
848
+ status: 'active',
849
+ })
850
+
851
+ const churned = churnCustomer(customer, {
852
+ reason: 'switched-to-competitor',
853
+ churnDate: new Date(),
854
+ feedback: 'Found better pricing elsewhere',
855
+ })
856
+
857
+ expect(churned.status).toBe('churned')
858
+ expect(churned.churnDate).toBeInstanceOf(Date)
859
+ expect(churned.churnReason).toBe('switched-to-competitor')
860
+ })
861
+ })
862
+ })
863
+
864
+ describe('Goals Type (v2)', () => {
865
+ describe('Goal interface', () => {
866
+ it('should have required properties', () => {
867
+ const goal: NewGoalDefinition = {
868
+ id: 'goal-001',
869
+ name: 'Increase Revenue',
870
+ status: 'active',
871
+ priority: 'high',
872
+ progress: 0,
873
+ }
874
+
875
+ expect(goal.id).toBe('goal-001')
876
+ expect(goal.name).toBe('Increase Revenue')
877
+ expect(goal.status).toBe('active')
878
+ })
879
+
880
+ it('should support hierarchy properties', () => {
881
+ const goal: NewGoalDefinition = {
882
+ id: 'goal-002',
883
+ name: 'Q1 Sales Target',
884
+ status: 'in-progress',
885
+ priority: 'high',
886
+ progress: 25,
887
+ parentId: 'goal-001',
888
+ level: 'team',
889
+ ownerId: 'emp-001',
890
+ teamId: 'team-001',
891
+ departmentId: 'dept-001',
892
+ alignedTo: ['goal-001'],
893
+ children: ['goal-003', 'goal-004'],
894
+ }
895
+
896
+ expect(goal.parentId).toBe('goal-001')
897
+ expect(goal.level).toBe('team')
898
+ expect(goal.alignedTo).toContain('goal-001')
899
+ })
900
+
901
+ it('should support measurement properties', () => {
902
+ const goal: NewGoalDefinition = {
903
+ id: 'goal-003',
904
+ name: 'Close 50 Deals',
905
+ status: 'in-progress',
906
+ priority: 'high',
907
+ progress: 40,
908
+ targetValue: 50,
909
+ currentValue: 20,
910
+ unit: 'deals',
911
+ startDate: new Date('2024-01-01'),
912
+ targetDate: new Date('2024-03-31'),
913
+ metrics: ['deals-closed', 'revenue-generated'],
914
+ }
915
+
916
+ expect(goal.targetValue).toBe(50)
917
+ expect(goal.currentValue).toBe(20)
918
+ expect(goal.progress).toBe(40)
919
+ })
920
+
921
+ it('should have valid status values', () => {
922
+ const statuses: GoalStatus[] = [
923
+ 'draft',
924
+ 'active',
925
+ 'in-progress',
926
+ 'at-risk',
927
+ 'behind',
928
+ 'completed',
929
+ 'cancelled',
930
+ 'deferred',
931
+ ]
932
+ expect(statuses).toContain('active')
933
+ expect(statuses).toContain('at-risk')
934
+ expect(statuses).toContain('deferred')
935
+ })
936
+
937
+ it('should have valid priority values', () => {
938
+ const priorities: GoalPriority[] = ['critical', 'high', 'medium', 'low']
939
+ expect(priorities).toContain('critical')
940
+ expect(priorities).toContain('low')
941
+ })
942
+ })
943
+
944
+ describe('Goals factory functions', () => {
945
+ it('should create a goal hierarchy', () => {
946
+ const hierarchy = createGoalHierarchy({
947
+ company: {
948
+ name: 'Increase ARR by 50%',
949
+ priority: 'critical',
950
+ },
951
+ departments: [
952
+ {
953
+ name: 'Sales Revenue Target',
954
+ priority: 'high',
955
+ departmentId: 'sales',
956
+ },
957
+ {
958
+ name: 'Reduce Churn Rate',
959
+ priority: 'high',
960
+ departmentId: 'customer-success',
961
+ },
962
+ ],
963
+ teams: [
964
+ {
965
+ name: 'Enterprise Sales',
966
+ priority: 'high',
967
+ teamId: 'enterprise',
968
+ parentDepartment: 'sales',
969
+ },
970
+ ],
971
+ })
972
+
973
+ expect(hierarchy.company).toBeDefined()
974
+ expect(hierarchy.departments).toHaveLength(2)
975
+ expect(hierarchy.teams).toHaveLength(1)
976
+ expect(hierarchy.departments[0]?.alignedTo).toContain(hierarchy.company.id)
977
+ })
978
+
979
+ it('should align goals', () => {
980
+ const companyGoal = Goal({
981
+ name: 'Company Goal',
982
+ priority: 'critical',
983
+ level: 'company',
984
+ })
985
+
986
+ const teamGoal = Goal({
987
+ name: 'Team Goal',
988
+ priority: 'high',
989
+ level: 'team',
990
+ })
991
+
992
+ const aligned = alignGoals(teamGoal, companyGoal)
993
+
994
+ expect(aligned.alignedTo).toContain(companyGoal.id)
995
+ expect(aligned.parentId).toBe(companyGoal.id)
996
+ })
997
+
998
+ it('should cascade goals down the organization', () => {
999
+ const companyGoal = Goal({
1000
+ name: 'Grow 100%',
1001
+ priority: 'critical',
1002
+ level: 'company',
1003
+ })
1004
+
1005
+ const cascaded = cascadeGoals(companyGoal, {
1006
+ departments: ['sales', 'marketing', 'product'],
1007
+ splitStrategy: 'equal',
1008
+ })
1009
+
1010
+ expect(cascaded).toHaveLength(3)
1011
+ expect(cascaded[0]?.alignedTo).toContain(companyGoal.id)
1012
+ })
1013
+ })
1014
+
1015
+ describe('Goals helper functions', () => {
1016
+ it('should get goals by owner', () => {
1017
+ const goals = Goals([
1018
+ { name: 'Goal 1', priority: 'high', ownerId: 'emp-001' },
1019
+ { name: 'Goal 2', priority: 'medium', ownerId: 'emp-002' },
1020
+ { name: 'Goal 3', priority: 'low', ownerId: 'emp-001' },
1021
+ ])
1022
+
1023
+ const ownerGoals = getGoalsByOwner(goals, 'emp-001')
1024
+ expect(ownerGoals).toHaveLength(2)
1025
+ })
1026
+
1027
+ it('should get goals at risk', () => {
1028
+ const goals = Goals([
1029
+ { name: 'On Track', priority: 'high', status: 'in-progress', progress: 80 },
1030
+ { name: 'At Risk', priority: 'high', status: 'at-risk', progress: 30 },
1031
+ { name: 'Behind', priority: 'medium', status: 'behind', progress: 10 },
1032
+ ])
1033
+
1034
+ const atRiskGoals = getGoalsAtRisk(goals)
1035
+ expect(atRiskGoals).toHaveLength(2)
1036
+ expect(atRiskGoals.map((g) => g.name)).toContain('At Risk')
1037
+ expect(atRiskGoals.map((g) => g.name)).toContain('Behind')
1038
+ })
1039
+
1040
+ it('should calculate goal progress from current/target values', () => {
1041
+ const goal = Goal({
1042
+ name: 'Close Deals',
1043
+ priority: 'high',
1044
+ targetValue: 100,
1045
+ currentValue: 35,
1046
+ })
1047
+
1048
+ const progress = calculateGoalProgress(goal)
1049
+ expect(progress).toBe(35)
1050
+ })
1051
+
1052
+ it('should rollup progress from child goals', () => {
1053
+ const parentGoal = Goal({
1054
+ name: 'Parent Goal',
1055
+ priority: 'high',
1056
+ children: ['child-1', 'child-2', 'child-3'],
1057
+ })
1058
+
1059
+ const childGoals = Goals([
1060
+ { id: 'child-1', name: 'Child 1', priority: 'high', progress: 100 },
1061
+ { id: 'child-2', name: 'Child 2', priority: 'high', progress: 50 },
1062
+ { id: 'child-3', name: 'Child 3', priority: 'medium', progress: 25 },
1063
+ ])
1064
+
1065
+ const rolledUpProgress = rollupProgress(parentGoal, childGoals)
1066
+ expect(rolledUpProgress).toBeCloseTo(58.33, 1) // Average of 100, 50, 25
1067
+ })
1068
+
1069
+ it('should calculate weighted rollup based on priority', () => {
1070
+ const parentGoal = Goal({
1071
+ name: 'Parent Goal',
1072
+ priority: 'high',
1073
+ children: ['child-1', 'child-2'],
1074
+ })
1075
+
1076
+ const childGoals = Goals([
1077
+ { id: 'child-1', name: 'Critical Child', priority: 'critical', progress: 100, weight: 0.7 },
1078
+ { id: 'child-2', name: 'Low Child', priority: 'low', progress: 0, weight: 0.3 },
1079
+ ])
1080
+
1081
+ const weightedProgress = rollupProgress(parentGoal, childGoals, { weighted: true })
1082
+ expect(weightedProgress).toBe(70) // 100 * 0.7 + 0 * 0.3 = 70
1083
+ })
1084
+ })
1085
+
1086
+ describe('Goal relationships', () => {
1087
+ it('should support parent-child relationships', () => {
1088
+ const parent = Goal({
1089
+ name: 'Parent',
1090
+ priority: 'high',
1091
+ })
1092
+
1093
+ const child1 = Goal({
1094
+ name: 'Child 1',
1095
+ priority: 'medium',
1096
+ parentId: parent.id,
1097
+ })
1098
+
1099
+ const child2 = Goal({
1100
+ name: 'Child 2',
1101
+ priority: 'medium',
1102
+ parentId: parent.id,
1103
+ })
1104
+
1105
+ expect(child1.parentId).toBe(parent.id)
1106
+ expect(child2.parentId).toBe(parent.id)
1107
+ })
1108
+
1109
+ it('should support alignment relationships', () => {
1110
+ const strategic = Goal({
1111
+ name: 'Strategic Goal',
1112
+ priority: 'critical',
1113
+ level: 'company',
1114
+ })
1115
+
1116
+ const operational = Goal({
1117
+ name: 'Operational Goal',
1118
+ priority: 'high',
1119
+ level: 'department',
1120
+ alignedTo: [strategic.id],
1121
+ })
1122
+
1123
+ expect(operational.alignedTo).toContain(strategic.id)
1124
+ })
1125
+
1126
+ it('should support dependency relationships', () => {
1127
+ const prerequisite = Goal({
1128
+ name: 'Prerequisite Goal',
1129
+ priority: 'high',
1130
+ })
1131
+
1132
+ const dependent = Goal({
1133
+ name: 'Dependent Goal',
1134
+ priority: 'medium',
1135
+ dependencies: [prerequisite.id],
1136
+ })
1137
+
1138
+ expect(dependent.dependencies).toContain(prerequisite.id)
1139
+ })
1140
+ })
1141
+ })