@elasticias/screens 1.0.49 → 1.0.50
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.
|
@@ -4,7 +4,7 @@ import { PersistedState, StorageUtils, AppUtils, CsvUtils } from '@elasticias/ut
|
|
|
4
4
|
import { Subject, combineLatest, take, from, merge, map, distinctUntilChanged } from 'rxjs';
|
|
5
5
|
import { Permissions } from '@elasticias/types';
|
|
6
6
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
7
|
-
import { CacheService, ToastService, ConfirmDialogService, EfShortcutService } from '@elasticias/core';
|
|
7
|
+
import { CacheService, ToastService, ConfirmDialogService, EfShortcutService, isServerReported, extractProblem } from '@elasticias/core';
|
|
8
8
|
import { Location } from '@angular/common';
|
|
9
9
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
10
10
|
import { TranslateService } from '@ngx-translate/core';
|
|
@@ -1575,6 +1575,7 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
|
|
|
1575
1575
|
this.totalCount.set(result?.totalCount ?? 0);
|
|
1576
1576
|
this.cacheService.setCache(this.screenStateKey, this.criteria());
|
|
1577
1577
|
this.loading.set(false);
|
|
1578
|
+
this.afterSearch(result);
|
|
1578
1579
|
},
|
|
1579
1580
|
error: (err) => {
|
|
1580
1581
|
if (seq !== this.searchSeq)
|
|
@@ -1585,6 +1586,15 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
|
|
|
1585
1586
|
},
|
|
1586
1587
|
});
|
|
1587
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Hook for a screen whose search returns more than a page of rows (counts,
|
|
1591
|
+
* facets, a summary): called with the raw response after `items` and
|
|
1592
|
+
* `totalCount` are set, for the latest issued search only. A stale or
|
|
1593
|
+
* failed search never reaches it.
|
|
1594
|
+
*/
|
|
1595
|
+
afterSearch(_result) {
|
|
1596
|
+
/* no-op by default */
|
|
1597
|
+
}
|
|
1588
1598
|
/* ── Convenience criteria mutators ───────────────────────────── */
|
|
1589
1599
|
setSearchText(text) {
|
|
1590
1600
|
// Templates bind this to ef-smart-bar's (search), and that binding has
|
|
@@ -2224,14 +2234,7 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
|
|
|
2224
2234
|
}
|
|
2225
2235
|
this.loadSub?.unsubscribe();
|
|
2226
2236
|
this.loadSub = this.serviceInstance.get(id).subscribe({
|
|
2227
|
-
next: (result) =>
|
|
2228
|
-
const vm = new ViewModelEntity(result);
|
|
2229
|
-
vm.id = id;
|
|
2230
|
-
this.entity.set(vm);
|
|
2231
|
-
this.loading.set(false);
|
|
2232
|
-
this.afterLoad();
|
|
2233
|
-
this.loadAuditHistory();
|
|
2234
|
-
},
|
|
2237
|
+
next: (result) => this.applyLoaded(result, id),
|
|
2235
2238
|
error: (err) => {
|
|
2236
2239
|
console.error('AbstractDetailScreenV2.loadData failed', err);
|
|
2237
2240
|
this.errorMsg.set(err?.message ?? 'Erreur de chargement');
|
|
@@ -2246,6 +2249,15 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
|
|
|
2246
2249
|
},
|
|
2247
2250
|
});
|
|
2248
2251
|
}
|
|
2252
|
+
/** Install a fetched (or save-returned) record as the current entity. */
|
|
2253
|
+
applyLoaded(result, id) {
|
|
2254
|
+
const vm = new ViewModelEntity(result);
|
|
2255
|
+
vm.id = id;
|
|
2256
|
+
this.entity.set(vm);
|
|
2257
|
+
this.loading.set(false);
|
|
2258
|
+
this.afterLoad();
|
|
2259
|
+
this.loadAuditHistory();
|
|
2260
|
+
}
|
|
2249
2261
|
/**
|
|
2250
2262
|
* Persist the entity. Routes to `service.create()` when in new
|
|
2251
2263
|
* or duplicate mode, `service.update(id, entity)` when editing.
|
|
@@ -2287,9 +2299,44 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
|
|
|
2287
2299
|
this.setFormErrors(response.errors);
|
|
2288
2300
|
this.onSaveError(response.errors);
|
|
2289
2301
|
}
|
|
2302
|
+
if (!isCreate && this.isStaleSave(response))
|
|
2303
|
+
this.offerStaleReload();
|
|
2290
2304
|
},
|
|
2291
2305
|
});
|
|
2292
2306
|
}
|
|
2307
|
+
/**
|
|
2308
|
+
* True when a failed save means the record changed since it was loaded:
|
|
2309
|
+
* a refusal whose `ruleCode` is in the config's `STALE_RULE_CODES`. A bare
|
|
2310
|
+
* 409 is a duplicate (e.g. a slug already taken), not a stale record, so
|
|
2311
|
+
* it never offers a reload. Only a refusal
|
|
2312
|
+
* the app's interceptor already toasted counts, so the user is never
|
|
2313
|
+
* told twice.
|
|
2314
|
+
*/
|
|
2315
|
+
isStaleSave(error) {
|
|
2316
|
+
if (!isServerReported(error))
|
|
2317
|
+
return false;
|
|
2318
|
+
const problem = extractProblem(error);
|
|
2319
|
+
if (!problem)
|
|
2320
|
+
return false;
|
|
2321
|
+
const codes = this.getConfig()?.STALE_RULE_CODES ?? [];
|
|
2322
|
+
return !!problem.ruleCode && codes.includes(problem.ruleCode);
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Asks, in an accessible confirm dialog (focus trapped, Escape cancels),
|
|
2326
|
+
* whether to reload the record. Accepting discards the unsaved edits,
|
|
2327
|
+
* and the message says so. The refusal itself was already toasted.
|
|
2328
|
+
* App keys: `ef_stale_reload` (button), `ef_stale_reload_confirm`
|
|
2329
|
+
* (message) and optionally `ef_stale_reload_header`.
|
|
2330
|
+
*/
|
|
2331
|
+
offerStaleReload() {
|
|
2332
|
+
this.confirmDialogService.confirm({
|
|
2333
|
+
messageKey: 'ef_stale_reload_confirm',
|
|
2334
|
+
headerKey: 'ef_stale_reload_header',
|
|
2335
|
+
acceptLabelKey: 'ef_stale_reload',
|
|
2336
|
+
icon: 'pi pi-refresh',
|
|
2337
|
+
accept: () => this.loadData(),
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2293
2340
|
/**
|
|
2294
2341
|
* Soft-delete-then-navigate-back. Confirms with the user first.
|
|
2295
2342
|
*/
|
|
@@ -2452,9 +2499,24 @@ class AbstractDetailScreenV2 extends AbstractScreenComponent {
|
|
|
2452
2499
|
this.router.navigate([this.detailsBaseUrl(), newId]);
|
|
2453
2500
|
}
|
|
2454
2501
|
else {
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2502
|
+
if (cfg?.RELOAD_AFTER_SAVE) {
|
|
2503
|
+
// The record carries server-owned state (a concurrency
|
|
2504
|
+
// revision): take the fresh copy. An update response that is
|
|
2505
|
+
// the saved record itself (same id) is used as is, anything
|
|
2506
|
+
// else (void, id, flag) triggers one GET. Both refresh history.
|
|
2507
|
+
const id = this.entityId();
|
|
2508
|
+
if (result && typeof result === 'object' && String(result.id) === String(id)) {
|
|
2509
|
+
this.applyLoaded(result, id);
|
|
2510
|
+
}
|
|
2511
|
+
else {
|
|
2512
|
+
this.loadData();
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
else {
|
|
2516
|
+
// Update succeeded in place — refresh the change-history so the new
|
|
2517
|
+
// entry shows without a manual reload.
|
|
2518
|
+
this.loadAuditHistory();
|
|
2519
|
+
}
|
|
2458
2520
|
}
|
|
2459
2521
|
}
|
|
2460
2522
|
/** Override to surface form-level validation errors after a 4xx
|
|
@@ -2811,6 +2873,24 @@ class ScreenConfig {
|
|
|
2811
2873
|
* to opt out. Must match the backend IAuditable.AuditEntityType (e.g. 'Client').
|
|
2812
2874
|
*/
|
|
2813
2875
|
AUDIT_ENTITY_TYPE;
|
|
2876
|
+
/**
|
|
2877
|
+
* Detail screens: business-rule codes (422 `ruleCode`) meaning "the record
|
|
2878
|
+
* changed since you loaded it", e.g. ERP `['BR_SO_0123']`. A failed update
|
|
2879
|
+
* with one of them offers a "reload" confirm; accepting
|
|
2880
|
+
* reloads the record and drops the unsaved edits. The app must define the
|
|
2881
|
+
* i18n keys `ef_stale_reload` (button), `ef_stale_reload_confirm` (message,
|
|
2882
|
+
* say that unsaved edits are lost) and `ef_stale_reload_header`.
|
|
2883
|
+
*/
|
|
2884
|
+
STALE_RULE_CODES;
|
|
2885
|
+
/**
|
|
2886
|
+
* Detail screens: after a successful in-place update (not a create or
|
|
2887
|
+
* duplicate) refresh the record, so server-owned fields such as a
|
|
2888
|
+
* concurrency revision are current for the next save. Uses the update
|
|
2889
|
+
* response when it is the saved record (an object whose `id` is the
|
|
2890
|
+
* screen's entity id), otherwise one `get(id)`. Change history reloads
|
|
2891
|
+
* either way. Default false: the record is left as edited.
|
|
2892
|
+
*/
|
|
2893
|
+
RELOAD_AFTER_SAVE;
|
|
2814
2894
|
/**
|
|
2815
2895
|
* Report screens (AbstractReportScreenV2): starting period preset for the
|
|
2816
2896
|
* screen's ef-datepicker-advanced. Unset → 'last_30_days'.
|