@masterteam/task-schedule 0.0.23 → 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
|
}
|
|
@@ -3133,12 +3145,12 @@ class TaskScheduleImportDialog {
|
|
|
3133
3145
|
subtasks: [],
|
|
3134
3146
|
children: [],
|
|
3135
3147
|
};
|
|
3136
|
-
payloadTask['startDate'] = this.
|
|
3137
|
-
payloadTask['finishDate'] = this.
|
|
3138
|
-
payloadTask['baselineStart'] = this.
|
|
3139
|
-
payloadTask['baselineFinish'] = this.
|
|
3140
|
-
payloadTask['actualStart'] = this.
|
|
3141
|
-
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);
|
|
3142
3154
|
payloadTask['customProperties'] =
|
|
3143
3155
|
task.customProperties ?? task.props ?? [];
|
|
3144
3156
|
payloadTask['isMilestone'] =
|
|
@@ -3336,6 +3348,21 @@ class TaskScheduleImportDialog {
|
|
|
3336
3348
|
const day = String(date.getDate()).padStart(2, '0');
|
|
3337
3349
|
return `${year}-${month}-${day}`;
|
|
3338
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
|
+
}
|
|
3339
3366
|
countTasks(tasks, maxCount) {
|
|
3340
3367
|
let count = 0;
|
|
3341
3368
|
const walk = (rows) => {
|
|
@@ -3949,25 +3976,22 @@ class TaskSchedule {
|
|
|
3949
3976
|
const { taskId, parentId } = rowDropUpdate;
|
|
3950
3977
|
const sub = this.actionService
|
|
3951
3978
|
.updateParent(context, taskId, parentId)
|
|
3952
|
-
.pipe(finalize(() => {
|
|
3953
|
-
const payload = this.actionService.buildOrderPayload(this.ganttObj?.flatData);
|
|
3954
|
-
if (!payload.length) {
|
|
3955
|
-
this.reloadSchedule();
|
|
3956
|
-
return;
|
|
3957
|
-
}
|
|
3958
|
-
const orderSub = this.actionService
|
|
3959
|
-
.updateOrder(context, payload)
|
|
3960
|
-
.subscribe({
|
|
3961
|
-
next: () => this.reloadSchedule(),
|
|
3962
|
-
error: () => this.reloadSchedule(),
|
|
3963
|
-
});
|
|
3964
|
-
this.operationSub.add(orderSub);
|
|
3965
|
-
}))
|
|
3966
3979
|
.subscribe({
|
|
3967
|
-
|
|
3980
|
+
next: () => this.persistCurrentOrder(context),
|
|
3981
|
+
error: (error) => this.emitActionError(this.getErrorMessage(error)),
|
|
3968
3982
|
});
|
|
3969
3983
|
this.operationSub.add(sub);
|
|
3970
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
|
+
}
|
|
3971
3995
|
updateTask(args) {
|
|
3972
3996
|
const context = this.context();
|
|
3973
3997
|
if (!context) {
|