@tsi-developpement/tsi-shared-ui 1.10.6 → 1.10.8

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.
@@ -3139,6 +3139,39 @@ function lowerFirst(value) {
3139
3139
  return '';
3140
3140
  return value.charAt(0).toLowerCase() + value.slice(1);
3141
3141
  }
3142
+ /**
3143
+ * Mirrors .NET's `JsonNamingPolicy.CamelCase`, which the API applies to a custom column's `Nom`
3144
+ * when it writes that column's value onto each row. Use this — not `lowerFirst` — whenever a
3145
+ * client-side field name has to line up with a server-serialized property name.
3146
+ *
3147
+ * `lowerFirst` is NOT equivalent: .NET lowercases the whole leading uppercase run, so `TVA` gives
3148
+ * `tva` where `lowerFirst` gives `tVA` and the value lookup misses.
3149
+ *
3150
+ * Assumes no separator characters, which `Nom` is validated against (`^[A-Za-z][A-Za-z0-9]*$`).
3151
+ * Note the "next char is not uppercase" test is true for DIGITS too, exactly as in .NET
3152
+ * (`AB2c` -> `aB2c`).
3153
+ *
3154
+ * @example
3155
+ * toDotNetCamelCase('Matricule2'); // 'matricule2'
3156
+ * toDotNetCamelCase('TVA'); // 'tva'
3157
+ * toDotNetCamelCase('ABCValue'); // 'abcValue'
3158
+ * toDotNetCamelCase('XMLHttp'); // 'xmlHttp'
3159
+ */
3160
+ function toDotNetCamelCase(value) {
3161
+ if (!value)
3162
+ return '';
3163
+ const isUpper = (c) => c !== c.toLowerCase() && c === c.toUpperCase();
3164
+ const chars = value.split('');
3165
+ for (let i = 0; i < chars.length; i++) {
3166
+ if (i === 1 && !isUpper(chars[i]))
3167
+ break;
3168
+ const hasNext = i + 1 < chars.length;
3169
+ if (i > 0 && hasNext && !isUpper(chars[i + 1]))
3170
+ break;
3171
+ chars[i] = chars[i].toLowerCase();
3172
+ }
3173
+ return chars.join('');
3174
+ }
3142
3175
 
3143
3176
  /**
3144
3177
  * Numeric helpers that avoid IEEE 754 floating-point accumulation errors.
@@ -10863,8 +10896,12 @@ class TsiGenericGridComponent {
10863
10896
  }
10864
10897
  set columns(value) {
10865
10898
  if (value && Array.isArray(value)) {
10899
+ // Captured before `_tempColumns` is refreshed below, otherwise nothing looks "late".
10900
+ const knownFields = new Set(this._tempColumns.map(c => c.field));
10866
10901
  this._columns = value;
10867
10902
  this.initialColumnsProcess();
10903
+ this._tempColumns = this.columns;
10904
+ this.adoptLateColumns(knownFields);
10868
10905
  }
10869
10906
  }
10870
10907
  get columns() {
@@ -11085,6 +11122,9 @@ class TsiGenericGridComponent {
11085
11122
  this.tempSearchFields = [];
11086
11123
  this._columns = [];
11087
11124
  this._kanbanSummaryColumns = [];
11125
+ /** Set at the end of ngOnInit. Before that the column pools are still being seeded, so nothing
11126
+ * arriving through the `columns` setter can be considered "late". */
11127
+ this._columnsInitialized = false;
11088
11128
  this._isSearchButtonDisabled = false;
11089
11129
  this.filteredColumns = [];
11090
11130
  this.showSearchBox = true;
@@ -11193,6 +11233,7 @@ class TsiGenericGridComponent {
11193
11233
  this.updatePrimeNGTableFilters();
11194
11234
  this._rtlModeSubscribe();
11195
11235
  this.extractDialogConfigData();
11236
+ this._columnsInitialized = true;
11196
11237
  }
11197
11238
  getStatusOption(value) {
11198
11239
  return this.statusMetadata?.options.find(o => o.statusValue === value);
@@ -11301,10 +11342,8 @@ class TsiGenericGridComponent {
11301
11342
  this.dt.tableWidthState = state.tableWidth;
11302
11343
  let columnOrder = state.columnOrder;
11303
11344
  if (columnOrder) {
11304
- let reorderedColumns = this.getReorderedColumns(columnOrder);
11305
- this.selectedColumns = reorderedColumns;
11306
- this.columns = reorderedColumns;
11307
- this.sortColumns();
11345
+ this._restoredColumnOrder = columnOrder;
11346
+ this.applyColumnOrder(columnOrder);
11308
11347
  }
11309
11348
  // Apply the remembered column widths to the DOM. Assigning columnWidthsState
11310
11349
  // alone is a no-op here because the grid is not PrimeNG-"stateful"
@@ -11312,6 +11351,18 @@ class TsiGenericGridComponent {
11312
11351
  this.dt.restoreColumnWidths();
11313
11352
  }
11314
11353
  }
11354
+ /**
11355
+ * Renders exactly the saved columns, in the saved order. `_columns` is assigned directly rather
11356
+ * than through the setter: every element has already been through `initialColumnsProcess()`, and
11357
+ * the setter would re-enter `adoptLateColumns()` and narrow `_tempColumns` — the full pool
11358
+ * `findColumnByKey` needs to resolve a saved key.
11359
+ */
11360
+ applyColumnOrder(columnOrder) {
11361
+ const reordered = this.getReorderedColumns(columnOrder);
11362
+ this.selectedColumns = reordered;
11363
+ this._columns = reordered;
11364
+ this.sortColumns();
11365
+ }
11315
11366
  onSelectedColumnsChange(newValue) {
11316
11367
  this.selectedColumns = newValue;
11317
11368
  this.sortColumns();
@@ -11338,6 +11389,34 @@ class TsiGenericGridComponent {
11338
11389
  ...otherColumns,
11339
11390
  ];
11340
11391
  }
11392
+ /**
11393
+ * The owner can append columns after init — custom columns arrive on the
11394
+ * `getColonnesPersonnalisee` response. The `columns` setter refreshes the chooser pool
11395
+ * (`_columns`) and the lookup pool (`_tempColumns`), but the rendered selection
11396
+ * (`_selectedColumns`) is a copy taken at init, so without this a late column shows up in the
11397
+ * column chooser and never renders.
11398
+ */
11399
+ adoptLateColumns(knownFields) {
11400
+ if (!this._columnsInitialized)
11401
+ return;
11402
+ const lateColumns = this._columns.filter(c => !knownFields.has(c.field));
11403
+ if (lateColumns.length === 0)
11404
+ return;
11405
+ // An undefined order sorts as 0 and would jump such a column to the front of applyEntityInfo()'s
11406
+ // reorder, so give it an explicit trailing order.
11407
+ const maxOrder = this._tempColumns.reduce((max, c) => Math.max(max, c.order ?? 0), 0);
11408
+ lateColumns.forEach((col, index) => col.order ??= maxOrder + 1 + index);
11409
+ if (this._restoredColumnOrder) {
11410
+ // A saved presentation decides what is visible: re-apply it now that the column exists, so it
11411
+ // renders at its saved position if the layout named it, and stays hidden if it did not.
11412
+ this.applyColumnOrder(this._restoredColumnOrder);
11413
+ }
11414
+ else {
11415
+ // No saved presentation — the default is "every column visible".
11416
+ this.selectedColumns = [...this.selectedColumns, ...lateColumns];
11417
+ this.sortColumns();
11418
+ }
11419
+ }
11341
11420
  getReorderedColumns(columnOrder) {
11342
11421
  const important = this.importantColumns
11343
11422
  .filter(key => columnOrder.includes(key))
@@ -11369,6 +11448,8 @@ class TsiGenericGridComponent {
11369
11448
  this.dt.tableWidthState = state.tableWidth;
11370
11449
  let columnOrder = state.columnOrder;
11371
11450
  if (columnOrder) {
11451
+ // Memoized so a column arriving mid-preview follows the previewed layout.
11452
+ this._restoredColumnOrder = columnOrder;
11372
11453
  let reorderedColumns = this.getReorderedColumns(columnOrder);
11373
11454
  this.selectedColumns = reorderedColumns;
11374
11455
  this._columns = this._tempColumns;
@@ -14301,6 +14382,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
14301
14382
  }]
14302
14383
  }], ctorParameters: () => [{ type: i1.HttpClient }] });
14303
14384
 
14385
+ /**
14386
+ * `ColonnePersonnalisee.Type` (the `TypeColonneEnum` values) -> grid display type. Setting a real
14387
+ * display type is what lets the grid derive `filterInputType`, `isNumeric` and `numOfDecimal` on its
14388
+ * own and render the cell like a native column.
14389
+ *
14390
+ * Shared by both metadata sources: the grid response now reports this same raw vocabulary, so there
14391
+ * is nothing left to translate between them.
14392
+ */
14393
+ const CUSTOM_COLUMN_DISPLAY_TYPES = {
14394
+ INT: DisplayTypes.INTEGER_DISPLAY,
14395
+ DECIMAL: DisplayTypes.DECIMAL_DISPLAY,
14396
+ FLOAT: DisplayTypes.DECIMAL_DISPLAY,
14397
+ BOOLEAN: DisplayTypes.CHECKBOX_DISPLAY,
14398
+ STRING: DisplayTypes.TEXT_DISPLAY,
14399
+ BINAIRE: DisplayTypes.TEXT_DISPLAY,
14400
+ // Read path only — DATE has no storage column and no form input.
14401
+ DATE: DisplayTypes.DATE_DISPLAY,
14402
+ DATETIME: DisplayTypes.DATE_DISPLAY,
14403
+ };
14304
14404
  class TsiGenericCrudComponent {
14305
14405
  //#endregion
14306
14406
  //#region Inputs
@@ -14333,27 +14433,12 @@ class TsiGenericCrudComponent {
14333
14433
  }
14334
14434
  set businessClass(value) {
14335
14435
  this._businessClass = value;
14436
+ // A different entity means a different set of custom columns.
14437
+ this._customColumnsRegistered = false;
14336
14438
  if (this._businessClass) {
14337
14439
  this.getWorkflowEtatFormForCurrentBusinessClass();
14338
14440
  this._colonnePersonnaliseeService.getColonnesPersonnalisee(this._businessClass).subscribe({
14339
- next: (res) => {
14340
- let ccs = res.flatMap(r => r.colonnes);
14341
- console.log(ccs);
14342
- if (this.isCustomColumnRendered == false && ccs && ccs.length > 0) {
14343
- // Add custom columns to the grid
14344
- ccs.forEach(col => {
14345
- let customColumnGrid = {
14346
- header: col.libelle,
14347
- field: col.nom,
14348
- filterInputType: col.type,
14349
- isCustomColumn: true,
14350
- translatedHeader: col.libelle,
14351
- };
14352
- this.selectedColumns.push(customColumnGrid);
14353
- });
14354
- this.isCustomColumnRendered = true;
14355
- }
14356
- },
14441
+ next: (res) => this._registerCustomColumnsFromDefinitions(res.flatMap(r => r.colonnes)),
14357
14442
  });
14358
14443
  }
14359
14444
  }
@@ -14435,6 +14520,11 @@ class TsiGenericCrudComponent {
14435
14520
  this.selectionMode = GridSelectionMode.None;
14436
14521
  this.selectedItems = [];
14437
14522
  this.key = 'uid';
14523
+ /**
14524
+ * Custom columns are the same set whichever source delivers them, so they are registered exactly
14525
+ * once. Latched by whichever source gets there first; reset when the entity changes.
14526
+ */
14527
+ this._customColumnsRegistered = false;
14438
14528
  this.showGlobalSearch = true;
14439
14529
  this.showExportButton = true;
14440
14530
  this.showReporting = true;
@@ -14445,7 +14535,6 @@ class TsiGenericCrudComponent {
14445
14535
  this.buttonColumnClicked = new EventEmitter();
14446
14536
  this.onImportFromOutlookClicked = new EventEmitter();
14447
14537
  //#endregion Outputs
14448
- this.isCustomColumnRendered = false;
14449
14538
  this.clickCount = 1;
14450
14539
  this.currentIcon = "pi pi-align-justify";
14451
14540
  this.currentTooltip = "shared_tsi_component_grid_list_view";
@@ -14501,192 +14590,7 @@ class TsiGenericCrudComponent {
14501
14590
  if (this.columns == null && this.columns == undefined) {
14502
14591
  throw new Error('columns cant be null or undefined, developper must set columns of grid !');
14503
14592
  }
14504
- this.selectedColumns = [...this.columns];
14505
- if (this.showInfoButton == true) {
14506
- this.selectedColumns = [
14507
- {
14508
- header: 'shared_actions_info',
14509
- field: 'info',
14510
- isButton: true,
14511
- buttonIcon: 'pi pi-info',
14512
- styleClass: 'p-button-text hover:shadow-8',
14513
- isFrozen: true,
14514
- order: 3,
14515
- toolTipText: 'shared_actions_info',
14516
- isIconButton: true,
14517
- actionDisplay: ActionPlacement.Overflow,
14518
- },
14519
- ...this.selectedColumns,
14520
- ];
14521
- }
14522
- if (this.showDownloadButton == true) {
14523
- this.selectedColumns = [
14524
- {
14525
- header: 'shared_actions_download',
14526
- field: 'download',
14527
- isButton: true,
14528
- buttonIcon: 'pi pi-download',
14529
- styleClass: 'p-button-text hover:shadow-8',
14530
- isFrozen: true,
14531
- order: 2,
14532
- toolTipText: 'Télécharger',
14533
- isIconButton: true,
14534
- actionDisplay: ActionPlacement.Overflow,
14535
- },
14536
- ...this.selectedColumns,
14537
- ];
14538
- }
14539
- if (this.showUploadButton == true) {
14540
- this.selectedColumns = [
14541
- {
14542
- header: 'shared_actions_attachment',
14543
- field: 'upload',
14544
- isButton: true,
14545
- buttonIcon: 'pi pi-paperclip',
14546
- styleClass: 'p-button-text hover:shadow-8',
14547
- isFrozen: true,
14548
- order: 4,
14549
- toolTipText: 'shared_actions_attachment',
14550
- isIconButton: true,
14551
- actionDisplay: ActionPlacement.Overflow,
14552
- },
14553
- ...this.selectedColumns,
14554
- ];
14555
- }
14556
- if (this.showNoteButton) {
14557
- this.selectedColumns = [
14558
- {
14559
- header: 'shared_actions_notes',
14560
- buttonLabel: '',
14561
- field: 'notes',
14562
- isButton: true,
14563
- buttonIcon: 'pi pi-comment',
14564
- styleClass: 'p-button-text hover:shadow-8',
14565
- isFrozen: true,
14566
- order: 5,
14567
- toolTipText: 'shared_actions_notes',
14568
- isIconButton: true,
14569
- actionDisplay: ActionPlacement.Overflow,
14570
- },
14571
- ...this.selectedColumns,
14572
- ];
14573
- }
14574
- if (this.showActionButton) {
14575
- this.selectedColumns = [
14576
- {
14577
- header: 'shared_actions_action',
14578
- buttonLabel: '',
14579
- field: 'ActionERP',
14580
- isButton: true,
14581
- buttonIcon: 'pi pi-bolt',
14582
- styleClass: 'p-button-text hover:shadow-8',
14583
- isFrozen: true,
14584
- order: 10,
14585
- toolTipText: 'shared_actions_action',
14586
- isIconButton: true,
14587
- actionDisplay: ActionPlacement.Overflow,
14588
- },
14589
- ...this.selectedColumns,
14590
- ];
14591
- }
14592
- if (this.showFormButton) {
14593
- this.selectedColumns = [
14594
- {
14595
- header: 'shared_actions_form',
14596
- field: 'form',
14597
- isButton: true,
14598
- buttonIcon: 'pi pi-book',
14599
- styleClass: 'p-button-text hover:shadow-8',
14600
- iconStyleClass: 'text-indigo-500',
14601
- isFrozen: true,
14602
- order: 8,
14603
- toolTipText: 'shared_actions_form',
14604
- isIconButton: true,
14605
- actionDisplay: ActionPlacement.Overflow,
14606
- },
14607
- ...this.selectedColumns,
14608
- ];
14609
- }
14610
- if (this.showDeleteButton == true) {
14611
- this.selectedColumns = [
14612
- {
14613
- header: 'shared_actions_delete',
14614
- field: 'delete',
14615
- isButton: true,
14616
- buttonIcon: 'pi pi-trash',
14617
- styleClass: 'p-button-text hover:shadow-8 p-button-danger',
14618
- iconStyleClass: 'text-red-500',
14619
- isFrozen: true,
14620
- order: 1,
14621
- toolTipText: 'Delete',
14622
- buttonType: ButtonType.Submit,
14623
- isIconButton: true,
14624
- actionDisplay: ActionPlacement.Overflow,
14625
- },
14626
- ...this.selectedColumns,
14627
- ];
14628
- }
14629
- if (this.showEditButton == true) {
14630
- this.selectedColumns = [
14631
- {
14632
- header: 'shared_actions_edit',
14633
- field: 'edit',
14634
- isButton: true,
14635
- buttonIcon: 'pi pi-pencil',
14636
- styleClass: 'p-button-text hover:shadow-8',
14637
- isFrozen: true,
14638
- order: 6,
14639
- toolTipText: 'shared_actions_edit',
14640
- buttonType: ButtonType.Submit,
14641
- isIconButton: true,
14642
- actionDisplay: ActionPlacement.Inline
14643
- },
14644
- ...this.selectedColumns,
14645
- ];
14646
- }
14647
- if (this.showConsultButton == true) {
14648
- this.selectedColumns = [
14649
- {
14650
- header: 'shared_actions_consult',
14651
- field: 'consult',
14652
- isButton: true,
14653
- buttonIcon: 'pi pi-eye',
14654
- styleClass: 'p-button-text hover:shadow-8',
14655
- iconStyleClass: 'text-blue-500',
14656
- isFrozen: true,
14657
- order: 0,
14658
- toolTipText: 'Consulter',
14659
- isIconButton: true,
14660
- actionDisplay: ActionPlacement.Overflow,
14661
- },
14662
- ...this.selectedColumns,
14663
- ];
14664
- }
14665
- if (this.showDuplicateButton == true) {
14666
- this.selectedColumns = [
14667
- {
14668
- header: 'shared_actions_duplicate',
14669
- field: 'duplicate',
14670
- isButton: true,
14671
- buttonIcon: 'pi pi-clone',
14672
- styleClass: 'p-button-text hover:shadow-8',
14673
- iconStyleClass: 'text-purple-500',
14674
- isFrozen: true,
14675
- order: 7,
14676
- toolTipText: 'Dupliquer',
14677
- isIconButton: true,
14678
- actionDisplay: ActionPlacement.Overflow,
14679
- },
14680
- ...this.selectedColumns,
14681
- ];
14682
- }
14683
- this.selectedColumns = [
14684
- ...this.selectedColumns
14685
- .filter(col => col.order !== undefined)
14686
- .sort((a, b) => a.order - b.order),
14687
- ...this.selectedColumns.filter(col => col.order === undefined),
14688
- ];
14689
- this.selectedColumns.map((column) => (column.translatedHeader = column.header ? this.localize.transform(column.header) : column.header));
14593
+ this._initSelectedColumns();
14690
14594
  this.viewOptions = [
14691
14595
  {
14692
14596
  label: this.localize.transform('shared_tsi_component_grid_list_view'),
@@ -14711,6 +14615,92 @@ class TsiGenericCrudComponent {
14711
14615
  ];
14712
14616
  this._initModels();
14713
14617
  }
14618
+ _initSelectedColumns() {
14619
+ // Each entry is [enabled flag, column definition]
14620
+ const actionColumns = [
14621
+ [this.showConsultButton, {
14622
+ header: 'shared_actions_consult', field: 'consult', isButton: true,
14623
+ buttonIcon: 'pi pi-eye', styleClass: 'p-button-text hover:shadow-8',
14624
+ iconStyleClass: 'text-blue-500', isFrozen: true, order: 0,
14625
+ toolTipText: 'Consulter', isIconButton: true,
14626
+ actionDisplay: ActionPlacement.Overflow,
14627
+ }],
14628
+ [this.showDeleteButton, {
14629
+ header: 'shared_actions_delete', field: 'delete', isButton: true,
14630
+ buttonIcon: 'pi pi-trash', styleClass: 'p-button-text hover:shadow-8 p-button-danger',
14631
+ iconStyleClass: 'text-red-500', isFrozen: true, order: 1,
14632
+ toolTipText: 'Delete', buttonType: ButtonType.Submit, isIconButton: true,
14633
+ actionDisplay: ActionPlacement.Overflow,
14634
+ }],
14635
+ [this.showDownloadButton, {
14636
+ header: 'shared_actions_download', field: 'download', isButton: true,
14637
+ buttonIcon: 'pi pi-download', styleClass: 'p-button-text hover:shadow-8',
14638
+ isFrozen: true, order: 2, toolTipText: 'Télécharger', isIconButton: true,
14639
+ actionDisplay: ActionPlacement.Overflow,
14640
+ }],
14641
+ [this.showInfoButton, {
14642
+ header: 'shared_actions_info', field: 'info', isButton: true,
14643
+ buttonIcon: 'pi pi-info', styleClass: 'p-button-text hover:shadow-8',
14644
+ isFrozen: true, order: 3, toolTipText: 'shared_actions_info',
14645
+ isIconButton: true, actionDisplay: ActionPlacement.Overflow,
14646
+ }],
14647
+ [this.showUploadButton, {
14648
+ header: 'shared_actions_attachment', field: 'upload', isButton: true,
14649
+ buttonIcon: 'pi pi-paperclip', styleClass: 'p-button-text hover:shadow-8',
14650
+ isFrozen: true, order: 4, toolTipText: 'shared_actions_attachment',
14651
+ isIconButton: true, actionDisplay: ActionPlacement.Overflow,
14652
+ }],
14653
+ [this.showNoteButton, {
14654
+ header: 'shared_actions_notes', buttonLabel: '', field: 'notes', isButton: true,
14655
+ buttonIcon: 'pi pi-comment', styleClass: 'p-button-text hover:shadow-8',
14656
+ isFrozen: true, order: 5, toolTipText: 'shared_actions_notes',
14657
+ isIconButton: true, actionDisplay: ActionPlacement.Overflow,
14658
+ }],
14659
+ [this.showEditButton, {
14660
+ header: 'shared_actions_edit', field: 'edit', isButton: true,
14661
+ buttonIcon: 'pi pi-pencil', styleClass: 'p-button-text hover:shadow-8',
14662
+ isFrozen: true, order: 6, toolTipText: 'shared_actions_edit',
14663
+ buttonType: ButtonType.Submit, isIconButton: true,
14664
+ actionDisplay: ActionPlacement.Inline,
14665
+ }],
14666
+ [this.showDuplicateButton, {
14667
+ header: 'shared_actions_duplicate', field: 'duplicate', isButton: true,
14668
+ buttonIcon: 'pi pi-clone', styleClass: 'p-button-text hover:shadow-8',
14669
+ iconStyleClass: 'text-purple-500', isFrozen: true, order: 7,
14670
+ toolTipText: 'Dupliquer', isIconButton: true,
14671
+ actionDisplay: ActionPlacement.Overflow,
14672
+ }],
14673
+ [this.showFormButton, {
14674
+ header: 'shared_actions_form', field: 'form', isButton: true,
14675
+ buttonIcon: 'pi pi-book', styleClass: 'p-button-text hover:shadow-8',
14676
+ iconStyleClass: 'text-indigo-500', isFrozen: true, order: 8,
14677
+ toolTipText: 'shared_actions_form', isIconButton: true,
14678
+ actionDisplay: ActionPlacement.Overflow,
14679
+ }],
14680
+ [this.showActionButton, {
14681
+ header: 'shared_actions_action', buttonLabel: '', field: 'ActionERP', isButton: true,
14682
+ buttonIcon: 'pi pi-bolt', styleClass: 'p-button-text hover:shadow-8',
14683
+ isFrozen: true, order: 10, toolTipText: 'shared_actions_action',
14684
+ isIconButton: true, actionDisplay: ActionPlacement.Overflow,
14685
+ }],
14686
+ ];
14687
+ const buttons = actionColumns
14688
+ .filter(([enabled]) => enabled)
14689
+ .map(([, column]) => column);
14690
+ const allColumns = [...buttons, ...this.columns];
14691
+ // Ordered columns first (sorted by order), then the rest — assigned once
14692
+ this.selectedColumns = [
14693
+ ...allColumns
14694
+ .filter(col => col.order !== undefined)
14695
+ .sort((a, b) => a.order - b.order),
14696
+ ...allColumns.filter(col => col.order === undefined),
14697
+ ].map(column => ({
14698
+ ...column,
14699
+ translatedHeader: column.header
14700
+ ? this.localize.transform(column.header)
14701
+ : column.header,
14702
+ }));
14703
+ }
14714
14704
  /* toggleHeaderFilters(){
14715
14705
  if(this.showHeaderFilters === true){
14716
14706
  this.showHeaderFilters = false;
@@ -14941,18 +14931,13 @@ class TsiGenericCrudComponent {
14941
14931
  if (this.showNoteButton) {
14942
14932
  this.loadNotesCounts();
14943
14933
  }
14944
- if (this.isCustomColumnRendered == false && value.customColumnSchema && value.customColumnSchema.length > 0) {
14945
- // Add custom columns to the grid
14946
- value.customColumnSchema.forEach(ccs => {
14947
- let customColumnGrid = {
14948
- header: ccs.columnName,
14949
- field: ccs.columnName,
14950
- filterInputType: ccs.type,
14951
- isCustomColumn: true
14952
- };
14953
- this.selectedColumns.push(customColumnGrid);
14954
- });
14955
- this.isCustomColumnRendered = true;
14934
+ // Second source, equivalent to the definitions and the only one that reaches screens
14935
+ // they cannot serve: those that never set `businessClass`, and those whose
14936
+ // `businessClass` is a route segment or view name rather than an entity class name, so
14937
+ // it does not match `ColonnePersonnalisee.Classe`. The schema is derived server-side
14938
+ // from `typeof(TEntity).Name`, so it always matches. Whichever source answers first wins.
14939
+ if (!this._customColumnsRegistered && value.customColumnSchema?.length) {
14940
+ this._registerCustomColumnsFromSchema(value.customColumnSchema);
14956
14941
  }
14957
14942
  }
14958
14943
  this.page.loading = false;
@@ -14965,6 +14950,52 @@ class TsiGenericCrudComponent {
14965
14950
  });
14966
14951
  }
14967
14952
  }
14953
+ /** Definitions source — reached wherever `businessClass` matches `ColonnePersonnalisee.Classe`. */
14954
+ _registerCustomColumnsFromDefinitions(definitions) {
14955
+ this._registerCustomColumns(definitions.map(cp => ({
14956
+ // The API camelCases `Nom` when it writes the column's value onto each row, so the field has
14957
+ // to follow the same policy or the cell lookup misses.
14958
+ field: toDotNetCamelCase(cp.nom),
14959
+ header: cp.libelle,
14960
+ translatedHeader: cp.libelle, // a user-typed label is not a translation key
14961
+ type: CUSTOM_COLUMN_DISPLAY_TYPES[(cp.type ?? '').toUpperCase()] ?? DisplayTypes.TEXT_DISPLAY,
14962
+ isCustomColumn: true,
14963
+ })));
14964
+ }
14965
+ /**
14966
+ * Grid-response source. `columnName` is camelCased by the same serializer pass that writes the row
14967
+ * properties, and `type` is the same raw vocabulary the definitions report — so this produces an
14968
+ * identical column, not a degraded one.
14969
+ */
14970
+ _registerCustomColumnsFromSchema(schema) {
14971
+ this._registerCustomColumns(schema.map(ccs => ({
14972
+ field: ccs.columnName,
14973
+ header: ccs.columnLibelle ?? ccs.columnName,
14974
+ translatedHeader: ccs.columnLibelle ?? ccs.columnName,
14975
+ type: CUSTOM_COLUMN_DISPLAY_TYPES[(ccs.type ?? '').toUpperCase()] ?? DisplayTypes.TEXT_DISPLAY,
14976
+ isCustomColumn: true,
14977
+ })));
14978
+ }
14979
+ /**
14980
+ * The single registration point — whichever source got here first is the only one that runs.
14981
+ * Both derive `field` with the same camelCase policy, so the dedup is only a guard against a
14982
+ * custom column colliding with a native one, which must never be replaced.
14983
+ *
14984
+ * Assigns a NEW array: an in-place mutation keeps the same reference, so Angular's identity check
14985
+ * never re-invokes the grid's `columns` setter and the column is never rendered.
14986
+ */
14987
+ _registerCustomColumns(candidates) {
14988
+ if (!candidates.length)
14989
+ return;
14990
+ // The source delivered — nothing else should register after this, even if every candidate
14991
+ // turns out to be a duplicate.
14992
+ this._customColumnsRegistered = true;
14993
+ const known = new Set(this.selectedColumns.map(c => c.field?.toLowerCase()));
14994
+ const toAppend = candidates.filter(c => !known.has(c.field?.toLowerCase()));
14995
+ if (!toAppend.length)
14996
+ return;
14997
+ this.selectedColumns = [...this.selectedColumns, ...toAppend];
14998
+ }
14968
14999
  loadNotesCounts() {
14969
15000
  const ids = this.page.items.map(x => x[this.key])
14970
15001
  .filter(x => x !== null && x !== undefined)
@@ -17525,14 +17556,17 @@ class TsiFormComponentBaseComponent extends AppBaseComponent {
17525
17556
  }
17526
17557
  resolveFiche(val, apiResponse) {
17527
17558
  this._inputRegistry.resetDirtyStatusForAllInputs();
17528
- const hasUid = apiResponse?.uid != null;
17529
17559
  const isCreateOrDuplicate = this.mode == Component_Modes.CREATE ||
17530
17560
  this.mode == Component_Modes.DUPLICATE;
17531
17561
  const isEditableMode = isCreateOrDuplicate || this.mode == Component_Modes.EDIT;
17532
- if (hasUid && isEditableMode) {
17533
- this._formStateService.dataSaved$.next(apiResponse.uid);
17534
- }
17535
- if (hasUid && isCreateOrDuplicate) {
17562
+ // Which record was just saved: on create/duplicate the response carries the NEW uid, and
17563
+ // `this.id` is still null (create) or the SOURCE record (duplicate). On edit the update call
17564
+ // returns no body, so `this.id` — filled from the dialog config — is the only source.
17565
+ const savedOccurrence = apiResponse?.uid ?? this.id;
17566
+ if (isEditableMode && savedOccurrence) {
17567
+ this._formStateService.dataSaved$.next(savedOccurrence);
17568
+ }
17569
+ if (isCreateOrDuplicate && apiResponse?.uid != null) {
17536
17570
  this.id = apiResponse.uid;
17537
17571
  this.mode = Component_Modes.EDIT;
17538
17572
  this.cdRef?.detectChanges();
@@ -17555,6 +17589,10 @@ class TsiFormComponentBaseComponent extends AppBaseComponent {
17555
17589
  this.hide(apiResponse);
17556
17590
  }
17557
17591
  async openWorkflow() {
17592
+ if (!this.entityInfo?.isWorkflow) {
17593
+ this._messageInfoService.error('tsi_form_entity_not_marked_as_workflow');
17594
+ return;
17595
+ }
17558
17596
  const descriminator = this.form?.ignoreDescriminator ? undefined : this.descriminatorValue;
17559
17597
  try {
17560
17598
  const match = await firstValueFrom(this._workflowConfigurationService.getWorkflowByClasseAndDescriminator(this.businessClass, descriminator));
@@ -24192,5 +24230,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
24192
24230
  * Generated bundle index. Do not edit.
24193
24231
  */
24194
24232
 
24195
- export { ActionErpService, ActionPlacement, AdDirective, AdItem, AddReportPopupComponent, AdminRoutes, AdministrationEndpoints, AdministrationSegments, AdvancedSearchComponentService, AlertImportMessageComponent, ApiDateTimeFormat, ApiExplorerService, AppBaseComponent, AppRoutes, AppSettingsService, AuthenticationGuard, AuthorizationEndpoints, AuthorizationSegments, AuthorizationService, AuthorizationTokenMapper, AutoComplete, AutoSelectDirective, BsModalRef, BusyDirective, ButtonType, ChooseChoiceMfaMethodComponent, ClaimTypes, CodeFormat, CollectionChangeAction, ComboStatusModel, Component_Modes, ContextMenuComponent, CreateOrEditDynamicFormComponent, CreateOrEditEntityInformationsComponent, CreateOrEditModeleImportComponent, CrudBaseService, CurrentUser, CurrentUserService, CutLabelPipe, DateFormat, DateFormatDisplay, DateHelper, DateHelperService, DatePickerDateFormat, DatePickerDateTimeFormat, DatePickerTimeFormat, DateTimeFormat, DateTimeFormatDisplay, DecimalFormat, DefaultSocieteDevise, Devise, DialogDataService, DisplayNestedPropComponent, DisplayTypeColors, DisplayTypes, DynamicFormListComponent, EditableGridComponent, EndpointInfos, EntityColumnDetailResponse, EntityConfigurationService, EntityInfo, EntityInfoWithRelations, EntityRelation, EntityStatusService, EntityValidationsService, ErrorResponseManagerService, ExecuteImportRequest, ExportExcelHelper, Fichier, FilterByExistingValues, FilterByProprety, FilterHelper, FilterOperatorType, FilterOperatorTypeDescriptions, FormValidationError, FormulaService, GenericFormHelper, GenericValidationStateService, GetWorkflowInstanceInformationRequest, GridSelectionMode, GridSizingMode, GridSortMode, Guid, Helpers, HtmlTemplateGeneratorService, HtmlTemplates, HttpMethodType, IdentityManagerService, IdentityPlatformAuthenticationService, IdentityPlatformEndpoints, IdentityPlatformSegments, IdentityRoutes, ImportExportEndPoints, ImportExportSegments, ImportResult, InputRegistryService, InputTypes, JournalisationApplication, JournalisationApplicationService, KeyboardShortcutService, LanguageInformations, LanguageLocale, LanguagesCodes, LayoutHelperService, LayoutOrientation, LocaleCode, LocalePocComponent, LocalizationKeys, LocalizePipe, LogoutPopupComponent, MFAEnum, MailingService, ManageImportExportComponent, ManageReportingComponent, ManageReportingRoutes, MappingHelper, ModalComponent, ModalLabelPipe, ModalLoaderComponent, ModalSizeEnum, ModeleImportCreateRequest, ModeleImportDetail, ModeleImportService, Modes, ModuleCodeService, ModuleMainParameterDirective, ModuleMainParameterService, NoteService, NumericDisplayTypes, ObjectHelper, ObservableCollection, PagedResultRequestBase, PermissionGuard, PresentationDesignerDirective, PresentationDesignerDirectiveBase, PresentationDesignerService, PresentationDesignerTabPanelDirective, PresentationSetting, PresentationSettingDetail, PresentationSettingService, ProceedLoginRequest, ProgressBarHelperService, ProgressBarResponse, ProgressBarService, ProgressBarUpdateRequest, RecordInfoPopupService, ReportingComponent, ReportingEndpoints, ReportingSegments, ReportingService, RequestTrackerInterceptor, RequestTrackerService, Segment, Segments, SeparateurDecimal, SeparateurMillier, SharedComponentReferenceHelper, SharedModule, SharedUiTsiProviders, ShortenPipe, SpinnerComponent, Status, StatusHistory, StatusHistoryService, StorageManager, SubscriptionResult, TS_ValidDateFormat, TS_ValidDateTimeFormat, TS_ValidTimeFormat, TextBoxType, Themes, TimeFormat, TimeOnly, TimeZoneLocale, ToNumberPipe, TooltipPosition, TranslationService, TsiActionErpCreateOrEditComponent, TsiActionErpListComponent, TsiBaseComponent, TsiBsModalService, TsiBubbleInfoComponent, TsiButtonComponent, TsiCalenderComponent, TsiCard, TsiCardComponent, TsiCardListComponent, TsiChangePasswordComponent, TsiCheckboxComponent, TsiCheckboxDisplayComponent, TsiCodeGeneratorComponent, TsiComponentEnum, TsiComponentFinderService, TsiConfirmationService, TsiCurrencyOtherDisplayComponent, TsiCurrencyOtherInputComponent, TsiCurrencyPipe, TsiCurrencySocieteDisplayComponent, TsiCurrencySocieteInputComponent, TsiDateDisplayComponent, TsiDatePickerComponent, TsiDecimalDisplayComponent, TsiDecimalInputComponent, TsiDecimalPipe, TsiDisplayGridComponent, TsiDividerComponent, TsiErrorMessageComponent, TsiFileManagerComponent, TsiFilePreviewComponent, TsiFormComponent, TsiFormComponentBaseComponent, TsiFormModalFooterComponent, TsiFormulaBoxComponent, TsiGenericCrudComponent, TsiGenericFormComponent, TsiGenericGridComponent, TsiImageDisplayComponent, TsiInfoMessageComponent, TsiInputBase, TsiIntegerComponent, TsiIntegerDisplayComponent, TsiKanbanComponent, TsiKanbanWorkflowEtatComponent, TsiLabelComponent, TsiLabelLegendComponent, TsiLegendComponent, TsiListBaseComponent, TsiLogEventHistoryComponent, TsiMessageService, TsiModalComponent, TsiModalFooterComponent, TsiModalHeaderComponent, TsiModalService, TsiNotesAvertirComponent, TsiNotesComponent, TsiNotesGridComponent, TsiNotificationService, TsiParagraphComponent, TsiPopupTextViewerComponent, TsiProgressBarComponent, TsiRadioButtonComponent, TsiRateDisplayComponent, TsiRateInputComponent, TsiRedirectionMessageComponent, TsiReportType, TsiSearchBoxComponent, TsiSearchComboComponent, TsiSendMailComponent, TsiSubmitButtonComponent, TsiSuccessMessageComponent, TsiTabPanelComponent, TsiTabViewComponent, TsiTemplate, TsiTenantsComponent, TsiTextAreaComponent, TsiTextBoxComponent, TsiTimeDisplayComponent, TsiTimePickerComponent, TsiTooltipComponent, TsiUploadFicheComponent, TsiUploadGridComponent, TsiViewGridComponent, TypeFichier, TypeImportEnum, TypeRegistryHelper, UpdateEntityInfoWithRelationsRequest, UserIdentityService, ValidationDirective, VerificationCodeComponent, VerificationCodeInscriptionComponent, VerificationCodeSmsComponent, WorkflowConfigurationService, WorkflowEtatForm, WorkflowEtatKanbanRequest, WorkflowEtatSuivantResponse, WorkflowResponse, appProviders, authorization, dashboardConst, defaultCardHeader, distinct, executeImportResult, initSharedComponents, isNullOrEmpty, isPinnableCssSize, lengthValidator, locals, logoutReasonEnum, lowerFirst, menuItems, menuPreferences, preciseAdd, preciseSum, primengDateLocalAr, primengLocaleAr, queryParamNamesConst, rangeValidator, registerLibraryLocales, requestHeaderNames, resolveCssSize, rtlModeKeys, splitArrayIntoChunks, tokenQueryParamName, updateEntityWorkflowInformation, updateEntityWorkflowInstanceResponse, userLanguage, workflowEtatDetailledResponse, workflowInstanceInformationResponse };
24233
+ export { ActionErpService, ActionPlacement, AdDirective, AdItem, AddReportPopupComponent, AdminRoutes, AdministrationEndpoints, AdministrationSegments, AdvancedSearchComponentService, AlertImportMessageComponent, ApiDateTimeFormat, ApiExplorerService, AppBaseComponent, AppRoutes, AppSettingsService, AuthenticationGuard, AuthorizationEndpoints, AuthorizationSegments, AuthorizationService, AuthorizationTokenMapper, AutoComplete, AutoSelectDirective, BsModalRef, BusyDirective, ButtonType, ChooseChoiceMfaMethodComponent, ClaimTypes, CodeFormat, CollectionChangeAction, ComboStatusModel, Component_Modes, ContextMenuComponent, CreateOrEditDynamicFormComponent, CreateOrEditEntityInformationsComponent, CreateOrEditModeleImportComponent, CrudBaseService, CurrentUser, CurrentUserService, CutLabelPipe, DateFormat, DateFormatDisplay, DateHelper, DateHelperService, DatePickerDateFormat, DatePickerDateTimeFormat, DatePickerTimeFormat, DateTimeFormat, DateTimeFormatDisplay, DecimalFormat, DefaultSocieteDevise, Devise, DialogDataService, DisplayNestedPropComponent, DisplayTypeColors, DisplayTypes, DynamicFormListComponent, EditableGridComponent, EndpointInfos, EntityColumnDetailResponse, EntityConfigurationService, EntityInfo, EntityInfoWithRelations, EntityRelation, EntityStatusService, EntityValidationsService, ErrorResponseManagerService, ExecuteImportRequest, ExportExcelHelper, Fichier, FilterByExistingValues, FilterByProprety, FilterHelper, FilterOperatorType, FilterOperatorTypeDescriptions, FormValidationError, FormulaService, GenericFormHelper, GenericValidationStateService, GetWorkflowInstanceInformationRequest, GridSelectionMode, GridSizingMode, GridSortMode, Guid, Helpers, HtmlTemplateGeneratorService, HtmlTemplates, HttpMethodType, IdentityManagerService, IdentityPlatformAuthenticationService, IdentityPlatformEndpoints, IdentityPlatformSegments, IdentityRoutes, ImportExportEndPoints, ImportExportSegments, ImportResult, InputRegistryService, InputTypes, JournalisationApplication, JournalisationApplicationService, KeyboardShortcutService, LanguageInformations, LanguageLocale, LanguagesCodes, LayoutHelperService, LayoutOrientation, LocaleCode, LocalePocComponent, LocalizationKeys, LocalizePipe, LogoutPopupComponent, MFAEnum, MailingService, ManageImportExportComponent, ManageReportingComponent, ManageReportingRoutes, MappingHelper, ModalComponent, ModalLabelPipe, ModalLoaderComponent, ModalSizeEnum, ModeleImportCreateRequest, ModeleImportDetail, ModeleImportService, Modes, ModuleCodeService, ModuleMainParameterDirective, ModuleMainParameterService, NoteService, NumericDisplayTypes, ObjectHelper, ObservableCollection, PagedResultRequestBase, PermissionGuard, PresentationDesignerDirective, PresentationDesignerDirectiveBase, PresentationDesignerService, PresentationDesignerTabPanelDirective, PresentationSetting, PresentationSettingDetail, PresentationSettingService, ProceedLoginRequest, ProgressBarHelperService, ProgressBarResponse, ProgressBarService, ProgressBarUpdateRequest, RecordInfoPopupService, ReportingComponent, ReportingEndpoints, ReportingSegments, ReportingService, RequestTrackerInterceptor, RequestTrackerService, Segment, Segments, SeparateurDecimal, SeparateurMillier, SharedComponentReferenceHelper, SharedModule, SharedUiTsiProviders, ShortenPipe, SpinnerComponent, Status, StatusHistory, StatusHistoryService, StorageManager, SubscriptionResult, TS_ValidDateFormat, TS_ValidDateTimeFormat, TS_ValidTimeFormat, TextBoxType, Themes, TimeFormat, TimeOnly, TimeZoneLocale, ToNumberPipe, TooltipPosition, TranslationService, TsiActionErpCreateOrEditComponent, TsiActionErpListComponent, TsiBaseComponent, TsiBsModalService, TsiBubbleInfoComponent, TsiButtonComponent, TsiCalenderComponent, TsiCard, TsiCardComponent, TsiCardListComponent, TsiChangePasswordComponent, TsiCheckboxComponent, TsiCheckboxDisplayComponent, TsiCodeGeneratorComponent, TsiComponentEnum, TsiComponentFinderService, TsiConfirmationService, TsiCurrencyOtherDisplayComponent, TsiCurrencyOtherInputComponent, TsiCurrencyPipe, TsiCurrencySocieteDisplayComponent, TsiCurrencySocieteInputComponent, TsiDateDisplayComponent, TsiDatePickerComponent, TsiDecimalDisplayComponent, TsiDecimalInputComponent, TsiDecimalPipe, TsiDisplayGridComponent, TsiDividerComponent, TsiErrorMessageComponent, TsiFileManagerComponent, TsiFilePreviewComponent, TsiFormComponent, TsiFormComponentBaseComponent, TsiFormModalFooterComponent, TsiFormulaBoxComponent, TsiGenericCrudComponent, TsiGenericFormComponent, TsiGenericGridComponent, TsiImageDisplayComponent, TsiInfoMessageComponent, TsiInputBase, TsiIntegerComponent, TsiIntegerDisplayComponent, TsiKanbanComponent, TsiKanbanWorkflowEtatComponent, TsiLabelComponent, TsiLabelLegendComponent, TsiLegendComponent, TsiListBaseComponent, TsiLogEventHistoryComponent, TsiMessageService, TsiModalComponent, TsiModalFooterComponent, TsiModalHeaderComponent, TsiModalService, TsiNotesAvertirComponent, TsiNotesComponent, TsiNotesGridComponent, TsiNotificationService, TsiParagraphComponent, TsiPopupTextViewerComponent, TsiProgressBarComponent, TsiRadioButtonComponent, TsiRateDisplayComponent, TsiRateInputComponent, TsiRedirectionMessageComponent, TsiReportType, TsiSearchBoxComponent, TsiSearchComboComponent, TsiSendMailComponent, TsiSubmitButtonComponent, TsiSuccessMessageComponent, TsiTabPanelComponent, TsiTabViewComponent, TsiTemplate, TsiTenantsComponent, TsiTextAreaComponent, TsiTextBoxComponent, TsiTimeDisplayComponent, TsiTimePickerComponent, TsiTooltipComponent, TsiUploadFicheComponent, TsiUploadGridComponent, TsiViewGridComponent, TypeFichier, TypeImportEnum, TypeRegistryHelper, UpdateEntityInfoWithRelationsRequest, UserIdentityService, ValidationDirective, VerificationCodeComponent, VerificationCodeInscriptionComponent, VerificationCodeSmsComponent, WorkflowConfigurationService, WorkflowEtatForm, WorkflowEtatKanbanRequest, WorkflowEtatSuivantResponse, WorkflowResponse, appProviders, authorization, dashboardConst, defaultCardHeader, distinct, executeImportResult, initSharedComponents, isNullOrEmpty, isPinnableCssSize, lengthValidator, locals, logoutReasonEnum, lowerFirst, menuItems, menuPreferences, preciseAdd, preciseSum, primengDateLocalAr, primengLocaleAr, queryParamNamesConst, rangeValidator, registerLibraryLocales, requestHeaderNames, resolveCssSize, rtlModeKeys, splitArrayIntoChunks, toDotNetCamelCase, tokenQueryParamName, updateEntityWorkflowInformation, updateEntityWorkflowInstanceResponse, userLanguage, workflowEtatDetailledResponse, workflowInstanceInformationResponse };
24196
24234
  //# sourceMappingURL=tsi-developpement-tsi-shared-ui.mjs.map