@masterteam/work-center 0.0.81 → 0.0.83

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.
@@ -15,8 +15,8 @@ import { SkeletonModule } from 'primeng/skeleton';
15
15
  import { catchError, EMPTY, map, take, firstValueFrom, of } from 'rxjs';
16
16
  import { ModalRef } from '@masterteam/components/dialog';
17
17
  import { DrawerController } from '@masterteam/components/dynamic-drawer';
18
- import { ClientForm, ClientFormWarningsDialog, ClientFormApiService } from '@masterteam/forms/client-form';
19
- import { HttpClient } from '@angular/common/http';
18
+ import { ClientForm, ClientFormWarningsDialog } from '@masterteam/forms/client-form';
19
+ import { HttpClient, HttpHeaders } from '@angular/common/http';
20
20
  import { Router, ActivatedRoute } from '@angular/router';
21
21
  import { Drawer } from '@masterteam/components/drawer';
22
22
  import { RuntimeActionContextStore, RuntimeActionRunner, RUNTIME_ACTION_CONFIRM_DIALOG, hasActionFormFields, buildActionPayload, resolveActionLabel } from '@masterteam/components/runtime-action';
@@ -1322,20 +1322,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1322
1322
 
1323
1323
  /**
1324
1324
  * The channel between the client form in the modal body and the action footer,
1325
- * route-scoped alongside `RuntimeActionContextStore`. It carries three things
1325
+ * route-scoped alongside `RuntimeActionContextStore`. It carries two things
1326
1326
  * in both directions:
1327
1327
  *
1328
- * - the form's submit context, for the validate pre-flight;
1329
1328
  * - whether the form is locally complete, so the footer can gate its actions;
1330
1329
  * - the failure messages of the last action, so they can be rendered above the
1331
1330
  * form instead of squeezed beside the buttons.
1332
1331
  */
1333
1332
  class WorkCenterActionFormStore {
1334
- formContext = signal(null, ...(ngDevMode ? [{ debugName: "formContext" }] : /* istanbul ignore next */ []));
1335
1333
  formIncomplete = signal(false, ...(ngDevMode ? [{ debugName: "formIncomplete" }] : /* istanbul ignore next */ []));
1336
1334
  actionErrors = signal([], ...(ngDevMode ? [{ debugName: "actionErrors" }] : /* istanbul ignore next */ []));
1337
1335
  errorHosted = signal(false, ...(ngDevMode ? [{ debugName: "errorHosted" }] : /* istanbul ignore next */ []));
1338
- context = this.formContext.asReadonly();
1339
1336
  /**
1340
1337
  * True while the on-screen form fails its own local validation — a required
1341
1338
  * field left empty, a formula error. The footer disables the actions that
@@ -1353,9 +1350,6 @@ class WorkCenterActionFormStore {
1353
1350
  * claims them, so an item type with no form still reports a failure.
1354
1351
  */
1355
1352
  hasErrorHost = this.errorHosted.asReadonly();
1356
- setContext(context) {
1357
- this.formContext.set(context);
1358
- }
1359
1353
  setIncomplete(incomplete) {
1360
1354
  this.formIncomplete.set(incomplete);
1361
1355
  }
@@ -2421,22 +2415,6 @@ class WorkCenterProcessRequestType {
2421
2415
  effect(() => {
2422
2416
  this.actionContext?.setFormSource(this.readOnly() ? null : (this.clientForm() ?? null));
2423
2417
  });
2424
- // See the equivalent effect in `WorkCenterProcessStepType`. Gated on
2425
- // `isReadonly` rather than `readOnly`, since the request form is only
2426
- // editable while the request sits in `Returned`.
2427
- effect(() => {
2428
- this.formStore?.setContext(this.isReadonly() || !this.canRenderForm()
2429
- ? null
2430
- : {
2431
- moduleKey: this.moduleKey(),
2432
- operationKey: this.operationKey(),
2433
- moduleId: this.moduleId(),
2434
- levelId: this.levelId(),
2435
- levelDataId: this.levelDataId(),
2436
- moduleDataId: this.moduleDataId(),
2437
- requestSchemaId: this.requestSchemaId(),
2438
- });
2439
- });
2440
2418
  // `formControl.status` is not a signal, so mirror it into one — without
2441
2419
  // this the footer would keep its stale enabled/disabled state while the
2442
2420
  // approver fills the form in.
@@ -2466,7 +2444,6 @@ class WorkCenterProcessRequestType {
2466
2444
  }
2467
2445
  ngOnDestroy() {
2468
2446
  this.actionContext?.setFormSource(null);
2469
- this.formStore?.setContext(null);
2470
2447
  this.formStore?.setIncomplete(false);
2471
2448
  this.formStore?.setErrorHost(false);
2472
2449
  }
@@ -2613,23 +2590,6 @@ class WorkCenterProcessStepType {
2613
2590
  effect(() => {
2614
2591
  this.actionContext?.setFormSource(this.readOnly() ? null : (this.clientForm() ?? null));
2615
2592
  });
2616
- // Publishes what the footer's `process-submit/validate` pre-flight needs.
2617
- // Only the editable case is published: on a read-only form the approver
2618
- // cannot fix anything, so blocking them on a rule failure would strand the
2619
- // action instead of helping.
2620
- effect(() => {
2621
- this.formStore?.setContext(this.readOnly() || !this.canRenderForm()
2622
- ? null
2623
- : {
2624
- moduleKey: this.moduleKey(),
2625
- operationKey: this.operationKey(),
2626
- moduleId: this.moduleId(),
2627
- levelId: this.levelId(),
2628
- levelDataId: this.levelDataId(),
2629
- moduleDataId: this.moduleDataId(),
2630
- requestSchemaId: this.requestSchemaId(),
2631
- });
2632
- });
2633
2593
  // `formControl.status` is not a signal, so mirror it into one — without
2634
2594
  // this the footer would keep its stale enabled/disabled state while the
2635
2595
  // approver fills the form in.
@@ -2659,7 +2619,6 @@ class WorkCenterProcessStepType {
2659
2619
  }
2660
2620
  ngOnDestroy() {
2661
2621
  this.actionContext?.setFormSource(null);
2662
- this.formStore?.setContext(null);
2663
2622
  this.formStore?.setIncomplete(false);
2664
2623
  this.formStore?.setErrorHost(false);
2665
2624
  }
@@ -3143,27 +3102,42 @@ class WorkCenterFormValidationError extends Error {
3143
3102
  }
3144
3103
 
3145
3104
  const FORM_VALIDATION_WARNINGS = 'FormValidationWarnings';
3146
- /** Handles the process action validation handshake owned by Work Center. */
3105
+ /**
3106
+ * Renders what a process action's validation has to say, before it runs
3107
+ * (`confirmWarnings`, driven by the pre-flight) and after it fails
3108
+ * (`resolveErrorMessages`, `recover`).
3109
+ */
3147
3110
  class WorkCenterActionValidationHandler {
3148
3111
  modal = inject(ModalService);
3149
3112
  runner = inject(RuntimeActionRunner);
3150
3113
  transloco = inject(TranslocoService);
3151
- async recover(action, error, submittedPayload, defaultAfterSuccess,
3152
3114
  /**
3153
- * The user already answered this action's warning prompt in the
3154
- * `process-submit/validate` pre-flight, against the same values. The
3155
- * backend still raises them, because the first request carried no token —
3156
- * so complete the handshake silently rather than asking again.
3115
+ * Recovers the backend's `FormValidationWarnings` fallback.
3116
+ *
3117
+ * The backend revalidates immediately before it changes workflow state, so a
3118
+ * clean pre-flight is not a promise: configuration or persisted values may
3119
+ * have moved, the workflow may have advanced, or the receipt may have gone
3120
+ * stale. When that happens the action is refused with `400` and a
3121
+ * **replacement** token.
3122
+ *
3123
+ * These are warnings the user has not seen — that is the whole reason the
3124
+ * pre-flight's answer no longer holds — so they are shown and confirmed like
3125
+ * any other, and only then does the action go out again carrying the new
3126
+ * token. Declining reports the action as `cancelled`, and the workflow stays
3127
+ * where it was.
3128
+ *
3129
+ * `FormValidationErrors` — the blocking kind — are never recovered here.
3157
3130
  */
3158
- warningsAlreadyAcknowledged = false) {
3131
+ async recover(action, error, submittedPayload, defaultAfterSuccess) {
3159
3132
  const validationError = readProcessActionValidationError(error);
3160
3133
  if (validationError?.kind !== FORM_VALIDATION_WARNINGS ||
3161
3134
  !validationError.requiresAcknowledgement ||
3162
- !validationError.validationWarningsToken) {
3135
+ !validationError.validationWarningsToken ||
3136
+ validationError.validationWarningsToken ===
3137
+ submittedPayload?.['validationWarningsToken']) {
3163
3138
  return null;
3164
3139
  }
3165
- const confirmed = warningsAlreadyAcknowledged ||
3166
- (await this.confirmWarnings(this.resolveValidationMessages(validationError)));
3140
+ const confirmed = await this.confirmWarnings(this.resolveValidationMessages(validationError));
3167
3141
  if (!confirmed) {
3168
3142
  return {
3169
3143
  status: 'cancelled',
@@ -3175,8 +3149,10 @@ class WorkCenterActionValidationHandler {
3175
3149
  ...(submittedPayload ?? {}),
3176
3150
  validationWarningsToken: validationError.validationWarningsToken,
3177
3151
  };
3178
- // This descriptor is intentionally ephemeral. A later action rebuilds its
3179
- // payload from the current form and therefore cannot reuse this token.
3152
+ // This descriptor is intentionally ephemeral, and the retry is a new
3153
+ // request with its own idempotency key. A later action rebuilds its payload
3154
+ // from the current form, so a token can never outlive the values it was
3155
+ // issued for.
3180
3156
  return this.runner.execute({
3181
3157
  ...action,
3182
3158
  needConfirmation: false,
@@ -3222,8 +3198,10 @@ class WorkCenterActionValidationHandler {
3222
3198
  return messages;
3223
3199
  }
3224
3200
  /**
3225
- * Shared by the backend-driven acknowledgement handshake above and by the
3226
- * `process-submit/validate` pre-flight in `WorkCenterActionFormValidator`.
3201
+ * Shared by the `actions/validate` pre-flight in
3202
+ * `WorkCenterActionFormValidator` and by the `recover` fallback above. In the
3203
+ * normal flow only the pre-flight raises it; a second prompt means the
3204
+ * backend came back with warnings the pre-flight had not seen.
3227
3205
  *
3228
3206
  * Uses the client form's own warnings dialog so an approver sees the exact
3229
3207
  * same prompt the normal submit path raises — same layout, same wording
@@ -3321,78 +3299,106 @@ function readNonEmptyString$1(value) {
3321
3299
 
3322
3300
  const PROCEED = {
3323
3301
  status: 'proceed',
3324
- warningsAcknowledged: false,
3302
+ validationWarningsToken: null,
3325
3303
  };
3326
- /** The payload key carrying the edited client-form values. */
3327
- const FORM_VALUES_PAYLOAD_KEY = 'values';
3328
3304
  /**
3329
- * Runs `process-submit/validate` before a Work Center action that submits
3330
- * edited client-form values (Approve, Resubmit, …).
3305
+ * The backend action names this endpoint accepts, keyed by the runtime
3306
+ * descriptor's `actionKey`. Reject / Return / Terminate are deliberately
3307
+ * absent: they are not validated against the approval form and must never be
3308
+ * pre-flighted.
3309
+ */
3310
+ const PREFLIGHT_ACTIONS = {
3311
+ approve: 'Approve',
3312
+ resubmit: 'Resubmit',
3313
+ };
3314
+ const SILENT_MESSAGE_HEADERS = new HttpHeaders({ noMessage: 'true' });
3315
+ /**
3316
+ * Runs the action-aware validation pre-flight before Approve and Resubmit.
3317
+ *
3318
+ * The endpoint is `POST processes/{requestId}/actions/validate`, a sibling of
3319
+ * the action itself. It is deliberately **not** `process-submit/validate`:
3320
+ * that one answers for a create/edit submit and carries no active request,
3321
+ * step, action or effective approval form, so it validates the wrong scope for
3322
+ * an approval.
3331
3323
  *
3332
- * Why this exists: the process action endpoints answer `200` for non-blocking
3333
- * rule failures, so an approver never sees the warnings that the same values
3334
- * would raise on the normal submit path. This pre-flight reproduces the
3335
- * client-form submit ceremony — blocking failures stop the action, warnings
3336
- * raise the acknowledge-and-continue prompt — without submitting anything.
3324
+ * Three outcomes, all HTTP `200`:
3337
3325
  *
3338
- * It is advisory only. The action endpoint stays authoritative, and the
3339
- * backend-driven `FormValidationWarnings` handshake in
3340
- * `WorkCenterActionValidationHandler` keeps working untouched on top of it.
3326
+ * - `Blocked` the action is never sent, and the messages go to the footer;
3327
+ * - `Warning` the messages are shown, and a confirmed prompt hands the
3328
+ * `validationWarningsToken` to the action that follows;
3329
+ * - `Valid` — straight through, no token.
3330
+ *
3331
+ * The action endpoint stays authoritative and revalidates immediately before it
3332
+ * changes workflow state, so a pre-flight that cannot be reached lets the action
3333
+ * through rather than stranding it. See
3334
+ * `WorkCenterActionValidationHandler.recover` for what happens when that second
3335
+ * opinion disagrees.
3341
3336
  */
3342
3337
  class WorkCenterActionFormValidator {
3343
- api = inject(ClientFormApiService);
3338
+ http = inject(HttpClient);
3344
3339
  actionContext = inject(RuntimeActionContextStore);
3345
- contextStore = inject(WorkCenterActionFormStore);
3346
3340
  warnings = inject(WorkCenterActionValidationHandler);
3347
3341
  transloco = inject(TranslocoService);
3348
3342
  async validate(action) {
3349
- const context = this.contextStore.context();
3350
- if (!context || !submitsFormValues(action)) {
3343
+ const actionName = PREFLIGHT_ACTIONS[normalizeActionKey(action.actionKey)];
3344
+ const url = resolveValidateUrl(action);
3345
+ if (!actionName || !url) {
3351
3346
  return PROCEED;
3352
3347
  }
3353
- const fields = this.actionContext.resolveFormValues();
3354
- if (!fields.length) {
3355
- return PROCEED;
3356
- }
3357
- const validation = await this.runValidation(context, fields);
3348
+ const validation = await this.runValidation(url, {
3349
+ stepId: resolveStepId(action),
3350
+ action: actionName,
3351
+ // An action form with no editable fields sends `[]`, which the backend
3352
+ // accepts a read-only approver is still validated.
3353
+ values: this.actionContext.resolveFormValues(),
3354
+ });
3358
3355
  // A transport failure must not strand an otherwise valid action: the
3359
3356
  // pre-flight is advisory and the action endpoint validates again anyway.
3360
3357
  if (!validation) {
3361
3358
  return PROCEED;
3362
3359
  }
3363
- const failures = (validation.results ?? []).filter(shouldSurface);
3364
- const blocking = failures.filter(isBlocking);
3365
- if (validation.hasBlockingFailures || blocking.length) {
3366
- const messages = this.resolveMessages(blocking.length ? blocking : failures);
3360
+ const failures = (validation.validation?.results ?? []).filter((result) => !result.passed);
3361
+ if (validation.status === 'Blocked' ||
3362
+ !validation.canProceed ||
3363
+ validation.validation?.hasBlockingFailures) {
3367
3364
  return {
3368
3365
  status: 'blocked',
3369
- error: new WorkCenterFormValidationError(messages.length
3370
- ? messages
3371
- : [
3372
- this.transloco.translate('workCenter.actions.validationBlocked'),
3373
- ]),
3366
+ error: this.blockedError(validation, failures),
3374
3367
  };
3375
3368
  }
3376
- if (!failures.length) {
3369
+ if (validation.status !== 'Warning' &&
3370
+ !validation.requiresAcknowledgement) {
3377
3371
  return PROCEED;
3378
3372
  }
3379
3373
  const confirmed = await this.warnings.confirmWarnings(this.resolveMessages(failures));
3380
3374
  return confirmed
3381
- ? { status: 'proceed', warningsAcknowledged: true }
3375
+ ? {
3376
+ status: 'proceed',
3377
+ validationWarningsToken: readNonEmptyString(validation.validationWarningsToken),
3378
+ }
3382
3379
  : { status: 'cancelled' };
3383
3380
  }
3384
- async runValidation(context, fields) {
3385
- const response = await firstValueFrom(this.api
3386
- .validate(buildValidationRequest(context, fields), true)
3381
+ async runValidation(url, request) {
3382
+ const response = await firstValueFrom(this.http
3383
+ .post(url, request, { headers: SILENT_MESSAGE_HEADERS })
3387
3384
  .pipe(catchError(() => of(null))));
3388
- return response?.data ?? null;
3385
+ return response?.code === 1 ? (response.data ?? null) : null;
3386
+ }
3387
+ blockedError(validation, failures) {
3388
+ const messages = this.resolveMessages(failures);
3389
+ if (messages.length) {
3390
+ return new WorkCenterFormValidationError(messages);
3391
+ }
3392
+ return new WorkCenterFormValidationError([
3393
+ readNonEmptyString(validation.message) ??
3394
+ this.transloco.translate('workCenter.actions.validationBlocked'),
3395
+ ]);
3389
3396
  }
3390
3397
  resolveMessages(results) {
3391
3398
  const messages = [];
3392
3399
  const seen = new Set();
3393
3400
  for (const result of results) {
3394
- const message = resolveLocalizedText(result.message, this.transloco.getActiveLang()) ??
3395
- readNonEmptyString(result.errorMessage);
3401
+ const message = resolveLocalizedText(result.message, this.transloco.getActiveLang());
3396
3402
  if (!message || seen.has(message))
3397
3403
  continue;
3398
3404
  seen.add(message);
@@ -3407,51 +3413,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
3407
3413
  type: Injectable
3408
3414
  }] });
3409
3415
  /**
3410
- * Mirrors `buildSubmitRequest` in `ClientForm`, minus the pieces a pre-flight
3411
- * must never send (`templateId`) or cannot know from outside the form
3412
- * (`IsSteps`, which only applies to stepped/wizard forms).
3416
+ * The pre-flight is the action's own sibling route, so it is derived from the
3417
+ * descriptor's URL rather than rebuilt from a `requestId` the FE would have to
3418
+ * source somewhere else `.../processes/{requestId}/actions/approve` becomes
3419
+ * `.../processes/{requestId}/actions/validate`. That also keeps both calls on
3420
+ * whatever base the backend handed out.
3413
3421
  */
3414
- function buildValidationRequest(context, fields) {
3415
- const request = {
3416
- moduleKey: context.moduleKey,
3417
- operationKey: context.operationKey,
3418
- fields,
3419
- };
3420
- if (context.moduleId != null)
3421
- request.moduleId = context.moduleId;
3422
- if (context.levelId != null)
3423
- request.levelId = context.levelId;
3424
- if (context.levelDataId != null)
3425
- request.levelDataId = context.levelDataId;
3426
- if (context.moduleDataId != null)
3427
- request.moduleDataId = context.moduleDataId;
3428
- if (context.requestSchemaId != null) {
3429
- request.requestSchemaId = context.requestSchemaId;
3430
- }
3431
- const recordId = context.moduleDataId ??
3432
- (context.moduleKey === 'LevelData' && context.operationKey !== 'Create'
3433
- ? context.levelDataId
3434
- : undefined);
3435
- if (recordId != null)
3436
- request.recordId = recordId;
3437
- return request;
3438
- }
3439
- /**
3440
- * Only actions that carry the edited form values are worth validating —
3441
- * Delete, Close and friends submit nothing the rules can read.
3442
- */
3443
- function submitsFormValues(action) {
3444
- return ((action.payloadKeys ?? []).includes(FORM_VALUES_PAYLOAD_KEY) ||
3445
- (!!action.payloadTemplate &&
3446
- FORM_VALUES_PAYLOAD_KEY in action.payloadTemplate));
3422
+ function resolveValidateUrl(action) {
3423
+ const url = action.url?.trim().split(/[?#]/)[0].replace(/\/+$/, '');
3424
+ if (!url) {
3425
+ return null;
3426
+ }
3427
+ const lastSlash = url.lastIndexOf('/');
3428
+ return lastSlash > 0 ? `${url.slice(0, lastSlash)}/validate` : null;
3447
3429
  }
3448
- // These two mirror `process-validation.util` in `@masterteam/forms/client-form`,
3449
- // which the package does not export. Keep them in step with it.
3450
- function shouldSurface(result) {
3451
- return !result.passed || result.isEvaluationError === true;
3430
+ /** The same resolution the action payload uses for its own `stepId`. */
3431
+ function resolveStepId(action) {
3432
+ const templateStepId = action.payloadTemplate?.['stepId'];
3433
+ if (typeof templateStepId === 'number' ||
3434
+ typeof templateStepId === 'string') {
3435
+ return templateStepId;
3436
+ }
3437
+ return action.stepIds?.[0] ?? null;
3452
3438
  }
3453
- function isBlocking(result) {
3454
- return (result.isEvaluationError === true || (!result.passed && result.isBlocking));
3439
+ function normalizeActionKey(value) {
3440
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
3455
3441
  }
3456
3442
  function readNonEmptyString(value) {
3457
3443
  if (typeof value !== 'string')
@@ -3460,7 +3446,16 @@ function readNonEmptyString(value) {
3460
3446
  return normalized.length ? normalized : null;
3461
3447
  }
3462
3448
 
3463
- /** Executes Work Center actions while preserving form payloads for validation retries. */
3449
+ /**
3450
+ * Runs a Work Center footer action, normally as a single HTTP request.
3451
+ *
3452
+ * Everything negotiable happens before it: the `actions/validate` pre-flight
3453
+ * blocks the action or takes the warning acknowledgement, then the reason/note
3454
+ * dialog collects its fields, and the acknowledgement receipt rides along on
3455
+ * the request itself. A second request happens only when the backend's own
3456
+ * revalidation disagrees with the pre-flight — see
3457
+ * `WorkCenterActionValidationHandler.recover`.
3458
+ */
3464
3459
  class WorkCenterRuntimeActionExecutor {
3465
3460
  runner = inject(RuntimeActionRunner);
3466
3461
  actionContext = inject(RuntimeActionContextStore);
@@ -3490,11 +3485,13 @@ class WorkCenterRuntimeActionExecutor {
3490
3485
  error: preflight.error,
3491
3486
  };
3492
3487
  }
3493
- const warningsAcknowledged = preflight.warningsAcknowledged;
3488
+ // The receipt for the warnings the user just acknowledged. It goes out with
3489
+ // the action itself, so an acknowledged Approve is a single request.
3490
+ const warningsToken = preflight.validationWarningsToken;
3494
3491
  if (action.needConfirmation &&
3495
3492
  hasActionFormFields(action) &&
3496
3493
  this.confirmDialogClass) {
3497
- return this.executeFormAction(action, defaultAfterSuccess, warningsAcknowledged);
3494
+ return this.executeFormAction(action, defaultAfterSuccess, warningsToken);
3498
3495
  }
3499
3496
  let submittedPayload;
3500
3497
  let result = await this.runner.execute(action, {
@@ -3504,17 +3501,17 @@ class WorkCenterRuntimeActionExecutor {
3504
3501
  values: () => this.actionContext.resolveFormValues(),
3505
3502
  },
3506
3503
  transformPayload: (payload) => {
3507
- submittedPayload = payload;
3508
- return payload;
3504
+ submittedPayload = withWarningsToken(payload, warningsToken);
3505
+ return submittedPayload;
3509
3506
  },
3510
3507
  });
3511
3508
  if (result.status === 'error') {
3512
3509
  result =
3513
- (await this.validation.recover(action, result.error, submittedPayload, defaultAfterSuccess, warningsAcknowledged)) ?? result;
3510
+ (await this.validation.recover(action, result.error, submittedPayload, defaultAfterSuccess)) ?? result;
3514
3511
  }
3515
3512
  return result;
3516
3513
  }
3517
- executeFormAction(action, defaultAfterSuccess, warningsAcknowledged) {
3514
+ executeFormAction(action, defaultAfterSuccess, warningsToken) {
3518
3515
  return new Promise((resolve) => {
3519
3516
  let pendingRun = null;
3520
3517
  let finalResult = null;
@@ -3525,9 +3522,9 @@ class WorkCenterRuntimeActionExecutor {
3525
3522
  inputValues: {
3526
3523
  action,
3527
3524
  onSubmit: async (formValue) => {
3528
- const submittedPayload = buildActionPayload(action, formValue, {
3525
+ const submittedPayload = withWarningsToken(buildActionPayload(action, formValue, {
3529
3526
  values: () => this.actionContext.resolveFormValues(),
3530
- });
3527
+ }), warningsToken);
3531
3528
  const executablePayload = submittedPayload ?? {};
3532
3529
  const executableAction = {
3533
3530
  ...action,
@@ -3542,7 +3539,7 @@ class WorkCenterRuntimeActionExecutor {
3542
3539
  });
3543
3540
  if (result.status === 'error') {
3544
3541
  result =
3545
- (await this.validation.recover(action, result.error, submittedPayload, defaultAfterSuccess, warningsAcknowledged)) ?? result;
3542
+ (await this.validation.recover(action, result.error, submittedPayload, defaultAfterSuccess)) ?? result;
3546
3543
  }
3547
3544
  return result;
3548
3545
  })();
@@ -3586,6 +3583,17 @@ class WorkCenterRuntimeActionExecutor {
3586
3583
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: WorkCenterRuntimeActionExecutor, decorators: [{
3587
3584
  type: Injectable
3588
3585
  }] });
3586
+ /**
3587
+ * Attaches the pre-flight's acknowledgement receipt to the payload the action
3588
+ * is about to send. Without a receipt — a `Valid` pre-flight, or one that never
3589
+ * ran — the payload goes out exactly as built.
3590
+ */
3591
+ function withWarningsToken(payload, warningsToken) {
3592
+ if (!warningsToken || !payload) {
3593
+ return payload;
3594
+ }
3595
+ return { ...payload, validationWarningsToken: warningsToken };
3596
+ }
3589
3597
 
3590
3598
  const DEFAULT_GENERAL_TASK_MODAL_STYLE_CLASS = '!w-[70vw] !h-[90vh]';
3591
3599
  class WorkCenterItemModalFooterActions {