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.
- package/dist/config/database.config.js +1 -1
- package/dist/config/database.config.js.map +1 -1
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.d.ts +6 -0
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js +32 -0
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js.map +1 -0
- package/dist/module/entity_json/controller/entity_json.controller.d.ts +2 -9
- package/dist/module/entity_json/controller/entity_json.controller.js.map +1 -1
- package/dist/module/entity_json/entity/entityJson.entity.d.ts +2 -1
- package/dist/module/entity_json/entity/entityJson.entity.js +5 -1
- package/dist/module/entity_json/entity/entityJson.entity.js.map +1 -1
- package/dist/module/entity_json/entity_json.module.js +7 -2
- package/dist/module/entity_json/entity_json.module.js.map +1 -1
- package/dist/module/entity_json/service/entity_json.service.d.ts +5 -12
- package/dist/module/entity_json/service/entity_json.service.js +111 -29
- package/dist/module/entity_json/service/entity_json.service.js.map +1 -1
- package/dist/module/filter/controller/filter.controller.d.ts +12 -0
- package/dist/module/filter/controller/filter.controller.js +1 -1
- package/dist/module/filter/controller/filter.controller.js.map +1 -1
- package/dist/module/filter/filter.module.js +11 -2
- package/dist/module/filter/filter.module.js.map +1 -1
- package/dist/module/filter/service/filter.service.d.ts +38 -2
- package/dist/module/filter/service/filter.service.js +42 -49
- package/dist/module/filter/service/filter.service.js.map +1 -1
- package/dist/module/filter/service/flatjson-filter.service.d.ts +32 -0
- package/dist/module/filter/service/flatjson-filter.service.js +632 -0
- package/dist/module/filter/service/flatjson-filter.service.js.map +1 -0
- package/dist/module/filter/service/saved-filter.service.d.ts +3 -2
- package/dist/module/filter/service/saved-filter.service.js +14 -18
- package/dist/module/filter/service/saved-filter.service.js.map +1 -1
- package/dist/module/linked_attributes/controller/linked_attributes.controller.d.ts +19 -0
- package/dist/module/linked_attributes/controller/linked_attributes.controller.js +77 -0
- package/dist/module/linked_attributes/controller/linked_attributes.controller.js.map +1 -1
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.d.ts +13 -0
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js +64 -0
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js.map +1 -0
- package/dist/module/linked_attributes/linked_attributes.module.js +4 -2
- package/dist/module/linked_attributes/linked_attributes.module.js.map +1 -1
- package/dist/module/linked_attributes/service/linked_attributes.service.d.ts +41 -1
- package/dist/module/linked_attributes/service/linked_attributes.service.js +265 -1
- package/dist/module/linked_attributes/service/linked_attributes.service.js.map +1 -1
- package/dist/module/meta/controller/attribute-master.controller.d.ts +3 -0
- package/dist/module/meta/controller/attribute-master.controller.js +12 -0
- package/dist/module/meta/controller/attribute-master.controller.js.map +1 -1
- package/dist/module/meta/entity.module.js +2 -2
- package/dist/module/meta/entity.module.js.map +1 -1
- package/dist/module/meta/service/attribute-master.service.d.ts +6 -1
- package/dist/module/meta/service/attribute-master.service.js +20 -2
- package/dist/module/meta/service/attribute-master.service.js.map +1 -1
- package/dist/module/meta/service/entity-master.service.js +1 -0
- package/dist/module/meta/service/entity-master.service.js.map +1 -1
- package/dist/module/meta/service/entity-relation.service.d.ts +4 -3
- package/dist/module/meta/service/entity-relation.service.js +10 -4
- package/dist/module/meta/service/entity-relation.service.js.map +1 -1
- package/dist/module/meta/service/entity-service-impl.service.d.ts +1 -1
- package/dist/module/meta/service/entity-service-impl.service.js +2 -2
- package/dist/module/meta/service/entity-service-impl.service.js.map +1 -1
- package/dist/module/meta/service/entity-table.service.d.ts +5 -4
- package/dist/module/meta/service/entity-table.service.js +45 -24
- package/dist/module/meta/service/entity-table.service.js.map +1 -1
- package/dist/module/meta/service/resolver.service.d.ts +1 -1
- package/dist/module/meta/service/resolver.service.js +6 -3
- package/dist/module/meta/service/resolver.service.js.map +1 -1
- package/dist/module/workflow-automation/service/workflow-automation.service.js +2 -3
- package/dist/module/workflow-automation/service/workflow-automation.service.js.map +1 -1
- package/dist/table.config.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/config/database.config.ts +1 -1
- package/src/migrations/1732612800000-AddEntityJsonGinIndex.ts +41 -0
- package/src/module/entity_json/controller/entity_json.controller.ts +13 -0
- package/src/module/entity_json/docs/FlatJson_Filterin_System.md +2804 -0
- package/src/module/entity_json/entity/entityJson.entity.ts +4 -1
- package/src/module/entity_json/entity_json.module.ts +9 -5
- package/src/module/entity_json/service/entity_json.service.ts +236 -49
- package/src/module/filter/controller/filter.controller.ts +4 -4
- package/src/module/filter/filter.module.ts +12 -3
- package/src/module/filter/service/filter.service.ts +130 -71
- package/src/module/filter/service/flatjson-filter.service.ts +903 -0
- package/src/module/filter/service/saved-filter.service.ts +16 -26
- package/src/module/filter/test/flatjson-filter.service.spec.ts +415 -0
- package/src/module/linked_attributes/controller/linked_attributes.controller.ts +85 -0
- package/src/module/linked_attributes/dto/create-linked-attribute-smart.dto.ts +54 -0
- package/src/module/linked_attributes/linked_attributes.module.ts +5 -3
- package/src/module/linked_attributes/service/linked_attributes.service.ts +545 -2
- package/src/module/linked_attributes/test/linked-attributes.service.spec.ts +244 -0
- package/src/module/meta/controller/attribute-master.controller.ts +12 -0
- package/src/module/meta/entity.module.ts +3 -3
- package/src/module/meta/service/attribute-master.service.ts +29 -1
- package/src/module/meta/service/entity-master.service.ts +1 -0
- package/src/module/meta/service/entity-relation.service.ts +10 -6
- package/src/module/meta/service/entity-service-impl.service.ts +2 -1
- package/src/module/meta/service/entity-table.service.ts +82 -68
- package/src/module/meta/service/entity.service.ts +0 -1
- package/src/module/meta/service/resolver.service.ts +7 -3
- 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
|
|
11
|
-
constructor(
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|