@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
- 'tasks/{levelId}/import', {
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.toApiDate(task.startDate);
3137
- payloadTask['finishDate'] = this.toApiDate(task.finishDate);
3138
- payloadTask['baselineStart'] = this.toApiDate(task.baselineStartDate ?? task.baselineStart);
3139
- payloadTask['baselineFinish'] = this.toApiDate(task.baselineEndDate ?? task.baselineFinish);
3140
- payloadTask['actualStart'] = this.toApiDate(task.actualStartDate ?? task.actualStart);
3141
- payloadTask['actualFinish'] = this.toApiDate(task.actualFinishDate ?? task.actualFinish);
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
- error: () => this.reloadSchedule(),
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) {