@critical-path/core 0.11.0 → 0.13.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 +21 -0
- package/README.md +39 -0
- package/dist/deliverables.test.d.ts +2 -0
- package/dist/deliverables.test.d.ts.map +1 -0
- package/dist/deliverables.test.js +232 -0
- package/dist/deliverables.test.js.map +1 -0
- package/dist/domain/entities.d.ts +25 -1
- package/dist/domain/entities.d.ts.map +1 -1
- package/dist/domain/entities.js +153 -0
- package/dist/domain/entities.js.map +1 -1
- package/dist/domain/events.d.ts +31 -3
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/engine/index.d.ts +9 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +173 -2
- package/dist/engine/index.js.map +1 -1
- package/dist/index.test.js +28 -0
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +6 -1
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +41 -0
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/index.d.ts +15 -2
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +38 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +7 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +103 -6
- package/dist/store/sqlite.js.map +1 -1
- package/dist/types/index.d.ts +41 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/workflow.d.ts +2 -0
- package/dist/utils/workflow.d.ts.map +1 -1
- package/dist/utils/workflow.js +64 -0
- package/dist/utils/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/deliverables.test.ts +284 -0
- package/src/domain/entities.ts +185 -2
- package/src/domain/events.ts +28 -3
- package/src/engine/index.ts +197 -2
- package/src/index.test.ts +33 -0
- package/src/store/firebase.ts +47 -1
- package/src/store/index.ts +54 -1
- package/src/store/sqlite.ts +146 -5
- package/src/types/index.ts +60 -1
- package/src/utils/workflow.ts +77 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/sqlite.ts
CHANGED
|
@@ -12,7 +12,9 @@ import type {
|
|
|
12
12
|
Activity,
|
|
13
13
|
Webhook,
|
|
14
14
|
TaskDependency,
|
|
15
|
-
Workflow
|
|
15
|
+
Workflow,
|
|
16
|
+
Deliverable,
|
|
17
|
+
CreateDeliverableInput
|
|
16
18
|
} from '../types/index.js';
|
|
17
19
|
import { generateProjectKey } from '../utils/key.js';
|
|
18
20
|
|
|
@@ -111,11 +113,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
111
113
|
priority TEXT NOT NULL,
|
|
112
114
|
taskType TEXT,
|
|
113
115
|
assigneeId TEXT,
|
|
116
|
+
assignees TEXT,
|
|
114
117
|
reporterId TEXT,
|
|
115
118
|
reviewerId TEXT,
|
|
116
119
|
iterationId TEXT,
|
|
117
120
|
teamId TEXT,
|
|
118
121
|
containerId TEXT,
|
|
122
|
+
deliverableId TEXT,
|
|
119
123
|
plannedStartDate TEXT,
|
|
120
124
|
actualStartDate TEXT,
|
|
121
125
|
actualEndDate TEXT,
|
|
@@ -135,6 +139,24 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
135
139
|
updatedAt TEXT NOT NULL
|
|
136
140
|
);
|
|
137
141
|
|
|
142
|
+
CREATE TABLE IF NOT EXISTS deliverables (
|
|
143
|
+
id TEXT PRIMARY KEY,
|
|
144
|
+
projectId TEXT NOT NULL,
|
|
145
|
+
title TEXT NOT NULL,
|
|
146
|
+
description TEXT,
|
|
147
|
+
status TEXT NOT NULL,
|
|
148
|
+
format TEXT,
|
|
149
|
+
specs TEXT,
|
|
150
|
+
leadId TEXT,
|
|
151
|
+
reviewerId TEXT,
|
|
152
|
+
dueDate TEXT,
|
|
153
|
+
deliveredAt TEXT,
|
|
154
|
+
outputUrls TEXT,
|
|
155
|
+
customFields TEXT,
|
|
156
|
+
createdAt TEXT NOT NULL,
|
|
157
|
+
updatedAt TEXT NOT NULL
|
|
158
|
+
);
|
|
159
|
+
|
|
138
160
|
CREATE TABLE IF NOT EXISTS iterations (
|
|
139
161
|
id TEXT PRIMARY KEY,
|
|
140
162
|
projectId TEXT NOT NULL,
|
|
@@ -226,6 +248,18 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
226
248
|
} catch {
|
|
227
249
|
// Column may already exist
|
|
228
250
|
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
this.db.exec('ALTER TABLE tasks ADD COLUMN assignees TEXT');
|
|
254
|
+
} catch {
|
|
255
|
+
// Column may already exist
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
this.db.exec('ALTER TABLE tasks ADD COLUMN deliverableId TEXT');
|
|
260
|
+
} catch {
|
|
261
|
+
// Column may already exist
|
|
262
|
+
}
|
|
229
263
|
}
|
|
230
264
|
|
|
231
265
|
// --- Projects ---
|
|
@@ -503,6 +537,98 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
503
537
|
return (result.changes ?? 0) > 0;
|
|
504
538
|
}
|
|
505
539
|
|
|
540
|
+
// --- Deliverables ---
|
|
541
|
+
async getDeliverables(projectId: string): Promise<Deliverable[]> {
|
|
542
|
+
const stmt = this.db.prepare('SELECT * FROM deliverables WHERE projectId = ?');
|
|
543
|
+
const rows = stmt.all(projectId) as any[];
|
|
544
|
+
return rows.map((r) => this.mapDeliverable(r));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async getDeliverable(id: string): Promise<Deliverable | null> {
|
|
548
|
+
const stmt = this.db.prepare('SELECT * FROM deliverables WHERE id = ?');
|
|
549
|
+
const row = stmt.get(id) as any;
|
|
550
|
+
return row ? this.mapDeliverable(row) : null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async createDeliverable(deliverable: CreateDeliverableInput): Promise<Deliverable> {
|
|
554
|
+
const id = `deliv_${Math.random().toString(36).substring(2, 9)}`;
|
|
555
|
+
const now = new Date().toISOString();
|
|
556
|
+
const newDeliverable: Deliverable = {
|
|
557
|
+
...deliverable,
|
|
558
|
+
id,
|
|
559
|
+
status: deliverable.status || 'planned',
|
|
560
|
+
outputUrls: deliverable.outputUrls ? [...deliverable.outputUrls] : [],
|
|
561
|
+
customFields: deliverable.customFields ? { ...deliverable.customFields } : {},
|
|
562
|
+
createdAt: now,
|
|
563
|
+
updatedAt: now
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const stmt = this.db.prepare(`
|
|
567
|
+
INSERT INTO deliverables (
|
|
568
|
+
id, projectId, title, description, status, format, specs, leadId, reviewerId,
|
|
569
|
+
dueDate, deliveredAt, outputUrls, customFields, createdAt, updatedAt
|
|
570
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
571
|
+
`);
|
|
572
|
+
stmt.run(
|
|
573
|
+
newDeliverable.id,
|
|
574
|
+
newDeliverable.projectId,
|
|
575
|
+
newDeliverable.title,
|
|
576
|
+
newDeliverable.description || null,
|
|
577
|
+
newDeliverable.status,
|
|
578
|
+
newDeliverable.format || null,
|
|
579
|
+
newDeliverable.specs ? JSON.stringify(newDeliverable.specs) : null,
|
|
580
|
+
newDeliverable.leadId || null,
|
|
581
|
+
newDeliverable.reviewerId || null,
|
|
582
|
+
newDeliverable.dueDate || null,
|
|
583
|
+
newDeliverable.deliveredAt || null,
|
|
584
|
+
JSON.stringify(newDeliverable.outputUrls || []),
|
|
585
|
+
JSON.stringify(newDeliverable.customFields || {}),
|
|
586
|
+
newDeliverable.createdAt,
|
|
587
|
+
newDeliverable.updatedAt
|
|
588
|
+
);
|
|
589
|
+
return newDeliverable;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async updateDeliverable(id: string, updates: Partial<Deliverable>): Promise<Deliverable | null> {
|
|
593
|
+
const existing = await this.getDeliverable(id);
|
|
594
|
+
if (!existing) return null;
|
|
595
|
+
|
|
596
|
+
const updated: Deliverable = {
|
|
597
|
+
...existing,
|
|
598
|
+
...updates,
|
|
599
|
+
updatedAt: new Date().toISOString()
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const stmt = this.db.prepare(`
|
|
603
|
+
UPDATE deliverables SET
|
|
604
|
+
title = ?, description = ?, status = ?, format = ?, specs = ?, leadId = ?, reviewerId = ?,
|
|
605
|
+
dueDate = ?, deliveredAt = ?, outputUrls = ?, customFields = ?, updatedAt = ?
|
|
606
|
+
WHERE id = ?
|
|
607
|
+
`);
|
|
608
|
+
stmt.run(
|
|
609
|
+
updated.title,
|
|
610
|
+
updated.description || null,
|
|
611
|
+
updated.status,
|
|
612
|
+
updated.format || null,
|
|
613
|
+
updated.specs ? JSON.stringify(updated.specs) : null,
|
|
614
|
+
updated.leadId || null,
|
|
615
|
+
updated.reviewerId || null,
|
|
616
|
+
updated.dueDate || null,
|
|
617
|
+
updated.deliveredAt || null,
|
|
618
|
+
JSON.stringify(updated.outputUrls || []),
|
|
619
|
+
JSON.stringify(updated.customFields || {}),
|
|
620
|
+
updated.updatedAt,
|
|
621
|
+
id
|
|
622
|
+
);
|
|
623
|
+
return updated;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async deleteDeliverable(id: string): Promise<boolean> {
|
|
627
|
+
const stmt = this.db.prepare('DELETE FROM deliverables WHERE id = ?');
|
|
628
|
+
const result = stmt.run(id);
|
|
629
|
+
return (result.changes ?? 0) > 0;
|
|
630
|
+
}
|
|
631
|
+
|
|
506
632
|
// --- Tasks ---
|
|
507
633
|
async getTasks(projectId?: string): Promise<Task[]> {
|
|
508
634
|
let rows: any[];
|
|
@@ -529,12 +655,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
529
655
|
|
|
530
656
|
const stmt = this.db.prepare(`
|
|
531
657
|
INSERT INTO tasks (
|
|
532
|
-
id, projectId, title, description, status, priority, taskType, assigneeId, reporterId,
|
|
533
|
-
reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
|
|
658
|
+
id, projectId, title, description, status, priority, taskType, assigneeId, assignees, reporterId,
|
|
659
|
+
reviewerId, iterationId, teamId, containerId, deliverableId, plannedStartDate, actualStartDate, actualEndDate,
|
|
534
660
|
dueDate, estimatedHours, loggedHours, actualHours, billableHours,
|
|
535
661
|
estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
|
|
536
662
|
tags, customFields, parentId, createdAt, updatedAt
|
|
537
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
663
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
538
664
|
`);
|
|
539
665
|
stmt.run(
|
|
540
666
|
newTask.id,
|
|
@@ -545,11 +671,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
545
671
|
newTask.priority,
|
|
546
672
|
newTask.taskType || null,
|
|
547
673
|
newTask.assigneeId || null,
|
|
674
|
+
newTask.assignees ? JSON.stringify(newTask.assignees) : null,
|
|
548
675
|
newTask.reporterId || null,
|
|
549
676
|
newTask.reviewerId || null,
|
|
550
677
|
newTask.iterationId || null,
|
|
551
678
|
newTask.teamId || null,
|
|
552
679
|
newTask.containerId || null,
|
|
680
|
+
newTask.deliverableId || null,
|
|
553
681
|
newTask.plannedStartDate || null,
|
|
554
682
|
newTask.actualStartDate || null,
|
|
555
683
|
newTask.actualEndDate || null,
|
|
@@ -584,7 +712,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
584
712
|
const stmt = this.db.prepare(`
|
|
585
713
|
UPDATE tasks SET
|
|
586
714
|
projectId = ?, title = ?, description = ?, status = ?, priority = ?, taskType = ?,
|
|
587
|
-
assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
|
|
715
|
+
assigneeId = ?, assignees = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?, deliverableId = ?,
|
|
588
716
|
plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
|
|
589
717
|
estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
|
|
590
718
|
estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
|
|
@@ -599,11 +727,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
599
727
|
updated.priority,
|
|
600
728
|
updated.taskType || null,
|
|
601
729
|
updated.assigneeId || null,
|
|
730
|
+
updated.assignees ? JSON.stringify(updated.assignees) : null,
|
|
602
731
|
updated.reporterId || null,
|
|
603
732
|
updated.reviewerId || null,
|
|
604
733
|
updated.iterationId || null,
|
|
605
734
|
updated.teamId || null,
|
|
606
735
|
updated.containerId || null,
|
|
736
|
+
updated.deliverableId || null,
|
|
607
737
|
updated.plannedStartDate || null,
|
|
608
738
|
updated.actualStartDate || null,
|
|
609
739
|
updated.actualEndDate || null,
|
|
@@ -1059,7 +1189,18 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1059
1189
|
return {
|
|
1060
1190
|
...row,
|
|
1061
1191
|
taskType: row.taskType || undefined,
|
|
1192
|
+
deliverableId: row.deliverableId || undefined,
|
|
1062
1193
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
1194
|
+
assignees: row.assignees ? JSON.parse(row.assignees) : undefined,
|
|
1195
|
+
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private mapDeliverable(row: any): Deliverable {
|
|
1200
|
+
return {
|
|
1201
|
+
...row,
|
|
1202
|
+
specs: row.specs ? JSON.parse(row.specs) : undefined,
|
|
1203
|
+
outputUrls: row.outputUrls ? JSON.parse(row.outputUrls) : [],
|
|
1063
1204
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1064
1205
|
};
|
|
1065
1206
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -127,6 +127,14 @@ export interface TaskTodoItem {
|
|
|
127
127
|
completedAt?: string;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export interface TaskAssignee {
|
|
131
|
+
id: string;
|
|
132
|
+
name?: string;
|
|
133
|
+
role?: string;
|
|
134
|
+
type?: 'user' | 'agent' | 'team';
|
|
135
|
+
avatarUrl?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
export interface Task {
|
|
131
139
|
id: string;
|
|
132
140
|
projectId: string;
|
|
@@ -137,11 +145,13 @@ export interface Task {
|
|
|
137
145
|
priority: Priority;
|
|
138
146
|
taskType?: string; // e.g. "bug", "feature", "task", "epic"
|
|
139
147
|
assigneeId?: string;
|
|
148
|
+
assignees?: TaskAssignee[];
|
|
140
149
|
reporterId?: string;
|
|
141
150
|
reviewerId?: string;
|
|
142
151
|
iterationId?: string;
|
|
143
152
|
teamId?: string;
|
|
144
153
|
containerId?: string;
|
|
154
|
+
deliverableId?: string; // Direct link to parent deliverable
|
|
145
155
|
plannedStartDate?: string;
|
|
146
156
|
actualStartDate?: string;
|
|
147
157
|
actualEndDate?: string;
|
|
@@ -169,6 +179,50 @@ export type CreateTaskInput = Omit<Task, 'id' | 'createdAt' | 'updatedAt' | 'sta
|
|
|
169
179
|
priority?: Priority;
|
|
170
180
|
};
|
|
171
181
|
|
|
182
|
+
export type DeliverableStatus =
|
|
183
|
+
| 'draft'
|
|
184
|
+
| 'planned'
|
|
185
|
+
| 'in_production'
|
|
186
|
+
| 'internal_review'
|
|
187
|
+
| 'client_review'
|
|
188
|
+
| 'revision_requested'
|
|
189
|
+
| 'approved'
|
|
190
|
+
| 'delivered'
|
|
191
|
+
| 'canceled'
|
|
192
|
+
| (string & {});
|
|
193
|
+
|
|
194
|
+
export interface Deliverable {
|
|
195
|
+
id: string;
|
|
196
|
+
projectId: string;
|
|
197
|
+
title: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
status: DeliverableStatus;
|
|
200
|
+
format?: string; // e.g., '16:9 4K ProRes', 'Figma', 'PDF'
|
|
201
|
+
specs?: Record<string, unknown>; // Technical specifications (resolution, frame rate, etc.)
|
|
202
|
+
leadId?: string; // Creative director or lead creator
|
|
203
|
+
reviewerId?: string; // Approver or client reviewer
|
|
204
|
+
dueDate?: string;
|
|
205
|
+
deliveredAt?: string;
|
|
206
|
+
outputUrls?: string[]; // Links to final rendered assets, exports, or storage keys
|
|
207
|
+
customFields?: Record<string, unknown>;
|
|
208
|
+
createdAt: string;
|
|
209
|
+
updatedAt: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface DeliverableSummary {
|
|
213
|
+
deliverable: Deliverable;
|
|
214
|
+
totalTasks: number;
|
|
215
|
+
completedTasks: number;
|
|
216
|
+
activeTasks: number;
|
|
217
|
+
progressPercentage: number;
|
|
218
|
+
estimatedHours: number;
|
|
219
|
+
loggedHours: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export type CreateDeliverableInput = Omit<Deliverable, 'id' | 'createdAt' | 'updatedAt' | 'status'> & {
|
|
223
|
+
status?: DeliverableStatus;
|
|
224
|
+
};
|
|
225
|
+
|
|
172
226
|
export type CreateProjectInput = Omit<Project, 'id' | 'createdAt' | 'updatedAt'>;
|
|
173
227
|
|
|
174
228
|
export interface Iteration {
|
|
@@ -309,7 +363,11 @@ export type WebhookEvent =
|
|
|
309
363
|
| 'container.created'
|
|
310
364
|
| 'workflow.created'
|
|
311
365
|
| 'workflow.updated'
|
|
312
|
-
| 'workflow.deleted'
|
|
366
|
+
| 'workflow.deleted'
|
|
367
|
+
| 'deliverable.created'
|
|
368
|
+
| 'deliverable.updated'
|
|
369
|
+
| 'deliverable.deleted'
|
|
370
|
+
| 'deliverable.status_changed';
|
|
313
371
|
|
|
314
372
|
export interface PluginHooks {
|
|
315
373
|
beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
|
|
@@ -343,5 +401,6 @@ export interface CriticalPathConfig {
|
|
|
343
401
|
teams?: Team[];
|
|
344
402
|
containers?: TaskContainer[];
|
|
345
403
|
workflows?: Workflow[];
|
|
404
|
+
deliverables?: Deliverable[];
|
|
346
405
|
};
|
|
347
406
|
}
|
package/src/utils/workflow.ts
CHANGED
|
@@ -117,6 +117,44 @@ export const DEFAULT_VFX_WORKFLOW: Workflow = {
|
|
|
117
117
|
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
export const DEFAULT_CREATIVE_WORKFLOW: Workflow = {
|
|
121
|
+
id: 'wf_creative_default',
|
|
122
|
+
name: 'Creative Production Workflow',
|
|
123
|
+
description: 'Production pipeline for creative deliverables from briefing and concepts to internal review, client feedback, and final delivery.',
|
|
124
|
+
statuses: [
|
|
125
|
+
{ key: 'briefing', label: 'Briefing & Scoping', completionState: 'not_done', executionState: 'inactive' },
|
|
126
|
+
{ key: 'concept', label: 'Concept & Storyboarding', completionState: 'not_done', executionState: 'active' },
|
|
127
|
+
{ key: 'in_production', label: 'In Production', completionState: 'not_done', executionState: 'active' },
|
|
128
|
+
{ key: 'internal_review', label: 'Internal Review', completionState: 'not_done', executionState: 'active' },
|
|
129
|
+
{ key: 'client_review', label: 'Client Review', completionState: 'not_done', executionState: 'active' },
|
|
130
|
+
{ key: 'revision_requested', label: 'Revision Requested', completionState: 'not_done', executionState: 'active' },
|
|
131
|
+
{ key: 'approved', label: 'Approved', completionState: 'done', executionState: 'inactive' },
|
|
132
|
+
{ key: 'delivered', label: 'Delivered', completionState: 'done', executionState: 'inactive' },
|
|
133
|
+
DEFAULT_STATUS_DEFINITIONS.canceled
|
|
134
|
+
],
|
|
135
|
+
transitions: [
|
|
136
|
+
{ name: 'Start Concepts', fromStatusKey: 'briefing', toStatusKey: 'concept' },
|
|
137
|
+
{ name: 'Approve Concept for Production', fromStatusKey: 'concept', toStatusKey: 'in_production' },
|
|
138
|
+
{ name: 'Submit for Internal Review', fromStatusKey: 'in_production', toStatusKey: 'internal_review' },
|
|
139
|
+
{ name: 'Submit for Client Review', fromStatusKey: 'internal_review', toStatusKey: 'client_review' },
|
|
140
|
+
{ name: 'Request Revisions (Internal)', fromStatusKey: 'internal_review', toStatusKey: 'revision_requested' },
|
|
141
|
+
{ name: 'Request Revisions (Client)', fromStatusKey: 'client_review', toStatusKey: 'revision_requested' },
|
|
142
|
+
{ name: 'Address Revisions', fromStatusKey: 'revision_requested', toStatusKey: 'in_production' },
|
|
143
|
+
{ name: 'Approve Work', fromStatusKey: 'client_review', toStatusKey: 'approved' },
|
|
144
|
+
{ name: 'Deliver Final Assets', fromStatusKey: 'approved', toStatusKey: 'delivered' },
|
|
145
|
+
{ name: 'Cancel', fromStatusKey: '*', toStatusKey: 'canceled' }
|
|
146
|
+
],
|
|
147
|
+
taskTypes: [
|
|
148
|
+
{ key: 'deliverable', label: 'Deliverable', description: 'Finished asset, cutdown, or packaged export', icon: 'package' },
|
|
149
|
+
{ key: 'creative_task', label: 'Creative Task', description: 'Production task (e.g. Design, Copy, Editing, Color)', icon: 'pen-tool' },
|
|
150
|
+
{ key: 'review', label: 'Review Gate', description: 'Internal or external client sign-off', icon: 'check-circle' }
|
|
151
|
+
],
|
|
152
|
+
defaultStatusKey: 'briefing',
|
|
153
|
+
isDefault: false,
|
|
154
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
155
|
+
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
156
|
+
};
|
|
157
|
+
|
|
120
158
|
export function validateTransition(
|
|
121
159
|
workflow: Workflow | undefined,
|
|
122
160
|
fromStatus: string,
|
|
@@ -162,3 +200,42 @@ export function getAllowedNextStatuses(
|
|
|
162
200
|
|
|
163
201
|
return Array.from(allowed);
|
|
164
202
|
}
|
|
203
|
+
|
|
204
|
+
export function getAllowedPreviousStatuses(
|
|
205
|
+
workflow: Workflow | undefined,
|
|
206
|
+
currentStatus: string
|
|
207
|
+
): string[] {
|
|
208
|
+
if (!workflow || !workflow.statuses || workflow.statuses.length === 0) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const allStatusKeys = workflow.statuses.map((s) => s.key);
|
|
213
|
+
const currentIndex = allStatusKeys.indexOf(currentStatus);
|
|
214
|
+
if (currentIndex <= 0) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const previousStatusKeys = allStatusKeys.slice(0, currentIndex);
|
|
219
|
+
|
|
220
|
+
if (workflow.transitions && workflow.transitions.length > 0) {
|
|
221
|
+
const matches = workflow.transitions.filter(
|
|
222
|
+
(t) => t.fromStatusKey === '*' || t.fromStatusKey === currentStatus
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const allowed = new Set<string>();
|
|
226
|
+
for (const t of matches) {
|
|
227
|
+
if (t.toStatusKey === '*') {
|
|
228
|
+
return previousStatusKeys;
|
|
229
|
+
}
|
|
230
|
+
if (previousStatusKeys.includes(t.toStatusKey)) {
|
|
231
|
+
allowed.add(t.toStatusKey);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (allowed.size > 0) {
|
|
236
|
+
return Array.from(allowed);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return [allStatusKeys[currentIndex - 1]];
|
|
241
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
|