@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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
* backend
|
|
3156
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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
|
|
3179
|
-
//
|
|
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
|
|
3226
|
-
* `
|
|
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
|
-
|
|
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
|
-
*
|
|
3330
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
3339
|
-
*
|
|
3340
|
-
*
|
|
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
|
-
|
|
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
|
|
3350
|
-
|
|
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
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
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(
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
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:
|
|
3370
|
-
? messages
|
|
3371
|
-
: [
|
|
3372
|
-
this.transloco.translate('workCenter.actions.validationBlocked'),
|
|
3373
|
-
]),
|
|
3366
|
+
error: this.blockedError(validation, failures),
|
|
3374
3367
|
};
|
|
3375
3368
|
}
|
|
3376
|
-
if (
|
|
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
|
-
? {
|
|
3375
|
+
? {
|
|
3376
|
+
status: 'proceed',
|
|
3377
|
+
validationWarningsToken: readNonEmptyString(validation.validationWarningsToken),
|
|
3378
|
+
}
|
|
3382
3379
|
: { status: 'cancelled' };
|
|
3383
3380
|
}
|
|
3384
|
-
async runValidation(
|
|
3385
|
-
const response = await firstValueFrom(this.
|
|
3386
|
-
.
|
|
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
|
-
*
|
|
3411
|
-
*
|
|
3412
|
-
*
|
|
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
|
|
3415
|
-
const
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
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
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
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
|
|
3454
|
-
return
|
|
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
|
-
/**
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
3510
|
+
(await this.validation.recover(action, result.error, submittedPayload, defaultAfterSuccess)) ?? result;
|
|
3514
3511
|
}
|
|
3515
3512
|
return result;
|
|
3516
3513
|
}
|
|
3517
|
-
executeFormAction(action, defaultAfterSuccess,
|
|
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
|
|
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 {
|