@acorex-platform/module-platform-management-client 0.0.38 → 0.0.46

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.
Files changed (87) hide show
  1. package/fesm2022/{acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-BGVhn6wv.mjs → acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-YuJijjKh.mjs} +465 -101
  2. package/fesm2022/acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-YuJijjKh.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-command-definition.mjs +20 -0
  4. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-command-definition.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-entity-access-policy.mjs +20 -0
  6. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-entity-access-policy.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-entity-definition.mjs +1499 -30
  8. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-entity-definition.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-module-definition.mjs +396 -0
  10. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-module-definition.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-plugin-definition.mjs +20 -0
  12. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-plugin-definition.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-query-definition.mjs +49 -1
  14. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-query-definition.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-token.mjs +20 -0
  16. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-token.mjs.map +1 -1
  17. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-widget.mjs +20 -0
  18. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entities-widget.mjs.map +1 -1
  19. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-acorex-platform-module-platform-management-client-contracts-entity-catalog-BOwC5Ns9.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-acorex-platform-module-platform-management-client-contracts-entity-catalog-HWczTPH2.mjs} +15 -8
  20. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-acorex-platform-module-platform-management-client-contracts-entity-catalog-HWczTPH2.mjs.map +1 -0
  21. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-command-definition.entity-CdvQlwAk.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-command-definition.entity-C3B1_QMs.mjs} +16 -2
  22. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-command-definition.entity-C3B1_QMs.mjs.map +1 -0
  23. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-access-policy.entity-Dye4aSKE.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-access-policy.entity-BPkJloHy.mjs} +16 -2
  24. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-access-policy.entity-BPkJloHy.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-definition.entity-BL1I9pmK.mjs +1621 -0
  26. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-definition.entity-BL1I9pmK.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-module-definition.entity-Cknl0vbc.mjs +299 -0
  28. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-module-definition.entity-Cknl0vbc.mjs.map +1 -0
  29. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-plugin-definition.entity-CwU5hMjI.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-plugin-definition.entity-BxABTl8L.mjs} +16 -2
  30. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-plugin-definition.entity-BxABTl8L.mjs.map +1 -0
  31. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-query-definition.entity-DGK684Q1.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-query-definition.entity-Cv6HVQOU.mjs} +45 -3
  32. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-query-definition.entity-Cv6HVQOU.mjs.map +1 -0
  33. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-token.entity-DI_k6-ha.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-token.entity-BV8iSuoK.mjs} +16 -2
  34. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-token.entity-BV8iSuoK.mjs.map +1 -0
  35. package/fesm2022/{acorex-platform-module-platform-management-client-contracts-entity-catalog-widget.entity-C18msniH.mjs → acorex-platform-module-platform-management-client-contracts-entity-catalog-widget.entity-BYH6SIqU.mjs} +16 -2
  36. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-widget.entity-BYH6SIqU.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog.mjs +1 -1
  38. package/fesm2022/acorex-platform-module-platform-management-client-contracts.mjs +1664 -1
  39. package/fesm2022/acorex-platform-module-platform-management-client-contracts.mjs.map +1 -1
  40. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query-contract.util-D_af2BUi.mjs +586 -0
  41. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query-contract.util-D_af2BUi.mjs.map +1 -0
  42. package/fesm2022/{acorex-platform-module-platform-management-client-create-user-query.command-9Ku7C1BC.mjs → acorex-platform-module-platform-management-client-create-user-query.command-Bh49QHkl.mjs} +374 -324
  43. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.command-Bh49QHkl.mjs.map +1 -0
  44. package/fesm2022/acorex-platform-module-platform-management-client-entity-definition-form-page.component-BgBWob9p.mjs +329 -0
  45. package/fesm2022/acorex-platform-module-platform-management-client-entity-definition-form-page.component-BgBWob9p.mjs.map +1 -0
  46. package/fesm2022/acorex-platform-module-platform-management-client-entity-definition-form-page.provider-hLzgoJu3.mjs +23 -0
  47. package/fesm2022/acorex-platform-module-platform-management-client-entity-definition-form-page.provider-hLzgoJu3.mjs.map +1 -0
  48. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-CZM6JQou.mjs +197 -0
  49. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-CZM6JQou.mjs.map +1 -0
  50. package/fesm2022/acorex-platform-module-platform-management-client-local-entities.mjs +1 -0
  51. package/fesm2022/acorex-platform-module-platform-management-client-local-entities.mjs.map +1 -1
  52. package/fesm2022/{acorex-platform-module-platform-management-client-menu-list.component-uF5w1c7t.mjs → acorex-platform-module-platform-management-client-menu-list.component-iMEnFPEY.mjs} +2 -2
  53. package/fesm2022/{acorex-platform-module-platform-management-client-menu-list.component-uF5w1c7t.mjs.map → acorex-platform-module-platform-management-client-menu-list.component-iMEnFPEY.mjs.map} +1 -1
  54. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-column.component-COfOzMGh.mjs +45 -0
  55. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-column.component-COfOzMGh.mjs.map +1 -0
  56. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-edit.component-BW2pBOeg.mjs +166 -0
  57. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-edit.component-BW2pBOeg.mjs.map +1 -0
  58. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-view.component-DD9ETjFD.mjs +45 -0
  59. package/fesm2022/acorex-platform-module-platform-management-client-query-contract-widget-view.component-DD9ETjFD.mjs.map +1 -0
  60. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-B0g6dweQ.mjs → acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-DRkE2a7n.mjs} +2 -2
  61. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-B0g6dweQ.mjs.map → acorex-platform-module-platform-management-client-rest-api-generator-widget-column.component-DRkE2a7n.mjs.map} +1 -1
  62. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-A0v5MWRU.mjs → acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-CXI6fJi3.mjs} +2 -3
  63. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-A0v5MWRU.mjs.map → acorex-platform-module-platform-management-client-rest-api-generator-widget-edit.component-CXI6fJi3.mjs.map} +1 -1
  64. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-RR5zcsnI.mjs → acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-C3iHgoC2.mjs} +2 -2
  65. package/fesm2022/{acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-RR5zcsnI.mjs.map → acorex-platform-module-platform-management-client-rest-api-generator-widget-view.component-C3iHgoC2.mjs.map} +1 -1
  66. package/fesm2022/acorex-platform-module-platform-management-client-run-user-query.command-BzNa3B48.mjs +144 -0
  67. package/fesm2022/acorex-platform-module-platform-management-client-run-user-query.command-BzNa3B48.mjs.map +1 -0
  68. package/fesm2022/acorex-platform-module-platform-management-client.mjs +1 -1
  69. package/package.json +7 -3
  70. package/types/acorex-platform-module-platform-management-client-contracts-entities-module-definition.d.ts +5 -0
  71. package/types/acorex-platform-module-platform-management-client-contracts.d.ts +225 -5
  72. package/types/acorex-platform-module-platform-management-client.d.ts +35 -3
  73. package/fesm2022/acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-BGVhn6wv.mjs.map +0 -1
  74. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-acorex-platform-module-platform-management-client-contracts-entity-catalog-BOwC5Ns9.mjs.map +0 -1
  75. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-command-definition.entity-CdvQlwAk.mjs.map +0 -1
  76. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-access-policy.entity-Dye4aSKE.mjs.map +0 -1
  77. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-definition.entity-CcJ58vfr.mjs +0 -158
  78. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-entity-definition.entity-CcJ58vfr.mjs.map +0 -1
  79. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-plugin-definition.entity-CwU5hMjI.mjs.map +0 -1
  80. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-query-definition.entity-DGK684Q1.mjs.map +0 -1
  81. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-token.entity-DI_k6-ha.mjs.map +0 -1
  82. package/fesm2022/acorex-platform-module-platform-management-client-contracts-entity-catalog-widget.entity-C18msniH.mjs.map +0 -1
  83. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.command-9Ku7C1BC.mjs.map +0 -1
  84. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.types-DdygAori.mjs +0 -71
  85. package/fesm2022/acorex-platform-module-platform-management-client-create-user-query.types-DdygAori.mjs.map +0 -1
  86. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs +0 -136
  87. package/fesm2022/acorex-platform-module-platform-management-client-list-registered-entities.query-DjStd3a8.mjs.map +0 -1
@@ -1,287 +1,109 @@
1
+ import { extractRequestErrorInfo, platformErrorI18nKey } from '@acorex-platform/framework-client/common';
1
2
  import { resolveLayoutBuilderDialogResult, AXPLayoutBuilderService } from '@acorex-platform/framework-client/layout/builder';
2
- import { AXPEntityResolver } from '@acorex-platform/framework-client/layout/entity';
3
- import { resolveEntityActionCommandInput, entityActionDataRecord } from '@acorex-platform/framework-shared/runtime';
3
+ import { AXPEntityResolver, AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
4
+ import { ENTITY_INSERT_DEFAULT, resolveEntityActionCommandInput, entityActionDataRecord } from '@acorex-platform/framework-shared/runtime';
5
+ import { AXPCommandService } from '@acorex-platform/framework-client/runtime';
6
+ import { RootConfig } from '@acorex-platform/module-platform-management-client/contracts';
4
7
  import { AXToastService } from '@acorex/components/toast';
5
8
  import { AXTranslationService } from '@acorex/core/translation';
6
9
  import * as i0 from '@angular/core';
7
10
  import { inject, Injectable } from '@angular/core';
11
+ import { n as normalizeUserQueryMeasure, r as resolvePickedEntityFullName, w as withRequiredIdField, c as collectSelectedFieldGroupPaths, a as resolveAvailableContractFields, f as flattenEntityFieldItems, b as assembleEntityQueryContract, v as validateEntityQueryContract, p as parseEntityFullName, d as collectUserQueryEntityFieldGroups, e as defaultSelectedFieldGroups, U as USER_QUERY_RELATED_DEPTH, g as numericUserQueryFields, h as createDefaultEntityContract } from './acorex-platform-module-platform-management-client-create-user-query-contract.util-D_af2BUi.mjs';
12
+ import { C as CREATE_USER_QUERY_I18N, a as CREATE_USER_QUERY_WIZARD_NAME, n as normalizeRestApiOutput, c as createEmptyRestApiOutput, b as CREATE_USER_QUERY_ENTITY_CATALOG, U as USER_QUERY_KIND_OPTIONS, d as USER_QUERY_VALUE_TYPE_OPTIONS, e as USER_QUERY_MEASURE_REDUCER_OPTIONS, Q as QUERY_CONTRACT_WIDGET_NAME, R as REST_API_GENERATOR_WIDGET_NAME } from './acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-YuJijjKh.mjs';
8
13
  import { AXPWidgetsList } from '@acorex-platform/framework-shared/core';
9
- import { parsePropertyExposeEntries } from '@acorex-platform/framework-shared/entity';
10
- import { C as CREATE_USER_QUERY_I18N, a as CREATE_USER_QUERY_WIZARD_NAME, b as CREATE_USER_QUERY_ENTITY_CATALOG } from './acorex-platform-module-platform-management-client-create-user-query.types-DdygAori.mjs';
11
- import { n as normalizeRestApiOutput, c as createEmptyRestApiOutput, R as REST_API_GENERATOR_WIDGET_NAME } from './acorex-platform-module-platform-management-client-acorex-platform-module-platform-management-client-BGVhn6wv.mjs';
12
14
 
13
- //#region ----- Imports -----
14
- //#endregion
15
15
  //#region ----- Constants -----
16
- const LOOKUP_WIDGET_TYPES = new Set(['lookup-editor', 'select-editor']);
17
- const USER_QUERY_RELATED_DEPTH = 3;
16
+ const VALUE_REDUCERS = ['count', 'sum', 'min', 'max', 'avg'];
17
+ const EMPTY_USER_QUERY_WHERE = {
18
+ id: 'root',
19
+ logic: 'and',
20
+ filters: [],
21
+ order: 0,
22
+ title: 'Root',
23
+ widget: { type: 'string-filter', options: {} },
24
+ filterType: { advance: true, inline: true },
25
+ };
18
26
  //#endregion
19
27
  //#region ----- Public API -----
20
- function parseEntityFullName(fullName) {
21
- const trimmed = fullName.trim();
22
- const separator = trimmed.indexOf('.');
23
- if (separator <= 0 || separator === trimmed.length - 1) {
24
- return null;
25
- }
28
+ function emptyUserQueryBuilderValue() {
26
29
  return {
27
- module: trimmed.slice(0, separator),
28
- name: trimmed.slice(separator + 1),
29
- };
30
- }
31
- function resolvePickedEntityFullName(value) {
32
- if (typeof value === 'string') {
33
- return value.trim();
34
- }
35
- if (value == null || typeof value !== 'object') {
36
- return '';
37
- }
38
- const record = value;
39
- const id = typeof record['id'] === 'string' ? record['id'].trim() : '';
40
- if (id.includes('.')) {
41
- return id;
42
- }
43
- const moduleName = typeof record['module_id'] === 'string' ? record['module_id'].trim() : '';
44
- const name = typeof record['name'] === 'string' ? record['name'].trim() : '';
45
- if (moduleName && name) {
46
- return `${moduleName}.${name}`;
47
- }
48
- return id;
49
- }
50
- function collectUserQueryEntityFieldGroups(entity, relatedByName) {
51
- return collectGroups(entity, relatedByName, '', new Set());
52
- }
53
- function flattenEntityFieldGroups(groups) {
54
- const result = [];
55
- const walk = (group) => {
56
- result.push(group);
57
- for (const child of group.children ?? []) {
58
- walk(child);
59
- }
60
- };
61
- for (const group of groups) {
62
- walk(group);
63
- }
64
- return result;
65
- }
66
- function defaultSelectedFieldGroups(groups) {
67
- const selected = {};
68
- for (const group of flattenEntityFieldGroups(groups)) {
69
- selected[group.id] = [];
70
- }
71
- for (const group of groups) {
72
- if (group.kind !== 'relation') {
73
- selected[group.id] = group.items.map((item) => item.path);
74
- }
75
- }
76
- return selected;
77
- }
78
- function collectSelectedFieldGroupPaths(fieldGroups, groups) {
79
- const paths = [];
80
- const walk = (group) => {
81
- paths.push(...normalizeSelectedFieldPaths(fieldGroups?.[group.id]));
82
- for (const child of group.children ?? []) {
83
- walk(child);
84
- }
30
+ columns: [],
31
+ where: { ...EMPTY_USER_QUERY_WHERE, filters: [] },
32
+ sort: [],
33
+ groupBy: [],
34
+ aggregates: [],
85
35
  };
86
- for (const group of groups) {
87
- walk(group);
88
- }
89
- return paths;
90
- }
91
- function withRequiredIdField(paths) {
92
- const unique = [];
93
- const seen = new Set();
94
- for (const path of paths) {
95
- const trimmed = path.trim();
96
- if (!trimmed || seen.has(trimmed)) {
97
- continue;
98
- }
99
- seen.add(trimmed);
100
- unique.push(trimmed);
101
- }
102
- if (!seen.has('id')) {
103
- unique.unshift('id');
104
- }
105
- return unique;
106
36
  }
107
- function normalizeSelectedFieldPaths(value) {
108
- if (!Array.isArray(value)) {
109
- return [];
110
- }
111
- const paths = [];
112
- for (const item of value) {
113
- if (typeof item === 'string') {
114
- paths.push(item);
115
- continue;
116
- }
117
- if (item != null && typeof item === 'object') {
118
- const path = item.path;
119
- if (typeof path === 'string') {
120
- paths.push(path);
121
- }
122
- }
123
- }
124
- return paths;
125
- }
126
- //#endregion
127
- //#region ----- Group collection -----
128
- function collectGroups(entity, relatedByName, pathPrefix, ancestry) {
129
- const groups = [];
130
- const { host, lookups } = collectHostAndLookups(entity, pathPrefix);
131
- if (host.items.length) {
132
- groups.push(host);
133
- }
134
- groups.push(...lookups);
135
- const hostFullName = `${entity.module}.${entity.name}`;
136
- const nextAncestry = new Set(ancestry);
137
- nextAncestry.add(hostFullName);
138
- for (const related of entity.relatedEntities ?? []) {
139
- const group = collectRelatedGroup(related, relatedByName, pathPrefix, nextAncestry);
140
- if (group) {
141
- groups.push(group);
142
- }
143
- }
144
- return groups;
145
- }
146
- function collectHostAndLookups(entity, pathPrefix, allowedNames) {
147
- const hostItems = [];
148
- const lookups = [];
149
- for (const property of entity?.properties ?? []) {
150
- const name = property.name?.trim();
151
- if (!name || name.toLowerCase() === 'id') {
152
- continue;
153
- }
154
- if (allowedNames && !allowedNames.has(name)) {
155
- continue;
156
- }
157
- if (isLookupProperty(property)) {
158
- const lookupGroup = collectLookupGroup(property, pathPrefix);
159
- if (lookupGroup) {
160
- lookups.push(lookupGroup);
161
- }
162
- continue;
163
- }
164
- hostItems.push({
165
- path: joinPath(pathPrefix, name),
166
- title: property.title,
167
- fallback: name,
168
- });
169
- }
37
+ function toQueryBuilderDataSource(entityName, fields) {
38
+ const columns = fields.map((field) => ({
39
+ name: field.path,
40
+ title: field.text || field.path,
41
+ datatype: field.dataType || 'string',
42
+ type: field.filterWidget || 'text-editor',
43
+ }));
44
+ const filters = fields.map((field) => ({
45
+ id: field.path,
46
+ field: field.path,
47
+ title: field.text || field.path,
48
+ operator: { type: 'equal' },
49
+ widget: { type: field.filterWidget || 'string-filter', options: {} },
50
+ filterType: { advance: true, inline: true },
51
+ }));
170
52
  return {
171
- host: {
172
- id: pathPrefix ? `rel_${sanitizeGroupId(pathPrefix)}_fields` : 'host',
173
- kind: pathPrefix ? 'relation' : 'host',
174
- title: '',
175
- fallback: 'fields',
176
- items: hostItems,
177
- },
178
- lookups,
53
+ name: entityName,
54
+ title: entityName,
55
+ columns,
56
+ filters,
179
57
  };
180
58
  }
181
- function collectLookupGroup(property, pathPrefix) {
182
- const name = property.name?.trim();
183
- if (!name) {
184
- return null;
185
- }
186
- const items = [
187
- {
188
- path: joinPath(pathPrefix, name),
189
- title: property.title,
190
- fallback: name,
191
- },
192
- ];
193
- const opts = property.schema?.interface?.options;
194
- for (const entry of parsePropertyExposeEntries(opts)) {
195
- const target = entry.target.trim();
196
- if (!target || target === name) {
197
- continue;
198
- }
199
- items.push({
200
- path: joinPath(pathPrefix, target),
201
- title: entry.source,
202
- fallback: entry.source.trim() || target,
203
- });
204
- }
205
- return {
206
- id: `lookup_${sanitizeGroupId(joinPath(pathPrefix, name))}`,
207
- kind: 'lookup',
208
- title: property.title,
209
- fallback: name,
210
- items,
59
+ function measureFromBuilderValue(query, fallback, available) {
60
+ const first = query?.aggregates?.find((item) => typeof item?.function === 'string' && item.function.trim());
61
+ if (!first) {
62
+ return normalizeUserQueryMeasure(fallback, available);
63
+ }
64
+ const key = first.function.trim().toLowerCase();
65
+ const reducer = key === 'average' ? 'avg' : VALUE_REDUCERS.includes(key) ? key : 'count';
66
+ if (reducer === 'count') {
67
+ return { reducer: 'count', alias: first.alias?.trim() || 'value' };
68
+ }
69
+ return normalizeUserQueryMeasure({ reducer, field: first.field, alias: first.alias?.trim() || 'value' }, available);
70
+ }
71
+ function toUserQueryDefinitionRow(context, available) {
72
+ const contract = context.contract;
73
+ if (!contract) {
74
+ throw new Error('contract');
75
+ }
76
+ const entityName = typeof context.source?.entity?.entityName === 'string' ? context.source.entity.entityName.trim() : '';
77
+ const fields = context.source?.entity?.fields ?? [];
78
+ const query = (context.source?.entity?.query ?? emptyUserQueryBuilderValue());
79
+ const measure = contract.kind === 'value' ? measureFromBuilderValue(query, contract.measure, available) : undefined;
80
+ const source = {
81
+ kind: 'entity',
82
+ entityName,
83
+ fields,
84
+ fieldGroups: context.source?.entity?.fieldGroups,
85
+ query,
86
+ ...(measure ? { measure } : {}),
211
87
  };
212
- }
213
- function collectRelatedGroup(related, relatedByName, pathPrefix, ancestry) {
214
- if (related.hidden === true) {
215
- return null;
216
- }
217
- const entityName = related.entity?.trim();
218
- if (!entityName || ancestry.has(entityName)) {
219
- return null;
220
- }
221
- const relatedEntity = relatedByName.get(entityName);
222
- const dataPath = related.persistence?.dataPath?.trim() || entityName.split('.').pop() || entityName;
223
- const pathRoot = joinPath(pathPrefix, dataPath);
224
- const allowed = resolveAllowedPropertyNames(related, relatedEntity);
225
- const { host, lookups } = collectHostAndLookups(relatedEntity, pathRoot, allowed);
226
- const childAncestry = new Set(ancestry);
227
- childAncestry.add(entityName);
228
- const children = [...lookups];
229
- for (const nested of relatedEntity?.relatedEntities ?? []) {
230
- const child = collectRelatedGroup(nested, relatedByName, pathRoot, childAncestry);
231
- if (child) {
232
- children.push(child);
233
- }
234
- }
235
- if (!host.items.length && !children.length) {
236
- return null;
237
- }
238
88
  return {
239
- id: `rel_${sanitizeGroupId(pathRoot)}`,
240
- kind: 'relation',
241
- title: related.title,
242
- fallback: entityName,
243
- items: host.items,
244
- children: children.length ? children : undefined,
89
+ name: contract.name.trim(),
90
+ catalogTitle: contract.catalogTitle.trim(),
91
+ description: contract.description,
92
+ active: true,
93
+ executionMode: 'backend',
94
+ kind: contract.kind,
95
+ valueType: contract.kind === 'value' ? contract.valueType : undefined,
96
+ binding: contract.kind === 'collection' ? contract.capabilities?.binding : undefined,
97
+ args: { enabled: false },
98
+ filter: contract.capabilities?.filter ?? { enabled: false },
99
+ sort: contract.kind === 'collection' ? contract.capabilities?.sort : { enabled: false },
100
+ paging: contract.kind === 'collection' ? contract.capabilities?.paging : { enabled: false },
101
+ columns: contract.kind === 'value' ? undefined : contract.columns,
102
+ capabilities: ['ai'],
103
+ source,
104
+ stateInfo: { isSystem: false },
245
105
  };
246
106
  }
247
- function resolveAllowedPropertyNames(related, relatedEntity) {
248
- const excluded = new Set((related.excludeProperties ?? [])
249
- .filter((name) => typeof name === 'string')
250
- .map((name) => name.trim())
251
- .filter(Boolean));
252
- const fromConfig = Array.isArray(related.properties)
253
- ? related.properties.filter((name) => typeof name === 'string' && name.trim().length > 0)
254
- : [];
255
- if (fromConfig.length) {
256
- return new Set(fromConfig.map((name) => name.trim()).filter((name) => name.toLowerCase() !== 'id' && !excluded.has(name)));
257
- }
258
- if (!excluded.size) {
259
- return undefined;
260
- }
261
- const names = (relatedEntity?.properties ?? [])
262
- .map((property) => property.name?.trim() ?? '')
263
- .filter((name) => name && name.toLowerCase() !== 'id' && !excluded.has(name));
264
- return new Set(names);
265
- }
266
- function isLookupProperty(property) {
267
- const iface = property.schema?.interface;
268
- const opts = iface?.options;
269
- const lookupEntity = typeof opts?.['entity'] === 'string' ? String(opts['entity']).trim() : '';
270
- if (!lookupEntity) {
271
- return false;
272
- }
273
- const widgetType = typeof iface?.type === 'string' ? iface.type : '';
274
- if (LOOKUP_WIDGET_TYPES.has(widgetType)) {
275
- return true;
276
- }
277
- return /(Ids|IDs|Id|ID)$/i.test(property.name);
278
- }
279
- function joinPath(prefix, segment) {
280
- return prefix ? `${prefix}.${segment}` : segment;
281
- }
282
- function sanitizeGroupId(value) {
283
- return value.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_|_$/g, '');
284
- }
285
107
  //#endregion
286
108
 
287
109
  //#region ----- Imports -----
@@ -342,7 +164,10 @@ async function openEntityPickDialog(args, entityName) {
342
164
  .setCloseButton(true)
343
165
  .setContext(createInitialContext('entity', entityName))
344
166
  .content((flex) => {
345
- flex.setDirection('column').setGap('16px').stepWizard((wizard) => {
167
+ flex
168
+ .setDirection('column')
169
+ .setGap('16px')
170
+ .stepWizard((wizard) => {
346
171
  wizard
347
172
  .name(CREATE_USER_QUERY_WIZARD_NAME)
348
173
  .setLook('circular-icon')
@@ -361,7 +186,7 @@ async function openEntityPickDialog(args, entityName) {
361
186
  })
362
187
  .step('contract', CREATE_USER_QUERY_I18N.steps.contract, (step) => {
363
188
  step.setIcon('fa-light fa-file-contract').content((layout) => {
364
- fillContractPlaceholderStep(layout);
189
+ fillHintStep(layout, CREATE_USER_QUERY_I18N.steps.contract, CREATE_USER_QUERY_I18N.placeholders.contractNext);
365
190
  });
366
191
  });
367
192
  });
@@ -387,7 +212,7 @@ async function openEntityPickDialog(args, entityName) {
387
212
  })
388
213
  .custom({
389
214
  title: '@general:actions.submit.title',
390
- command: `widget:${CREATE_USER_QUERY_WIZARD_NAME}.submit`,
215
+ command: 'submit',
391
216
  icon: 'fa-regular fa-check',
392
217
  color: 'success',
393
218
  hidden: '{{!api.getStatus().isLast}}',
@@ -417,9 +242,12 @@ async function openEntityFieldsDialog(args, entityName, groups, selectedGroups)
417
242
  .setTitle(CREATE_USER_QUERY_I18N.titles.entity)
418
243
  .setSize('xl')
419
244
  .setCloseButton(true)
420
- .setContext(createInitialContext('entity', entityName, [], selectedGroups))
245
+ .setContext(createInitialContext('entity', entityName, [], selectedGroups, createEntityContractSeed(entityName, groups, selectedGroups)))
421
246
  .content((flex) => {
422
- flex.setDirection('column').setGap('16px').stepWizard((wizard) => {
247
+ flex
248
+ .setDirection('column')
249
+ .setGap('16px')
250
+ .stepWizard((wizard) => {
423
251
  wizard
424
252
  .name(CREATE_USER_QUERY_WIZARD_NAME)
425
253
  .setLook('circular-icon')
@@ -436,7 +264,7 @@ async function openEntityFieldsDialog(args, entityName, groups, selectedGroups)
436
264
  })
437
265
  .step('contract', CREATE_USER_QUERY_I18N.steps.contract, (step) => {
438
266
  step.setIcon('fa-light fa-file-contract').content((layout) => {
439
- fillContractPlaceholderStep(layout);
267
+ fillEntityContractStep(layout, entityName, groups);
440
268
  });
441
269
  });
442
270
  });
@@ -472,7 +300,7 @@ async function openEntityFieldsDialog(args, entityName, groups, selectedGroups)
472
300
  })
473
301
  .custom({
474
302
  title: '@general:actions.submit.title',
475
- command: `widget:${CREATE_USER_QUERY_WIZARD_NAME}.submit`,
303
+ command: 'submit',
476
304
  icon: 'fa-regular fa-check',
477
305
  color: 'success',
478
306
  hidden: '{{!api.getStatus().isLast}}',
@@ -494,12 +322,27 @@ async function openEntityFieldsDialog(args, entityName, groups, selectedGroups)
494
322
  if (countFieldGroupItems(groups) > 0 && fields.filter((path) => path !== 'id').length === 0) {
495
323
  throw new Error(CREATE_USER_QUERY_I18N.errors.fieldsRequired);
496
324
  }
325
+ const available = resolveAvailableContractFields(flattenEntityFieldItems(groups), fields);
326
+ const contract = assembleEntityQueryContract({
327
+ name: context.contract?.name ?? '',
328
+ catalogTitle: context.contract?.catalogTitle ?? '',
329
+ description: context.contract?.description,
330
+ kind: context.contract?.kind,
331
+ valueType: context.contract?.valueType,
332
+ measure: context.contract?.measure,
333
+ }, context.contract?.capabilities, available);
334
+ const contractError = validateEntityQueryContract(contract, available);
335
+ if (contractError) {
336
+ throw new Error(contractValidationErrorKey(contractError));
337
+ }
497
338
  return {
498
339
  ...context,
340
+ contract,
499
341
  source: {
500
342
  ...context.source,
501
343
  kind: 'entity',
502
344
  entity: {
345
+ ...context.source?.entity,
503
346
  entityName,
504
347
  fields,
505
348
  fieldGroups: context.source?.entity?.fieldGroups,
@@ -566,9 +409,7 @@ async function openApiSourceWizard(args) {
566
409
  }
567
410
  const groups = inferJsonFieldGroups(tested.source?.api?.lastTest?.body);
568
411
  const views = await toFieldGroupViews(groups, args.translationService);
569
- const selected = isSameApiRequest(previous, tested) && previous.source?.api?.fieldGroups
570
- ? previous.source.api.fieldGroups
571
- : defaultHostFieldGroups(groups);
412
+ const selected = isSameApiRequest(previous, tested) && previous.source?.api?.fieldGroups ? previous.source.api.fieldGroups : defaultHostFieldGroups(groups);
572
413
  const outcome = await openApiFieldsDialog(args, tested, views, selected);
573
414
  if (outcome === API_WIZARD_BACK) {
574
415
  previous = tested;
@@ -587,7 +428,10 @@ async function openApiSourceDialog(args, previous) {
587
428
  .setCloseButton(true)
588
429
  .setContext(previous)
589
430
  .content((flex) => {
590
- flex.setDirection('column').setGap('16px').stepWizard((wizard) => {
431
+ flex
432
+ .setDirection('column')
433
+ .setGap('16px')
434
+ .stepWizard((wizard) => {
591
435
  wizard
592
436
  .name(CREATE_USER_QUERY_WIZARD_NAME)
593
437
  .setLook('circular-icon')
@@ -632,7 +476,7 @@ async function openApiSourceDialog(args, previous) {
632
476
  })
633
477
  .custom({
634
478
  title: '@general:actions.submit.title',
635
- command: `widget:${CREATE_USER_QUERY_WIZARD_NAME}.submit`,
479
+ command: 'submit',
636
480
  icon: 'fa-regular fa-check',
637
481
  color: 'success',
638
482
  hidden: '{{!api.getStatus().isLast}}',
@@ -675,7 +519,10 @@ async function openApiFieldsDialog(args, tested, groups, selectedGroups) {
675
519
  .setCloseButton(true)
676
520
  .setContext(createApiWizardContext(tested.source?.api, selectedGroups))
677
521
  .content((flex) => {
678
- flex.setDirection('column').setGap('16px').stepWizard((wizard) => {
522
+ flex
523
+ .setDirection('column')
524
+ .setGap('16px')
525
+ .stepWizard((wizard) => {
679
526
  wizard
680
527
  .name(CREATE_USER_QUERY_WIZARD_NAME)
681
528
  .setLook('circular-icon')
@@ -728,7 +575,7 @@ async function openApiFieldsDialog(args, tested, groups, selectedGroups) {
728
575
  })
729
576
  .custom({
730
577
  title: '@general:actions.submit.title',
731
- command: `widget:${CREATE_USER_QUERY_WIZARD_NAME}.submit`,
578
+ command: 'submit',
732
579
  icon: 'fa-regular fa-check',
733
580
  color: 'success',
734
581
  hidden: '{{!api.getStatus().isLast}}',
@@ -805,9 +652,7 @@ function createApiWizardContext(api, fieldGroups = {}) {
805
652
  api: {
806
653
  request: api?.request ?? createEmptyRestApiOutput(),
807
654
  connectionId: api?.connectionId,
808
- lastTest: api?.lastTest
809
- ? { ...api.lastTest, status: String(api.lastTest.status) }
810
- : undefined,
655
+ lastTest: api?.lastTest ? { ...api.lastTest, status: String(api.lastTest.status) } : undefined,
811
656
  fields: api?.fields ?? [],
812
657
  fieldGroups: Object.keys(fieldGroups).length ? fieldGroups : (api?.fieldGroups ?? {}),
813
658
  },
@@ -935,9 +780,7 @@ function splitJsonEnvelope(root) {
935
780
  return Array.isArray(value) && value.some((item) => isPlainObject(item));
936
781
  });
937
782
  const otherKeys = keys.filter((key) => !arrayKeys.includes(key));
938
- if (arrayKeys.length === 1 &&
939
- otherKeys.length > 0 &&
940
- otherKeys.every((key) => isJsonScalar(root[key]))) {
783
+ if (arrayKeys.length === 1 && otherKeys.length > 0 && otherKeys.every((key) => isJsonScalar(root[key]))) {
941
784
  payloadKey = arrayKeys[0];
942
785
  }
943
786
  }
@@ -1059,11 +902,7 @@ async function openAdvancedPlaceholder(args) {
1059
902
  await args.layoutBuilder
1060
903
  .create()
1061
904
  .dialog((dialog) => {
1062
- dialog
1063
- .setTitle(CREATE_USER_QUERY_I18N.titles.advanced)
1064
- .setMessage(CREATE_USER_QUERY_I18N.placeholders.advanced)
1065
- .setSize('md')
1066
- .setCloseButton(true);
905
+ dialog.setTitle(CREATE_USER_QUERY_I18N.titles.advanced).setMessage(CREATE_USER_QUERY_I18N.placeholders.advanced).setSize('md').setCloseButton(true);
1067
906
  })
1068
907
  .show();
1069
908
  return null;
@@ -1077,7 +916,10 @@ function configureSimpleSourceWizard(dialog, args) {
1077
916
  .setCloseButton(true)
1078
917
  .setContext(createInitialContext(args.sourceKind))
1079
918
  .content((flex) => {
1080
- flex.setDirection('column').setGap('16px').stepWizard((wizard) => {
919
+ flex
920
+ .setDirection('column')
921
+ .setGap('16px')
922
+ .stepWizard((wizard) => {
1081
923
  wizard
1082
924
  .name(CREATE_USER_QUERY_WIZARD_NAME)
1083
925
  .setLook('circular-icon')
@@ -1121,7 +963,7 @@ function configureSimpleSourceWizard(dialog, args) {
1121
963
  })
1122
964
  .custom({
1123
965
  title: '@general:actions.submit.title',
1124
- command: `widget:${CREATE_USER_QUERY_WIZARD_NAME}.submit`,
966
+ command: 'submit',
1125
967
  icon: 'fa-regular fa-check',
1126
968
  color: 'success',
1127
969
  hidden: '{{!api.getStatus().isLast}}',
@@ -1158,9 +1000,8 @@ function fillEntityPickStep(layout, readonly = false) {
1158
1000
  fieldset.formField(CREATE_USER_QUERY_I18N.fields.entity, (field) => {
1159
1001
  field.path('source.entity.entityName').lookupBox({
1160
1002
  entity: CREATE_USER_QUERY_ENTITY_CATALOG,
1161
- valueField: 'id',
1162
- textField: 'id',
1163
- columns: ['name', 'module_id'],
1003
+ valueField: 'name',
1004
+ textField: 'name',
1164
1005
  look: 'select',
1165
1006
  searchable: true,
1166
1007
  allowClear: !readonly,
@@ -1172,15 +1013,115 @@ function fillEntityPickStep(layout, readonly = false) {
1172
1013
  });
1173
1014
  });
1174
1015
  }
1175
- function fillEntityFieldsStep(layout, entityName, groups) {
1016
+ function fillEntityContractStep(layout, entityName, groups) {
1017
+ const builderFields = flattenEntityFieldItems(groups);
1176
1018
  layout.flex((flex) => {
1177
1019
  flex.setDirection('column').setGap('16px').setAlignItems('stretch');
1178
1020
  flex.fieldset((fieldset) => {
1179
1021
  fieldset
1180
- .setTitle(entityName)
1181
- .setDescription(CREATE_USER_QUERY_I18N.placeholders.selectedEntityHint)
1182
- .setIcon('fa-light fa-database')
1183
- .setLook('group');
1022
+ .setTitle(CREATE_USER_QUERY_I18N.fields.identity)
1023
+ .setDescription(CREATE_USER_QUERY_I18N.placeholders.identity)
1024
+ .setIcon('fa-light fa-id-card')
1025
+ .setCols(2)
1026
+ .formField(CREATE_USER_QUERY_I18N.fields.queryName, (field) => {
1027
+ field.path('contract.name').textBox({ validations: [{ rule: 'required' }] });
1028
+ })
1029
+ .formField(CREATE_USER_QUERY_I18N.fields.catalogTitle, (field) => {
1030
+ field.path('contract.catalogTitle').textBox({ validations: [{ rule: 'required' }] });
1031
+ })
1032
+ .formField(CREATE_USER_QUERY_I18N.fields.description, (field) => {
1033
+ field.path('contract.description').largeTextBox({ rows: 3 }).layout(2);
1034
+ });
1035
+ });
1036
+ flex.fieldset((fieldset) => {
1037
+ fieldset
1038
+ .setTitle(CREATE_USER_QUERY_I18N.fields.kind)
1039
+ .setDescription(CREATE_USER_QUERY_I18N.placeholders.kind)
1040
+ .setIcon('fa-light fa-shapes')
1041
+ .setCols(2)
1042
+ .formField(CREATE_USER_QUERY_I18N.fields.kind, (field) => {
1043
+ field
1044
+ .path('contract.kind')
1045
+ .setShowLabel(false)
1046
+ .selectBox({
1047
+ valueField: 'value',
1048
+ textField: 'text',
1049
+ dataSource: USER_QUERY_KIND_OPTIONS,
1050
+ validations: [{ rule: 'required' }],
1051
+ });
1052
+ })
1053
+ .formField(CREATE_USER_QUERY_I18N.fields.valueType, (field) => {
1054
+ field
1055
+ .path('contract.valueType')
1056
+ .selectBox({
1057
+ valueField: 'value',
1058
+ textField: 'text',
1059
+ dataSource: USER_QUERY_VALUE_TYPE_OPTIONS,
1060
+ })
1061
+ .visible('{{ context.eval("contract.kind") === "value" }}');
1062
+ })
1063
+ .formField(CREATE_USER_QUERY_I18N.fields.measure, (field) => {
1064
+ field
1065
+ .path('contract.measure.reducer')
1066
+ .selectBox({
1067
+ valueField: 'value',
1068
+ textField: 'text',
1069
+ dataSource: USER_QUERY_MEASURE_REDUCER_OPTIONS,
1070
+ validations: [{ rule: 'required' }],
1071
+ })
1072
+ .visible('{{ context.eval("contract.kind") === "value" }}');
1073
+ })
1074
+ .formField(CREATE_USER_QUERY_I18N.fields.measureField, (field) => {
1075
+ field
1076
+ .path('contract.measure.field')
1077
+ .selectBox({
1078
+ valueField: 'path',
1079
+ textField: 'text',
1080
+ dataSource: numericUserQueryFields(flattenEntityFieldItems(groups)),
1081
+ })
1082
+ .visible('{{ context.eval("contract.kind") === "value" && context.eval("contract.measure.reducer") !== "count" }}');
1083
+ });
1084
+ });
1085
+ flex.fieldset((fieldset) => {
1086
+ fieldset
1087
+ .setTitle(CREATE_USER_QUERY_I18N.fields.predicates)
1088
+ .setDescription(CREATE_USER_QUERY_I18N.placeholders.predicates)
1089
+ .setIcon('fa-light fa-filter')
1090
+ .setCols(1)
1091
+ .formField(' ', (field) => {
1092
+ field
1093
+ .path('source.entity.query')
1094
+ .setShowLabel(false)
1095
+ .customWidget(AXPWidgetsList.Editors.QueryBuilder, {
1096
+ visibleTabs: ['where', 'aggregate'],
1097
+ dataSource: toQueryBuilderDataSource(entityName, builderFields),
1098
+ })
1099
+ .layout(1);
1100
+ });
1101
+ });
1102
+ flex.fieldset((fieldset) => {
1103
+ fieldset
1104
+ .setTitle(CREATE_USER_QUERY_I18N.fields.capabilities)
1105
+ .setDescription(CREATE_USER_QUERY_I18N.placeholders.capabilities)
1106
+ .setIcon('fa-light fa-sliders')
1107
+ .setCols(1)
1108
+ .formField(' ', (field) => {
1109
+ field
1110
+ .path('contract.capabilities')
1111
+ .setShowLabel(false)
1112
+ .customWidget(QUERY_CONTRACT_WIDGET_NAME, {
1113
+ fields: flattenEntityFieldItems(groups),
1114
+ })
1115
+ .layout(1);
1116
+ });
1117
+ });
1118
+ });
1119
+ }
1120
+ function fillEntityFieldsStep(layout, entityName, groups) {
1121
+ layout.flex((flex) => {
1122
+ flex.setDirection('column').setGap('16px').setAlignItems('stretch');
1123
+ flex.fieldset((fieldset) => {
1124
+ fieldset.setTitle(entityName).setDescription(CREATE_USER_QUERY_I18N.placeholders.selectedEntityHint).setIcon('fa-light fa-database').setLook('group');
1184
1125
  });
1185
1126
  for (const group of groups) {
1186
1127
  renderFieldGroup(flex, group);
@@ -1288,10 +1229,7 @@ function staticItemRepeaterChildren() {
1288
1229
  gap: '12px',
1289
1230
  alignItems: 'stretch',
1290
1231
  },
1291
- children: [
1292
- staticItemRepeaterField(CREATE_USER_QUERY_I18N.fields.value, 'value'),
1293
- staticItemRepeaterField(CREATE_USER_QUERY_I18N.fields.title, 'title'),
1294
- ],
1232
+ children: [staticItemRepeaterField(CREATE_USER_QUERY_I18N.fields.value, 'value'), staticItemRepeaterField(CREATE_USER_QUERY_I18N.fields.title, 'title')],
1295
1233
  },
1296
1234
  ];
1297
1235
  }
@@ -1339,11 +1277,7 @@ function fillApiSourceStep(layout) {
1339
1277
  }
1340
1278
  function fillContractPlaceholderStep(layout) {
1341
1279
  layout.fieldset((fieldset) => {
1342
- fieldset
1343
- .setTitle(CREATE_USER_QUERY_I18N.steps.contract)
1344
- .setDescription(CREATE_USER_QUERY_I18N.placeholders.contract)
1345
- .setIcon('fa-light fa-file-contract')
1346
- .setLook('group');
1280
+ fieldset.setTitle(CREATE_USER_QUERY_I18N.steps.contract).setDescription(CREATE_USER_QUERY_I18N.placeholders.contract).setIcon('fa-light fa-file-contract').setLook('group');
1347
1281
  });
1348
1282
  }
1349
1283
  //#endregion
@@ -1386,18 +1320,35 @@ function sourceStepErrorKey(sourceKind, context) {
1386
1320
  }
1387
1321
  return null;
1388
1322
  }
1389
- function createInitialContext(sourceKind, entityName = '', fields = [], fieldGroups = {}) {
1323
+ function createInitialContext(sourceKind, entityName = '', fields = [], fieldGroups = {}, contract) {
1390
1324
  const items = [{ value: '', title: '' }];
1391
1325
  return {
1392
1326
  sourceKind,
1393
1327
  source: {
1394
1328
  kind: sourceKind,
1395
- entity: { entityName, fields, fieldGroups },
1329
+ entity: { entityName, fields, fieldGroups, query: emptyUserQueryBuilderValue() },
1396
1330
  static: { items },
1397
1331
  api: { request: createEmptyRestApiOutput() },
1398
1332
  },
1333
+ contract,
1399
1334
  };
1400
1335
  }
1336
+ function createEntityContractSeed(entityName, groups, selectedGroups) {
1337
+ const available = resolveAvailableContractFields(flattenEntityFieldItems(groups), collectSelectedFieldGroupPaths(selectedGroups, groups));
1338
+ return createDefaultEntityContract(entityName, available);
1339
+ }
1340
+ function contractValidationErrorKey(error) {
1341
+ if (error === 'title') {
1342
+ return CREATE_USER_QUERY_I18N.errors.contractTitleRequired;
1343
+ }
1344
+ if (error === 'binding') {
1345
+ return CREATE_USER_QUERY_I18N.errors.contractBindingInvalid;
1346
+ }
1347
+ if (error === 'measure') {
1348
+ return CREATE_USER_QUERY_I18N.errors.contractMeasureInvalid;
1349
+ }
1350
+ return CREATE_USER_QUERY_I18N.errors.contractNameRequired;
1351
+ }
1401
1352
  function countFieldGroupItems(groups) {
1402
1353
  let count = 0;
1403
1354
  const walk = (group) => {
@@ -1412,9 +1363,9 @@ function countFieldGroupItems(groups) {
1412
1363
  return count;
1413
1364
  }
1414
1365
  async function toFieldGroupViews(groups, translationService) {
1415
- const hostTitle = resolveLabel(translationService, CREATE_USER_QUERY_I18N.fields.hostFields, 'Fields');
1366
+ const hostTitle = await resolveLabel(translationService, CREATE_USER_QUERY_I18N.fields.hostFields, 'Fields');
1416
1367
  const mapGroup = async (group) => {
1417
- const resolvedName = resolveLabel(translationService, group.title, group.fallback);
1368
+ const resolvedName = await resolveLabel(translationService, group.title, group.fallback);
1418
1369
  let title = resolvedName;
1419
1370
  if (group.kind === 'host') {
1420
1371
  title = hostTitle;
@@ -1422,17 +1373,17 @@ async function toFieldGroupViews(groups, translationService) {
1422
1373
  else if (group.kind === 'relation') {
1423
1374
  title = await formatRelatedGroupTitle(translationService, resolvedName);
1424
1375
  }
1425
- const children = group.children?.length
1426
- ? await Promise.all(group.children.map((child) => mapGroup(child)))
1427
- : undefined;
1376
+ const children = group.children?.length ? await Promise.all(group.children.map((child) => mapGroup(child))) : undefined;
1428
1377
  return {
1429
1378
  id: group.id,
1430
1379
  kind: group.kind,
1431
1380
  title,
1432
- items: group.items.map((item) => ({
1381
+ items: await Promise.all(group.items.map(async (item) => ({
1433
1382
  path: item.path,
1434
- text: resolveLabel(translationService, item.title, item.fallback),
1435
- })),
1383
+ text: await resolveLabel(translationService, item.title, item.fallback),
1384
+ filterWidget: item.filterWidget,
1385
+ dataType: item.dataType,
1386
+ }))),
1436
1387
  children,
1437
1388
  };
1438
1389
  };
@@ -1442,10 +1393,22 @@ async function formatRelatedGroupTitle(translationService, name) {
1442
1393
  const translated = await translationService.translateAsync(CREATE_USER_QUERY_I18N.placeholders.relatedTo, { params: { name } });
1443
1394
  return typeof translated === 'string' && translated.trim() ? translated.trim() : name;
1444
1395
  }
1445
- function resolveLabel(translationService, value, fallback) {
1396
+ async function resolveLabel(translationService, value, fallback) {
1446
1397
  if (value == null || value === '') {
1447
1398
  return fallback;
1448
1399
  }
1400
+ if (typeof value === 'string') {
1401
+ const trimmed = value.trim();
1402
+ if (!trimmed) {
1403
+ return fallback;
1404
+ }
1405
+ if (trimmed.startsWith('@')) {
1406
+ const translated = await translationService.translateAsync(trimmed);
1407
+ const text = typeof translated === 'string' ? translated.trim() : '';
1408
+ return text && text !== trimmed ? text : fallback;
1409
+ }
1410
+ return trimmed;
1411
+ }
1449
1412
  const resolved = translationService.resolve(value);
1450
1413
  const text = typeof resolved === 'string' ? resolved.trim() : '';
1451
1414
  return text || fallback;
@@ -1484,13 +1447,15 @@ function sourceStepIcon(sourceKind) {
1484
1447
  //#endregion
1485
1448
  //#region ----- Command -----
1486
1449
  /**
1487
- * Opens the user-query create wizard. Step 1 configures the source; nothing is persisted.
1450
+ * Opens the user-query create wizard and persists an entity-source QueryDefinition.
1488
1451
  */
1489
1452
  class AXMCreateUserQueryCommand {
1490
1453
  constructor() {
1491
1454
  //#region ----- Services & Dependencies -----
1492
1455
  this.layoutBuilder = inject(AXPLayoutBuilderService);
1493
1456
  this.entityResolver = inject(AXPEntityResolver);
1457
+ this.entityService = inject(AXPEntityService);
1458
+ this.commandService = inject(AXPCommandService);
1494
1459
  this.toastService = inject(AXToastService);
1495
1460
  this.translationService = inject(AXTranslationService);
1496
1461
  }
@@ -1498,20 +1463,99 @@ class AXMCreateUserQueryCommand {
1498
1463
  //#region ----- Execution -----
1499
1464
  async execute(input = {}) {
1500
1465
  const sourceKind = resolveSourceKind(input);
1501
- await openCreateUserQueryWizard({
1466
+ const context = await openCreateUserQueryWizard({
1502
1467
  layoutBuilder: this.layoutBuilder,
1503
1468
  toastService: this.toastService,
1504
1469
  translationService: this.translationService,
1505
1470
  entityResolver: this.entityResolver,
1506
1471
  sourceKind,
1507
1472
  });
1473
+ if (!context) {
1474
+ return {
1475
+ success: true,
1476
+ kind: 'object',
1477
+ data: { sourceKind },
1478
+ metadata: { refresh: false },
1479
+ };
1480
+ }
1481
+ if (context.sourceKind !== 'entity' || !context.contract) {
1482
+ return {
1483
+ success: true,
1484
+ kind: 'object',
1485
+ data: context,
1486
+ metadata: { refresh: false },
1487
+ };
1488
+ }
1489
+ try {
1490
+ await this.persistEntityQuery(context);
1491
+ }
1492
+ catch (error) {
1493
+ const message = await this.translatePersistError(error);
1494
+ this.toastService.show({
1495
+ color: 'danger',
1496
+ content: message,
1497
+ closeButton: true,
1498
+ timeOut: 5000,
1499
+ timeOutProgress: true,
1500
+ });
1501
+ return {
1502
+ success: false,
1503
+ message: { text: message },
1504
+ };
1505
+ }
1506
+ const ok = await this.translationService.translateAsync(CREATE_USER_QUERY_I18N.placeholders.persistOk, {
1507
+ params: { name: context.contract.name },
1508
+ });
1509
+ this.toastService.show({
1510
+ color: 'success',
1511
+ content: ok,
1512
+ closeButton: true,
1513
+ timeOut: 4000,
1514
+ timeOutProgress: true,
1515
+ });
1508
1516
  return {
1509
1517
  success: true,
1510
1518
  kind: 'object',
1511
- data: { sourceKind },
1512
- metadata: { refresh: false },
1519
+ data: context,
1520
+ metadata: { refresh: true },
1513
1521
  };
1514
1522
  }
1523
+ //#endregion
1524
+ //#region ----- Persist -----
1525
+ async persistEntityQuery(context) {
1526
+ const name = context.contract?.name?.trim() ?? '';
1527
+ const catalog = this.entityService
1528
+ .withEntity(RootConfig.module.name, RootConfig.entities.queryDefinition.name)
1529
+ .data();
1530
+ const existing = await catalog.query({
1531
+ skip: 0,
1532
+ take: 1,
1533
+ filter: { field: 'name', operator: { type: 'equal' }, value: name },
1534
+ });
1535
+ if ((existing.items?.length ?? 0) > 0) {
1536
+ throw new DuplicateUserQueryNameError();
1537
+ }
1538
+ const available = (context.source?.entity?.fields ?? []).map((path) => ({ path, text: path }));
1539
+ const row = toUserQueryDefinitionRow(context, available);
1540
+ const result = await this.commandService.execute(ENTITY_INSERT_DEFAULT, {
1541
+ entity: `${RootConfig.module.name}.${RootConfig.entities.queryDefinition.name}`,
1542
+ data: row,
1543
+ });
1544
+ if (!result?.success) {
1545
+ throw result ?? new Error('insert');
1546
+ }
1547
+ }
1548
+ async translatePersistError(error) {
1549
+ if (error instanceof DuplicateUserQueryNameError) {
1550
+ return this.translationService.translateAsync(CREATE_USER_QUERY_I18N.errors.persistDuplicate);
1551
+ }
1552
+ const info = extractRequestErrorInfo(error);
1553
+ const mapped = platformErrorI18nKey(info?.code);
1554
+ if (mapped) {
1555
+ return this.translationService.translateAsync(mapped);
1556
+ }
1557
+ return this.translationService.translateAsync(CREATE_USER_QUERY_I18N.errors.persistFailed);
1558
+ }
1515
1559
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMCreateUserQueryCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1516
1560
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMCreateUserQueryCommand, providedIn: 'root' }); }
1517
1561
  }
@@ -1521,6 +1565,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1521
1565
  }] });
1522
1566
  //#endregion
1523
1567
  //#region ----- Helpers -----
1568
+ class DuplicateUserQueryNameError extends Error {
1569
+ constructor() {
1570
+ super('duplicate-query-name');
1571
+ this.name = 'DuplicateUserQueryNameError';
1572
+ }
1573
+ }
1524
1574
  function resolveSourceKind(input) {
1525
1575
  const resolved = resolveEntityActionCommandInput(input);
1526
1576
  const data = entityActionDataRecord(resolved.data) ?? {};
@@ -1535,4 +1585,4 @@ function resolveSourceKind(input) {
1535
1585
  }
1536
1586
 
1537
1587
  export { AXMCreateUserQueryCommand };
1538
- //# sourceMappingURL=acorex-platform-module-platform-management-client-create-user-query.command-9Ku7C1BC.mjs.map
1588
+ //# sourceMappingURL=acorex-platform-module-platform-management-client-create-user-query.command-Bh49QHkl.mjs.map