@acorex/connectivity 21.0.0-beta.6 → 21.0.0-beta.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.
- package/fesm2022/acorex-connectivity-api.mjs +89 -3
- package/fesm2022/acorex-connectivity-api.mjs.map +1 -1
- package/fesm2022/{acorex-connectivity-mock-acorex-connectivity-mock-BwFZQ1Xw.mjs → acorex-connectivity-mock-acorex-connectivity-mock-DGbHYfeu.mjs} +881 -209
- package/fesm2022/acorex-connectivity-mock-acorex-connectivity-mock-DGbHYfeu.mjs.map +1 -0
- package/fesm2022/{acorex-connectivity-mock-assign-to-manager.activity-BgaXGmcT.mjs → acorex-connectivity-mock-assign-to-manager.activity-B4WcK-sl.mjs} +2 -2
- package/fesm2022/{acorex-connectivity-mock-assign-to-manager.activity-BgaXGmcT.mjs.map → acorex-connectivity-mock-assign-to-manager.activity-B4WcK-sl.mjs.map} +1 -1
- package/fesm2022/{acorex-connectivity-mock-chat-generate-image.command-IUvQRJU0.mjs → acorex-connectivity-mock-chat-generate-image.command-iFiQ5rHd.mjs} +2 -2
- package/fesm2022/{acorex-connectivity-mock-chat-generate-image.command-IUvQRJU0.mjs.map → acorex-connectivity-mock-chat-generate-image.command-iFiQ5rHd.mjs.map} +1 -1
- package/fesm2022/{acorex-connectivity-mock-chat-synthesize-speech.command-DE8YU17E.mjs → acorex-connectivity-mock-chat-synthesize-speech.command-BTv6P2ez.mjs} +2 -2
- package/fesm2022/{acorex-connectivity-mock-chat-synthesize-speech.command-DE8YU17E.mjs.map → acorex-connectivity-mock-chat-synthesize-speech.command-BTv6P2ez.mjs.map} +1 -1
- package/fesm2022/{acorex-connectivity-mock-chat-transcribe-speech.command-Cr0QRiqr.mjs → acorex-connectivity-mock-chat-transcribe-speech.command-NxlEgtW-.mjs} +2 -2
- package/fesm2022/{acorex-connectivity-mock-chat-transcribe-speech.command-Cr0QRiqr.mjs.map → acorex-connectivity-mock-chat-transcribe-speech.command-NxlEgtW-.mjs.map} +1 -1
- package/fesm2022/{acorex-connectivity-mock-user-roles-for-list-column.query-DBhWkX3w.mjs → acorex-connectivity-mock-user-roles-for-list-column.query-DyGLxPvP.mjs} +2 -2
- package/fesm2022/{acorex-connectivity-mock-user-roles-for-list-column.query-DBhWkX3w.mjs.map → acorex-connectivity-mock-user-roles-for-list-column.query-DyGLxPvP.mjs.map} +1 -1
- package/fesm2022/acorex-connectivity-mock.mjs +1 -1
- package/package.json +2 -2
- package/types/acorex-connectivity-api.d.ts +12 -1
- package/types/acorex-connectivity-mock.d.ts +45 -6
- package/fesm2022/acorex-connectivity-mock-acorex-connectivity-mock-BwFZQ1Xw.mjs.map +0 -1
|
@@ -2,8 +2,8 @@ import { AXSafePipe } from '@acorex/core/pipes';
|
|
|
2
2
|
import * as i2$2 from '@acorex/platform/auth';
|
|
3
3
|
import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER, AXP_APPLICATION_LOADER, AXP_FEATURE_LOADER, AXP_PERMISSION_LOADER, AXP_TENANT_LOADER, AXP_PERMISSION_CHECKER, AXP_FEATURE_CHECKER, AXPLoginChallengeComponentBase, AXPLoginChallengeProvider, AXPAuthStrategy, AXPAuthModule, AXP_LOGIN_CHALLENGE_PROVIDER } from '@acorex/platform/auth';
|
|
4
4
|
import * as i1 from '@acorex/platform/core';
|
|
5
|
-
import {
|
|
6
|
-
import { AXP_ENTITY_STORAGE_BACKEND, AXPEntityStorageService, isAXPMiddlewareAbortError, AXPEntityResolver, collectEntityQuickSearchFieldPaths, AXP_ENTITY_DEFINITION_CRUD_SERVICE, AXPEntityService, AXP_ENTITY_DEFINITION_LOADER, AXPMiddlewareAbortError, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_STORAGE_MIDDLEWARE, AXPEntityDefinitionRegistryService, eventDispatchMiddleware, AXPDataSeederService } from '@acorex/platform/layout/entity';
|
|
5
|
+
import { AXPDataGenerator, AXPIconLogoConfig, AXPModuleManifestRegistry, AXPActivityLogProvider, AXP_ACTIVITY_LOG_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPDataSourceDefinitionProviderService, AXPPlatformScope, AXPExpressionEvaluatorService, AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, provideLazyProvider } from '@acorex/platform/core';
|
|
6
|
+
import { runEntityQuery, filterSortEntityRows, computeEntityAggregates, AXP_ENTITY_STORAGE_BACKEND, AXPEntityStorageService, isAXPMiddlewareAbortError, AXPEntityResolver, collectEntityQuickSearchFieldPaths, AXP_ENTITY_DEFINITION_CRUD_SERVICE, AXPEntityService, AXP_ENTITY_DEFINITION_LOADER, AXPMiddlewareAbortError, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_STORAGE_MIDDLEWARE, AXPEntityDefinitionRegistryService, eventDispatchMiddleware, AXPDataSeederService } from '@acorex/platform/layout/entity';
|
|
7
7
|
import * as i0 from '@angular/core';
|
|
8
8
|
import { InjectionToken, inject, Injectable, NgModule, isDevMode, Injector, Inject, signal, Component } from '@angular/core';
|
|
9
9
|
import { AXP_MENU_PROVIDER, AXPFileStorageService, AXPSettingsService, AXPSystemStatusType, AXPAppVersionService, AXPFileStorageStatus, AXVChangeType as AXVChangeType$1, AXP_SEARCH_PROVIDER, AXPLockService, AXPVersioningService, AXPSystemStatuses, AXPStatusDefinitionProviderService } from '@acorex/platform/common';
|
|
@@ -23,13 +23,14 @@ import { AXMAssetManagementFeatureKeys, RootConfig as RootConfig$4, AXMAssetStat
|
|
|
23
23
|
import { AXMAssessmentManagementFeatureKeys, RootConfig as RootConfig$3, AXMAssessmentManagementPermissionKeys } from '@acorex/modules/assessment-management';
|
|
24
24
|
import { AXPCommonFeatureKeys, AXPWidgetsList } from '@acorex/modules/common';
|
|
25
25
|
import { AXMDataManagementFeatureKeys, AXMPermissionsKeys as AXMPermissionsKeys$2, RootConfig as RootConfig$d, AXMQueryServiceImpl, ShowMetaDataFormPopupCommand, ShowMetaDataBuilderPopupCommand, AXMQueryService } from '@acorex/modules/data-management';
|
|
26
|
-
import { AXPHumanCapitalManagementFeatureKeys, AXMPermissionsKeys as AXMPermissionsKeys$
|
|
26
|
+
import { AXPHumanCapitalManagementFeatureKeys, AXMPermissionsKeys as AXMPermissionsKeys$5, RootConfig as RootConfig$j, AXM_POSITION_ASSIGNMENT_OVERLAPPING_STATUS_IDS } from '@acorex/modules/human-capital-management';
|
|
27
27
|
import { RootConfig as RootConfig$5 } from '@acorex/modules/locale-management';
|
|
28
28
|
import { AXMPermissionsKeys as AXMPermissionsKeys$1, AXMSubscriptionManagementSubscriptionEntityService, AXMSubscriptionManagementSubscriptionPlanItemEntityService, RootConfig as RootConfig$w } from '@acorex/modules/subscription-management';
|
|
29
|
-
import { AXPSecurityManagementPermissionKeys, AXMUserAccessState, resolveAXMUserEffectiveState, RootConfig as RootConfig$6, AXMUsersEntityService } from '@acorex/modules/security-management';
|
|
29
|
+
import { AXPSecurityManagementPermissionKeys, AXMUserAccessState, resolveAXMUserEffectiveState, RootConfig as RootConfig$6, AXMUsersEntityService, AXMPolicyService } from '@acorex/modules/security-management';
|
|
30
30
|
import { AXMTenantManagementPermissionsKeys, AXMTenantService, AXMTenantManagementTenantUserEntityService, RootConfig as RootConfig$z } from '@acorex/modules/tenant-management';
|
|
31
|
-
import { AXMPermissionsKeys as AXMPermissionsKeys$
|
|
31
|
+
import { AXMPermissionsKeys as AXMPermissionsKeys$4, RootConfig as RootConfig$e, AXMDocumentTypeServiceImpl, AXPDocumentManagementService } from '@acorex/modules/document-management';
|
|
32
32
|
import { AXMSettingsManagementPermissionKeys } from '@acorex/modules/settings-management';
|
|
33
|
+
import { AXMPermissionsKeys as AXMPermissionsKeys$3, RootConfig as RootConfig$y, AXPWorkflowTaskProvider } from '@acorex/modules/task-management';
|
|
33
34
|
import { RootConfig as RootConfig$7 } from '@acorex/modules/business-core';
|
|
34
35
|
import { RootConfig as RootConfig$8 } from '@acorex/modules/calendar-management';
|
|
35
36
|
import { AXFileService } from '@acorex/core/file';
|
|
@@ -77,7 +78,6 @@ import { RootConfig as RootConfig$v } from '@acorex/modules/reservation-manageme
|
|
|
77
78
|
import { AXMSessionStatusTypes, AXMDeviceSessionsServiceImpl, AXMDeviceSessionsService, AXM_AUTH_CONFIG_TOKEN } from '@acorex/modules/auth';
|
|
78
79
|
import * as i1$3 from '@acorex/components/dialog';
|
|
79
80
|
import { RootConfig as RootConfig$x } from '@acorex/modules/supplier-management';
|
|
80
|
-
import { RootConfig as RootConfig$y, AXPWorkflowTaskProvider } from '@acorex/modules/task-management';
|
|
81
81
|
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
82
82
|
import { Router } from '@angular/router';
|
|
83
83
|
import { AXPWorkflowManager, AXPWorkflowDefinitionService, AXPActivityDefinitionService, AXP_WORKFLOW_PROVIDER, WorkflowExpressionScopeService, AXP_WORKFLOW_ENGINE } from '@acorex/platform/workflow';
|
|
@@ -105,112 +105,8 @@ const AXC_FIRESTORE_CONFIG = new InjectionToken('AXC_FIRESTORE_CONFIG', {
|
|
|
105
105
|
});
|
|
106
106
|
//#endregion
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Fields where `contains` means hierarchical category membership (expand to descendants).
|
|
112
|
-
* Excludes e.g. `roleIds`, which uses array membership only and must use normal filter logic.
|
|
113
|
-
*/
|
|
114
|
-
function isCategoryContainsFieldName(field) {
|
|
115
|
-
if (field === 'categoryIds') {
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
if (typeof field === 'string' && field.endsWith('Ids') && field !== 'roleIds') {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Check if the entity is a category entity (ends with 'Category').
|
|
125
|
-
*/
|
|
126
|
-
function isCategoryEntity(entityName) {
|
|
127
|
-
return entityName.endsWith('Category');
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Check if the filter is a category filter (contains operator on categoryIds field).
|
|
131
|
-
* Handles both simple filters and compound filters.
|
|
132
|
-
*/
|
|
133
|
-
function isCategoryFilter(filter) {
|
|
134
|
-
if (filter?.operator?.type === 'contains' && isCategoryContainsFieldName(filter.field)) {
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
if (filter?.logic && filter?.filters && Array.isArray(filter.filters)) {
|
|
138
|
-
return filter.filters.some((nestedFilter) => nestedFilter?.operator?.type === 'contains' && isCategoryContainsFieldName(nestedFilter.field));
|
|
139
|
-
}
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region ---- Query execution ----
|
|
144
|
-
/**
|
|
145
|
-
* Apply recursive category filtering: entities that belong to the category or any of its children.
|
|
146
|
-
*/
|
|
147
|
-
async function applyRecursiveCategoryFilter(result, filter, entityName, getAllChildCategoryIds) {
|
|
148
|
-
let categoryFilter = filter;
|
|
149
|
-
let otherFilters = [];
|
|
150
|
-
if (filter?.logic && filter?.filters && Array.isArray(filter.filters)) {
|
|
151
|
-
const categoryFilterIndex = filter.filters.findIndex((f) => f?.operator?.type === 'contains' && isCategoryContainsFieldName(f.field));
|
|
152
|
-
if (categoryFilterIndex !== -1) {
|
|
153
|
-
categoryFilter = filter.filters[categoryFilterIndex];
|
|
154
|
-
otherFilters = filter.filters.filter((_, index) => index !== categoryFilterIndex);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (!categoryFilter) {
|
|
158
|
-
return applyFilterArray(result, [filter]);
|
|
159
|
-
}
|
|
160
|
-
const categoryId = categoryFilter.value;
|
|
161
|
-
const categoryField = categoryFilter.field;
|
|
162
|
-
const allCategoryIds = await getAllChildCategoryIds(categoryId, entityName);
|
|
163
|
-
let filteredResult = result.filter((item) => {
|
|
164
|
-
const categoryIds = item[categoryField];
|
|
165
|
-
if (!categoryIds)
|
|
166
|
-
return false;
|
|
167
|
-
const itemCategoryIds = Array.isArray(categoryIds) ? categoryIds : [categoryIds];
|
|
168
|
-
return itemCategoryIds.some((itemCategoryId) => allCategoryIds.includes(itemCategoryId));
|
|
169
|
-
});
|
|
170
|
-
if (otherFilters.length > 0) {
|
|
171
|
-
filteredResult = applyFilterArray(filteredResult, otherFilters);
|
|
172
|
-
}
|
|
173
|
-
return filteredResult;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Calculate childrenCount for each category entity when missing.
|
|
177
|
-
*/
|
|
178
|
-
async function calculateChildrenCounts(items, entityName, getDirectChildCount) {
|
|
179
|
-
return Promise.all(items.map(async (item) => {
|
|
180
|
-
if (typeof item.childrenCount === 'number') {
|
|
181
|
-
return { ...item };
|
|
182
|
-
}
|
|
183
|
-
const childrenCount = await getDirectChildCount(item.id, entityName);
|
|
184
|
-
return { ...item, childrenCount };
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Shared entity query logic: sort, filter (including recursive category filter), childrenCount, pagination.
|
|
189
|
-
* Used by both Dexie and Firestore backends.
|
|
190
|
-
*/
|
|
191
|
-
async function runEntityQuery(entityName, request, adapters) {
|
|
192
|
-
let result = await adapters.getRawAll(entityName);
|
|
193
|
-
if (request.sort && request.sort.length) {
|
|
194
|
-
result = applySortArray(result, request.sort);
|
|
195
|
-
}
|
|
196
|
-
if (request.filter && isCategoryFilter(request.filter)) {
|
|
197
|
-
result = await applyRecursiveCategoryFilter(result, request.filter, entityName, adapters.getAllChildCategoryIds);
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
result = applyFilterArray(result, request.filter ? [request.filter] : []);
|
|
201
|
-
}
|
|
202
|
-
if (isCategoryEntity(entityName)) {
|
|
203
|
-
result = await calculateChildrenCounts(result, entityName, adapters.getDirectChildCount);
|
|
204
|
-
}
|
|
205
|
-
const skip = request.skip ?? 0;
|
|
206
|
-
const take = request.take ?? 0;
|
|
207
|
-
return {
|
|
208
|
-
total: result.length,
|
|
209
|
-
items: result.slice(skip, skip + take),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
//#endregion
|
|
213
|
-
|
|
108
|
+
/** Rows loaded into memory for aggregation on mock backends (safety cap). */
|
|
109
|
+
const ENTITY_AGGREGATE_MAX_ROWS$1 = 500_000;
|
|
214
110
|
class AXCDexieEntityStorageService extends Dexie {
|
|
215
111
|
constructor() {
|
|
216
112
|
super('ACoreXPlatform');
|
|
@@ -315,13 +211,61 @@ class AXCDexieEntityStorageService extends Dexie {
|
|
|
315
211
|
return await this.table('entity-store').where({ entityName }).toArray();
|
|
316
212
|
}
|
|
317
213
|
async query(entityName, request) {
|
|
318
|
-
return runEntityQuery(entityName, request,
|
|
214
|
+
return runEntityQuery(entityName, request, this.createQueryAdapters());
|
|
215
|
+
}
|
|
216
|
+
async count(entityName, request) {
|
|
217
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
218
|
+
return rows.length;
|
|
219
|
+
}
|
|
220
|
+
async queryAll(entityName, request, options) {
|
|
221
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
222
|
+
const maxRows = options?.maxRows ?? Number.POSITIVE_INFINITY;
|
|
223
|
+
const capped = Number.isFinite(maxRows) ? rows.slice(0, maxRows) : rows;
|
|
224
|
+
const truncated = Number.isFinite(maxRows) && rows.length > maxRows;
|
|
225
|
+
return {
|
|
226
|
+
items: capped,
|
|
227
|
+
total: rows.length,
|
|
228
|
+
truncated,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async getMany(entityName, ids) {
|
|
232
|
+
return Promise.all(ids.map(async (id) => {
|
|
233
|
+
const row = await this.getOne(entityName, id);
|
|
234
|
+
return row ?? null;
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
async exists(entityName, id) {
|
|
238
|
+
const row = await this.getOne(entityName, id);
|
|
239
|
+
return row != null;
|
|
240
|
+
}
|
|
241
|
+
async upsertOne(entityName, entity, options) {
|
|
242
|
+
const id = entity?.id;
|
|
243
|
+
if (id) {
|
|
244
|
+
const existing = await this.getOne(entityName, id);
|
|
245
|
+
if (existing != null) {
|
|
246
|
+
const merge = options?.merge !== false;
|
|
247
|
+
const next = merge ? { ...existing, ...entity } : { ...entity };
|
|
248
|
+
const { id: _omit, ...patch } = next;
|
|
249
|
+
await this.updateOne(entityName, id, patch);
|
|
250
|
+
return id;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return this.insertOne(entityName, entity);
|
|
254
|
+
}
|
|
255
|
+
async aggregate(entityName, request, options) {
|
|
256
|
+
const maxRows = options?.maxRows ?? ENTITY_AGGREGATE_MAX_ROWS$1;
|
|
257
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
258
|
+
const slice = rows.slice(0, maxRows);
|
|
259
|
+
return { rows: computeEntityAggregates(slice, request) };
|
|
260
|
+
}
|
|
261
|
+
//#region ---- Private Helper Methods (used by runEntityQuery adapters) ----
|
|
262
|
+
createQueryAdapters() {
|
|
263
|
+
return {
|
|
319
264
|
getRawAll: (name) => this.table('entity-store').where({ entityName: name }).toArray(),
|
|
320
265
|
getDirectChildCount: (categoryId, name) => this.getDirectChildCount(categoryId, name),
|
|
321
266
|
getAllChildCategoryIds: (categoryId, name) => this.getAllChildCategoryIds(categoryId, name),
|
|
322
|
-
}
|
|
267
|
+
};
|
|
323
268
|
}
|
|
324
|
-
//#region ---- Private Helper Methods (used by runEntityQuery adapters) ----
|
|
325
269
|
async getDirectChildCount(categoryId, entityName) {
|
|
326
270
|
const children = await this.table('entity-store')
|
|
327
271
|
.where({ entityName })
|
|
@@ -349,6 +293,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
349
293
|
}], ctorParameters: () => [] });
|
|
350
294
|
|
|
351
295
|
const ENTITY_STORE_COLLECTION = 'entity-store';
|
|
296
|
+
/** Rows loaded into memory for aggregation on mock backends (safety cap). */
|
|
297
|
+
const ENTITY_AGGREGATE_MAX_ROWS = 500_000;
|
|
352
298
|
/** Firestore batch write/delete limit per commit. */
|
|
353
299
|
const FIRESTORE_BATCH_MAX$1 = 500;
|
|
354
300
|
/**
|
|
@@ -533,14 +479,64 @@ class AXCFirestoreEntityStorageService {
|
|
|
533
479
|
return this.getRawAll(entityName);
|
|
534
480
|
}
|
|
535
481
|
async query(entityName, request) {
|
|
536
|
-
return runEntityQuery(entityName, request,
|
|
482
|
+
return runEntityQuery(entityName, request, this.createQueryAdapters());
|
|
483
|
+
}
|
|
484
|
+
async count(entityName, request) {
|
|
485
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
486
|
+
return rows.length;
|
|
487
|
+
}
|
|
488
|
+
async queryAll(entityName, request, options) {
|
|
489
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
490
|
+
const maxRows = options?.maxRows ?? Number.POSITIVE_INFINITY;
|
|
491
|
+
const capped = Number.isFinite(maxRows) ? rows.slice(0, maxRows) : rows;
|
|
492
|
+
const truncated = Number.isFinite(maxRows) && rows.length > maxRows;
|
|
493
|
+
return {
|
|
494
|
+
items: capped,
|
|
495
|
+
total: rows.length,
|
|
496
|
+
truncated,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
async getMany(entityName, ids) {
|
|
500
|
+
return Promise.all(ids.map(async (id) => {
|
|
501
|
+
const row = await this.getOne(entityName, id);
|
|
502
|
+
return row ?? null;
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
async exists(entityName, id) {
|
|
506
|
+
const row = await this.getOne(entityName, id);
|
|
507
|
+
return row != null;
|
|
508
|
+
}
|
|
509
|
+
async upsertOne(entityName, entity, options) {
|
|
510
|
+
const id = entity?.id;
|
|
511
|
+
if (id) {
|
|
512
|
+
const existing = await this.getOne(entityName, id);
|
|
513
|
+
if (existing != null) {
|
|
514
|
+
const merge = options?.merge !== false;
|
|
515
|
+
const next = merge
|
|
516
|
+
? { ...existing, ...entity }
|
|
517
|
+
: { ...entity };
|
|
518
|
+
const { id: _omit, ...patch } = next;
|
|
519
|
+
await this.updateOne(entityName, id, patch);
|
|
520
|
+
return id;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return this.insertOne(entityName, entity);
|
|
524
|
+
}
|
|
525
|
+
async aggregate(entityName, request, options) {
|
|
526
|
+
const maxRows = options?.maxRows ?? ENTITY_AGGREGATE_MAX_ROWS;
|
|
527
|
+
const rows = await filterSortEntityRows(entityName, request, this.createQueryAdapters());
|
|
528
|
+
const slice = rows.slice(0, maxRows);
|
|
529
|
+
return { rows: computeEntityAggregates(slice, request) };
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region ---- Adapters for runEntityQuery ----
|
|
533
|
+
createQueryAdapters() {
|
|
534
|
+
return {
|
|
537
535
|
getRawAll: (name) => this.getRawAll(name),
|
|
538
536
|
getDirectChildCount: (categoryId, name) => this.getDirectChildCount(categoryId, name),
|
|
539
537
|
getAllChildCategoryIds: (categoryId, name) => this.getAllChildCategoryIds(categoryId, name),
|
|
540
|
-
}
|
|
538
|
+
};
|
|
541
539
|
}
|
|
542
|
-
//#endregion
|
|
543
|
-
//#region ---- Adapters for runEntityQuery ----
|
|
544
540
|
/**
|
|
545
541
|
* Load all documents for an entity (same approach as Dexie: load by entityName, filter in runEntityQuery).
|
|
546
542
|
* Firestore and Dexie share the same runEntityQuery + applyFilterArray so behavior stays consistent.
|
|
@@ -813,6 +809,38 @@ function parseEntityStorageQueryRequest(raw) {
|
|
|
813
809
|
};
|
|
814
810
|
}
|
|
815
811
|
//#endregion
|
|
812
|
+
//#region ---- Aggregate request parse ----
|
|
813
|
+
/**
|
|
814
|
+
* Parses optional aggregate payload (JSON object or string). Requires `groupBy` and `measures`.
|
|
815
|
+
*/
|
|
816
|
+
function parseEntityStorageAggregateRequest(raw) {
|
|
817
|
+
if (raw == null || raw === '') {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
let obj = raw;
|
|
821
|
+
if (typeof raw === 'string') {
|
|
822
|
+
try {
|
|
823
|
+
obj = JSON.parse(raw);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
const o = obj;
|
|
833
|
+
if (!Array.isArray(o['groupBy']) || !Array.isArray(o['measures'])) {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
groupBy: o['groupBy'],
|
|
838
|
+
measures: o['measures'],
|
|
839
|
+
filter: o['filter'],
|
|
840
|
+
sort: o['sort'],
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
//#endregion
|
|
816
844
|
//#region ---- AI-friendly quick search → standard filter ----
|
|
817
845
|
/** Fields commonly present across entities; always included for quickSearch. */
|
|
818
846
|
const ENTITY_STORAGE_QUICK_SEARCH_BASE_FIELD_PATHS = ['id', 'code', 'title', 'name', 'shortName'];
|
|
@@ -863,6 +891,9 @@ function mergeQuickSearchIntoRequest(request, quickSearch, quickSearchExtraConta
|
|
|
863
891
|
}
|
|
864
892
|
//#endregion
|
|
865
893
|
|
|
894
|
+
//#region ---- Types ----
|
|
895
|
+
/** Default cap when draining rows (`resultMode: all`) or aggregating without `maxFetchRows`. */
|
|
896
|
+
const DEFAULT_MAX_FETCH_ROWS = 50_000;
|
|
866
897
|
/**
|
|
867
898
|
* Normalizes tool/AI payloads: string[] or JSON string array or comma-separated paths.
|
|
868
899
|
*/
|
|
@@ -891,10 +922,27 @@ function normalizeQuickSearchFieldPaths(raw) {
|
|
|
891
922
|
}
|
|
892
923
|
return [];
|
|
893
924
|
}
|
|
925
|
+
function normalizeResultMode(raw) {
|
|
926
|
+
const s = typeof raw === 'string' ? raw.trim().toLowerCase() : raw;
|
|
927
|
+
if (s === 'count' || s === 'all') {
|
|
928
|
+
return s;
|
|
929
|
+
}
|
|
930
|
+
return 'page';
|
|
931
|
+
}
|
|
932
|
+
function normalizeMaxFetchRows(raw) {
|
|
933
|
+
if (raw == null || raw === '') {
|
|
934
|
+
return DEFAULT_MAX_FETCH_ROWS;
|
|
935
|
+
}
|
|
936
|
+
const n = typeof raw === 'number' ? raw : Number(String(raw).trim());
|
|
937
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
938
|
+
return DEFAULT_MAX_FETCH_ROWS;
|
|
939
|
+
}
|
|
940
|
+
return Math.floor(n);
|
|
941
|
+
}
|
|
894
942
|
//#endregion
|
|
895
943
|
//#region ---- Query ----
|
|
896
944
|
/**
|
|
897
|
-
* Runs {@link AXPEntityStorageService
|
|
945
|
+
* Runs entity storage reads via {@link AXPEntityStorageService} (active connectivity backend).
|
|
898
946
|
*/
|
|
899
947
|
class AXCEntityStorageQueryQuery {
|
|
900
948
|
constructor() {
|
|
@@ -938,6 +986,46 @@ class AXCEntityStorageQueryQuery {
|
|
|
938
986
|
const quickPaths = [...new Set([...fromSchema, ...explicit])];
|
|
939
987
|
merged = mergeQuickSearchIntoRequest(request, input.quickSearch, quickPaths);
|
|
940
988
|
}
|
|
989
|
+
const filterSort = { filter: merged.filter, sort: merged.sort };
|
|
990
|
+
const maxFetch = normalizeMaxFetchRows(input.maxFetchRows);
|
|
991
|
+
const aggregateReq = parseEntityStorageAggregateRequest(input.aggregate ?? null);
|
|
992
|
+
if (aggregateReq) {
|
|
993
|
+
const aggPayload = {
|
|
994
|
+
...aggregateReq,
|
|
995
|
+
filter: aggregateReq.filter ?? merged.filter,
|
|
996
|
+
sort: aggregateReq.sort ?? merged.sort,
|
|
997
|
+
};
|
|
998
|
+
const agg = await this.entityStorage.aggregate(entity, aggPayload, {
|
|
999
|
+
maxRows: maxFetch,
|
|
1000
|
+
pageSize: 500,
|
|
1001
|
+
});
|
|
1002
|
+
return {
|
|
1003
|
+
success: true,
|
|
1004
|
+
total: agg.rows.length,
|
|
1005
|
+
aggregateRows: agg.rows,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
const mode = normalizeResultMode(input.resultMode);
|
|
1009
|
+
if (mode === 'count') {
|
|
1010
|
+
const total = await this.entityStorage.count(entity, filterSort);
|
|
1011
|
+
return {
|
|
1012
|
+
success: true,
|
|
1013
|
+
total,
|
|
1014
|
+
items: [],
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (mode === 'all') {
|
|
1018
|
+
const all = await this.entityStorage.queryAll(entity, filterSort, {
|
|
1019
|
+
maxRows: maxFetch,
|
|
1020
|
+
pageSize: 500,
|
|
1021
|
+
});
|
|
1022
|
+
return {
|
|
1023
|
+
success: true,
|
|
1024
|
+
total: all.total,
|
|
1025
|
+
items: all.items,
|
|
1026
|
+
truncated: all.truncated,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
941
1029
|
const page = await this.entityStorage.query(entity, merged);
|
|
942
1030
|
return {
|
|
943
1031
|
success: true,
|
|
@@ -13908,8 +13996,8 @@ const AI_AGENT_MOCK = [
|
|
|
13908
13996
|
'You are the platform entity specialist for ACoreX.',
|
|
13909
13997
|
'Use PlatformManagement:ListRegisteredEntities to discover or validate Module.Entity keys and to inspect entity definitions (required fields, types).',
|
|
13910
13998
|
'When explaining fields to end users in chat, mention **human-readable `title`** text from **`properties`**; use internal **`name`** only when the user wants technical/schema detail.',
|
|
13911
|
-
'Use ConnectivityMock:EntityStorage:Query to
|
|
13912
|
-
'If
|
|
13999
|
+
'Use ConnectivityMock:EntityStorage:Query to read stored rows once the entity key is known (data layer: same tool name). Prefer resultMode `all` with maxFetchRows for the full filtered set in one call, `count` for totals only, or `aggregate` (groupBy + measures) for grouped summaries before charts.',
|
|
14000
|
+
'If you stay on default paging (`resultMode` page / skip–take) and totals require **every** row, fetch remaining pages in further tool turns until complete — do not ask the user to approve each page.',
|
|
13913
14001
|
'Use Entity:Create and Entity:Update only with arguments that match each command schema; confirm outcomes from tool results and never fabricate success.',
|
|
13914
14002
|
'Prefer read-only queries before mutations when you still need identifiers or schema details.',
|
|
13915
14003
|
'When the user wants a **chart** from stored rows, finish accurate aggregation first, then let the supervisor route **widget-chart-layout**; keep your own prose minimal.',
|
|
@@ -19274,7 +19362,7 @@ class AXCAiManagementMockModule {
|
|
|
19274
19362
|
provideCommandSetups([
|
|
19275
19363
|
{
|
|
19276
19364
|
key: 'AiManagement:ChatGenerateImage',
|
|
19277
|
-
command: () => import('./acorex-connectivity-mock-chat-generate-image.command-
|
|
19365
|
+
command: () => import('./acorex-connectivity-mock-chat-generate-image.command-iFiQ5rHd.mjs').then((c) => c.AiManagementChatGenerateImageCommand),
|
|
19278
19366
|
},
|
|
19279
19367
|
{
|
|
19280
19368
|
key: 'AiManagement:ExtractDocumentText',
|
|
@@ -19286,11 +19374,11 @@ class AXCAiManagementMockModule {
|
|
|
19286
19374
|
},
|
|
19287
19375
|
{
|
|
19288
19376
|
key: 'AiManagement:ChatTranscribeSpeech',
|
|
19289
|
-
command: () => import('./acorex-connectivity-mock-chat-transcribe-speech.command-
|
|
19377
|
+
command: () => import('./acorex-connectivity-mock-chat-transcribe-speech.command-NxlEgtW-.mjs').then((c) => c.AiManagementChatTranscribeSpeechCommand),
|
|
19290
19378
|
},
|
|
19291
19379
|
{
|
|
19292
19380
|
key: 'AiManagement:ChatSynthesizeSpeech',
|
|
19293
|
-
command: () => import('./acorex-connectivity-mock-chat-synthesize-speech.command-
|
|
19381
|
+
command: () => import('./acorex-connectivity-mock-chat-synthesize-speech.command-BTv6P2ez.mjs').then((c) => c.AiManagementChatSynthesizeSpeechCommand),
|
|
19294
19382
|
},
|
|
19295
19383
|
]),
|
|
19296
19384
|
] }); }
|
|
@@ -19333,7 +19421,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
19333
19421
|
provideCommandSetups([
|
|
19334
19422
|
{
|
|
19335
19423
|
key: 'AiManagement:ChatGenerateImage',
|
|
19336
|
-
command: () => import('./acorex-connectivity-mock-chat-generate-image.command-
|
|
19424
|
+
command: () => import('./acorex-connectivity-mock-chat-generate-image.command-iFiQ5rHd.mjs').then((c) => c.AiManagementChatGenerateImageCommand),
|
|
19337
19425
|
},
|
|
19338
19426
|
{
|
|
19339
19427
|
key: 'AiManagement:ExtractDocumentText',
|
|
@@ -19345,11 +19433,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
19345
19433
|
},
|
|
19346
19434
|
{
|
|
19347
19435
|
key: 'AiManagement:ChatTranscribeSpeech',
|
|
19348
|
-
command: () => import('./acorex-connectivity-mock-chat-transcribe-speech.command-
|
|
19436
|
+
command: () => import('./acorex-connectivity-mock-chat-transcribe-speech.command-NxlEgtW-.mjs').then((c) => c.AiManagementChatTranscribeSpeechCommand),
|
|
19349
19437
|
},
|
|
19350
19438
|
{
|
|
19351
19439
|
key: 'AiManagement:ChatSynthesizeSpeech',
|
|
19352
|
-
command: () => import('./acorex-connectivity-mock-chat-synthesize-speech.command-
|
|
19440
|
+
command: () => import('./acorex-connectivity-mock-chat-synthesize-speech.command-BTv6P2ez.mjs').then((c) => c.AiManagementChatSynthesizeSpeechCommand),
|
|
19353
19441
|
},
|
|
19354
19442
|
]),
|
|
19355
19443
|
],
|
|
@@ -30225,6 +30313,8 @@ const mockRoleDefinitions = [
|
|
|
30225
30313
|
AXMPermissionsKeys$2.DataManagement.DataSource.Management,
|
|
30226
30314
|
AXMPermissionsKeys$2.DataManagement.MetaDataDefinition.Management,
|
|
30227
30315
|
AXMPermissionsKeys$2.DataManagement.Tag.Management,
|
|
30316
|
+
//
|
|
30317
|
+
AXMPermissionsKeys$3.TaskManagement.Task.ViewAll,
|
|
30228
30318
|
],
|
|
30229
30319
|
stateInfo: {
|
|
30230
30320
|
isSystem: true,
|
|
@@ -30433,9 +30523,9 @@ const mockRoleDefinitions = [
|
|
|
30433
30523
|
AXPSecurityManagementPermissionKeys.Role.Edit,
|
|
30434
30524
|
AXPSecurityManagementPermissionKeys.Role.Delete,
|
|
30435
30525
|
//
|
|
30436
|
-
AXMPermissionsKeys$
|
|
30437
|
-
AXMPermissionsKeys$
|
|
30438
|
-
AXMPermissionsKeys$
|
|
30526
|
+
AXMPermissionsKeys$4.DocumentManagement.Management,
|
|
30527
|
+
AXMPermissionsKeys$4.DocumentManagement.Driver.Management,
|
|
30528
|
+
AXMPermissionsKeys$4.DocumentManagement.DocumentType.Management,
|
|
30439
30529
|
//
|
|
30440
30530
|
AXMPermissionsKeys$2.DataManagement.MetaDataDefinition.Management,
|
|
30441
30531
|
AXMPermissionsKeys$2.DataManagement.Tag.Management,
|
|
@@ -30443,9 +30533,9 @@ const mockRoleDefinitions = [
|
|
|
30443
30533
|
AXMAssessmentManagementPermissionKeys.AssessmentManagement.Questionnaire.Management,
|
|
30444
30534
|
AXMAssessmentManagementPermissionKeys.AssessmentManagement.QuestionBank,
|
|
30445
30535
|
//
|
|
30446
|
-
AXMPermissionsKeys$
|
|
30447
|
-
AXMPermissionsKeys$
|
|
30448
|
-
AXMPermissionsKeys$
|
|
30536
|
+
AXMPermissionsKeys$5.HumanCapitalManagement.Employee.Management,
|
|
30537
|
+
AXMPermissionsKeys$5.HumanCapitalManagement.LeaveType.Management,
|
|
30538
|
+
AXMPermissionsKeys$5.HumanCapitalManagement.LeaveRequest.Management,
|
|
30449
30539
|
//
|
|
30450
30540
|
AXMSettingsManagementPermissionKeys.TenantSettings.Management,
|
|
30451
30541
|
],
|
|
@@ -30559,8 +30649,8 @@ const rawMockUsers = [
|
|
|
30559
30649
|
id: userIds.sarahJohnson,
|
|
30560
30650
|
username: 'sarah.johnson',
|
|
30561
30651
|
displayName: 'Sarah Johnson',
|
|
30562
|
-
roleIds: [roleIdMap.
|
|
30563
|
-
roles: getRolesByIds([roleIdMap.
|
|
30652
|
+
roleIds: [roleIdMap.user],
|
|
30653
|
+
roles: getRolesByIds([roleIdMap.user]),
|
|
30564
30654
|
password: '123',
|
|
30565
30655
|
accountState: AXPSystemStatusType.Active,
|
|
30566
30656
|
accessState: AXMUserAccessState.Allowed,
|
|
@@ -31102,6 +31192,7 @@ const personMock = [
|
|
|
31102
31192
|
maritalStatus: maritalStatusRef(maritalStatusIds.married),
|
|
31103
31193
|
educationLevelId: educationLevelIds.masters,
|
|
31104
31194
|
educationLevel: educationLevelRef(educationLevelIds.masters),
|
|
31195
|
+
emails: [{ value: 'fatima.alzahra@example.com' }],
|
|
31105
31196
|
},
|
|
31106
31197
|
{
|
|
31107
31198
|
id: personIds.jamesOConnor,
|
|
@@ -31710,22 +31801,32 @@ const toLocalized = (enUS, faIR) => ({
|
|
|
31710
31801
|
});
|
|
31711
31802
|
const getStatusTitle = (statusId) => {
|
|
31712
31803
|
switch (statusId) {
|
|
31804
|
+
case AXMAssetStatus.Created:
|
|
31805
|
+
return toLocalized('Created', 'ایجاد شده');
|
|
31713
31806
|
case AXMAssetStatus.Available:
|
|
31714
31807
|
return toLocalized('Available', 'در دسترس');
|
|
31715
|
-
case AXMAssetStatus.
|
|
31716
|
-
return toLocalized('
|
|
31717
|
-
case AXMAssetStatus.
|
|
31718
|
-
return toLocalized('
|
|
31808
|
+
case AXMAssetStatus.InStock:
|
|
31809
|
+
return toLocalized('In Stock', 'موجود در انبار');
|
|
31810
|
+
case AXMAssetStatus.Reserved:
|
|
31811
|
+
return toLocalized('Reserved', 'رزرو شده');
|
|
31812
|
+
case AXMAssetStatus.Assigned:
|
|
31813
|
+
return toLocalized('Assigned', 'تخصیص داده شده');
|
|
31814
|
+
case AXMAssetStatus.Installed:
|
|
31815
|
+
return toLocalized('Installed', 'نصب شده');
|
|
31816
|
+
case AXMAssetStatus.UnderInspection:
|
|
31817
|
+
return toLocalized('Under Inspection', 'تحت بازرسی');
|
|
31818
|
+
case AXMAssetStatus.UnderMaintenance:
|
|
31819
|
+
return toLocalized('Under Maintenance', 'تحت نگهداری');
|
|
31820
|
+
case AXMAssetStatus.OutOfService:
|
|
31821
|
+
return toLocalized('Out of Service', 'خارج از سرویس');
|
|
31719
31822
|
case AXMAssetStatus.Retired:
|
|
31720
31823
|
return toLocalized('Retired', 'بازنشسته');
|
|
31721
|
-
case AXMAssetStatus.
|
|
31722
|
-
return toLocalized('
|
|
31723
|
-
case AXMAssetStatus.
|
|
31724
|
-
return toLocalized('
|
|
31725
|
-
case AXMAssetStatus.
|
|
31726
|
-
return toLocalized('
|
|
31727
|
-
case AXMAssetStatus.Inactive:
|
|
31728
|
-
return toLocalized('Inactive', 'غیرفعال');
|
|
31824
|
+
case AXMAssetStatus.Disposed:
|
|
31825
|
+
return toLocalized('Disposed', 'امحا شده');
|
|
31826
|
+
case AXMAssetStatus.Lost:
|
|
31827
|
+
return toLocalized('Lost', 'مفقود');
|
|
31828
|
+
case AXMAssetStatus.ReturnedToSupplier:
|
|
31829
|
+
return toLocalized('Returned to Supplier', 'بازگشت به تامین کننده');
|
|
31729
31830
|
default:
|
|
31730
31831
|
return toLocalized(String(statusId ?? ''), String(statusId ?? ''));
|
|
31731
31832
|
}
|
|
@@ -31849,7 +31950,7 @@ const assetEntries = [
|
|
|
31849
31950
|
code: `ASST-${MOCK_ASSET_CODE_YYYYMM}-${MOCK_ASSET_CODE_SEQ(7)}`,
|
|
31850
31951
|
title: 'Field Service Van 01',
|
|
31851
31952
|
titleFa: 'ون خدمات میدانی ۰۱',
|
|
31852
|
-
statusId: AXMAssetStatus.
|
|
31953
|
+
statusId: AXMAssetStatus.Assigned,
|
|
31853
31954
|
ownerEmployeeCode: EMPLOYEES_MOCK[0]?.employeeCode,
|
|
31854
31955
|
assignedEmployeeCode: EMPLOYEES_MOCK[2]?.employeeCode ?? EMPLOYEES_MOCK[0]?.employeeCode,
|
|
31855
31956
|
locationCode: 'LA-TH-001',
|
|
@@ -32534,18 +32635,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
32534
32635
|
//#endregion
|
|
32535
32636
|
//#region ---- Mock Permission Checker ----
|
|
32536
32637
|
/**
|
|
32537
|
-
* Mock permission checker
|
|
32538
|
-
* -
|
|
32539
|
-
* -
|
|
32638
|
+
* Mock permission checker.
|
|
32639
|
+
* - `root`, `root2`, `super-root`: pass all permission checks (platform operators in demo).
|
|
32640
|
+
* - Everyone else: use role-granted permissions only (e.g. `Task.ViewAll` is not implied for `heidi.thonig`).
|
|
32540
32641
|
*/
|
|
32541
32642
|
class MockPermissionChecker {
|
|
32643
|
+
static { this.platformRootUsernames = new Set(['root', 'root2', 'super-root']); }
|
|
32542
32644
|
check(keys, context, baseResult) {
|
|
32543
|
-
const
|
|
32544
|
-
|
|
32545
|
-
if (user?.name === 'super-root' || user?.name === 'heidi.thonig' || user?.name === 'emily.rodriguez') {
|
|
32645
|
+
const username = context.user?.name;
|
|
32646
|
+
if (username != null && MockPermissionChecker.platformRootUsernames.has(username)) {
|
|
32546
32647
|
return true;
|
|
32547
32648
|
}
|
|
32548
|
-
// Otherwise, use base result
|
|
32549
32649
|
return baseResult;
|
|
32550
32650
|
}
|
|
32551
32651
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MockPermissionChecker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -42970,9 +43070,10 @@ class AXCDocumentManagementService {
|
|
|
42970
43070
|
return await this.getFolderPath(file.parentId, stopFolderId);
|
|
42971
43071
|
}
|
|
42972
43072
|
async searchFiles(searchTerm, rootFolderId, options) {
|
|
43073
|
+
console.log('options', options);
|
|
42973
43074
|
const metadataFilters = options?.metadataFilters;
|
|
42974
43075
|
const documentTypeIds = options?.documentTypeIds;
|
|
42975
|
-
const hasMetadataFilters = metadataFilters != null &&
|
|
43076
|
+
const hasMetadataFilters = metadataFilters != null && metadataFilters.length > 0;
|
|
42976
43077
|
const hasDocTypeFilter = documentTypeIds != null && documentTypeIds.length > 0;
|
|
42977
43078
|
if (hasMetadataFilters || hasDocTypeFilter) {
|
|
42978
43079
|
const all = [];
|
|
@@ -43007,7 +43108,7 @@ class AXCDocumentManagementService {
|
|
|
43007
43108
|
return v;
|
|
43008
43109
|
}
|
|
43009
43110
|
/**
|
|
43010
|
-
* Reads a comparable value from a stored metaDataForm entry.
|
|
43111
|
+
* Reads a comparable value from a stored metaDataForm entry (select-like `{ value, text }` or plain scalars).
|
|
43011
43112
|
*/
|
|
43012
43113
|
storedMetaEntryToComparable(raw) {
|
|
43013
43114
|
if (raw != null && typeof raw === 'object' && !Array.isArray(raw) && 'value' in raw) {
|
|
@@ -43020,29 +43121,98 @@ class AXCDocumentManagementService {
|
|
|
43020
43121
|
return filterKey.startsWith(prefix) ? filterKey.slice(prefix.length) : filterKey;
|
|
43021
43122
|
}
|
|
43022
43123
|
/**
|
|
43023
|
-
* Whether the document's metaDataForm satisfies all metadata filter
|
|
43124
|
+
* Whether the document's metaDataForm satisfies all metadata filter queries (AND), using operator semantics
|
|
43125
|
+
* aligned with the query filter panel ({@link AXPFilterQuery}).
|
|
43024
43126
|
*/
|
|
43025
|
-
documentDtoMatchesMetadataFilters(file,
|
|
43127
|
+
documentDtoMatchesMetadataFilters(file, queries) {
|
|
43128
|
+
return queries.every((q) => this.documentDtoMatchesOneMetadataQuery(file, q));
|
|
43129
|
+
}
|
|
43130
|
+
documentDtoMatchesOneMetadataQuery(file, query) {
|
|
43131
|
+
const field = query.field;
|
|
43132
|
+
if (!field) {
|
|
43133
|
+
return true;
|
|
43134
|
+
}
|
|
43135
|
+
const key = this.metaDataFormKeyForFilter(field);
|
|
43026
43136
|
const form = file.metaDataForm;
|
|
43137
|
+
/** Runtime operator names from filter widgets may extend the CDK-typed union on {@link AXPFilterQuery}. */
|
|
43138
|
+
const opType = query.operator?.type;
|
|
43139
|
+
if (opType === 'isEmpty' || opType === 'isNull') {
|
|
43140
|
+
if (!form || typeof form !== 'object' || Array.isArray(form)) {
|
|
43141
|
+
return true;
|
|
43142
|
+
}
|
|
43143
|
+
const raw = form[key];
|
|
43144
|
+
const v = this.storedMetaEntryToComparable(raw);
|
|
43145
|
+
return v == null || v === '';
|
|
43146
|
+
}
|
|
43147
|
+
if (opType === 'isNotEmpty') {
|
|
43148
|
+
if (!form || typeof form !== 'object' || Array.isArray(form)) {
|
|
43149
|
+
return false;
|
|
43150
|
+
}
|
|
43151
|
+
const raw = form[key];
|
|
43152
|
+
const v = this.storedMetaEntryToComparable(raw);
|
|
43153
|
+
return v != null && v !== '';
|
|
43154
|
+
}
|
|
43027
43155
|
if (!form || typeof form !== 'object' || Array.isArray(form)) {
|
|
43028
43156
|
return false;
|
|
43029
43157
|
}
|
|
43030
43158
|
const record = form;
|
|
43031
|
-
|
|
43032
|
-
|
|
43033
|
-
const
|
|
43034
|
-
|
|
43035
|
-
|
|
43159
|
+
const metaValue = this.storedMetaEntryToComparable(record[key]);
|
|
43160
|
+
if (opType === 'between') {
|
|
43161
|
+
const betweenRaw = query.value;
|
|
43162
|
+
const fromN = betweenRaw?.from != null && betweenRaw.from !== '' ? Number(betweenRaw.from) : NaN;
|
|
43163
|
+
const toN = betweenRaw?.to != null && betweenRaw.to !== '' ? Number(betweenRaw.to) : NaN;
|
|
43164
|
+
const n = Number(metaValue);
|
|
43165
|
+
if (Number.isNaN(n)) {
|
|
43166
|
+
return false;
|
|
43036
43167
|
}
|
|
43037
|
-
|
|
43038
|
-
if (metaValue == null) {
|
|
43168
|
+
if (!Number.isNaN(fromN) && n < fromN) {
|
|
43039
43169
|
return false;
|
|
43040
43170
|
}
|
|
43041
|
-
if (
|
|
43042
|
-
return
|
|
43171
|
+
if (!Number.isNaN(toN) && n > toN) {
|
|
43172
|
+
return false;
|
|
43043
43173
|
}
|
|
43044
|
-
return
|
|
43045
|
-
}
|
|
43174
|
+
return true;
|
|
43175
|
+
}
|
|
43176
|
+
const filterComparable = this.unwrapMetadataComparableValue(query.value);
|
|
43177
|
+
if (filterComparable == null || filterComparable === '') {
|
|
43178
|
+
return true;
|
|
43179
|
+
}
|
|
43180
|
+
if (metaValue == null) {
|
|
43181
|
+
return false;
|
|
43182
|
+
}
|
|
43183
|
+
const metaStr = String(metaValue);
|
|
43184
|
+
const filterStr = String(filterComparable);
|
|
43185
|
+
switch (opType) {
|
|
43186
|
+
case 'contains':
|
|
43187
|
+
return metaStr.toLowerCase().includes(filterStr.toLowerCase());
|
|
43188
|
+
case 'notContains':
|
|
43189
|
+
return !metaStr.toLowerCase().includes(filterStr.toLowerCase());
|
|
43190
|
+
case 'startsWith':
|
|
43191
|
+
return metaStr.toLowerCase().startsWith(filterStr.toLowerCase());
|
|
43192
|
+
case 'endsWith':
|
|
43193
|
+
return metaStr.toLowerCase().endsWith(filterStr.toLowerCase());
|
|
43194
|
+
case 'notEqual':
|
|
43195
|
+
return metaValue !== filterComparable && metaStr !== filterStr;
|
|
43196
|
+
case 'greaterThan':
|
|
43197
|
+
case 'gt':
|
|
43198
|
+
return Number(metaValue) > Number(filterComparable);
|
|
43199
|
+
case 'greaterThanOrEqual':
|
|
43200
|
+
case 'gte':
|
|
43201
|
+
return Number(metaValue) >= Number(filterComparable);
|
|
43202
|
+
case 'lessThan':
|
|
43203
|
+
case 'lt':
|
|
43204
|
+
return Number(metaValue) < Number(filterComparable);
|
|
43205
|
+
case 'lessThanOrEqual':
|
|
43206
|
+
case 'lte':
|
|
43207
|
+
return Number(metaValue) <= Number(filterComparable);
|
|
43208
|
+
case 'in': {
|
|
43209
|
+
const list = Array.isArray(filterComparable) ? filterComparable : [filterComparable];
|
|
43210
|
+
return list.some((x) => x === metaValue || String(x) === metaStr);
|
|
43211
|
+
}
|
|
43212
|
+
case 'equal':
|
|
43213
|
+
default:
|
|
43214
|
+
return metaValue === filterComparable || metaStr === filterStr;
|
|
43215
|
+
}
|
|
43046
43216
|
}
|
|
43047
43217
|
/**
|
|
43048
43218
|
* Recursive collect of all files under a folder.
|
|
@@ -43656,6 +43826,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
43656
43826
|
type: Injectable
|
|
43657
43827
|
}] });
|
|
43658
43828
|
|
|
43829
|
+
/**
|
|
43830
|
+
* Drive mock tree: `parentId` is the source of truth for hierarchy. Each folder's `folders` and
|
|
43831
|
+
* `documents` arrays contain direct children only (no duplicate projection into ancestors).
|
|
43832
|
+
* `folderStorageMiddleware` updates embedded lists on the immediate parent only and skips
|
|
43833
|
+
* `stateInfo.isSystem` parents so platform root folders are not persisted on every deep create.
|
|
43834
|
+
*/
|
|
43659
43835
|
// Section: Constants and Initial Setup
|
|
43660
43836
|
const checkbox = '{"type":"document-layout","children":[{"type":"page-layout","children":[{"type":"checkbox-editor","name":"checkbox-editor1","options":{"label":"i accept","readonly":false,"disabled":false},"defaultValue":false,"path":"accept-check-box"}],"name":"page-layout1"}],"name":"document"}';
|
|
43661
43837
|
const allTypes = DOCUMENT_TYPES;
|
|
@@ -43743,7 +43919,7 @@ const rootTenantsFolder = {
|
|
|
43743
43919
|
size: getRandomSize(),
|
|
43744
43920
|
description: `Folder Of Tenants`,
|
|
43745
43921
|
parentId: rootFolder.id,
|
|
43746
|
-
stateInfo: { isSystem:
|
|
43922
|
+
stateInfo: { isSystem: true, isHidden: false },
|
|
43747
43923
|
auditInfo: {
|
|
43748
43924
|
created: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
43749
43925
|
updated: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
@@ -43769,9 +43945,9 @@ function getRandomMetadata() {
|
|
|
43769
43945
|
const docNo = sampleDocNos[Math.floor(Math.random() * sampleDocNos.length)];
|
|
43770
43946
|
const projectTypeKey = sampleProjectTypes[Math.floor(Math.random() * sampleProjectTypes.length)];
|
|
43771
43947
|
return {
|
|
43772
|
-
'project-name': { value: projectName,
|
|
43773
|
-
'doc-no': { value: docNo,
|
|
43774
|
-
'project-type': { value: projectTypeKey,
|
|
43948
|
+
'project-name': { value: projectName, text: 'Project Name' },
|
|
43949
|
+
'doc-no': { value: docNo, text: 'Doc No' },
|
|
43950
|
+
'project-type': { value: projectTypeKey, text: sampleProjectTypeLabels[projectTypeKey] ?? projectTypeKey },
|
|
43775
43951
|
};
|
|
43776
43952
|
}
|
|
43777
43953
|
// Section: Sample File Types
|
|
@@ -43899,7 +44075,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
43899
44075
|
size: getRandomSize(),
|
|
43900
44076
|
description: `Folder Of Users`,
|
|
43901
44077
|
parentId: mainTenantFolder.id,
|
|
43902
|
-
stateInfo: { isSystem:
|
|
44078
|
+
stateInfo: { isSystem: true, isHidden: false },
|
|
43903
44079
|
auditInfo: {
|
|
43904
44080
|
created: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
43905
44081
|
updated: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
@@ -43925,7 +44101,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
43925
44101
|
size: getRandomSize(),
|
|
43926
44102
|
description: `Folder of User : ${user.displayName}`,
|
|
43927
44103
|
parentId: rootUsersFolder.id,
|
|
43928
|
-
stateInfo: { isSystem:
|
|
44104
|
+
stateInfo: { isSystem: true, isHidden: false },
|
|
43929
44105
|
auditInfo: {
|
|
43930
44106
|
created: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
43931
44107
|
updated: { at: new Date(), by: { id: 'system', type: 'user' } },
|
|
@@ -43963,8 +44139,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
43963
44139
|
metaDataForm: {
|
|
43964
44140
|
type: {
|
|
43965
44141
|
value: extension == 'png' || extension == 'jpg' ? 'image' : extension,
|
|
43966
|
-
|
|
43967
|
-
title: 'Type',
|
|
44142
|
+
text: 'Type',
|
|
43968
44143
|
},
|
|
43969
44144
|
...getRandomMetadata(),
|
|
43970
44145
|
},
|
|
@@ -44049,8 +44224,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
44049
44224
|
metaDataForm: {
|
|
44050
44225
|
type: {
|
|
44051
44226
|
value: fileType.ext == 'png' || fileType.ext == 'jpg' ? 'image' : fileType.ext,
|
|
44052
|
-
|
|
44053
|
-
name: 'title',
|
|
44227
|
+
text: 'Type',
|
|
44054
44228
|
},
|
|
44055
44229
|
...getRandomMetadata(),
|
|
44056
44230
|
},
|
|
@@ -44112,8 +44286,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
44112
44286
|
metaDataForm: {
|
|
44113
44287
|
type: {
|
|
44114
44288
|
value: extension == 'png' || extension == 'jpg' ? 'image' : extension,
|
|
44115
|
-
|
|
44116
|
-
name: 'title',
|
|
44289
|
+
text: 'Type',
|
|
44117
44290
|
},
|
|
44118
44291
|
...getRandomMetadata(),
|
|
44119
44292
|
},
|
|
@@ -44170,8 +44343,7 @@ function createTenantFolderStructure(tenantId, tenantName) {
|
|
|
44170
44343
|
metaDataForm: {
|
|
44171
44344
|
type: {
|
|
44172
44345
|
value: fileType.ext == 'png' || fileType.ext == 'jpg' ? 'image' : fileType.ext,
|
|
44173
|
-
|
|
44174
|
-
name: 'title',
|
|
44346
|
+
text: 'Type',
|
|
44175
44347
|
},
|
|
44176
44348
|
...getRandomMetadata(),
|
|
44177
44349
|
},
|
|
@@ -44222,14 +44394,91 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
44222
44394
|
}] });
|
|
44223
44395
|
|
|
44224
44396
|
const FOLDER_ENTITY$1 = 'DocumentManagement.Folder';
|
|
44397
|
+
const DOCUMENT_ENTITY$2 = 'DocumentManagement.Document';
|
|
44398
|
+
function isSystemFolder(folder) {
|
|
44399
|
+
return Boolean(folder?.stateInfo?.isSystem);
|
|
44400
|
+
}
|
|
44401
|
+
function filterByParentIdEquals(parentId) {
|
|
44402
|
+
return {
|
|
44403
|
+
operator: null,
|
|
44404
|
+
logic: 'and',
|
|
44405
|
+
filters: [{ field: 'parentId', operator: { type: 'equal' }, value: parentId }],
|
|
44406
|
+
};
|
|
44407
|
+
}
|
|
44408
|
+
/**
|
|
44409
|
+
* Replaces each direct child folder with the persisted row from `getOne`.
|
|
44410
|
+
* Embedded snapshots in `parent.folders` stay stale after documents or nested folders are added;
|
|
44411
|
+
* only rows missing from the embed were previously re-fetched.
|
|
44412
|
+
*/
|
|
44413
|
+
async function refreshDirectChildFolderSnapshotsFromStorage(backend, folderEntityName, folders) {
|
|
44414
|
+
const out = [];
|
|
44415
|
+
for (const f of folders) {
|
|
44416
|
+
if (!f?.id) {
|
|
44417
|
+
out.push(f);
|
|
44418
|
+
continue;
|
|
44419
|
+
}
|
|
44420
|
+
try {
|
|
44421
|
+
const fresh = (await backend.getOne(folderEntityName, f.id));
|
|
44422
|
+
out.push(fresh ?? f);
|
|
44423
|
+
}
|
|
44424
|
+
catch {
|
|
44425
|
+
out.push(f);
|
|
44426
|
+
}
|
|
44427
|
+
}
|
|
44428
|
+
return out;
|
|
44429
|
+
}
|
|
44430
|
+
/**
|
|
44431
|
+
* Appends direct child folders/documents discovered by `parentId` when they are missing from embedded arrays,
|
|
44432
|
+
* then refreshes every direct child folder from storage so embedded denormalized rows match persisted content.
|
|
44433
|
+
* Keeps drive listing and mock `searchFolders` / `searchFiles` (which walk `folders` from `getOne`) aligned with persisted rows.
|
|
44434
|
+
*/
|
|
44435
|
+
async function mergeMissingDirectChildrenFromStorage(backend, folderEntityName, folder) {
|
|
44436
|
+
if (!folder.id)
|
|
44437
|
+
return;
|
|
44438
|
+
const parentFilter = filterByParentIdEquals(folder.id);
|
|
44439
|
+
const embeddedFolders = folder.folders ?? [];
|
|
44440
|
+
const folderIdSet = new Set(embeddedFolders.map((f) => f.id));
|
|
44441
|
+
const mergedFolders = [...embeddedFolders];
|
|
44442
|
+
const folderPage = await backend.query(folderEntityName, { skip: 0, take: 2000, filter: parentFilter });
|
|
44443
|
+
for (const row of folderPage.items) {
|
|
44444
|
+
if (!row?.id || folderIdSet.has(row.id))
|
|
44445
|
+
continue;
|
|
44446
|
+
folderIdSet.add(row.id);
|
|
44447
|
+
try {
|
|
44448
|
+
const full = (await backend.getOne(folderEntityName, row.id));
|
|
44449
|
+
mergedFolders.push(full ?? row);
|
|
44450
|
+
}
|
|
44451
|
+
catch {
|
|
44452
|
+
mergedFolders.push(row);
|
|
44453
|
+
}
|
|
44454
|
+
}
|
|
44455
|
+
folder.folders = await refreshDirectChildFolderSnapshotsFromStorage(backend, folderEntityName, mergedFolders);
|
|
44456
|
+
const embeddedDocs = (folder.documents ?? []);
|
|
44457
|
+
const docIdSet = new Set(embeddedDocs.map((d) => d.id).filter((id) => Boolean(id)));
|
|
44458
|
+
const mergedDocs = [...embeddedDocs];
|
|
44459
|
+
const docPage = await backend.query(DOCUMENT_ENTITY$2, { skip: 0, take: 2000, filter: parentFilter });
|
|
44460
|
+
for (const row of docPage.items) {
|
|
44461
|
+
if (!row?.id || docIdSet.has(row.id))
|
|
44462
|
+
continue;
|
|
44463
|
+
docIdSet.add(row.id);
|
|
44464
|
+
try {
|
|
44465
|
+
const full = await backend.getOne(DOCUMENT_ENTITY$2, row.id);
|
|
44466
|
+
mergedDocs.push(full != null ? full : row);
|
|
44467
|
+
}
|
|
44468
|
+
catch {
|
|
44469
|
+
mergedDocs.push(row);
|
|
44470
|
+
}
|
|
44471
|
+
}
|
|
44472
|
+
folder.documents = mergedDocs;
|
|
44473
|
+
}
|
|
44225
44474
|
/**
|
|
44226
44475
|
* Folder Storage Middleware
|
|
44227
44476
|
*
|
|
44228
44477
|
* Applies folder-specific behavior via storage layer (no service override):
|
|
44229
|
-
* - create: enriches ownership from session, default inherit; after persist syncs parent.folders.
|
|
44230
|
-
* - update: after persist, removes from old parent if parentId changed, syncs parent.folders.
|
|
44231
|
-
* - delete: before delete loads folder, after delete removes from parent.folders.
|
|
44232
|
-
* - getOne: enriches
|
|
44478
|
+
* - create: enriches ownership from session, default inherit; after persist syncs immediate parent.folders only.
|
|
44479
|
+
* - update: after persist, removes from old parent if parentId changed, syncs immediate parent.folders only.
|
|
44480
|
+
* - delete: before delete loads folder, after delete removes from parent.folders (immediate parent only).
|
|
44481
|
+
* - getOne: merges missing direct children from storage by `parentId`, refreshes each embedded child folder from storage, then enriches counts and inherited properties.
|
|
44233
44482
|
*/
|
|
44234
44483
|
const folderStorageMiddleware = {
|
|
44235
44484
|
target: {
|
|
@@ -44277,34 +44526,36 @@ const folderStorageMiddleware = {
|
|
|
44277
44526
|
}
|
|
44278
44527
|
await next();
|
|
44279
44528
|
const backend = ctx.backend;
|
|
44280
|
-
|
|
44529
|
+
/**
|
|
44530
|
+
* Updates only the direct parent's embedded `folders` list (no ancestor walk).
|
|
44531
|
+
* Skips persistence when the parent is a system folder so mock roots are not mutated.
|
|
44532
|
+
*/
|
|
44533
|
+
const syncImmediateParentFoldersList = async (folder) => {
|
|
44281
44534
|
if (!folder.parentId)
|
|
44282
44535
|
return;
|
|
44283
44536
|
const parent = (await backend.getOne(ctx.entityName, folder.parentId));
|
|
44284
|
-
if (!parent?.id)
|
|
44537
|
+
if (!parent?.id || isSystemFolder(parent))
|
|
44285
44538
|
return;
|
|
44286
44539
|
const folders = parent.folders ?? [];
|
|
44287
44540
|
const idx = folders.findIndex((f) => f.id === folder.id);
|
|
44288
44541
|
const nextFolders = idx === -1 ? [...folders, folder] : folders.map((f, i) => (i === idx ? folder : f));
|
|
44289
44542
|
await backend.updateOne(ctx.entityName, parent.id, { folders: nextFolders });
|
|
44290
|
-
await updateParentFolders(parent);
|
|
44291
44543
|
};
|
|
44292
|
-
const
|
|
44544
|
+
const removeFromImmediateParentFoldersList = async (folder) => {
|
|
44293
44545
|
if (!folder.parentId)
|
|
44294
44546
|
return;
|
|
44295
44547
|
const parent = (await backend.getOne(ctx.entityName, folder.parentId));
|
|
44296
|
-
if (!parent?.id || !parent.folders)
|
|
44548
|
+
if (!parent?.id || !parent.folders || isSystemFolder(parent))
|
|
44297
44549
|
return;
|
|
44298
44550
|
const nextFolders = parent.folders.filter((f) => f.id !== folder.id);
|
|
44299
44551
|
await backend.updateOne(ctx.entityName, parent.id, { folders: nextFolders });
|
|
44300
|
-
await updateParentFolders(parent);
|
|
44301
44552
|
};
|
|
44302
|
-
// ---- create: after insert, sync parent.folders ----
|
|
44553
|
+
// ---- create: after insert, sync immediate parent.folders ----
|
|
44303
44554
|
if (ctx.op === 'create' && ctx.result) {
|
|
44304
44555
|
const newId = ctx.result;
|
|
44305
44556
|
const folderData = (await backend.getOne(ctx.entityName, newId));
|
|
44306
44557
|
if (folderData) {
|
|
44307
|
-
await
|
|
44558
|
+
await syncImmediateParentFoldersList(folderData);
|
|
44308
44559
|
}
|
|
44309
44560
|
}
|
|
44310
44561
|
// ---- update: remove from old parent if parentId changed, then sync new parent ----
|
|
@@ -44312,17 +44563,18 @@ const folderStorageMiddleware = {
|
|
|
44312
44563
|
const updated = ctx.result;
|
|
44313
44564
|
const data = (ctx.data || {});
|
|
44314
44565
|
if (data.parentId !== undefined && currentFolder.parentId !== data.parentId) {
|
|
44315
|
-
await
|
|
44566
|
+
await removeFromImmediateParentFoldersList(currentFolder);
|
|
44316
44567
|
}
|
|
44317
|
-
await
|
|
44568
|
+
await syncImmediateParentFoldersList(updated);
|
|
44318
44569
|
}
|
|
44319
44570
|
// ---- delete: remove from parent after delete ----
|
|
44320
44571
|
if (ctx.op === 'delete' && folderBeforeDelete) {
|
|
44321
|
-
await
|
|
44572
|
+
await removeFromImmediateParentFoldersList(folderBeforeDelete);
|
|
44322
44573
|
}
|
|
44323
|
-
// ---- getOne:
|
|
44574
|
+
// ---- getOne: merge direct children by parentId, enrich counts, inherited props ----
|
|
44324
44575
|
if (ctx.op === 'getOne' && ctx.result) {
|
|
44325
44576
|
const folder = ctx.result;
|
|
44577
|
+
await mergeMissingDirectChildrenFromStorage(backend, ctx.entityName, folder);
|
|
44326
44578
|
folder.childrenCount = folder.folders?.length ?? 0;
|
|
44327
44579
|
folder.itemsCount = folder.documents?.length ?? 0;
|
|
44328
44580
|
if (folder.folders) {
|
|
@@ -53068,6 +53320,45 @@ function defaultIdField(dataPath) {
|
|
|
53068
53320
|
const leaf = (dataPath || '').split('.').filter(Boolean).pop() || 'related';
|
|
53069
53321
|
return `${leaf}Id`;
|
|
53070
53322
|
}
|
|
53323
|
+
/**
|
|
53324
|
+
* Reads merge-detail child data for referenced persistence: nested `dataPath` object and/or dotted keys
|
|
53325
|
+
* on the parent payload (`person.emails`, ...). Some clients send only flat dotted keys; without this,
|
|
53326
|
+
* `PersonCore.Person` is never updated and `getOne` hydrate keeps outdated contact fields on the host.
|
|
53327
|
+
*/
|
|
53328
|
+
function readMergeDetailSubtree(parentData, dataPath) {
|
|
53329
|
+
if (!dataPath || parentData == null || typeof parentData !== 'object') {
|
|
53330
|
+
return undefined;
|
|
53331
|
+
}
|
|
53332
|
+
const root = get(parentData, dataPath);
|
|
53333
|
+
const prefix = `${dataPath}.`;
|
|
53334
|
+
const flatKeys = Object.keys(parentData).filter((k) => k.startsWith(prefix));
|
|
53335
|
+
const hasFlat = flatKeys.length > 0;
|
|
53336
|
+
if (root != null && typeof root === 'object' && !Array.isArray(root)) {
|
|
53337
|
+
const base = { ...root };
|
|
53338
|
+
if (hasFlat) {
|
|
53339
|
+
for (const key of flatKeys) {
|
|
53340
|
+
const tail = key.slice(prefix.length);
|
|
53341
|
+
if (!tail) {
|
|
53342
|
+
continue;
|
|
53343
|
+
}
|
|
53344
|
+
set(base, tail, get(parentData, key));
|
|
53345
|
+
}
|
|
53346
|
+
}
|
|
53347
|
+
return base;
|
|
53348
|
+
}
|
|
53349
|
+
if (!hasFlat) {
|
|
53350
|
+
return undefined;
|
|
53351
|
+
}
|
|
53352
|
+
const base = {};
|
|
53353
|
+
for (const key of flatKeys) {
|
|
53354
|
+
const tail = key.slice(prefix.length);
|
|
53355
|
+
if (!tail) {
|
|
53356
|
+
continue;
|
|
53357
|
+
}
|
|
53358
|
+
set(base, tail, get(parentData, key));
|
|
53359
|
+
}
|
|
53360
|
+
return base;
|
|
53361
|
+
}
|
|
53071
53362
|
/** Only string/number ids are valid FK/PK for storage; ignore objects/arrays (bad form state). */
|
|
53072
53363
|
function coerceRecordId(value) {
|
|
53073
53364
|
if (value == null) {
|
|
@@ -53106,7 +53397,7 @@ const mergeDetailRelationMiddleware = {
|
|
|
53106
53397
|
const idField = p.idField || defaultIdField(dp);
|
|
53107
53398
|
const entries = Object.entries(p.map || {});
|
|
53108
53399
|
const requiresChild = entries.some(([_, src]) => typeof src === 'string' && !src.startsWith('$parent.'));
|
|
53109
|
-
const child = dp ?
|
|
53400
|
+
const child = dp ? readMergeDetailSubtree(ctx.data, dp) : undefined;
|
|
53110
53401
|
if (requiresChild && (dp == null || child == null)) {
|
|
53111
53402
|
// No available child object while mapping expects it
|
|
53112
53403
|
continue;
|
|
@@ -53977,6 +54268,380 @@ async function resolveCategoriesWithAncestors(backend, categoryEntityName, categ
|
|
|
53977
54268
|
return Array.from(collected.values());
|
|
53978
54269
|
}
|
|
53979
54270
|
|
|
54271
|
+
/** Cache TTL in ms - policies rarely change during a session */
|
|
54272
|
+
const POLICY_CACHE_TTL_MS = 30_000;
|
|
54273
|
+
/** In-memory cache for policies - avoids N+1 Policy queries per page (one per entity operation) */
|
|
54274
|
+
let policyCache = null;
|
|
54275
|
+
/** In-flight promise for request deduplication - parallel entity ops share one Policy load */
|
|
54276
|
+
let policyLoadPromise = null;
|
|
54277
|
+
/**
|
|
54278
|
+
* resolve whether a policy matches current entity by wildcard (*) or exact name
|
|
54279
|
+
*/
|
|
54280
|
+
function matchesEntity(target, entityName) {
|
|
54281
|
+
if (!target || target === '*')
|
|
54282
|
+
return true;
|
|
54283
|
+
return target === entityName;
|
|
54284
|
+
}
|
|
54285
|
+
/**
|
|
54286
|
+
* apply masking to a value using a simple standard mask
|
|
54287
|
+
*/
|
|
54288
|
+
function maskValue(value) {
|
|
54289
|
+
if (value == null)
|
|
54290
|
+
return value;
|
|
54291
|
+
const str = String(value);
|
|
54292
|
+
if (str.length <= 4)
|
|
54293
|
+
return '****';
|
|
54294
|
+
return `${'*'.repeat(Math.max(0, str.length - 4))}${str.slice(-4)}`;
|
|
54295
|
+
}
|
|
54296
|
+
/**
|
|
54297
|
+
* remove fields from an object by dotted paths; supports single-level for now
|
|
54298
|
+
*/
|
|
54299
|
+
function removeFields(obj, paths) {
|
|
54300
|
+
if (!obj || !paths?.length)
|
|
54301
|
+
return;
|
|
54302
|
+
// Helper: delete by dotted path (e.g., "a.b.c")
|
|
54303
|
+
const deleteByPath = (target, path) => {
|
|
54304
|
+
const parts = path.split('.');
|
|
54305
|
+
let cursor = target;
|
|
54306
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
54307
|
+
const key = parts[i];
|
|
54308
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
54309
|
+
return;
|
|
54310
|
+
cursor = cursor[key];
|
|
54311
|
+
}
|
|
54312
|
+
const lastKey = parts[parts.length - 1];
|
|
54313
|
+
if (cursor && typeof cursor === 'object' && Object.prototype.hasOwnProperty.call(cursor, lastKey)) {
|
|
54314
|
+
delete cursor[lastKey];
|
|
54315
|
+
}
|
|
54316
|
+
};
|
|
54317
|
+
// If wildcard present, remove all top-level fields
|
|
54318
|
+
if (paths.includes('*')) {
|
|
54319
|
+
for (const key of Object.keys(obj)) {
|
|
54320
|
+
delete obj[key];
|
|
54321
|
+
}
|
|
54322
|
+
}
|
|
54323
|
+
for (const p of paths) {
|
|
54324
|
+
if (!p || p === '*')
|
|
54325
|
+
continue;
|
|
54326
|
+
if (p.includes('.')) {
|
|
54327
|
+
deleteByPath(obj, p);
|
|
54328
|
+
}
|
|
54329
|
+
else if (Object.prototype.hasOwnProperty.call(obj, p)) {
|
|
54330
|
+
delete obj[p];
|
|
54331
|
+
}
|
|
54332
|
+
}
|
|
54333
|
+
}
|
|
54334
|
+
/**
|
|
54335
|
+
* set masked fields in object by dotted paths; supports single-level for now
|
|
54336
|
+
*/
|
|
54337
|
+
function setMasked(obj, paths) {
|
|
54338
|
+
if (!obj || !paths?.length)
|
|
54339
|
+
return;
|
|
54340
|
+
// Helper: set mask by dotted path (e.g., "a.b.c")
|
|
54341
|
+
const maskByPath = (target, path) => {
|
|
54342
|
+
const parts = path.split('.');
|
|
54343
|
+
let cursor = target;
|
|
54344
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
54345
|
+
const key = parts[i];
|
|
54346
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
54347
|
+
return;
|
|
54348
|
+
cursor = cursor[key];
|
|
54349
|
+
}
|
|
54350
|
+
const lastKey = parts[parts.length - 1];
|
|
54351
|
+
if (cursor && typeof cursor === 'object' && Object.prototype.hasOwnProperty.call(cursor, lastKey)) {
|
|
54352
|
+
cursor[lastKey] = maskValue(cursor[lastKey]);
|
|
54353
|
+
}
|
|
54354
|
+
};
|
|
54355
|
+
// If wildcard present, mask all top-level primitive fields
|
|
54356
|
+
if (paths.includes('*')) {
|
|
54357
|
+
for (const key of Object.keys(obj)) {
|
|
54358
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
54359
|
+
const val = obj[key];
|
|
54360
|
+
if (val == null || typeof val !== 'object') {
|
|
54361
|
+
obj[key] = maskValue(val);
|
|
54362
|
+
}
|
|
54363
|
+
}
|
|
54364
|
+
}
|
|
54365
|
+
}
|
|
54366
|
+
for (const p of paths) {
|
|
54367
|
+
if (!p || p === '*')
|
|
54368
|
+
continue;
|
|
54369
|
+
if (p.includes('.')) {
|
|
54370
|
+
maskByPath(obj, p);
|
|
54371
|
+
}
|
|
54372
|
+
else if (Object.prototype.hasOwnProperty.call(obj, p)) {
|
|
54373
|
+
obj[p] = maskValue(obj[p]);
|
|
54374
|
+
}
|
|
54375
|
+
}
|
|
54376
|
+
}
|
|
54377
|
+
/**
|
|
54378
|
+
* collect effect targets from a list of rules filtered by effect name
|
|
54379
|
+
*/
|
|
54380
|
+
function collectTargets(rules, effect) {
|
|
54381
|
+
return rules.filter(r => r.effect === effect).map(r => r.target).filter(Boolean);
|
|
54382
|
+
}
|
|
54383
|
+
/**
|
|
54384
|
+
* Evaluate a policy's rules against the provided scope
|
|
54385
|
+
*/
|
|
54386
|
+
async function isRuleActive(conditions, evaluator, extraScope) {
|
|
54387
|
+
// Note: conditions is AXPExpression and may be a template string {{ ... }} or boolean
|
|
54388
|
+
const result = await evaluator.evaluate(conditions, extraScope);
|
|
54389
|
+
return Boolean(result);
|
|
54390
|
+
}
|
|
54391
|
+
const policyEnforceMiddleware = {
|
|
54392
|
+
target: { ops: ['create', 'update', 'delete', 'getOne', 'getAll', 'query'], order: 5 },
|
|
54393
|
+
execute: async (ctx, next) => {
|
|
54394
|
+
// try { console.warn('[policy] enter-middleware', ctx.op, ctx.entityName, ctx.id); } catch {}
|
|
54395
|
+
// Inject services at the top (before any await) to ensure injection context
|
|
54396
|
+
const policyService = inject(AXMPolicyService);
|
|
54397
|
+
const evaluator = inject(AXPExpressionEvaluatorService);
|
|
54398
|
+
const backend = inject(AXP_ENTITY_STORAGE_BACKEND);
|
|
54399
|
+
// Prevent recursion: skip enforcement when querying the Policy entity itself
|
|
54400
|
+
const policyEntityFullName = `${RootConfig$6.module.name}.${RootConfig$6.entities.policy.name}`;
|
|
54401
|
+
if (ctx.entityName === policyEntityFullName) {
|
|
54402
|
+
// Invalidate cache when Policy is modified so next entity op gets fresh policies
|
|
54403
|
+
if (ctx.op === 'create' || ctx.op === 'update' || ctx.op === 'delete') {
|
|
54404
|
+
policyCache = null;
|
|
54405
|
+
}
|
|
54406
|
+
await next();
|
|
54407
|
+
return;
|
|
54408
|
+
}
|
|
54409
|
+
// Load all policies (cached + request deduplication - parallel entity ops share one Policy load)
|
|
54410
|
+
let policies = [];
|
|
54411
|
+
const now = Date.now();
|
|
54412
|
+
if (policyCache && now - policyCache.timestamp < POLICY_CACHE_TTL_MS) {
|
|
54413
|
+
policies = policyCache.policies;
|
|
54414
|
+
}
|
|
54415
|
+
else {
|
|
54416
|
+
if (!policyLoadPromise) {
|
|
54417
|
+
policyLoadPromise = (async () => {
|
|
54418
|
+
try {
|
|
54419
|
+
const res = await policyService.query({ skip: 0, take: 1000 });
|
|
54420
|
+
const items = (res?.items ?? []);
|
|
54421
|
+
policyCache = { policies: items, timestamp: Date.now() };
|
|
54422
|
+
return items;
|
|
54423
|
+
}
|
|
54424
|
+
catch {
|
|
54425
|
+
return [];
|
|
54426
|
+
}
|
|
54427
|
+
finally {
|
|
54428
|
+
policyLoadPromise = null;
|
|
54429
|
+
}
|
|
54430
|
+
})();
|
|
54431
|
+
}
|
|
54432
|
+
policies = await policyLoadPromise;
|
|
54433
|
+
}
|
|
54434
|
+
const applicable = policies.filter(p => p?.resource?.type === 'entity' && matchesEntity(p.resource?.target, ctx.entityName));
|
|
54435
|
+
if (applicable.length === 0) {
|
|
54436
|
+
await next();
|
|
54437
|
+
return;
|
|
54438
|
+
}
|
|
54439
|
+
// Build evaluation scope per operation
|
|
54440
|
+
const buildScope = (overrides) => {
|
|
54441
|
+
const existing = overrides?.existing;
|
|
54442
|
+
const result = overrides?.result ?? ctx.result;
|
|
54443
|
+
return {
|
|
54444
|
+
// Additional scopes are provided via providers (session, permission). Here we add op-specific context.
|
|
54445
|
+
context: {
|
|
54446
|
+
op: ctx.op,
|
|
54447
|
+
entity: ctx.entityName,
|
|
54448
|
+
id: ctx.id,
|
|
54449
|
+
data: ctx.data,
|
|
54450
|
+
existing: existing,
|
|
54451
|
+
result: result,
|
|
54452
|
+
// Add eval function to access data fields
|
|
54453
|
+
eval: (key) => {
|
|
54454
|
+
// Helper function to get nested property
|
|
54455
|
+
const getNestedProperty = (obj, path) => {
|
|
54456
|
+
if (!obj || !path)
|
|
54457
|
+
return undefined;
|
|
54458
|
+
const parts = path.split('.');
|
|
54459
|
+
let current = obj;
|
|
54460
|
+
for (const part of parts) {
|
|
54461
|
+
if (current && typeof current === 'object' && part in current) {
|
|
54462
|
+
current = current[part];
|
|
54463
|
+
}
|
|
54464
|
+
else {
|
|
54465
|
+
return undefined;
|
|
54466
|
+
}
|
|
54467
|
+
}
|
|
54468
|
+
return current;
|
|
54469
|
+
};
|
|
54470
|
+
// For delete operations, use existing data
|
|
54471
|
+
if (ctx.op === 'delete' && existing) {
|
|
54472
|
+
return getNestedProperty(existing, key);
|
|
54473
|
+
}
|
|
54474
|
+
// For create/update operations, use data
|
|
54475
|
+
if (ctx.data) {
|
|
54476
|
+
const dataValue = getNestedProperty(ctx.data, key);
|
|
54477
|
+
if (dataValue !== undefined) {
|
|
54478
|
+
return dataValue;
|
|
54479
|
+
}
|
|
54480
|
+
}
|
|
54481
|
+
// Fallback to existing for update operations
|
|
54482
|
+
if (existing) {
|
|
54483
|
+
return getNestedProperty(existing, key);
|
|
54484
|
+
}
|
|
54485
|
+
return undefined;
|
|
54486
|
+
}
|
|
54487
|
+
}
|
|
54488
|
+
};
|
|
54489
|
+
};
|
|
54490
|
+
// Helper: evaluate active rules for a given scope
|
|
54491
|
+
const evaluateActiveRules = async (scope) => {
|
|
54492
|
+
const list = [];
|
|
54493
|
+
for (const pol of applicable) {
|
|
54494
|
+
for (const rule of pol.rules ?? []) {
|
|
54495
|
+
const active = await isRuleActive(rule.conditions, evaluator, scope);
|
|
54496
|
+
if (active) {
|
|
54497
|
+
for (const eff of rule.effects ?? []) {
|
|
54498
|
+
list.push(eff);
|
|
54499
|
+
}
|
|
54500
|
+
}
|
|
54501
|
+
}
|
|
54502
|
+
}
|
|
54503
|
+
return list;
|
|
54504
|
+
};
|
|
54505
|
+
// Resolve effects per operation following simple standard semantics
|
|
54506
|
+
const deriveTargets = (rules) => ({
|
|
54507
|
+
readonlyTargets: collectTargets(rules, 'readonly'),
|
|
54508
|
+
immutableTargets: collectTargets(rules, 'immutable'),
|
|
54509
|
+
hiddenTargets: collectTargets(rules, 'hidden'),
|
|
54510
|
+
maskedTargets: collectTargets(rules, 'masked'),
|
|
54511
|
+
editableTargets: collectTargets(rules, 'editable'),
|
|
54512
|
+
});
|
|
54513
|
+
let activeRules = [];
|
|
54514
|
+
let readonlyTargets = [];
|
|
54515
|
+
let immutableTargets = [];
|
|
54516
|
+
let hiddenTargets = [];
|
|
54517
|
+
let maskedTargets = [];
|
|
54518
|
+
let editableTargets = [];
|
|
54519
|
+
// Conflict resolution (standard): editable overrides readonly (not immutable), hidden beats everything in output, immutable strongest on write
|
|
54520
|
+
const isEditable = (t) => editableTargets.includes(t) || editableTargets.includes('*');
|
|
54521
|
+
// WRITE PATH
|
|
54522
|
+
if (ctx.op === 'create' || ctx.op === 'update') {
|
|
54523
|
+
// For update, load existing to allow conditions to reference current record state
|
|
54524
|
+
let existing;
|
|
54525
|
+
if (ctx.op === 'update' && ctx.id && backend?.getOne) {
|
|
54526
|
+
try {
|
|
54527
|
+
existing = await backend.getOne(ctx.entityName, ctx.id);
|
|
54528
|
+
}
|
|
54529
|
+
catch {
|
|
54530
|
+
existing = undefined;
|
|
54531
|
+
}
|
|
54532
|
+
}
|
|
54533
|
+
// Re-evaluate rules with existing in scope
|
|
54534
|
+
activeRules = await evaluateActiveRules(buildScope({ existing }));
|
|
54535
|
+
({ readonlyTargets, immutableTargets, hiddenTargets, maskedTargets, editableTargets } = deriveTargets(activeRules));
|
|
54536
|
+
if (ctx.data && typeof ctx.data === 'object') {
|
|
54537
|
+
// Check if entire object is immutable
|
|
54538
|
+
const immutableAll = immutableTargets.includes('*');
|
|
54539
|
+
if (immutableAll) {
|
|
54540
|
+
throw new AXPMiddlewareAbortError('Update forbidden by policy: Record is marked as system and cannot be modified', {
|
|
54541
|
+
status: 409,
|
|
54542
|
+
code: 'POLICY_IMMUTABLE_RECORD',
|
|
54543
|
+
details: { entity: ctx.entityName, id: ctx.id },
|
|
54544
|
+
});
|
|
54545
|
+
}
|
|
54546
|
+
// For specific immutable/readonly fields, remove them from payload
|
|
54547
|
+
const forbiddenPaths = [];
|
|
54548
|
+
const addForbiddenFromTargets = (targets) => {
|
|
54549
|
+
for (const t of targets) {
|
|
54550
|
+
if (t === '*') {
|
|
54551
|
+
// This case is handled above
|
|
54552
|
+
continue;
|
|
54553
|
+
}
|
|
54554
|
+
else if (!isEditable(t)) {
|
|
54555
|
+
forbiddenPaths.push(t);
|
|
54556
|
+
}
|
|
54557
|
+
}
|
|
54558
|
+
};
|
|
54559
|
+
// readonly and immutable (excluding '*')
|
|
54560
|
+
addForbiddenFromTargets(readonlyTargets);
|
|
54561
|
+
addForbiddenFromTargets(immutableTargets);
|
|
54562
|
+
// Remove forbidden fields from payload
|
|
54563
|
+
const uniqueForbidden = Array.from(new Set(forbiddenPaths));
|
|
54564
|
+
if (uniqueForbidden.length > 0) {
|
|
54565
|
+
removeFields(ctx.data, uniqueForbidden);
|
|
54566
|
+
// Log which fields were removed
|
|
54567
|
+
// try {
|
|
54568
|
+
// console.warn('[policy] fields-removed', {
|
|
54569
|
+
// entity: ctx.entityName,
|
|
54570
|
+
// id: ctx.id,
|
|
54571
|
+
// removedFields: uniqueForbidden
|
|
54572
|
+
// });
|
|
54573
|
+
// } catch {}
|
|
54574
|
+
}
|
|
54575
|
+
}
|
|
54576
|
+
}
|
|
54577
|
+
// DELETE PATH
|
|
54578
|
+
if (ctx.op === 'delete') {
|
|
54579
|
+
// Load existing to evaluate conditions based on record's current state
|
|
54580
|
+
let existing;
|
|
54581
|
+
if (ctx.id && backend?.getOne) {
|
|
54582
|
+
try {
|
|
54583
|
+
existing = await backend.getOne(ctx.entityName, ctx.id);
|
|
54584
|
+
}
|
|
54585
|
+
catch {
|
|
54586
|
+
existing = undefined;
|
|
54587
|
+
}
|
|
54588
|
+
}
|
|
54589
|
+
activeRules = await evaluateActiveRules(buildScope({ existing }));
|
|
54590
|
+
({ readonlyTargets, immutableTargets, hiddenTargets, maskedTargets, editableTargets } = deriveTargets(activeRules));
|
|
54591
|
+
// If immutable '*' exists, block delete by short-circuiting
|
|
54592
|
+
const immutableAll = immutableTargets.includes('*');
|
|
54593
|
+
// Temporary debug logs for troubleshooting policy delete enforcement
|
|
54594
|
+
try {
|
|
54595
|
+
// eslint-disable-next-line no-console
|
|
54596
|
+
console.warn('[policy] delete-check', {
|
|
54597
|
+
entity: ctx.entityName,
|
|
54598
|
+
id: ctx.id,
|
|
54599
|
+
existingIsSystem: existing?.control?.isSystem,
|
|
54600
|
+
immutableTargets,
|
|
54601
|
+
readonlyTargets,
|
|
54602
|
+
editableTargets,
|
|
54603
|
+
immutableAll,
|
|
54604
|
+
});
|
|
54605
|
+
}
|
|
54606
|
+
catch { }
|
|
54607
|
+
if (immutableAll) {
|
|
54608
|
+
// Block delete and throw error to notify frontend
|
|
54609
|
+
try {
|
|
54610
|
+
console.warn('[policy] delete-blocked', { entity: ctx.entityName, id: ctx.id });
|
|
54611
|
+
}
|
|
54612
|
+
catch { }
|
|
54613
|
+
throw new AXPMiddlewareAbortError('Delete forbidden by policy: Record is marked as system and cannot be deleted', {
|
|
54614
|
+
status: 409,
|
|
54615
|
+
code: 'POLICY_IMMUTABLE_RECORD',
|
|
54616
|
+
details: { entity: ctx.entityName, id: ctx.id },
|
|
54617
|
+
});
|
|
54618
|
+
}
|
|
54619
|
+
}
|
|
54620
|
+
await next();
|
|
54621
|
+
// try { console.warn('[policy] after-next', ctx.op, ctx.entityName); } catch {}
|
|
54622
|
+
// READ PATH (after next to transform response)
|
|
54623
|
+
if (ctx.op === 'getOne' || ctx.op === 'getAll' || ctx.op === 'query') {
|
|
54624
|
+
// Re-evaluate rules with result in scope (read conditions can depend on returned data)
|
|
54625
|
+
activeRules = await evaluateActiveRules(buildScope({ result: ctx.result }));
|
|
54626
|
+
({ readonlyTargets, immutableTargets, hiddenTargets, maskedTargets, editableTargets } = deriveTargets(activeRules));
|
|
54627
|
+
const applyReadTransforms = (obj) => {
|
|
54628
|
+
if (!obj || typeof obj !== 'object')
|
|
54629
|
+
return;
|
|
54630
|
+
// hidden ⇒ remove field
|
|
54631
|
+
removeFields(obj, hiddenTargets);
|
|
54632
|
+
// masked ⇒ mask value
|
|
54633
|
+
setMasked(obj, maskedTargets);
|
|
54634
|
+
};
|
|
54635
|
+
if (Array.isArray(ctx.result)) {
|
|
54636
|
+
ctx.result.forEach(applyReadTransforms);
|
|
54637
|
+
}
|
|
54638
|
+
else {
|
|
54639
|
+
applyReadTransforms(ctx.result);
|
|
54640
|
+
}
|
|
54641
|
+
}
|
|
54642
|
+
},
|
|
54643
|
+
};
|
|
54644
|
+
|
|
53980
54645
|
class AXCMiddlewaresModule {
|
|
53981
54646
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXCMiddlewaresModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
53982
54647
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXCMiddlewaresModule, imports: [AXCEntityStorageModule, AXCEntityDefinitionsModule] }); }
|
|
@@ -53984,7 +54649,7 @@ class AXCMiddlewaresModule {
|
|
|
53984
54649
|
// Middlewares (ordered by execution priority)
|
|
53985
54650
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: statusDefaultMiddleware }, // order: 3 - backend: default status, security-sensitive defaults
|
|
53986
54651
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: entityValidationMiddleware }, // order: 5 - entity pre-save validation
|
|
53987
|
-
|
|
54652
|
+
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: policyEnforceMiddleware }, // order: 5 - Handles both isSystem and isImmutable
|
|
53988
54653
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: lockGuardMiddleware }, // order: 10
|
|
53989
54654
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: historyMiddleware }, // order: 15 - audit + versioning
|
|
53990
54655
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: eventDispatchMiddleware },
|
|
@@ -54011,7 +54676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
54011
54676
|
// Middlewares (ordered by execution priority)
|
|
54012
54677
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: statusDefaultMiddleware }, // order: 3 - backend: default status, security-sensitive defaults
|
|
54013
54678
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: entityValidationMiddleware }, // order: 5 - entity pre-save validation
|
|
54014
|
-
|
|
54679
|
+
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: policyEnforceMiddleware }, // order: 5 - Handles both isSystem and isImmutable
|
|
54015
54680
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: lockGuardMiddleware }, // order: 10
|
|
54016
54681
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: historyMiddleware }, // order: 15 - audit + versioning
|
|
54017
54682
|
{ provide: AXP_ENTITY_STORAGE_MIDDLEWARE, multi: true, useValue: eventDispatchMiddleware },
|
|
@@ -55525,6 +56190,9 @@ function composeOrgChartTreeForMode(workspace, mode, showVacantLeafSeats) {
|
|
|
55525
56190
|
if (!pos) {
|
|
55526
56191
|
continue;
|
|
55527
56192
|
}
|
|
56193
|
+
const seatCapacity = typeof pos?.capacity === 'number' && Number.isFinite(pos.capacity) && pos.capacity >= 1
|
|
56194
|
+
? Math.floor(pos.capacity)
|
|
56195
|
+
: 1;
|
|
55528
56196
|
const list = byPosition.get(positionId) ?? [];
|
|
55529
56197
|
const distinctAssignees = axcDistinctAssignmentsByEmployee(axcOrderAssignmentsForRepresentative(list));
|
|
55530
56198
|
const hasAnyIncumbent = distinctAssignees.length > 0;
|
|
@@ -55552,6 +56220,8 @@ function composeOrgChartTreeForMode(workspace, mode, showVacantLeafSeats) {
|
|
|
55552
56220
|
chartTemplate: 'position-ic',
|
|
55553
56221
|
vacant: true,
|
|
55554
56222
|
businessUnitId: String(bu.id),
|
|
56223
|
+
capacity: seatCapacity,
|
|
56224
|
+
activeAssignmentCount: 0,
|
|
55555
56225
|
stats: { directReports: directReportsCount },
|
|
55556
56226
|
},
|
|
55557
56227
|
isExpanded: true,
|
|
@@ -55574,6 +56244,8 @@ function composeOrgChartTreeForMode(workspace, mode, showVacantLeafSeats) {
|
|
|
55574
56244
|
const positionMetadata = {
|
|
55575
56245
|
chartTemplate: 'merged-manager',
|
|
55576
56246
|
businessUnitId: String(bu.id),
|
|
56247
|
+
capacity: seatCapacity,
|
|
56248
|
+
activeAssignmentCount: distinctAssignees.length,
|
|
55577
56249
|
occupant: employeeName
|
|
55578
56250
|
? {
|
|
55579
56251
|
displayName: employeeName,
|
|
@@ -62010,7 +62682,7 @@ class AXCSecurityManagementMockModule {
|
|
|
62010
62682
|
provideQuerySetups([
|
|
62011
62683
|
{
|
|
62012
62684
|
key: 'SecurityManagement:User:RolesForListColumn',
|
|
62013
|
-
loader: () => import('./acorex-connectivity-mock-user-roles-for-list-column.query-
|
|
62685
|
+
loader: () => import('./acorex-connectivity-mock-user-roles-for-list-column.query-DyGLxPvP.mjs').then((m) => m.UserRolesForListColumnQuery),
|
|
62014
62686
|
},
|
|
62015
62687
|
]),
|
|
62016
62688
|
] }); }
|
|
@@ -62054,7 +62726,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
62054
62726
|
provideQuerySetups([
|
|
62055
62727
|
{
|
|
62056
62728
|
key: 'SecurityManagement:User:RolesForListColumn',
|
|
62057
|
-
loader: () => import('./acorex-connectivity-mock-user-roles-for-list-column.query-
|
|
62729
|
+
loader: () => import('./acorex-connectivity-mock-user-roles-for-list-column.query-DyGLxPvP.mjs').then((m) => m.UserRolesForListColumnQuery),
|
|
62058
62730
|
},
|
|
62059
62731
|
]),
|
|
62060
62732
|
],
|
|
@@ -71779,7 +72451,7 @@ class AXCWorkflowManagementMockModule {
|
|
|
71779
72451
|
provideCommandSetups([
|
|
71780
72452
|
{
|
|
71781
72453
|
key: 'workflow-activity:assign-to-manager',
|
|
71782
|
-
command: () => import('./acorex-connectivity-mock-assign-to-manager.activity-
|
|
72454
|
+
command: () => import('./acorex-connectivity-mock-assign-to-manager.activity-B4WcK-sl.mjs').then((c) => c.AssignToManagerActivity),
|
|
71783
72455
|
},
|
|
71784
72456
|
{
|
|
71785
72457
|
key: 'workflow-activity:get-current-user-manager',
|
|
@@ -71890,7 +72562,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
71890
72562
|
provideCommandSetups([
|
|
71891
72563
|
{
|
|
71892
72564
|
key: 'workflow-activity:assign-to-manager',
|
|
71893
|
-
command: () => import('./acorex-connectivity-mock-assign-to-manager.activity-
|
|
72565
|
+
command: () => import('./acorex-connectivity-mock-assign-to-manager.activity-B4WcK-sl.mjs').then((c) => c.AssignToManagerActivity),
|
|
71894
72566
|
},
|
|
71895
72567
|
{
|
|
71896
72568
|
key: 'workflow-activity:get-current-user-manager',
|
|
@@ -76344,4 +77016,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
76344
77016
|
*/
|
|
76345
77017
|
|
|
76346
77018
|
export { AXCFinancialCoreMockModule as $, AXCAiChatRouterService as A, AXCCalendarDataSeeder as B, AXCCalendarEventDataSeeder as C, AXCCalendarManagementMockModule as D, AXCCommandRegistryDataSeeder as E, AXCCommonMockModule as F, AXCContactCoreMockModule as G, AXCContentManagementMockModule as H, AXCConversationDataSeeder as I, AXCConversationMockConversationApi as J, AXCConversationMockMessageApi as K, AXCConversationMockModule as L, AXCConversationMockRealtimeApi as M, AXCConversationMockUserApi as N, AXCCustomerManagementMockModule as O, AXCDashboardManagementMockModule as P, AXCDataManagementMockModule as Q, AXCDexieEntityStorageService as R, AXCDocumentManagementMockModule as S, AXCEditionDataSeeder as T, AXCEntityDefinitionSeederService as U, AXCEntityDefinitionsModule as V, AXCEntityStorageInsertCommand as W, AXCEntityStorageModule as X, AXCEntityStorageQueryQuery as Y, AXCFileStorageServiceDexie as Z, AXCFileStorageServiceFirestore as _, AXMAiModelStableIds as a, AXMEmploymentTypeDataSeeder as a$, AXCFirestoreEntityStorageService as a0, AXCFormTemplateManagementMockModule as a1, AXCGoogleStrategyMock as a2, AXCHealthCoreMockModule as a3, AXCHelpDeskMockModule as a4, AXCHumanCapitalManagementMockModule as a5, AXCIdentifierManagementMockModule as a6, AXCImageCaptchaChallengeProviderMock as a7, AXCLearningManagementMockModule as a8, AXCLocaleManagementMockModule as a9, AXCTenantManagementMockModule as aA, AXCTenantSeeder as aB, AXCTextTemplateRenderBackend as aC, AXCTokensDataSeeder as aD, AXCUserPassStrategyMock as aE, AXCVersionDB as aF, AXCVersioningService as aG, AXCWidgetCatalogDataSeeder as aH, AXCWorkflowCategorySeeder as aI, AXCWorkflowDefinitionDataSeeder as aJ, AXCWorkflowEngine as aK, AXCWorkflowExecutionDB as aL, AXCWorkflowExecutionStoreDexie as aM, AXCWorkflowExecutionStoreFirestore as aN, AXCWorkflowInstanceCartableDemoSeeder as aO, AXCWorkflowManagementMockModule as aP, AXC_ENTITY_STORAGE_BACKEND_TYPE as aQ, AXC_FIRESTORE_CONFIG as aR, AXC_FIRESTORE_DB as aS, AXC_WORKFLOW_EXECUTION_STORE as aT, AXMAiDemisAssistantAssistId as aU, AXMAiPlatformAssistantAssistId as aV, AXMAutomationDataSeeder as aW, AXMBusinessUnitDataSeeder as aX, AXMCalendarEventTypeSeeder as aY, AXMDashboardChartDataSourceDefinition as aZ, AXMEmployeeDataSeeder as a_, AXCLocationManagementMockModule as aa, AXCLockService as ab, AXCMeasurementCoreMockModule as ac, AXCMeetingManagementMockModule as ad, AXCMetaDataDefinitionDataSeeder as ae, AXCMetadataCategorySeeder as af, AXCMiddlewaresModule as ag, AXCMockCaptchaChallengeComponent as ah, AXCMockEntityLogListener as ai, AXCMockModule as aj, AXCMockShowMetaDataFormPopupCommand as ak, AXCNotificationManagementMockModule as al, AXCOrderManagementMockModule as am, AXCOrganizationManagementMockModule as an, AXCPersonCoreMockModule as ao, AXCPlatformManagementMockModule as ap, AXCProcurementManagementMockModule as aq, AXCProductCatalogMockModule as ar, AXCProjectManagementMockModule as as, AXCQueryRegistryDataSeeder as at, AXCReportManagementMockModule as au, AXCSecurityManagementMockModule as av, AXCStoredEntityDefinitionLoader as aw, AXCSubscriptionManagementMockModule as ax, AXCSupplierManagementMockModule as ay, AXCTaskManagementMockModule as az, resolveDelegatedAssistOptionString as b, HW as b$, AXMFormDataSourcesProvider as b0, AXMJobDefinitionDataSeeder as b1, AXMJobLevelDataSeeder as b2, AXMLeaveRequestDataSeeder as b3, AXMMeetingDataSeeder as b4, AXMMeetingFilesDataSeeder as b5, AXMMeetingParticipantDataSeeder as b6, AXMMeetingRoleTypeDataSeeder as b7, AXMMeetingSessionDataSeeder as b8, AXMMeetingTypeDataSeeder as b9, AXPMockSequenceProvider as bA, AXPRoomDataSeeder as bB, AXPSecurityManagementRoleDataSeeder as bC, AXPSecurityManagementUserDataSeeder as bD, AXPTaskBoardPlatformManagementTaskProvider as bE, AXPTaskBoardProjectManagementTaskProvider as bF, AXVChangeType as bG, BCC as bH, BOC as bI, BUSINESS_UNITS_MOCK as bJ, CHAT_LAST_MESSAGE_IDS as bK, COSTMANAGER_ENTERPRISE as bL, COSTMANAGER_PROFESSIONAL as bM, COSTMANAGER_STANDARD as bN, CP as bO, CRM_ENTERPRISE as bP, DASHBOARDS as bQ, EDITIONS as bR, EMPLOYEES_MOCK as bS, EMPLOYMENT_TYPES_MOCK as bT, ENTITY_DEFINITION_MAP as bU, ENTITY_REGISTRY as bV, FINANCE_BASIC as bW, FINANCE_ENTERPRISE as bX, FINANCE_PRO as bY, G as bZ, HR_ENTERPRISE as b_, AXMMeetingTypeFileTemplateDataSeeder as ba, AXMPositionAssignmentDataSeeder as bb, AXMPositionDataSeeder as bc, AXMQuestionBankItemCategoryDataSeeder as bd, AXMQuestionBankItemDataSeeder as be, AXMQuestionnaireCalculationDataSeeder as bf, AXMQuestionnaireCategoryDataSeeder as bg, AXMQuestionnaireDataSeeder as bh, AXMResponsibilityDataSeeder as bi, AXMTagDataSeeder as bj, AXMTeamBusinessUnitDataSeeder as bk, AXMTeamDataSeeder as bl, AXMTeamMemberDataSeeder as bm, AXMTeamMemberRoleDataSeeder as bn, AXMWorkflowDashboardChartDataSourceDefinition as bo, AXM_AI_REGISTRY_COMMAND_NAME as bp, AXM_AI_REGISTRY_QUERY_NAME as bq, AXM_CORPORATE_BUSINESS_UNIT_ID as br, AXPDashboardDataSeeder as bs, AXPIdentifierDB as bt, AXPMessageDataSeeder as bu, AXPMockChecksumProvider as bv, AXPMockClockProvider as bw, AXPMockIdentifierService as bx, AXPMockLookupProvider as by, AXPMockPolicyProvider as bz, axcSyncSingleEmployeeDenormalizedOrgFields as c, awaitConversationMockSharedStorage as c$, JOB_DEFINITIONS_CATEGORY_MOCK as c0, JOB_DEFINITIONS_MOCK as c1, JOB_DEFINITION_CATEGORY_KEY_TO_ID as c2, JOB_LEVELS_MOCK as c3, LASER_PLUMBING_TENANT_ID as c4, LEAVE_REQUESTS_MOCK as c5, METADATA_CATEGORY_IDS as c6, METADATA_GENERAL_CATEGORY_REFS as c7, METADATA_SYSTEM_CATEGORY_REFS as c8, MLC as c9, SHOP_BASIC as cA, SHOP_ENTERPRISE as cB, SHOP_PRO as cC, TAGS_MOCK as cD, TASKS as cE, TASK_STATUSES as cF, TASK_TEMPLATES as cG, TASK_TYPES as cH, TEAMS_CATEGORY_MOCK as cI, TEAMS_MOCK as cJ, TEAM_BUSINESS_UNITS_MOCK as cK, TEAM_CATEGORY_KEY_TO_ID as cL, TEAM_MEMBERS_MOCK as cM, TEAM_MEMBER_ROLES_MOCK as cN, TIMEPLICITY_TENANT_ID as cO, TLA as cP, TOKENS as cQ, TPC as cR, VISIBILITY_FILTER_BYPASS as cS, WORKFLOW_CATEGORIES as cT, WS as cU, activityCategoryMocks as cV, activityDefinitionEntityMock as cW, applyEntityDefinitionFirestoreSnapshot as cX, automationCommandMiddleware as cY, automationMock as cZ, avatarInterface as c_, OHR as ca, ORDERING_BASIC as cb, ORDERING_ENTERPRISE as cc, ORDERING_STANDARD as cd, OWNERSHIP_FILTER_BYPASS as ce, PLATFORM_CONSOLE as cf, PLATFORM_TENANT_ID as cg, PM as ch, POSITIONS_CATEGORY_MOCK as ci, POSITIONS_MOCK as cj, POSITION_ASSIGNMENTS_MOCK as ck, QCP as cl, QGEN as cm, QHW as cn, QOHR as co, QPM as cp, QTLE as cq, QUESTIONNAIRE_CATEGORY_MOCK as cr, QUESTION_BANK_ITEM_CATEGORY_MOCK as cs, QWS as ct, RESPONSIBILITIES_CATEGORY_MOCK as cu, RESPONSIBILITIES_MOCK as cv, RESPONSIBILITY_CATEGORY_KEY_TO_ID as cw, SAFETYMINDER_BASIC as cx, SAFETYMINDER_ENTERPRISE as cy, SAFETYMINDER_PROFESSIONAL as cz, ACTIVITY_CATEGORIES as d, mockParticipantIndexForSecurityUserId as d$, axVersionDB as d0, axWorkflowExecutionDB as d1, axcPatchEmployeeActivePrimaryBusinessUnitRefsFromRows as d2, axcPatchEmployeeActivePrimaryPositionRefsFromRows as d3, axcPatchEmployeeManagerRefsFromRows as d4, axcResolveCommandRegistrySeedId as d5, axcResolveQueryRegistrySeedId as d6, axcResolveWidgetCatalogSeedId as d7, bankCategory as d8, buildEntityStorageQuickSearchFilter as d9, fileUploaderInterface as dA, finalizeActivityCategoryItemCounts as dB, findEmployeeById as dC, folderStorageMiddleware as dD, groupOrderItemCalculatorMiddleware as dE, historyMiddleware as dF, identifierCommitMiddleware as dG, imageInterface as dH, inspectionOkXNaInterface as dI, loadMockPropertyDefinitions as dJ, lockGuardMiddleware as dK, longTextAnswerInterface as dL, lookupInterface as dM, lookupResolverMiddleware as dN, mapInterface as dO, meetingFilesMock as dP, meetingIds as dQ, meetingMock as dR, meetingParticipantMock as dS, meetingRoleTypeMock as dT, meetingSessionMock as dU, meetingTimeSlotMock as dV, meetingTypeFileTemplateMock as dW, meetingTypeMock as dX, mergeDetailRelationMiddleware as dY, mergeQuickSearchIntoRequest as dZ, metadataCategoryMocks as d_, buildWorkflowInstanceCartableDemoRows as da, bypassAllFilters as db, bypassOwnershipFilter as dc, bypassVisibilityFilter as dd, calendarEventMock as de, calendarEventTypeMockData as df, calendarMock as dg, chatMessageMock as dh, chatMock as di, checkboxInterface as dj, childCountMiddleware as dk, clearEntityDefinitionFirestoreCollections as dl, colorInterface as dm, computeDiff as dn, contactInterface as dp, conversationDemoSeedUserIds as dq, createFileCastMiddleware as dr, createWidgetLayoutConfig as ds, dateInterface as dt, dateTimeInterface as du, descriptionInterface as dv, documentFolderSyncMiddleware as dw, emailInterface as dx, entityDefDb as dy, entityValidationMiddleware as dz, ACTIVITY_CATEGORY_ID_BY_KEY as e, mockQuestionBankItemByName as e0, mockRoleDefinitions as e1, multipleChoiceInterface as e2, multipleSelectInterface as e3, normalizeAnyFileArrayForView as e4, normalizeSnapshotFileFieldsForView as e5, numberInterface as e6, parseEntityStorageInsertData as e7, parseEntityStorageQueryRequest as e8, participantIds as e9, textInterface as eA, timeDurationInterface as eB, titleInterface as eC, toUiRows as eD, toggleInterface as eE, tokenCategoryIds as eF, tokenCategoryMocks as eG, urlInterface as eH, versionInterface as eI, visibilityFilterMiddleware as eJ, workflowDefinitionEntityMock as eK, workflowDefinitionIds as eL, workflowDefinitionMock as eM, yesNoInterface as eN, participantMock as ea, passwordInterface as eb, personIdentifierTypeMock as ec, personIds as ed, personLegalIds as ee, personMock as ef, personNaturalIds as eg, phoneInterface as eh, primaryMiddleware as ei, provideMockPropertySetups as ej, qrcodeInterface as ek, questionBankItemMock as el, questionnaireCalculationMock as em, questionnaireMock as en, ratingInterface as eo, richTextInterface as ep, scaleInterface as eq, selectInterface as er, selectionListInterface as es, sexAtBirthInterface as et, signatureInterface as eu, signatureLoaderMiddleware as ev, statusDefaultMiddleware as ew, tagInterface as ex, tenantMocks as ey, textAreaInterface as ez, ACTIVITY_DEFINITIONS as f, AI_ASSIST_MOCK as g, AI_MODEL_MOCK as h, APPLICATIONS as i, APPLICATION_CATEGORY_IDS as j, APPLICATION_CATEGORY_MOCKS as k, AXCActivityCategorySeeder as l, mockUsers as m, AXCActivityDefinitionSeeder as n, AXCAiAssistSeeder as o, AXCAiManagementMockModule as p, AXCAiModelSeeder as q, resolveDelegatedAssistOptionModel as r, AXCAppTermDataSeeder as s, AXCAppVersionDataSeeder as t, AXCApplicationCategoryDataSeeder as u, AXCApplicationDataSeeder as v, AXCApplicationManagementMockModule as w, AXCAssessmentManagementMockModule as x, AXCAssetManagementMockModule as y, AXCAuthMockModule as z };
|
|
76347
|
-
//# sourceMappingURL=acorex-connectivity-mock-acorex-connectivity-mock-
|
|
77019
|
+
//# sourceMappingURL=acorex-connectivity-mock-acorex-connectivity-mock-DGbHYfeu.mjs.map
|