@masterteam/task-schedule 0.0.22 → 0.0.24
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/README.md
CHANGED
|
@@ -26,10 +26,10 @@ Main context fields:
|
|
|
26
26
|
- `dateFormat`: default `dd/MM/yyyy`
|
|
27
27
|
- `height`: default `760px`
|
|
28
28
|
- `pdfFonts`: optional `{ regular?: string; arabic?: string; size?: number }`
|
|
29
|
-
- `mppRequestTimeoutMs`: optional timeout for MPP import, apply, and export requests. Defaults to `300000` ms.
|
|
30
|
-
- `mppPreviewMaxRows`: optional MPP import normalization cap. Defaults to `400` preview rows.
|
|
31
|
-
- `mppPreviewMaxResponseBytes`: optional maximum import response size to parse for preview rows. Defaults to `5000000` bytes; larger successful responses are treated as direct backend-applied imports.
|
|
32
|
-
- `mppKeepRawPayload`: optional flag to retain the raw MPP import response on `TaskScheduleImportResult.raw`. Defaults to `false`.
|
|
29
|
+
- `mppRequestTimeoutMs`: optional timeout for MPP import, apply, and export requests. Defaults to `300000` ms.
|
|
30
|
+
- `mppPreviewMaxRows`: optional MPP import normalization cap. Defaults to `400` preview rows.
|
|
31
|
+
- `mppPreviewMaxResponseBytes`: optional maximum import response size to parse for preview rows. Defaults to `5000000` bytes; larger successful responses are treated as direct backend-applied imports.
|
|
32
|
+
- `mppKeepRawPayload`: optional flag to retain the raw MPP import response on `TaskScheduleImportResult.raw`. Defaults to `false`.
|
|
33
33
|
|
|
34
34
|
## Outputs
|
|
35
35
|
|
|
@@ -108,16 +108,17 @@ Backend routes used:
|
|
|
108
108
|
- Custom view config: `GET /api/schedulemanager/{levelId}/schedule/{customViewId}`
|
|
109
109
|
- Team members (edit/resources mode): `GET /api/levels/{levelId}/TeamMember`
|
|
110
110
|
|
|
111
|
-
Runtime mutations (when `levelDataId` exists):
|
|
112
|
-
|
|
113
|
-
- `POST /api/process-submit` for create/update/delete/progress
|
|
114
|
-
- `PUT /api/levels/{levelId}/{levelDataId}/schedule/bulk-update`
|
|
115
|
-
- `PUT /api/levels/{levelId}/{levelDataId}/schedule/reorder`
|
|
111
|
+
Runtime mutations (when `levelDataId` exists):
|
|
112
|
+
|
|
113
|
+
- `POST /api/process-submit` for create/update/delete/progress
|
|
114
|
+
- `PUT /api/levels/{levelId}/{levelDataId}/schedule/bulk-update`
|
|
115
|
+
- `PUT /api/levels/{levelId}/{levelDataId}/schedule/reorder`
|
|
116
116
|
- `POST /api/levels/{levelId}/{levelDataId}/schedule/baselines` (set baseline)
|
|
117
117
|
- `GET /api/levels/{levelId}/{levelDataId}/schedule/mpp` (export)
|
|
118
|
-
|
|
119
|
-
Plain schedule reads no longer use legacy `/api/tasks/...` read routes.
|
|
120
|
-
Import dialog keeps its current MPP preview/apply flow unless `endpoints.importTasks` / `endpoints.applyImportedTasks` are overridden.
|
|
118
|
+
|
|
119
|
+
Plain schedule reads no longer use legacy `/api/tasks/...` read routes.
|
|
120
|
+
Import dialog keeps its current MPP preview/apply flow unless `endpoints.importTasks` / `endpoints.applyImportedTasks` are overridden.
|
|
121
|
+
Row drag/drop reorder is optimistic: the component sends the parent/order write requests and leaves the current Gantt view in place instead of immediately reloading the schedule.
|
|
121
122
|
|
|
122
123
|
For `schedule/read` query responses, `TaskScheduleFetchService` maps native fields through `catalog.properties`: it resolves each property's `normalizedKey` to the Gantt field and reads the matching `record.values[property.key]` wrapper. Date fields prefer `raw` values and are converted from valid date/UTC strings to local date-only `Date` objects before binding to Syncfusion Gantt.
|
|
123
124
|
|
|
@@ -141,11 +142,11 @@ When no PDF font is provided, package fallback uses embedded `TASK_SCHEDULE_DEFA
|
|
|
141
142
|
|
|
142
143
|
## Import Dialog
|
|
143
144
|
|
|
144
|
-
Use `TaskScheduleImportDialog` with `ModalService` to import `.mpp`/`.xer` files and apply tasks (replace or append).
|
|
145
|
-
The dialog is Tailwind-based and uses `@masterteam/components` (`mt-button`, `mt-entity-preview`).
|
|
146
|
-
Imported rows render as a checkbox tree so nested MPP summary tasks and children stay readable before apply.
|
|
147
|
-
If the MPP import endpoint returns a successful empty body or a response larger than `mppPreviewMaxResponseBytes`, the package treats it as a direct backend-applied import, closes the dialog with a success result, and lets the host schedule reload instead of rendering an empty preview.
|
|
148
|
-
The preview is capped by `maxRows`/`context.mppPreviewMaxRows` to keep large MPP files responsive. Select-all and apply operate on the visible preview rows only when the backend returns more rows than the preview cap. The full raw import payload is not retained unless `context.mppKeepRawPayload` is enabled.
|
|
145
|
+
Use `TaskScheduleImportDialog` with `ModalService` to import `.mpp`/`.xer` files and apply tasks (replace or append).
|
|
146
|
+
The dialog is Tailwind-based and uses `@masterteam/components` (`mt-button`, `mt-entity-preview`).
|
|
147
|
+
Imported rows render as a checkbox tree so nested MPP summary tasks and children stay readable before apply.
|
|
148
|
+
If the MPP import endpoint returns a successful empty body or a response larger than `mppPreviewMaxResponseBytes`, the package treats it as a direct backend-applied import, closes the dialog with a success result, and lets the host schedule reload instead of rendering an empty preview.
|
|
149
|
+
The preview is capped by `maxRows`/`context.mppPreviewMaxRows` to keep large MPP files responsive. Select-all and apply operate on the visible preview rows only when the backend returns more rows than the preview cap. The full raw import payload is not retained unless `context.mppKeepRawPayload` is enabled.
|
|
149
150
|
|
|
150
151
|
## Shell Component
|
|
151
152
|
|
|
@@ -1280,7 +1280,6 @@ function normalizeLookupKey(value) {
|
|
|
1280
1280
|
.replace(/[^a-z0-9]/g, '');
|
|
1281
1281
|
}
|
|
1282
1282
|
|
|
1283
|
-
const DEFAULT_TASK_MODULE_ID = 3;
|
|
1284
1283
|
const DEFAULT_MPP_REQUEST_TIMEOUT_MS = 300_000;
|
|
1285
1284
|
class TaskScheduleActionService {
|
|
1286
1285
|
http = inject(HttpClient);
|
|
@@ -1507,9 +1506,14 @@ class TaskScheduleActionService {
|
|
|
1507
1506
|
return this.submitTaskMutation(context, 'UpdateProgress', payload, taskId);
|
|
1508
1507
|
}
|
|
1509
1508
|
applyImportedTasks(context, payload) {
|
|
1509
|
+
// Submit/import the reviewed preview tasks. The level-scoped route is the
|
|
1510
|
+
// preferred contract (Pplus4-style split flow): upload/parse only previews,
|
|
1511
|
+
// this PATCH saves. The old `tasks/{levelDataId}/import` shape fed levelId
|
|
1512
|
+
// into a levelDataId slot — wrong record — so imports never saved correctly.
|
|
1510
1513
|
const endpoint = this.interpolateEndpointTemplate(this.readEndpoint(context, 'applyImportedTasks') ??
|
|
1511
|
-
'
|
|
1514
|
+
'levels/{levelId}/{levelDataId}/tasks/import', {
|
|
1512
1515
|
levelId: this.requireId(context.levelId, 'Apply import requires levelId.'),
|
|
1516
|
+
levelDataId: this.requireId(context.levelDataId, 'Apply import requires levelDataId.'),
|
|
1513
1517
|
});
|
|
1514
1518
|
return this.readData(this.http
|
|
1515
1519
|
.patch(endpoint, payload)
|
|
@@ -1521,9 +1525,17 @@ class TaskScheduleActionService {
|
|
|
1521
1525
|
operationKey: 'SetBaseline',
|
|
1522
1526
|
levelId: this.requireProcessId(context.levelId),
|
|
1523
1527
|
levelDataId: this.requireProcessId(context.levelDataId),
|
|
1524
|
-
moduleId: context.taskModuleId ?? DEFAULT_TASK_MODULE_ID,
|
|
1525
1528
|
fields: [],
|
|
1526
1529
|
};
|
|
1530
|
+
// The backend resolves the module from `moduleKey` — the task
|
|
1531
|
+
// create/update/delete/progress mutations (buildProcessSubmitRequest) all
|
|
1532
|
+
// omit moduleId and work. SetBaseline used to force a hardcoded moduleId
|
|
1533
|
+
// (was DEFAULT_TASK_MODULE_ID = 3), which targets the wrong module on any
|
|
1534
|
+
// template whose Task module isn't id 3 → "Record '<levelDataId>' was not
|
|
1535
|
+
// found". Only forward an explicitly-provided override.
|
|
1536
|
+
if (context.taskModuleId !== undefined && context.taskModuleId !== null) {
|
|
1537
|
+
request.moduleId = context.taskModuleId;
|
|
1538
|
+
}
|
|
1527
1539
|
const endpoint = this.interpolateEndpointTemplate(this.readEndpoint(context, 'processSubmit') ?? 'process-submit', {});
|
|
1528
1540
|
return this.readData(this.http.post(endpoint, request));
|
|
1529
1541
|
}
|
|
@@ -2641,15 +2653,24 @@ class TaskScheduleDialog {
|
|
|
2641
2653
|
this.ref.close(response);
|
|
2642
2654
|
}
|
|
2643
2655
|
mapSubmitRequest(request) {
|
|
2644
|
-
|
|
2645
|
-
|
|
2656
|
+
// Schedule items submit under their type-specific schedule module key
|
|
2657
|
+
// ('Task' | 'Milestone' | 'Deliverable'), not the generic 'ModuleData' the
|
|
2658
|
+
// form loads with. The backend only maps the task_* fields (and the parent
|
|
2659
|
+
// link) when the schedule module key is used.
|
|
2660
|
+
const moduleKey = this.selectedType() ?? request.moduleKey;
|
|
2661
|
+
const isSubtaskCreate = this.mode() === 'create' && !!this.parentGuid();
|
|
2662
|
+
if (!isSubtaskCreate) {
|
|
2663
|
+
return { ...request, moduleKey };
|
|
2646
2664
|
}
|
|
2647
2665
|
return {
|
|
2648
2666
|
...request,
|
|
2667
|
+
moduleKey,
|
|
2649
2668
|
fields: [
|
|
2650
2669
|
...(request.fields ?? []),
|
|
2651
2670
|
{
|
|
2652
|
-
|
|
2671
|
+
// Parent reference is the anchor row's schedule guid / externalId,
|
|
2672
|
+
// not its numeric database id.
|
|
2673
|
+
propertyKey: 'externalParentId',
|
|
2653
2674
|
value: this.parentGuid(),
|
|
2654
2675
|
},
|
|
2655
2676
|
],
|
|
@@ -3124,12 +3145,12 @@ class TaskScheduleImportDialog {
|
|
|
3124
3145
|
subtasks: [],
|
|
3125
3146
|
children: [],
|
|
3126
3147
|
};
|
|
3127
|
-
payloadTask['startDate'] = this.
|
|
3128
|
-
payloadTask['finishDate'] = this.
|
|
3129
|
-
payloadTask['baselineStart'] = this.
|
|
3130
|
-
payloadTask['baselineFinish'] = this.
|
|
3131
|
-
payloadTask['actualStart'] = this.
|
|
3132
|
-
payloadTask['actualFinish'] = this.
|
|
3148
|
+
payloadTask['startDate'] = this.toApiDateTime(task.startDate);
|
|
3149
|
+
payloadTask['finishDate'] = this.toApiDateTime(task.finishDate);
|
|
3150
|
+
payloadTask['baselineStart'] = this.toApiDateTime(task.baselineStartDate ?? task.baselineStart);
|
|
3151
|
+
payloadTask['baselineFinish'] = this.toApiDateTime(task.baselineEndDate ?? task.baselineFinish);
|
|
3152
|
+
payloadTask['actualStart'] = this.toApiDateTime(task.actualStartDate ?? task.actualStart);
|
|
3153
|
+
payloadTask['actualFinish'] = this.toApiDateTime(task.actualFinishDate ?? task.actualFinish);
|
|
3133
3154
|
payloadTask['customProperties'] =
|
|
3134
3155
|
task.customProperties ?? task.props ?? [];
|
|
3135
3156
|
payloadTask['isMilestone'] =
|
|
@@ -3327,6 +3348,21 @@ class TaskScheduleImportDialog {
|
|
|
3327
3348
|
const day = String(date.getDate()).padStart(2, '0');
|
|
3328
3349
|
return `${year}-${month}-${day}`;
|
|
3329
3350
|
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Submit/import payload dates must be ISO 8601 UTC timestamps ending with
|
|
3353
|
+
* 'Z' (backend rejects bare 'YYYY-MM-DD' with VAL_001). `toApiDate` yields a
|
|
3354
|
+
* calendar day for display/editing; this widens it to a full UTC instant for
|
|
3355
|
+
* the import call. Date-only strings parse as UTC midnight, so the calendar
|
|
3356
|
+
* day is preserved (no timezone shift).
|
|
3357
|
+
*/
|
|
3358
|
+
toApiDateTime(value) {
|
|
3359
|
+
const normalized = this.toApiDate(value);
|
|
3360
|
+
if (!normalized) {
|
|
3361
|
+
return null;
|
|
3362
|
+
}
|
|
3363
|
+
const date = new Date(normalized);
|
|
3364
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
3365
|
+
}
|
|
3330
3366
|
countTasks(tasks, maxCount) {
|
|
3331
3367
|
let count = 0;
|
|
3332
3368
|
const walk = (rows) => {
|
|
@@ -3940,25 +3976,22 @@ class TaskSchedule {
|
|
|
3940
3976
|
const { taskId, parentId } = rowDropUpdate;
|
|
3941
3977
|
const sub = this.actionService
|
|
3942
3978
|
.updateParent(context, taskId, parentId)
|
|
3943
|
-
.pipe(finalize(() => {
|
|
3944
|
-
const payload = this.actionService.buildOrderPayload(this.ganttObj?.flatData);
|
|
3945
|
-
if (!payload.length) {
|
|
3946
|
-
this.reloadSchedule();
|
|
3947
|
-
return;
|
|
3948
|
-
}
|
|
3949
|
-
const orderSub = this.actionService
|
|
3950
|
-
.updateOrder(context, payload)
|
|
3951
|
-
.subscribe({
|
|
3952
|
-
next: () => this.reloadSchedule(),
|
|
3953
|
-
error: () => this.reloadSchedule(),
|
|
3954
|
-
});
|
|
3955
|
-
this.operationSub.add(orderSub);
|
|
3956
|
-
}))
|
|
3957
3979
|
.subscribe({
|
|
3958
|
-
|
|
3980
|
+
next: () => this.persistCurrentOrder(context),
|
|
3981
|
+
error: (error) => this.emitActionError(this.getErrorMessage(error)),
|
|
3959
3982
|
});
|
|
3960
3983
|
this.operationSub.add(sub);
|
|
3961
3984
|
}
|
|
3985
|
+
persistCurrentOrder(context) {
|
|
3986
|
+
const payload = this.actionService.buildOrderPayload(this.ganttObj?.flatData);
|
|
3987
|
+
if (!payload.length) {
|
|
3988
|
+
return;
|
|
3989
|
+
}
|
|
3990
|
+
const orderSub = this.actionService.updateOrder(context, payload).subscribe({
|
|
3991
|
+
error: (error) => this.emitActionError(this.getErrorMessage(error)),
|
|
3992
|
+
});
|
|
3993
|
+
this.operationSub.add(orderSub);
|
|
3994
|
+
}
|
|
3962
3995
|
updateTask(args) {
|
|
3963
3996
|
const context = this.context();
|
|
3964
3997
|
if (!context) {
|