business-as-code 0.2.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +17 -0
- package/IMPLEMENTATION.md +226 -0
- package/README.md +1133 -193
- package/dist/business.d.ts +62 -0
- package/dist/business.d.ts.map +1 -0
- package/dist/business.js +109 -0
- package/dist/business.js.map +1 -0
- package/dist/dollar.d.ts +60 -0
- package/dist/dollar.d.ts.map +1 -0
- package/dist/dollar.js +107 -0
- package/dist/dollar.js.map +1 -0
- package/dist/entities/assets.d.ts +21 -0
- package/dist/entities/assets.d.ts.map +1 -0
- package/dist/entities/assets.js +323 -0
- package/dist/entities/assets.js.map +1 -0
- package/dist/entities/business.d.ts +36 -0
- package/dist/entities/business.d.ts.map +1 -0
- package/dist/entities/business.js +370 -0
- package/dist/entities/business.js.map +1 -0
- package/dist/entities/communication.d.ts +21 -0
- package/dist/entities/communication.d.ts.map +1 -0
- package/dist/entities/communication.js +255 -0
- package/dist/entities/communication.js.map +1 -0
- package/dist/entities/customers.d.ts +58 -0
- package/dist/entities/customers.d.ts.map +1 -0
- package/dist/entities/customers.js +989 -0
- package/dist/entities/customers.js.map +1 -0
- package/dist/entities/financials.d.ts +59 -0
- package/dist/entities/financials.d.ts.map +1 -0
- package/dist/entities/financials.js +932 -0
- package/dist/entities/financials.js.map +1 -0
- package/dist/entities/goals.d.ts +58 -0
- package/dist/entities/goals.d.ts.map +1 -0
- package/dist/entities/goals.js +800 -0
- package/dist/entities/goals.js.map +1 -0
- package/dist/entities/index.d.ts +299 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +198 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/legal.d.ts +21 -0
- package/dist/entities/legal.d.ts.map +1 -0
- package/dist/entities/legal.js +301 -0
- package/dist/entities/legal.js.map +1 -0
- package/dist/entities/market.d.ts +21 -0
- package/dist/entities/market.d.ts.map +1 -0
- package/dist/entities/market.js +301 -0
- package/dist/entities/market.js.map +1 -0
- package/dist/entities/marketing.d.ts +67 -0
- package/dist/entities/marketing.d.ts.map +1 -0
- package/dist/entities/marketing.js +1157 -0
- package/dist/entities/marketing.js.map +1 -0
- package/dist/entities/offerings.d.ts +51 -0
- package/dist/entities/offerings.d.ts.map +1 -0
- package/dist/entities/offerings.js +727 -0
- package/dist/entities/offerings.js.map +1 -0
- package/dist/entities/operations.d.ts +58 -0
- package/dist/entities/operations.d.ts.map +1 -0
- package/dist/entities/operations.js +787 -0
- package/dist/entities/operations.js.map +1 -0
- package/dist/entities/organization.d.ts +57 -0
- package/dist/entities/organization.d.ts.map +1 -0
- package/dist/entities/organization.js +807 -0
- package/dist/entities/organization.js.map +1 -0
- package/dist/entities/partnerships.d.ts +21 -0
- package/dist/entities/partnerships.d.ts.map +1 -0
- package/dist/entities/partnerships.js +300 -0
- package/dist/entities/partnerships.js.map +1 -0
- package/dist/entities/planning.d.ts +87 -0
- package/dist/entities/planning.d.ts.map +1 -0
- package/dist/entities/planning.js +271 -0
- package/dist/entities/planning.js.map +1 -0
- package/dist/entities/projects.d.ts +25 -0
- package/dist/entities/projects.d.ts.map +1 -0
- package/dist/entities/projects.js +349 -0
- package/dist/entities/projects.js.map +1 -0
- package/dist/entities/risk.d.ts +21 -0
- package/dist/entities/risk.d.ts.map +1 -0
- package/dist/entities/risk.js +293 -0
- package/dist/entities/risk.js.map +1 -0
- package/dist/entities/sales.d.ts +72 -0
- package/dist/entities/sales.d.ts.map +1 -0
- package/dist/entities/sales.js +1248 -0
- package/dist/entities/sales.js.map +1 -0
- package/dist/financials.d.ts +130 -0
- package/dist/financials.d.ts.map +1 -0
- package/dist/financials.js +297 -0
- package/dist/financials.js.map +1 -0
- package/dist/goals.d.ts +87 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +215 -0
- package/dist/goals.js.map +1 -0
- package/dist/index.d.ts +97 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -1079
- package/dist/index.js.map +1 -1
- package/dist/kpis.d.ts +118 -0
- package/dist/kpis.d.ts.map +1 -0
- package/dist/kpis.js +232 -0
- package/dist/kpis.js.map +1 -0
- package/dist/metrics.d.ts +448 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +325 -0
- package/dist/metrics.js.map +1 -0
- package/dist/okrs.d.ts +123 -0
- package/dist/okrs.d.ts.map +1 -0
- package/dist/okrs.js +269 -0
- package/dist/okrs.js.map +1 -0
- package/dist/organization.d.ts +585 -0
- package/dist/organization.d.ts.map +1 -0
- package/dist/organization.js +173 -0
- package/dist/organization.js.map +1 -0
- package/dist/process.d.ts +112 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +241 -0
- package/dist/process.js.map +1 -0
- package/dist/product.d.ts +85 -0
- package/dist/product.d.ts.map +1 -0
- package/dist/product.js +145 -0
- package/dist/product.js.map +1 -0
- package/dist/queries.d.ts +304 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +415 -0
- package/dist/queries.js.map +1 -0
- package/dist/roles.d.ts +340 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +255 -0
- package/dist/roles.js.map +1 -0
- package/dist/service.d.ts +61 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +140 -0
- package/dist/service.js.map +1 -0
- package/dist/types.d.ts +459 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/vision.d.ts +38 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +68 -0
- package/dist/vision.js.map +1 -0
- package/dist/workflow.d.ts +115 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +247 -0
- package/dist/workflow.js.map +1 -0
- package/examples/basic-usage.ts +307 -0
- package/package.json +19 -60
- package/src/business.ts +121 -0
- package/src/dollar.ts +132 -0
- package/src/entities/assets.ts +332 -0
- package/src/entities/business.ts +406 -0
- package/src/entities/communication.ts +264 -0
- package/src/entities/customers.ts +1072 -0
- package/src/entities/financials.ts +1011 -0
- package/src/entities/goals.ts +871 -0
- package/src/entities/index.ts +383 -0
- package/src/entities/legal.ts +310 -0
- package/src/entities/market.ts +310 -0
- package/src/entities/marketing.ts +1249 -0
- package/src/entities/offerings.ts +789 -0
- package/src/entities/operations.ts +861 -0
- package/src/entities/organization.ts +876 -0
- package/src/entities/partnerships.ts +309 -0
- package/src/entities/planning.ts +307 -0
- package/src/entities/projects.ts +360 -0
- package/src/entities/risk.ts +302 -0
- package/src/entities/sales.ts +1352 -0
- package/src/financials.ts +352 -0
- package/src/goals.ts +250 -0
- package/src/index.test.ts +336 -0
- package/src/index.ts +530 -0
- package/src/kpis.ts +275 -0
- package/src/metrics.ts +825 -0
- package/src/okrs.ts +325 -0
- package/src/organization.ts +909 -0
- package/src/process.ts +272 -0
- package/src/product.ts +178 -0
- package/src/queries.ts +767 -0
- package/src/roles.ts +686 -0
- package/src/service.ts +164 -0
- package/src/types.ts +493 -0
- package/src/vision.ts +88 -0
- package/src/workflow.ts +280 -0
- package/tsconfig.json +9 -0
- package/dist/loaders/index.d.ts +0 -174
- package/dist/loaders/index.js +0 -366
- package/dist/loaders/index.js.map +0 -1
- package/dist/schema/index.d.ts +0 -146
- package/dist/schema/index.js +0 -716
- package/dist/schema/index.js.map +0 -1
- package/dist/types-CJ9eGS_C.d.ts +0 -86
package/src/process.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business process definition and management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ProcessDefinition, ProcessStep, ProcessMetric } from './types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Define a business process with steps, inputs, outputs, and metrics
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const process = Process({
|
|
13
|
+
* name: 'Customer Onboarding',
|
|
14
|
+
* description: 'Process for onboarding new customers',
|
|
15
|
+
* category: 'core',
|
|
16
|
+
* owner: 'Customer Success Team',
|
|
17
|
+
* steps: [
|
|
18
|
+
* {
|
|
19
|
+
* order: 1,
|
|
20
|
+
* name: 'Welcome Email',
|
|
21
|
+
* description: 'Send personalized welcome email',
|
|
22
|
+
* responsible: 'CS Manager',
|
|
23
|
+
* duration: '5 minutes',
|
|
24
|
+
* automationLevel: 'automated',
|
|
25
|
+
* },
|
|
26
|
+
* {
|
|
27
|
+
* order: 2,
|
|
28
|
+
* name: 'Initial Setup Call',
|
|
29
|
+
* description: 'Schedule and conduct setup call',
|
|
30
|
+
* responsible: 'CS Rep',
|
|
31
|
+
* duration: '30 minutes',
|
|
32
|
+
* automationLevel: 'manual',
|
|
33
|
+
* },
|
|
34
|
+
* {
|
|
35
|
+
* order: 3,
|
|
36
|
+
* name: 'Account Configuration',
|
|
37
|
+
* description: 'Configure customer account settings',
|
|
38
|
+
* responsible: 'CS Rep',
|
|
39
|
+
* duration: '15 minutes',
|
|
40
|
+
* automationLevel: 'semi-automated',
|
|
41
|
+
* },
|
|
42
|
+
* ],
|
|
43
|
+
* inputs: ['Customer Information', 'Subscription Plan'],
|
|
44
|
+
* outputs: ['Configured Account', 'Training Materials'],
|
|
45
|
+
* metrics: [
|
|
46
|
+
* {
|
|
47
|
+
* name: 'Time to First Value',
|
|
48
|
+
* description: 'Time from signup to first successful use',
|
|
49
|
+
* target: 24,
|
|
50
|
+
* unit: 'hours',
|
|
51
|
+
* },
|
|
52
|
+
* {
|
|
53
|
+
* name: 'Onboarding Completion Rate',
|
|
54
|
+
* description: 'Percentage of customers completing onboarding',
|
|
55
|
+
* target: 90,
|
|
56
|
+
* unit: 'percent',
|
|
57
|
+
* },
|
|
58
|
+
* ],
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function Process(definition: ProcessDefinition): ProcessDefinition {
|
|
63
|
+
if (!definition.name) {
|
|
64
|
+
throw new Error('Process name is required')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...definition,
|
|
69
|
+
category: definition.category || 'support',
|
|
70
|
+
steps: definition.steps || [],
|
|
71
|
+
inputs: definition.inputs || [],
|
|
72
|
+
outputs: definition.outputs || [],
|
|
73
|
+
metrics: definition.metrics || [],
|
|
74
|
+
metadata: definition.metadata || {},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get process steps in order
|
|
80
|
+
*/
|
|
81
|
+
export function getStepsInOrder(process: ProcessDefinition): ProcessStep[] {
|
|
82
|
+
return [...(process.steps || [])].sort((a, b) => a.order - b.order)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get steps by automation level
|
|
87
|
+
*/
|
|
88
|
+
export function getStepsByAutomationLevel(
|
|
89
|
+
process: ProcessDefinition,
|
|
90
|
+
level: ProcessStep['automationLevel']
|
|
91
|
+
): ProcessStep[] {
|
|
92
|
+
return process.steps?.filter(step => step.automationLevel === level) || []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Calculate total process duration in minutes
|
|
97
|
+
*/
|
|
98
|
+
export function calculateTotalDuration(process: ProcessDefinition): number {
|
|
99
|
+
return (
|
|
100
|
+
process.steps?.reduce((total, step) => {
|
|
101
|
+
return total + parseDurationToMinutes(step.duration)
|
|
102
|
+
}, 0) || 0
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse duration string to minutes
|
|
108
|
+
*/
|
|
109
|
+
function parseDurationToMinutes(duration?: string): number {
|
|
110
|
+
if (!duration) return 0
|
|
111
|
+
|
|
112
|
+
const lower = duration.toLowerCase()
|
|
113
|
+
const match = lower.match(/(\d+)\s*(minute|minutes|min|hour|hours|hr|day|days|week|weeks)/)
|
|
114
|
+
if (!match) return 0
|
|
115
|
+
|
|
116
|
+
const value = parseInt(match[1] || '0', 10)
|
|
117
|
+
const unit = match[2]
|
|
118
|
+
|
|
119
|
+
switch (unit) {
|
|
120
|
+
case 'minute':
|
|
121
|
+
case 'minutes':
|
|
122
|
+
case 'min':
|
|
123
|
+
return value
|
|
124
|
+
case 'hour':
|
|
125
|
+
case 'hours':
|
|
126
|
+
case 'hr':
|
|
127
|
+
return value * 60
|
|
128
|
+
case 'day':
|
|
129
|
+
case 'days':
|
|
130
|
+
return value * 60 * 24
|
|
131
|
+
case 'week':
|
|
132
|
+
case 'weeks':
|
|
133
|
+
return value * 60 * 24 * 7
|
|
134
|
+
default:
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Format minutes to human-readable duration
|
|
141
|
+
*/
|
|
142
|
+
export function formatDuration(minutes: number): string {
|
|
143
|
+
if (minutes < 60) {
|
|
144
|
+
return `${minutes} minutes`
|
|
145
|
+
} else if (minutes < 60 * 24) {
|
|
146
|
+
const hours = Math.floor(minutes / 60)
|
|
147
|
+
const mins = minutes % 60
|
|
148
|
+
return mins > 0 ? `${hours} hours ${mins} minutes` : `${hours} hours`
|
|
149
|
+
} else {
|
|
150
|
+
const days = Math.floor(minutes / (60 * 24))
|
|
151
|
+
const hours = Math.floor((minutes % (60 * 24)) / 60)
|
|
152
|
+
return hours > 0 ? `${days} days ${hours} hours` : `${days} days`
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Calculate automation percentage
|
|
158
|
+
*/
|
|
159
|
+
export function calculateAutomationPercentage(process: ProcessDefinition): number {
|
|
160
|
+
if (!process.steps || process.steps.length === 0) return 0
|
|
161
|
+
|
|
162
|
+
const automatedSteps = process.steps.filter(
|
|
163
|
+
step => step.automationLevel === 'automated' || step.automationLevel === 'semi-automated'
|
|
164
|
+
).length
|
|
165
|
+
|
|
166
|
+
return (automatedSteps / process.steps.length) * 100
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get metric by name
|
|
171
|
+
*/
|
|
172
|
+
export function getMetric(process: ProcessDefinition, name: string): ProcessMetric | undefined {
|
|
173
|
+
return process.metrics?.find(m => m.name === name)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if metric meets target
|
|
178
|
+
*/
|
|
179
|
+
export function meetsTarget(metric: ProcessMetric): boolean {
|
|
180
|
+
if (metric.target === undefined || metric.current === undefined) return false
|
|
181
|
+
return metric.current >= metric.target
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Calculate metric achievement percentage
|
|
186
|
+
*/
|
|
187
|
+
export function calculateMetricAchievement(metric: ProcessMetric): number {
|
|
188
|
+
if (metric.target === undefined || metric.current === undefined) return 0
|
|
189
|
+
if (metric.target === 0) return 100
|
|
190
|
+
return (metric.current / metric.target) * 100
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Update metric current value
|
|
195
|
+
*/
|
|
196
|
+
export function updateMetric(
|
|
197
|
+
process: ProcessDefinition,
|
|
198
|
+
metricName: string,
|
|
199
|
+
currentValue: number
|
|
200
|
+
): ProcessDefinition {
|
|
201
|
+
const metrics = process.metrics?.map(m =>
|
|
202
|
+
m.name === metricName ? { ...m, current: currentValue } : m
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
...process,
|
|
207
|
+
metrics,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Add step to process
|
|
213
|
+
*/
|
|
214
|
+
export function addStep(process: ProcessDefinition, step: ProcessStep): ProcessDefinition {
|
|
215
|
+
return {
|
|
216
|
+
...process,
|
|
217
|
+
steps: [...(process.steps || []), step],
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Remove step from process
|
|
223
|
+
*/
|
|
224
|
+
export function removeStep(process: ProcessDefinition, stepOrder: number): ProcessDefinition {
|
|
225
|
+
return {
|
|
226
|
+
...process,
|
|
227
|
+
steps: process.steps?.filter(s => s.order !== stepOrder),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Validate process definition
|
|
233
|
+
*/
|
|
234
|
+
export function validateProcess(process: ProcessDefinition): { valid: boolean; errors: string[] } {
|
|
235
|
+
const errors: string[] = []
|
|
236
|
+
|
|
237
|
+
if (!process.name) {
|
|
238
|
+
errors.push('Process name is required')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (process.steps) {
|
|
242
|
+
const orders = new Set<number>()
|
|
243
|
+
for (const step of process.steps) {
|
|
244
|
+
if (!step.name) {
|
|
245
|
+
errors.push(`Step at order ${step.order} must have a name`)
|
|
246
|
+
}
|
|
247
|
+
if (orders.has(step.order)) {
|
|
248
|
+
errors.push(`Duplicate step order: ${step.order}`)
|
|
249
|
+
}
|
|
250
|
+
orders.add(step.order)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (process.metrics) {
|
|
255
|
+
for (const metric of process.metrics) {
|
|
256
|
+
if (!metric.name) {
|
|
257
|
+
errors.push('Metric must have a name')
|
|
258
|
+
}
|
|
259
|
+
if (metric.target !== undefined && metric.target < 0) {
|
|
260
|
+
errors.push(`Metric ${metric.name} target cannot be negative`)
|
|
261
|
+
}
|
|
262
|
+
if (metric.current !== undefined && metric.current < 0) {
|
|
263
|
+
errors.push(`Metric ${metric.name} current value cannot be negative`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
valid: errors.length === 0,
|
|
270
|
+
errors,
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/product.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product definition and management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ProductDefinition, RoadmapItem } from './types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Define a product with pricing, features, and roadmap
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const product = Product({
|
|
13
|
+
* name: 'Widget Pro',
|
|
14
|
+
* description: 'Enterprise-grade widget management platform',
|
|
15
|
+
* category: 'SaaS',
|
|
16
|
+
* targetSegment: 'Enterprise',
|
|
17
|
+
* valueProposition: 'Reduce widget management costs by 50%',
|
|
18
|
+
* pricingModel: 'subscription',
|
|
19
|
+
* price: 99,
|
|
20
|
+
* currency: 'USD',
|
|
21
|
+
* cogs: 20,
|
|
22
|
+
* features: [
|
|
23
|
+
* 'Unlimited widgets',
|
|
24
|
+
* 'Advanced analytics',
|
|
25
|
+
* 'API access',
|
|
26
|
+
* '24/7 support',
|
|
27
|
+
* ],
|
|
28
|
+
* roadmap: [
|
|
29
|
+
* {
|
|
30
|
+
* name: 'Mobile app',
|
|
31
|
+
* description: 'Native iOS and Android apps',
|
|
32
|
+
* targetDate: new Date('2024-09-01'),
|
|
33
|
+
* priority: 'high',
|
|
34
|
+
* status: 'in-progress',
|
|
35
|
+
* },
|
|
36
|
+
* {
|
|
37
|
+
* name: 'AI-powered insights',
|
|
38
|
+
* description: 'Automated widget optimization suggestions',
|
|
39
|
+
* targetDate: new Date('2024-12-01'),
|
|
40
|
+
* priority: 'high',
|
|
41
|
+
* status: 'planned',
|
|
42
|
+
* },
|
|
43
|
+
* ],
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function Product(definition: ProductDefinition): ProductDefinition {
|
|
48
|
+
if (!definition.name) {
|
|
49
|
+
throw new Error('Product name is required')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...definition,
|
|
54
|
+
pricingModel: definition.pricingModel || 'one-time',
|
|
55
|
+
currency: definition.currency || 'USD',
|
|
56
|
+
features: definition.features || [],
|
|
57
|
+
roadmap: definition.roadmap || [],
|
|
58
|
+
metadata: definition.metadata || {},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Calculate gross margin for a product
|
|
64
|
+
*/
|
|
65
|
+
export function calculateGrossMargin(product: ProductDefinition): number {
|
|
66
|
+
if (!product.price || !product.cogs) return 0
|
|
67
|
+
return ((product.price - product.cogs) / product.price) * 100
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calculate gross profit for a product
|
|
72
|
+
*/
|
|
73
|
+
export function calculateGrossProfit(product: ProductDefinition, unitsSold: number): number {
|
|
74
|
+
if (!product.price || !product.cogs) return 0
|
|
75
|
+
return (product.price - product.cogs) * unitsSold
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get roadmap items by status
|
|
80
|
+
*/
|
|
81
|
+
export function getRoadmapByStatus(
|
|
82
|
+
product: ProductDefinition,
|
|
83
|
+
status: RoadmapItem['status']
|
|
84
|
+
): RoadmapItem[] {
|
|
85
|
+
return product.roadmap?.filter(item => item.status === status) || []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get roadmap items by priority
|
|
90
|
+
*/
|
|
91
|
+
export function getRoadmapByPriority(
|
|
92
|
+
product: ProductDefinition,
|
|
93
|
+
priority: RoadmapItem['priority']
|
|
94
|
+
): RoadmapItem[] {
|
|
95
|
+
return product.roadmap?.filter(item => item.priority === priority) || []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get overdue roadmap items
|
|
100
|
+
*/
|
|
101
|
+
export function getOverdueRoadmapItems(product: ProductDefinition): RoadmapItem[] {
|
|
102
|
+
const now = new Date()
|
|
103
|
+
return (
|
|
104
|
+
product.roadmap?.filter(
|
|
105
|
+
item =>
|
|
106
|
+
item.targetDate &&
|
|
107
|
+
item.targetDate < now &&
|
|
108
|
+
item.status !== 'completed' &&
|
|
109
|
+
item.status !== 'cancelled'
|
|
110
|
+
) || []
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update roadmap item status
|
|
116
|
+
*/
|
|
117
|
+
export function updateRoadmapItem(
|
|
118
|
+
product: ProductDefinition,
|
|
119
|
+
itemName: string,
|
|
120
|
+
updates: Partial<RoadmapItem>
|
|
121
|
+
): ProductDefinition {
|
|
122
|
+
const roadmap = product.roadmap?.map(item =>
|
|
123
|
+
item.name === itemName ? { ...item, ...updates } : item
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...product,
|
|
128
|
+
roadmap,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add feature to product
|
|
134
|
+
*/
|
|
135
|
+
export function addFeature(product: ProductDefinition, feature: string): ProductDefinition {
|
|
136
|
+
return {
|
|
137
|
+
...product,
|
|
138
|
+
features: [...(product.features || []), feature],
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Remove feature from product
|
|
144
|
+
*/
|
|
145
|
+
export function removeFeature(product: ProductDefinition, feature: string): ProductDefinition {
|
|
146
|
+
return {
|
|
147
|
+
...product,
|
|
148
|
+
features: product.features?.filter(f => f !== feature) || [],
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate product definition
|
|
154
|
+
*/
|
|
155
|
+
export function validateProduct(product: ProductDefinition): { valid: boolean; errors: string[] } {
|
|
156
|
+
const errors: string[] = []
|
|
157
|
+
|
|
158
|
+
if (!product.name) {
|
|
159
|
+
errors.push('Product name is required')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (product.price && product.price < 0) {
|
|
163
|
+
errors.push('Product price cannot be negative')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (product.cogs && product.cogs < 0) {
|
|
167
|
+
errors.push('Product COGS cannot be negative')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (product.price && product.cogs && product.cogs > product.price) {
|
|
171
|
+
errors.push('Product COGS cannot exceed price')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
valid: errors.length === 0,
|
|
176
|
+
errors,
|
|
177
|
+
}
|
|
178
|
+
}
|