@critical-path/core 0.33.0 → 0.35.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 +1 -1
- package/dist/domain/entities.js +17 -17
- package/dist/domain/entities.js.map +1 -1
- package/dist/domain/events.d.ts +53 -2
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +7 -0
- package/dist/domain/events.js.map +1 -1
- package/dist/engine/entity-events.test.d.ts +2 -0
- package/dist/engine/entity-events.test.d.ts.map +1 -0
- package/dist/engine/entity-events.test.js +61 -0
- package/dist/engine/entity-events.test.js.map +1 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +106 -39
- package/dist/engine/index.js.map +1 -1
- package/dist/schemas/index.d.ts +6 -6
- package/dist/store/firebase.js +5 -5
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +30 -13
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.js +13 -13
- package/dist/store/sqlite.js.map +1 -1
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +15 -0
- package/dist/testing/conformance.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/entities.ts +17 -17
- package/src/domain/events.ts +49 -0
- package/src/engine/entity-events.test.ts +67 -0
- package/src/engine/index.ts +115 -39
- package/src/store/firebase.ts +5 -5
- package/src/store/index.ts +30 -13
- package/src/store/sqlite.ts +13 -13
- package/src/testing/conformance.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/engine/index.js
CHANGED
|
@@ -64,6 +64,14 @@ export class CriticalPathEngine {
|
|
|
64
64
|
Object.defineProperty(scoped, 'actor', { value: Object.freeze({ ...actor }), enumerable: true });
|
|
65
65
|
return scoped;
|
|
66
66
|
}
|
|
67
|
+
/** Publishes a domain event, filling in its id and timestamp. */
|
|
68
|
+
async publishEvent(event) {
|
|
69
|
+
await this.events.publish({
|
|
70
|
+
...event,
|
|
71
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
72
|
+
occurredAt: new Date().toISOString()
|
|
73
|
+
});
|
|
74
|
+
}
|
|
67
75
|
actorIdOr(fallback) {
|
|
68
76
|
return this.actor?.userId ?? fallback;
|
|
69
77
|
}
|
|
@@ -222,7 +230,7 @@ export class CriticalPathEngine {
|
|
|
222
230
|
const created = await this.store.createWorkflow({ ...workflow, tenantId: this.actor ? this.actor.tenantId : workflow.tenantId });
|
|
223
231
|
const now = new Date().toISOString();
|
|
224
232
|
const event = {
|
|
225
|
-
id: `evt_${
|
|
233
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
226
234
|
name: 'workflow.created',
|
|
227
235
|
aggregateId: created.id,
|
|
228
236
|
aggregateType: 'Workflow',
|
|
@@ -247,7 +255,7 @@ export class CriticalPathEngine {
|
|
|
247
255
|
if (updated) {
|
|
248
256
|
const now = new Date().toISOString();
|
|
249
257
|
const event = {
|
|
250
|
-
id: `evt_${
|
|
258
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
251
259
|
name: 'workflow.updated',
|
|
252
260
|
aggregateId: updated.id,
|
|
253
261
|
aggregateType: 'Workflow',
|
|
@@ -272,7 +280,7 @@ export class CriticalPathEngine {
|
|
|
272
280
|
if (deleted) {
|
|
273
281
|
const now = new Date().toISOString();
|
|
274
282
|
const event = {
|
|
275
|
-
id: `evt_${
|
|
283
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
276
284
|
name: 'workflow.deleted',
|
|
277
285
|
aggregateId: id,
|
|
278
286
|
aggregateType: 'Workflow',
|
|
@@ -349,7 +357,7 @@ export class CriticalPathEngine {
|
|
|
349
357
|
const created = await this.store.createProject(input);
|
|
350
358
|
const now = new Date().toISOString();
|
|
351
359
|
const event = {
|
|
352
|
-
id: `evt_${
|
|
360
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
353
361
|
name: 'project.created',
|
|
354
362
|
aggregateId: created.id,
|
|
355
363
|
aggregateType: 'Project',
|
|
@@ -378,7 +386,7 @@ export class CriticalPathEngine {
|
|
|
378
386
|
if (updated) {
|
|
379
387
|
const now = new Date().toISOString();
|
|
380
388
|
const event = {
|
|
381
|
-
id: `evt_${
|
|
389
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
382
390
|
name: 'project.updated',
|
|
383
391
|
aggregateId: updated.id,
|
|
384
392
|
aggregateType: 'Project',
|
|
@@ -424,7 +432,7 @@ export class CriticalPathEngine {
|
|
|
424
432
|
if (deleted) {
|
|
425
433
|
const now = new Date().toISOString();
|
|
426
434
|
const event = {
|
|
427
|
-
id: `evt_${
|
|
435
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
428
436
|
name: 'project.deleted',
|
|
429
437
|
aggregateId: id,
|
|
430
438
|
aggregateType: 'Project',
|
|
@@ -551,7 +559,7 @@ export class CriticalPathEngine {
|
|
|
551
559
|
await this.plugins.runAfterTaskCreate(created);
|
|
552
560
|
// Publish typed Domain Event
|
|
553
561
|
const event = {
|
|
554
|
-
id: `evt_${
|
|
562
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
555
563
|
name: 'task.created',
|
|
556
564
|
aggregateId: created.id,
|
|
557
565
|
aggregateType: 'Task',
|
|
@@ -746,7 +754,7 @@ export class CriticalPathEngine {
|
|
|
746
754
|
// Publish typed Domain Events
|
|
747
755
|
if (isStatusChange) {
|
|
748
756
|
const statusEvent = {
|
|
749
|
-
id: `evt_${
|
|
757
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
750
758
|
name: 'task.status_changed',
|
|
751
759
|
aggregateId: updated.id,
|
|
752
760
|
aggregateType: 'Task',
|
|
@@ -768,7 +776,7 @@ export class CriticalPathEngine {
|
|
|
768
776
|
}
|
|
769
777
|
else {
|
|
770
778
|
const updateEvent = {
|
|
771
|
-
id: `evt_${
|
|
779
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
772
780
|
name: 'task.updated',
|
|
773
781
|
aggregateId: updated.id,
|
|
774
782
|
aggregateType: 'Task',
|
|
@@ -788,7 +796,7 @@ export class CriticalPathEngine {
|
|
|
788
796
|
if (!wasBlocked && isNowBlocked) {
|
|
789
797
|
const blockedReason = updated.blockedReason ?? null;
|
|
790
798
|
const blockedEvent = {
|
|
791
|
-
id: `evt_${
|
|
799
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
792
800
|
name: 'task.blocked',
|
|
793
801
|
aggregateId: updated.id,
|
|
794
802
|
aggregateType: 'Task',
|
|
@@ -812,7 +820,7 @@ export class CriticalPathEngine {
|
|
|
812
820
|
}
|
|
813
821
|
else if (wasBlocked && !isNowBlocked) {
|
|
814
822
|
const unblockedEvent = {
|
|
815
|
-
id: `evt_${
|
|
823
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
816
824
|
name: 'task.unblocked',
|
|
817
825
|
aggregateId: updated.id,
|
|
818
826
|
aggregateType: 'Task',
|
|
@@ -887,7 +895,7 @@ export class CriticalPathEngine {
|
|
|
887
895
|
}
|
|
888
896
|
const now = new Date().toISOString();
|
|
889
897
|
const unblockedEvent = {
|
|
890
|
-
id: `evt_${
|
|
898
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
891
899
|
name: 'task.unblocked',
|
|
892
900
|
aggregateId: downstream.id,
|
|
893
901
|
aggregateType: 'Task',
|
|
@@ -929,7 +937,7 @@ export class CriticalPathEngine {
|
|
|
929
937
|
await this.plugins.runAfterTaskDelete(id, existing);
|
|
930
938
|
const now = new Date().toISOString();
|
|
931
939
|
const event = {
|
|
932
|
-
id: `evt_${
|
|
940
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
933
941
|
name: 'task.deleted',
|
|
934
942
|
aggregateId: id,
|
|
935
943
|
aggregateType: 'Task',
|
|
@@ -995,7 +1003,7 @@ export class CriticalPathEngine {
|
|
|
995
1003
|
const removed = await this.store.removeDependency(id);
|
|
996
1004
|
if (removed) {
|
|
997
1005
|
const event = {
|
|
998
|
-
id: `evt_${
|
|
1006
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
999
1007
|
name: 'dependency.removed',
|
|
1000
1008
|
aggregateId: id,
|
|
1001
1009
|
aggregateType: 'Dependency',
|
|
@@ -1025,7 +1033,7 @@ export class CriticalPathEngine {
|
|
|
1025
1033
|
const created = await this.store.addDependency(dep);
|
|
1026
1034
|
const now = new Date().toISOString();
|
|
1027
1035
|
const event = {
|
|
1028
|
-
id: `evt_${
|
|
1036
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1029
1037
|
name: 'dependency.added',
|
|
1030
1038
|
aggregateId: created.id,
|
|
1031
1039
|
aggregateType: 'Dependency',
|
|
@@ -1132,7 +1140,7 @@ export class CriticalPathEngine {
|
|
|
1132
1140
|
const created = await this.store.logTime(fullEntry);
|
|
1133
1141
|
const now = new Date().toISOString();
|
|
1134
1142
|
const event = {
|
|
1135
|
-
id: `evt_${
|
|
1143
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1136
1144
|
name: 'time.logged',
|
|
1137
1145
|
aggregateId: entry.taskId,
|
|
1138
1146
|
aggregateType: 'Task',
|
|
@@ -1177,7 +1185,7 @@ export class CriticalPathEngine {
|
|
|
1177
1185
|
const created = await this.store.addComment({ ...comment, mentions });
|
|
1178
1186
|
const now = new Date().toISOString();
|
|
1179
1187
|
const event = {
|
|
1180
|
-
id: `evt_${
|
|
1188
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1181
1189
|
name: 'comment.created',
|
|
1182
1190
|
aggregateId: created.id,
|
|
1183
1191
|
aggregateType: 'Comment',
|
|
@@ -1202,7 +1210,7 @@ export class CriticalPathEngine {
|
|
|
1202
1210
|
return null;
|
|
1203
1211
|
const now = new Date().toISOString();
|
|
1204
1212
|
const event = {
|
|
1205
|
-
id: `evt_${
|
|
1213
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1206
1214
|
name: 'comment.updated',
|
|
1207
1215
|
aggregateId: updated.id,
|
|
1208
1216
|
aggregateType: 'Comment',
|
|
@@ -1224,7 +1232,7 @@ export class CriticalPathEngine {
|
|
|
1224
1232
|
if (deleted) {
|
|
1225
1233
|
const now = new Date().toISOString();
|
|
1226
1234
|
const event = {
|
|
1227
|
-
id: `evt_${
|
|
1235
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1228
1236
|
name: 'comment.deleted',
|
|
1229
1237
|
aggregateId: id,
|
|
1230
1238
|
aggregateType: 'Comment',
|
|
@@ -1261,7 +1269,7 @@ export class CriticalPathEngine {
|
|
|
1261
1269
|
return null;
|
|
1262
1270
|
const now = new Date().toISOString();
|
|
1263
1271
|
const event = {
|
|
1264
|
-
id: `evt_${
|
|
1272
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1265
1273
|
name: 'comment.reaction.added',
|
|
1266
1274
|
aggregateId: commentId,
|
|
1267
1275
|
aggregateType: 'Comment',
|
|
@@ -1297,7 +1305,7 @@ export class CriticalPathEngine {
|
|
|
1297
1305
|
return null;
|
|
1298
1306
|
const now = new Date().toISOString();
|
|
1299
1307
|
const event = {
|
|
1300
|
-
id: `evt_${
|
|
1308
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1301
1309
|
name: 'comment.reaction.removed',
|
|
1302
1310
|
aggregateId: commentId,
|
|
1303
1311
|
aggregateType: 'Comment',
|
|
@@ -1340,7 +1348,7 @@ export class CriticalPathEngine {
|
|
|
1340
1348
|
const created = await this.store.createAttachment(attachment);
|
|
1341
1349
|
const now = new Date().toISOString();
|
|
1342
1350
|
const event = {
|
|
1343
|
-
id: `evt_${
|
|
1351
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1344
1352
|
name: 'attachment.created',
|
|
1345
1353
|
aggregateId: created.id,
|
|
1346
1354
|
aggregateType: 'Attachment',
|
|
@@ -1370,7 +1378,7 @@ export class CriticalPathEngine {
|
|
|
1370
1378
|
if (deleted) {
|
|
1371
1379
|
const now = new Date().toISOString();
|
|
1372
1380
|
const event = {
|
|
1373
|
-
id: `evt_${
|
|
1381
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1374
1382
|
name: 'attachment.deleted',
|
|
1375
1383
|
aggregateId: id,
|
|
1376
1384
|
aggregateType: 'Attachment',
|
|
@@ -1498,7 +1506,7 @@ export class CriticalPathEngine {
|
|
|
1498
1506
|
const created = await this.store.createTeam({ ...team, tenantId: this.actor ? this.actor.tenantId : team.tenantId });
|
|
1499
1507
|
const now = new Date().toISOString();
|
|
1500
1508
|
const event = {
|
|
1501
|
-
id: `evt_${
|
|
1509
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1502
1510
|
name: 'team.created',
|
|
1503
1511
|
aggregateId: created.id,
|
|
1504
1512
|
aggregateType: 'Team',
|
|
@@ -1509,16 +1517,36 @@ export class CriticalPathEngine {
|
|
|
1509
1517
|
return created;
|
|
1510
1518
|
}
|
|
1511
1519
|
async updateTeam(id, updates) {
|
|
1512
|
-
|
|
1520
|
+
const existing = await this.getTeam(id);
|
|
1521
|
+
if (!existing)
|
|
1513
1522
|
return null;
|
|
1514
1523
|
await this.requireWorkspaceAccess('workspace.manage');
|
|
1515
|
-
|
|
1524
|
+
const updated = await this.store.updateTeam(id, this.withoutTenant(updates));
|
|
1525
|
+
if (updated) {
|
|
1526
|
+
await this.publishEvent({
|
|
1527
|
+
name: 'team.updated',
|
|
1528
|
+
aggregateId: id,
|
|
1529
|
+
aggregateType: 'Team',
|
|
1530
|
+
payload: { team: updated, previous: existing }
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return updated;
|
|
1516
1534
|
}
|
|
1517
1535
|
async deleteTeam(id) {
|
|
1518
|
-
|
|
1536
|
+
const existing = await this.getTeam(id);
|
|
1537
|
+
if (!existing)
|
|
1519
1538
|
return false;
|
|
1520
1539
|
await this.requireWorkspaceAccess('workspace.manage');
|
|
1521
|
-
|
|
1540
|
+
const deleted = await this.store.deleteTeam(id);
|
|
1541
|
+
if (deleted) {
|
|
1542
|
+
await this.publishEvent({
|
|
1543
|
+
name: 'team.deleted',
|
|
1544
|
+
aggregateId: id,
|
|
1545
|
+
aggregateType: 'Team',
|
|
1546
|
+
payload: { teamId: id, name: existing.name, tenantId: existing.tenantId }
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
return deleted;
|
|
1522
1550
|
}
|
|
1523
1551
|
// --- Containers ---
|
|
1524
1552
|
async getContainers(projectId) {
|
|
@@ -1537,7 +1565,7 @@ export class CriticalPathEngine {
|
|
|
1537
1565
|
const created = await this.store.createContainer(container);
|
|
1538
1566
|
const now = new Date().toISOString();
|
|
1539
1567
|
const event = {
|
|
1540
|
-
id: `evt_${
|
|
1568
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1541
1569
|
name: 'container.created',
|
|
1542
1570
|
aggregateId: created.id,
|
|
1543
1571
|
aggregateType: 'Container',
|
|
@@ -1552,7 +1580,16 @@ export class CriticalPathEngine {
|
|
|
1552
1580
|
if (!existing)
|
|
1553
1581
|
return null;
|
|
1554
1582
|
await this.requireProjectAccess('plan.manage', existing.projectId);
|
|
1555
|
-
|
|
1583
|
+
const updated = await this.store.updateContainer(id, updates);
|
|
1584
|
+
if (updated) {
|
|
1585
|
+
await this.publishEvent({
|
|
1586
|
+
name: 'container.updated',
|
|
1587
|
+
aggregateId: id,
|
|
1588
|
+
aggregateType: 'Container',
|
|
1589
|
+
payload: { container: updated, previous: existing }
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return updated;
|
|
1556
1593
|
}
|
|
1557
1594
|
/** Deletes a container. Its tasks and nested containers are kept but no longer reference it. */
|
|
1558
1595
|
async deleteContainer(id) {
|
|
@@ -1564,7 +1601,16 @@ export class CriticalPathEngine {
|
|
|
1564
1601
|
for (const child of (await this.store.getContainers(existing.projectId)).filter((c) => c.parentId === id)) {
|
|
1565
1602
|
await this.store.updateContainer(child.id, { parentId: undefined });
|
|
1566
1603
|
}
|
|
1567
|
-
|
|
1604
|
+
const deleted = await this.store.deleteContainer(id);
|
|
1605
|
+
if (deleted) {
|
|
1606
|
+
await this.publishEvent({
|
|
1607
|
+
name: 'container.deleted',
|
|
1608
|
+
aggregateId: id,
|
|
1609
|
+
aggregateType: 'Container',
|
|
1610
|
+
payload: { containerId: id, projectId: existing.projectId, name: existing.name }
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
return deleted;
|
|
1568
1614
|
}
|
|
1569
1615
|
/** Unsets a reference (e.g. `iterationId`) on every task in the project that points at `id`. */
|
|
1570
1616
|
async clearTaskReferences(projectId, field, id) {
|
|
@@ -1599,7 +1645,7 @@ export class CriticalPathEngine {
|
|
|
1599
1645
|
});
|
|
1600
1646
|
const now = new Date().toISOString();
|
|
1601
1647
|
const event = {
|
|
1602
|
-
id: `evt_${
|
|
1648
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1603
1649
|
name: 'deliverable.created',
|
|
1604
1650
|
aggregateId: created.id,
|
|
1605
1651
|
aggregateType: 'Deliverable',
|
|
@@ -1628,7 +1674,7 @@ export class CriticalPathEngine {
|
|
|
1628
1674
|
const now = new Date().toISOString();
|
|
1629
1675
|
if (updates.status && updates.status !== existing.status) {
|
|
1630
1676
|
const statusEvent = {
|
|
1631
|
-
id: `evt_${
|
|
1677
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1632
1678
|
name: 'deliverable.status_changed',
|
|
1633
1679
|
aggregateId: updated.id,
|
|
1634
1680
|
aggregateType: 'Deliverable',
|
|
@@ -1642,7 +1688,7 @@ export class CriticalPathEngine {
|
|
|
1642
1688
|
await this.events.publish(statusEvent);
|
|
1643
1689
|
}
|
|
1644
1690
|
const updateEvent = {
|
|
1645
|
-
id: `evt_${
|
|
1691
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1646
1692
|
name: 'deliverable.updated',
|
|
1647
1693
|
aggregateId: updated.id,
|
|
1648
1694
|
aggregateType: 'Deliverable',
|
|
@@ -1669,7 +1715,7 @@ export class CriticalPathEngine {
|
|
|
1669
1715
|
if (deleted) {
|
|
1670
1716
|
const now = new Date().toISOString();
|
|
1671
1717
|
const event = {
|
|
1672
|
-
id: `evt_${
|
|
1718
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1673
1719
|
name: 'deliverable.deleted',
|
|
1674
1720
|
aggregateId: id,
|
|
1675
1721
|
aggregateType: 'Deliverable',
|
|
@@ -1745,10 +1791,16 @@ export class CriticalPathEngine {
|
|
|
1745
1791
|
async createIteration(iteration) {
|
|
1746
1792
|
await this.requireProjectAccess('plan.manage', iteration.projectId);
|
|
1747
1793
|
const created = await this.store.createIteration(iteration);
|
|
1794
|
+
await this.publishEvent({
|
|
1795
|
+
name: 'iteration.created',
|
|
1796
|
+
aggregateId: created.id,
|
|
1797
|
+
aggregateType: 'Iteration',
|
|
1798
|
+
payload: { iteration: created }
|
|
1799
|
+
});
|
|
1748
1800
|
if (created.status === 'active') {
|
|
1749
1801
|
const now = new Date().toISOString();
|
|
1750
1802
|
const event = {
|
|
1751
|
-
id: `evt_${
|
|
1803
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1752
1804
|
name: 'iteration.started',
|
|
1753
1805
|
aggregateId: created.id,
|
|
1754
1806
|
aggregateType: 'Iteration',
|
|
@@ -1766,10 +1818,16 @@ export class CriticalPathEngine {
|
|
|
1766
1818
|
await this.requireProjectAccess('plan.manage', existing.projectId);
|
|
1767
1819
|
const updated = await this.store.updateIteration(id, updates);
|
|
1768
1820
|
if (updated) {
|
|
1821
|
+
await this.publishEvent({
|
|
1822
|
+
name: 'iteration.updated',
|
|
1823
|
+
aggregateId: id,
|
|
1824
|
+
aggregateType: 'Iteration',
|
|
1825
|
+
payload: { iteration: updated, previous: existing }
|
|
1826
|
+
});
|
|
1769
1827
|
const now = new Date().toISOString();
|
|
1770
1828
|
if (existing.status !== 'active' && updated.status === 'active') {
|
|
1771
1829
|
const event = {
|
|
1772
|
-
id: `evt_${
|
|
1830
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1773
1831
|
name: 'iteration.started',
|
|
1774
1832
|
aggregateId: updated.id,
|
|
1775
1833
|
aggregateType: 'Iteration',
|
|
@@ -1780,7 +1838,7 @@ export class CriticalPathEngine {
|
|
|
1780
1838
|
}
|
|
1781
1839
|
else if (existing.status !== 'completed' && updated.status === 'completed') {
|
|
1782
1840
|
const event = {
|
|
1783
|
-
id: `evt_${
|
|
1841
|
+
id: `evt_${crypto.randomUUID()}`,
|
|
1784
1842
|
name: 'iteration.completed',
|
|
1785
1843
|
aggregateId: updated.id,
|
|
1786
1844
|
aggregateType: 'Iteration',
|
|
@@ -1799,7 +1857,16 @@ export class CriticalPathEngine {
|
|
|
1799
1857
|
return false;
|
|
1800
1858
|
await this.requireProjectAccess('plan.manage', existing.projectId);
|
|
1801
1859
|
await this.clearTaskReferences(existing.projectId, 'iterationId', id);
|
|
1802
|
-
|
|
1860
|
+
const deleted = await this.store.deleteIteration(id);
|
|
1861
|
+
if (deleted) {
|
|
1862
|
+
await this.publishEvent({
|
|
1863
|
+
name: 'iteration.deleted',
|
|
1864
|
+
aggregateId: id,
|
|
1865
|
+
aggregateType: 'Iteration',
|
|
1866
|
+
payload: { iterationId: id, projectId: existing.projectId, name: existing.name }
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
return deleted;
|
|
1803
1870
|
}
|
|
1804
1871
|
// --- Ladder of Abstraction & Critical Path Method ---
|
|
1805
1872
|
async calculateCriticalPath(projectId, options = {}) {
|