rez_core 5.0.211 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/config/database.config.js +1 -1
  2. package/dist/config/database.config.js.map +1 -1
  3. package/dist/migrations/1732612800000-AddEntityJsonGinIndex.d.ts +6 -0
  4. package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js +32 -0
  5. package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js.map +1 -0
  6. package/dist/module/entity_json/controller/entity_json.controller.d.ts +2 -9
  7. package/dist/module/entity_json/controller/entity_json.controller.js.map +1 -1
  8. package/dist/module/entity_json/entity/entityJson.entity.d.ts +2 -1
  9. package/dist/module/entity_json/entity/entityJson.entity.js +5 -1
  10. package/dist/module/entity_json/entity/entityJson.entity.js.map +1 -1
  11. package/dist/module/entity_json/entity_json.module.js +7 -2
  12. package/dist/module/entity_json/entity_json.module.js.map +1 -1
  13. package/dist/module/entity_json/service/entity_json.service.d.ts +5 -12
  14. package/dist/module/entity_json/service/entity_json.service.js +111 -29
  15. package/dist/module/entity_json/service/entity_json.service.js.map +1 -1
  16. package/dist/module/filter/controller/filter.controller.d.ts +12 -0
  17. package/dist/module/filter/controller/filter.controller.js +1 -1
  18. package/dist/module/filter/controller/filter.controller.js.map +1 -1
  19. package/dist/module/filter/filter.module.js +11 -2
  20. package/dist/module/filter/filter.module.js.map +1 -1
  21. package/dist/module/filter/service/filter.service.d.ts +38 -2
  22. package/dist/module/filter/service/filter.service.js +42 -49
  23. package/dist/module/filter/service/filter.service.js.map +1 -1
  24. package/dist/module/filter/service/flatjson-filter.service.d.ts +32 -0
  25. package/dist/module/filter/service/flatjson-filter.service.js +632 -0
  26. package/dist/module/filter/service/flatjson-filter.service.js.map +1 -0
  27. package/dist/module/filter/service/saved-filter.service.d.ts +3 -2
  28. package/dist/module/filter/service/saved-filter.service.js +14 -18
  29. package/dist/module/filter/service/saved-filter.service.js.map +1 -1
  30. package/dist/module/linked_attributes/controller/linked_attributes.controller.d.ts +19 -0
  31. package/dist/module/linked_attributes/controller/linked_attributes.controller.js +77 -0
  32. package/dist/module/linked_attributes/controller/linked_attributes.controller.js.map +1 -1
  33. package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.d.ts +13 -0
  34. package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js +64 -0
  35. package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js.map +1 -0
  36. package/dist/module/linked_attributes/linked_attributes.module.js +4 -2
  37. package/dist/module/linked_attributes/linked_attributes.module.js.map +1 -1
  38. package/dist/module/linked_attributes/service/linked_attributes.service.d.ts +41 -1
  39. package/dist/module/linked_attributes/service/linked_attributes.service.js +265 -1
  40. package/dist/module/linked_attributes/service/linked_attributes.service.js.map +1 -1
  41. package/dist/module/meta/controller/attribute-master.controller.d.ts +3 -0
  42. package/dist/module/meta/controller/attribute-master.controller.js +12 -0
  43. package/dist/module/meta/controller/attribute-master.controller.js.map +1 -1
  44. package/dist/module/meta/entity.module.js +2 -2
  45. package/dist/module/meta/entity.module.js.map +1 -1
  46. package/dist/module/meta/service/attribute-master.service.d.ts +6 -1
  47. package/dist/module/meta/service/attribute-master.service.js +20 -2
  48. package/dist/module/meta/service/attribute-master.service.js.map +1 -1
  49. package/dist/module/meta/service/entity-master.service.js +1 -0
  50. package/dist/module/meta/service/entity-master.service.js.map +1 -1
  51. package/dist/module/meta/service/entity-relation.service.d.ts +4 -3
  52. package/dist/module/meta/service/entity-relation.service.js +10 -4
  53. package/dist/module/meta/service/entity-relation.service.js.map +1 -1
  54. package/dist/module/meta/service/entity-service-impl.service.d.ts +1 -1
  55. package/dist/module/meta/service/entity-service-impl.service.js +2 -2
  56. package/dist/module/meta/service/entity-service-impl.service.js.map +1 -1
  57. package/dist/module/meta/service/entity-table.service.d.ts +5 -4
  58. package/dist/module/meta/service/entity-table.service.js +45 -24
  59. package/dist/module/meta/service/entity-table.service.js.map +1 -1
  60. package/dist/module/meta/service/resolver.service.d.ts +1 -1
  61. package/dist/module/meta/service/resolver.service.js +6 -3
  62. package/dist/module/meta/service/resolver.service.js.map +1 -1
  63. package/dist/module/workflow-automation/service/workflow-automation.service.js +2 -3
  64. package/dist/module/workflow-automation/service/workflow-automation.service.js.map +1 -1
  65. package/dist/table.config.d.ts +1 -1
  66. package/dist/tsconfig.build.tsbuildinfo +1 -1
  67. package/package.json +1 -1
  68. package/src/config/database.config.ts +1 -1
  69. package/src/migrations/1732612800000-AddEntityJsonGinIndex.ts +41 -0
  70. package/src/module/entity_json/controller/entity_json.controller.ts +13 -0
  71. package/src/module/entity_json/docs/FlatJson_Filterin_System.md +2804 -0
  72. package/src/module/entity_json/entity/entityJson.entity.ts +4 -1
  73. package/src/module/entity_json/entity_json.module.ts +9 -5
  74. package/src/module/entity_json/service/entity_json.service.ts +236 -49
  75. package/src/module/filter/controller/filter.controller.ts +4 -4
  76. package/src/module/filter/filter.module.ts +12 -3
  77. package/src/module/filter/service/filter.service.ts +130 -71
  78. package/src/module/filter/service/flatjson-filter.service.ts +903 -0
  79. package/src/module/filter/service/saved-filter.service.ts +16 -26
  80. package/src/module/filter/test/flatjson-filter.service.spec.ts +415 -0
  81. package/src/module/linked_attributes/controller/linked_attributes.controller.ts +85 -0
  82. package/src/module/linked_attributes/dto/create-linked-attribute-smart.dto.ts +54 -0
  83. package/src/module/linked_attributes/linked_attributes.module.ts +5 -3
  84. package/src/module/linked_attributes/service/linked_attributes.service.ts +545 -2
  85. package/src/module/linked_attributes/test/linked-attributes.service.spec.ts +244 -0
  86. package/src/module/meta/controller/attribute-master.controller.ts +12 -0
  87. package/src/module/meta/entity.module.ts +3 -3
  88. package/src/module/meta/service/attribute-master.service.ts +29 -1
  89. package/src/module/meta/service/entity-master.service.ts +1 -0
  90. package/src/module/meta/service/entity-relation.service.ts +10 -6
  91. package/src/module/meta/service/entity-service-impl.service.ts +2 -1
  92. package/src/module/meta/service/entity-table.service.ts +82 -68
  93. package/src/module/meta/service/entity.service.ts +0 -1
  94. package/src/module/meta/service/resolver.service.ts +7 -3
  95. package/src/module/workflow-automation/service/workflow-automation.service.ts +2 -4
@@ -1,4 +1,4 @@
1
- import { BadRequestException, Injectable } from '@nestjs/common';
1
+ import { BadRequestException, Inject, Injectable, forwardRef } from '@nestjs/common';
2
2
  import { EntityServiceImpl } from 'src/module/meta/service/entity-service-impl.service';
3
3
  import { BaseEntity } from '../../meta/entity/base-entity.entity';
4
4
  import { UserData } from '../../user/entity/user.entity';
@@ -7,10 +7,12 @@ import { ENTITYTYPE_SAVEDFILTERMASTER } from 'src/constant/global.constant';
7
7
  import { SavedFilterRepositoryService } from '../repository/saved-filter.repository';
8
8
 
9
9
  @Injectable()
10
- export class SavedFilterService extends EntityServiceImpl {
11
- constructor(private readonly savedFilterRepo: SavedFilterRepositoryService) {
12
- super();
13
- }
10
+ export class SavedFilterService {
11
+ constructor(
12
+ private readonly savedFilterRepo: SavedFilterRepositoryService,
13
+ @Inject(forwardRef(() => EntityServiceImpl))
14
+ private readonly entityServiceImpl: EntityServiceImpl,
15
+ ) {}
14
16
 
15
17
  async createEntity(
16
18
  entityData: any,
@@ -33,7 +35,7 @@ export class SavedFilterService extends EntityServiceImpl {
33
35
  entityData.user_id = loggedInUser?.id ?? null;
34
36
 
35
37
  // Save the master record without code
36
- const savedMaster = await super.createEntity(master, loggedInUser);
38
+ const savedMaster = await this.entityServiceImpl.createEntity(master, loggedInUser);
37
39
 
38
40
  // Generate the code like SFM00001 using the saved ID
39
41
  const code = `SFM${savedMaster.id.toString().padStart(5, '0')}`;
@@ -74,7 +76,7 @@ export class SavedFilterService extends EntityServiceImpl {
74
76
  ): Promise<BaseEntity> {
75
77
  const master = entityData as SavedFilterMaster;
76
78
 
77
- const existing = await this.getEntityData(
79
+ const existing = await this.entityServiceImpl.getEntityData(
78
80
  ENTITYTYPE_SAVEDFILTERMASTER,
79
81
  master.id,
80
82
  loggedInUser,
@@ -96,7 +98,7 @@ export class SavedFilterService extends EntityServiceImpl {
96
98
  }
97
99
 
98
100
  const { filterDetails, ...persistableData } = master as any;
99
- const updated = await super.updateEntity(persistableData, loggedInUser);
101
+ const updated = await this.entityServiceImpl.updateEntity(persistableData, loggedInUser);
100
102
 
101
103
  if (filterDetails?.length) {
102
104
  await this.savedFilterRepo.deleteDetailsByCode(updated.code);
@@ -120,34 +122,22 @@ export class SavedFilterService extends EntityServiceImpl {
120
122
  }
121
123
 
122
124
  async deleteEntity(entityType: string, entityId: number, loggedInUser) {
123
- const entityMaster = await this.entityMasterService.getEntityData(
125
+ // First get the entity data to retrieve the code
126
+ const entityData = await this.entityServiceImpl.getEntityData(
124
127
  entityType,
128
+ entityId,
125
129
  loggedInUser,
126
130
  );
127
131
 
128
- const repoService = this.reflectionHelper.getRepoService(
129
- entityMaster.entity_data_class,
130
- );
131
- if (!repoService) {
132
- throw new Error(
133
- `Repository service not found for entityType: ${entityType}`,
134
- );
135
- }
136
-
137
- const entityData = await repoService.findOne({ where: { id: entityId } });
138
-
139
132
  if (!entityData) {
140
133
  throw new Error(`Entity not found for entityType: ${entityType}`);
141
134
  }
142
135
 
136
+ // Delete associated filter details
143
137
  await this.savedFilterRepo.deleteDetailsByCode(entityData.code);
144
138
 
145
- await repoService.delete(entityId);
146
-
147
- return {
148
- success: true,
149
- message: `Entity with id ${entityId} deleted successfully.`,
150
- };
139
+ // Then delete the entity itself
140
+ return await this.entityServiceImpl.deleteEntity(entityType, entityId, loggedInUser);
151
141
  }
152
142
 
153
143
  async getDetailsByCode(code: string) {
@@ -0,0 +1,415 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { FlatjsonFilterService } from '../service/flatjson-filter.service';
3
+ import { EntityManager } from 'typeorm';
4
+ import { EntityMasterService } from 'src/module/meta/service/entity-master.service';
5
+ import { AttributeMasterService } from 'src/module/meta/service/attribute-master.service';
6
+ import { ResolverService } from 'src/module/meta/service/resolver.service';
7
+ import { LoggingService } from 'src/utils/service/loggingUtil.service';
8
+ import { ConfigService } from '@nestjs/config';
9
+ import { SavedFilterRepositoryService } from '../repository/saved-filter.repository';
10
+
11
+ describe('FlatjsonFilterService - Phase 2 Tests', () => {
12
+ let service: FlatjsonFilterService;
13
+
14
+ const mockEntityManager = {
15
+ getRepository: jest.fn(),
16
+ };
17
+
18
+ const mockEntityMasterService = {};
19
+ const mockAttributeMasterService = {
20
+ findAttributesByMappedEntityType: jest.fn(),
21
+ };
22
+ const mockResolverService = {
23
+ resolveEntitiesByIds: jest.fn(),
24
+ };
25
+ const mockLoggingService = {
26
+ log: jest.fn(),
27
+ };
28
+ const mockConfigService = {};
29
+ const mockSavedFilterRepositoryService = {
30
+ getDetailsByCode: jest.fn(),
31
+ };
32
+
33
+ beforeEach(async () => {
34
+ const module: TestingModule = await Test.createTestingModule({
35
+ providers: [
36
+ FlatjsonFilterService,
37
+ { provide: EntityManager, useValue: mockEntityManager },
38
+ { provide: EntityMasterService, useValue: mockEntityMasterService },
39
+ {
40
+ provide: AttributeMasterService,
41
+ useValue: mockAttributeMasterService,
42
+ },
43
+ { provide: ResolverService, useValue: mockResolverService },
44
+ { provide: LoggingService, useValue: mockLoggingService },
45
+ { provide: ConfigService, useValue: mockConfigService },
46
+ {
47
+ provide: SavedFilterRepositoryService,
48
+ useValue: mockSavedFilterRepositoryService,
49
+ },
50
+ ],
51
+ }).compile();
52
+
53
+ service = module.get<FlatjsonFilterService>(FlatjsonFilterService);
54
+ });
55
+
56
+ afterEach(() => {
57
+ jest.clearAllMocks();
58
+ });
59
+
60
+ describe('Task 2.3: Text Conditions', () => {
61
+ it('should build equal condition for text', () => {
62
+ const condition = service['buildTextCondition'](
63
+ 'LEAD__name',
64
+ 'equal',
65
+ 'John',
66
+ 'param1',
67
+ );
68
+
69
+ expect(condition).not.toBeNull();
70
+ expect(condition?.query).toContain("json_data->>'LEAD__name'");
71
+ expect(condition?.query).toContain('=');
72
+ expect(condition?.params.param1).toBe('john'); // lowercase
73
+ });
74
+
75
+ it('should build contains condition with LIKE', () => {
76
+ const condition = service['buildTextCondition'](
77
+ 'LEAD__name',
78
+ 'contains',
79
+ 'John',
80
+ 'param1',
81
+ );
82
+
83
+ expect(condition?.query).toContain('LIKE');
84
+ expect(condition?.params.param1).toBe('%john%');
85
+ });
86
+
87
+ it('should build starts_with condition', () => {
88
+ const condition = service['buildTextCondition'](
89
+ 'LEAD__name',
90
+ 'starts_with',
91
+ 'John',
92
+ 'param1',
93
+ );
94
+
95
+ expect(condition?.params.param1).toBe('john%');
96
+ });
97
+
98
+ it('should build ends_with condition', () => {
99
+ const condition = service['buildTextCondition'](
100
+ 'LEAD__name',
101
+ 'ends_with',
102
+ 'Smith',
103
+ 'param1',
104
+ );
105
+
106
+ expect(condition?.params.param1).toBe('%smith');
107
+ });
108
+
109
+ it('should build empty condition', () => {
110
+ const condition = service['buildTextCondition'](
111
+ 'LEAD__name',
112
+ 'empty',
113
+ null,
114
+ 'param1',
115
+ );
116
+
117
+ expect(condition?.query).toContain('IS NULL');
118
+ expect(Object.keys(condition?.params || {})).toHaveLength(0);
119
+ });
120
+ });
121
+
122
+ describe('Task 2.4: Number Conditions', () => {
123
+ it('should build equal condition for numbers', () => {
124
+ const condition = service['buildNumberCondition'](
125
+ 'LEAD__age',
126
+ 'equal',
127
+ 25,
128
+ 'param1',
129
+ );
130
+
131
+ expect(condition?.query).toContain('::numeric');
132
+ expect(condition?.query).toContain('=');
133
+ expect(condition?.params.param1).toBe(25);
134
+ });
135
+
136
+ it('should build greater_than condition', () => {
137
+ const condition = service['buildNumberCondition'](
138
+ 'LEAD__age',
139
+ 'greater_than',
140
+ 18,
141
+ 'param1',
142
+ );
143
+
144
+ expect(condition?.query).toContain('>');
145
+ expect(condition?.params.param1).toBe(18);
146
+ });
147
+
148
+ it('should build between condition with array', () => {
149
+ const condition = service['buildNumberCondition'](
150
+ 'LEAD__age',
151
+ 'between',
152
+ [18, 65],
153
+ 'param1',
154
+ );
155
+
156
+ expect(condition?.query).toContain('BETWEEN');
157
+ expect(condition?.params.param1_min).toBe(18);
158
+ expect(condition?.params.param1_max).toBe(65);
159
+ });
160
+
161
+ it('should build between condition with string', () => {
162
+ const condition = service['buildNumberCondition'](
163
+ 'LEAD__age',
164
+ 'between',
165
+ '18, 65',
166
+ 'param1',
167
+ );
168
+
169
+ expect(condition?.query).toContain('BETWEEN');
170
+ expect(condition?.params.param1_min).toBe(18);
171
+ expect(condition?.params.param1_max).toBe(65);
172
+ });
173
+ });
174
+
175
+ describe('Task 2.5: Date Conditions', () => {
176
+ it('should build equal condition for dates', () => {
177
+ const condition = service['buildDateCondition'](
178
+ 'LEAD__created_date',
179
+ 'equal',
180
+ '2024-01-01',
181
+ 'param1',
182
+ );
183
+
184
+ expect(condition?.query).toContain('::bigint');
185
+ expect(condition?.query).toContain('=');
186
+ expect(typeof condition?.params.param1).toBe('number');
187
+ });
188
+
189
+ it('should build before condition', () => {
190
+ const condition = service['buildDateCondition'](
191
+ 'LEAD__created_date',
192
+ 'before',
193
+ '2024-12-31',
194
+ 'param1',
195
+ );
196
+
197
+ expect(condition?.query).toContain('<');
198
+ });
199
+
200
+ it('should build between condition for dates', () => {
201
+ const condition = service['buildDateCondition'](
202
+ 'LEAD__created_date',
203
+ 'between',
204
+ ['2024-01-01', '2024-12-31'],
205
+ 'param1',
206
+ );
207
+
208
+ expect(condition?.query).toContain('BETWEEN');
209
+ expect(condition?.params.param1_start).toBeDefined();
210
+ expect(condition?.params.param1_end).toBeDefined();
211
+ });
212
+
213
+ it('should build today condition', () => {
214
+ const condition = service['buildDateCondition'](
215
+ 'LEAD__created_date',
216
+ 'today',
217
+ null,
218
+ 'param1',
219
+ );
220
+
221
+ expect(condition?.query).toContain('BETWEEN');
222
+ expect(condition?.params.param1_start).toBeDefined();
223
+ expect(condition?.params.param1_end).toBeDefined();
224
+ });
225
+
226
+ it('should build in_last_day condition', () => {
227
+ const condition = service['buildDateCondition'](
228
+ 'LEAD__created_date',
229
+ 'in_last_day',
230
+ 7,
231
+ 'param1',
232
+ );
233
+
234
+ expect(condition?.query).toContain('BETWEEN');
235
+ });
236
+ });
237
+
238
+ describe('Task 2.6: Select/Multiselect Conditions', () => {
239
+ it('should build equal condition for select', () => {
240
+ const condition = service['buildSelectCondition'](
241
+ 'LEAD__status',
242
+ 'equal',
243
+ 'NEW',
244
+ 'param1',
245
+ );
246
+
247
+ expect(condition?.query).toContain("json_data->>'LEAD__status'");
248
+ expect(condition?.params.param1).toBe('NEW');
249
+ });
250
+
251
+ it('should build IN condition for array values', () => {
252
+ const condition = service['buildSelectCondition'](
253
+ 'LEAD__status',
254
+ 'equal',
255
+ ['NEW', 'ACTIVE'],
256
+ 'param1',
257
+ );
258
+
259
+ expect(condition?.query).toContain('ANY');
260
+ expect(Array.isArray(condition?.params.param1)).toBe(true);
261
+ });
262
+
263
+ it('should build contains condition for multiselect', () => {
264
+ const condition = service['buildMultiSelectCondition'](
265
+ 'LEAD__languages',
266
+ 'contains',
267
+ 'hindi',
268
+ 'param1',
269
+ );
270
+
271
+ expect(condition?.query).toContain('?');
272
+ expect(condition?.params.param1).toBe('hindi');
273
+ });
274
+
275
+ it('should build contains_all condition', () => {
276
+ const condition = service['buildMultiSelectCondition'](
277
+ 'LEAD__languages',
278
+ 'contains_all',
279
+ ['hindi', 'english'],
280
+ 'param1',
281
+ );
282
+
283
+ expect(condition?.query).toContain('?&');
284
+ expect(Array.isArray(condition?.params.param1)).toBe(true);
285
+ });
286
+
287
+ it('should build contains_any condition', () => {
288
+ const condition = service['buildMultiSelectCondition'](
289
+ 'LEAD__languages',
290
+ 'contains_any',
291
+ ['hindi', 'english'],
292
+ 'param1',
293
+ );
294
+
295
+ expect(condition?.query).toContain('?|');
296
+ });
297
+
298
+ it('should handle comma-separated string input', () => {
299
+ const condition = service['buildMultiSelectCondition'](
300
+ 'LEAD__languages',
301
+ 'contains_any',
302
+ 'hindi, english, tamil',
303
+ 'param1',
304
+ );
305
+
306
+ expect(Array.isArray(condition?.params.param1)).toBe(true);
307
+ expect(condition?.params.param1).toHaveLength(3);
308
+ });
309
+ });
310
+
311
+ describe('Task 2.9: Sorting', () => {
312
+ it('should apply numeric sorting for number fields', () => {
313
+ const mockQb = {
314
+ orderBy: jest.fn().mockReturnThis(),
315
+ addOrderBy: jest.fn().mockReturnThis(),
316
+ };
317
+
318
+ const attributeMetaMap = {
319
+ age: {
320
+ data_type: 'number',
321
+ flat_json_key: 'LEAD__age',
322
+ mapped_entity_type: 'LEAD',
323
+ },
324
+ };
325
+
326
+ const sortby = [{ sortColum: 'age', sortType: 'DESC' }];
327
+
328
+ service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
329
+
330
+ expect(mockQb.orderBy).toHaveBeenCalled();
331
+ const call = mockQb.orderBy.mock.calls[0];
332
+ expect(call[0]).toContain('::numeric');
333
+ expect(call[1]).toBe('DESC');
334
+ });
335
+
336
+ it('should apply bigint sorting for date fields', () => {
337
+ const mockQb = {
338
+ orderBy: jest.fn().mockReturnThis(),
339
+ };
340
+
341
+ const attributeMetaMap = {
342
+ created_date: {
343
+ data_type: 'date',
344
+ flat_json_key: 'LEAD__created_date',
345
+ mapped_entity_type: 'LEAD',
346
+ },
347
+ };
348
+
349
+ const sortby = [{ sortColum: 'created_date', sortType: 'ASC' }];
350
+
351
+ service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
352
+
353
+ const call = mockQb.orderBy.mock.calls[0];
354
+ expect(call[0]).toContain('::bigint');
355
+ });
356
+
357
+ it('should apply text sorting for text fields', () => {
358
+ const mockQb = {
359
+ orderBy: jest.fn().mockReturnThis(),
360
+ };
361
+
362
+ const attributeMetaMap = {
363
+ name: {
364
+ data_type: 'text',
365
+ flat_json_key: 'LEAD__name',
366
+ mapped_entity_type: 'LEAD',
367
+ },
368
+ };
369
+
370
+ const sortby = [{ sortColum: 'name', sortType: 'ASC' }];
371
+
372
+ service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
373
+
374
+ const call = mockQb.orderBy.mock.calls[0];
375
+ expect(call[0]).not.toContain('::');
376
+ expect(call[0]).toContain("->>"); // Text extraction
377
+ });
378
+ });
379
+
380
+ describe('Edge Cases', () => {
381
+ it('should handle NULL values in text conditions', () => {
382
+ const condition = service['buildTextCondition'](
383
+ 'LEAD__name',
384
+ 'equal',
385
+ null,
386
+ 'param1',
387
+ );
388
+
389
+ expect(condition).not.toBeNull();
390
+ expect(condition?.params.param1).toBe('');
391
+ });
392
+
393
+ it('should return null for unsupported operators', () => {
394
+ const condition = service['buildTextCondition'](
395
+ 'LEAD__name',
396
+ 'invalid_operator',
397
+ 'test',
398
+ 'param1',
399
+ );
400
+
401
+ expect(condition).toBeNull();
402
+ });
403
+
404
+ it('should handle empty array in multiselect', () => {
405
+ const condition = service['buildMultiSelectCondition'](
406
+ 'LEAD__languages',
407
+ 'contains',
408
+ [],
409
+ 'param1',
410
+ );
411
+
412
+ expect(condition?.query).toBe('1=1'); // Always true
413
+ });
414
+ });
415
+ });
@@ -7,10 +7,14 @@ import {
7
7
  Post,
8
8
  Req,
9
9
  UseGuards,
10
+ Query,
10
11
  } from '@nestjs/common';
12
+ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
11
13
  import { LinkedAttributesService } from '../service/linked_attributes.service';
12
14
  import { JwtAuthGuard } from 'src/module/auth/guards/jwt.guard';
15
+ import { CreateLinkedAttributeSmartDto } from '../dto/create-linked-attribute-smart.dto';
13
16
 
17
+ @ApiTags('Linked Attributes')
14
18
  @Controller('linked-attributes')
15
19
  export class LinkedAttributesController {
16
20
  constructor(
@@ -49,4 +53,85 @@ export class LinkedAttributesController {
49
53
  loggedInUser,
50
54
  );
51
55
  }
56
+
57
+ @Post('smart')
58
+ @UseGuards(JwtAuthGuard)
59
+ @ApiOperation({ summary: 'Create linked attribute with auto-generation' })
60
+ @ApiResponse({
61
+ status: 201,
62
+ description: 'Linked attribute created successfully',
63
+ })
64
+ @ApiResponse({ status: 400, description: 'Validation failed' })
65
+ async createSmart(
66
+ @Body() dto: CreateLinkedAttributeSmartDto,
67
+ @Req() req: any,
68
+ ) {
69
+ const loggedInUser = req.user?.userData;
70
+ return await this.linkedAttributesService.createLinkedAttributeSmart(
71
+ dto,
72
+ loggedInUser,
73
+ );
74
+ }
75
+
76
+ @Post(':id/backfill')
77
+ @UseGuards(JwtAuthGuard)
78
+ @ApiOperation({
79
+ summary: 'Backfill existing entities with new linked attribute',
80
+ })
81
+ @ApiResponse({ status: 200, description: 'Backfill completed' })
82
+ @ApiResponse({ status: 404, description: 'Linked attribute not found' })
83
+ async backfill(@Param('id') id: number, @Req() req: any) {
84
+ const loggedInUser = req.user?.userData;
85
+ return await this.linkedAttributesService.backfillLinkedAttribute(
86
+ id,
87
+ loggedInUser,
88
+ );
89
+ }
90
+
91
+ @Post('backfill-all')
92
+ @UseGuards(JwtAuthGuard)
93
+ @ApiOperation({ summary: 'Backfill all entities of a given type' })
94
+ @ApiResponse({ status: 200, description: 'Backfill completed' })
95
+ async backfillAll(
96
+ @Query('entity_type') entity_type: string,
97
+ @Req() req: any,
98
+ ) {
99
+ const loggedInUser = req.user?.userData;
100
+ return await this.linkedAttributesService.backfillAllForEntity(
101
+ entity_type,
102
+ loggedInUser,
103
+ );
104
+ }
105
+
106
+ @Get('preview-key')
107
+ @UseGuards(JwtAuthGuard)
108
+ @ApiOperation({
109
+ summary: 'Preview generated attribute key before creation',
110
+ })
111
+ @ApiResponse({ status: 200, description: 'Preview generated successfully' })
112
+ async previewKey(
113
+ @Query('mapped_entity_type') mapped_entity_type: string,
114
+ @Query('entity_type') entity_type: string,
115
+ @Query('attribute_key') attribute_key: string,
116
+ @Query('organization_id') organization_id: number,
117
+ @Req() req: any,
118
+ ) {
119
+ const loggedInUser = req.user?.userData;
120
+
121
+ const sequence =
122
+ await this.linkedAttributesService['generateNextSequence'](
123
+ mapped_entity_type,
124
+ entity_type,
125
+ attribute_key,
126
+ organization_id,
127
+ );
128
+
129
+ const generated_key = this.linkedAttributesService.generateAttributeKey(
130
+ entity_type,
131
+ attribute_key,
132
+ sequence,
133
+ );
134
+
135
+ return { generated_key, sequence };
136
+ }
52
137
  }
@@ -0,0 +1,54 @@
1
+ import {
2
+ IsString,
3
+ IsNotEmpty,
4
+ IsArray,
5
+ IsOptional,
6
+ IsBoolean,
7
+ ValidateNested,
8
+ } from 'class-validator';
9
+ import { Type } from 'class-transformer';
10
+
11
+ export class FilterConditionDto {
12
+ @IsString()
13
+ @IsNotEmpty()
14
+ filter_attribute: string;
15
+
16
+ @IsString()
17
+ @IsNotEmpty()
18
+ filter_operator: string;
19
+
20
+ @IsNotEmpty()
21
+ filter_value: any;
22
+ }
23
+
24
+ export class CreateLinkedAttributeSmartDto {
25
+ @IsString()
26
+ @IsNotEmpty()
27
+ field_name: string;
28
+
29
+ @IsString()
30
+ @IsNotEmpty()
31
+ mapped_entity_type: string;
32
+
33
+ @IsString()
34
+ @IsNotEmpty()
35
+ applicable_entity_type: string;
36
+
37
+ @IsString()
38
+ @IsNotEmpty()
39
+ applicable_attribute_key: string;
40
+
41
+ @IsString()
42
+ @IsNotEmpty()
43
+ saved_filter_code: string;
44
+
45
+ // @IsArray()
46
+ // @IsOptional()
47
+ // @ValidateNested({ each: true })
48
+ // @Type(() => FilterConditionDto)
49
+ // filter_conditions?: FilterConditionDto[];
50
+
51
+ @IsBoolean()
52
+ @IsOptional()
53
+ backfill?: boolean;
54
+ }
@@ -1,16 +1,18 @@
1
- import { Module } from '@nestjs/common';
1
+ import { Module, forwardRef } from '@nestjs/common';
2
2
  import { EntityModule } from '../meta/entity.module';
3
+ import { FilterModule } from '../filter/filter.module';
4
+ import { EntityJSONModule } from '../entity_json/entity_json.module';
3
5
  import { TypeOrmModule } from '@nestjs/typeorm';
4
6
  import { LinkedAttributes } from './entity/linked_attribute.entity';
5
7
  import { LinkedAttributesController } from './controller/linked_attributes.controller';
6
8
  import { LinkedAttributesService } from './service/linked_attributes.service';
7
- import { FilterModule } from '../filter/filter.module';
8
9
 
9
10
  @Module({
10
11
  imports: [
11
12
  EntityModule,
13
+ forwardRef(() => FilterModule),
14
+ forwardRef(() => EntityJSONModule),
12
15
  TypeOrmModule.forFeature([LinkedAttributes]),
13
- FilterModule,
14
16
  ],
15
17
  controllers: [LinkedAttributesController],
16
18
  providers: [