tango-app-api-infra 3.9.5-vms.7 → 3.9.5-vms.71
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.
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { chunkArray, download, logger, sendMessageToFIFOQueue, sendMessageToQueue } from 'tango-app-api-middleware';
|
|
2
|
-
import { bulkUpdate, getOpenSearchById, getOpenSearchCount, getOpenSearchData, insertWithId, updateOpenSearchData
|
|
2
|
+
import { bulkUpdate, getOpenSearchById, getOpenSearchCount, getOpenSearchData, insertWithId, updateOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
3
3
|
import { findOneStore } from '../services/store.service.js';
|
|
4
4
|
import { countDocumnetsCamera } from '../services/camera.service.js';
|
|
5
5
|
import { findOneRevopDownload, upsertRevopDownload } from '../services/revopDownload.service.js';
|
|
6
6
|
import dayjs from 'dayjs';
|
|
7
7
|
import utc from 'dayjs/plugin/utc.js';
|
|
8
8
|
import timezone from 'dayjs/plugin/timezone.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import { findOneClient } from '../services/client.service.js';
|
|
10
|
+
import { findUser, findOneUser } from '../services/user.service.js';
|
|
11
|
+
import { sendPushNotification } from 'tango-app-api-middleware';
|
|
11
12
|
dayjs.extend( utc );
|
|
12
13
|
dayjs.extend( timezone );
|
|
13
14
|
|
|
@@ -60,6 +61,750 @@ export async function createTicket( req, res ) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
export async function createinternalTicket( req, res ) {
|
|
65
|
+
try {
|
|
66
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
67
|
+
let inputData = req.body;
|
|
68
|
+
let record = {
|
|
69
|
+
|
|
70
|
+
storeId: inputData.storeId,
|
|
71
|
+
type: 'internal',
|
|
72
|
+
dateString: inputData.dateString,
|
|
73
|
+
storeName: inputData?.storeName,
|
|
74
|
+
ticketName: inputData.ticketName || 'footfall-directory',
|
|
75
|
+
footfallCount: inputData.footfallCount,
|
|
76
|
+
clientId: inputData?.clientId,
|
|
77
|
+
ticketId: 'TE_FDT_' + new Date().valueOf(),
|
|
78
|
+
createdAt: new Date(),
|
|
79
|
+
updatedAt: new Date(),
|
|
80
|
+
status: 'open',
|
|
81
|
+
comments: inputData?.comments || '',
|
|
82
|
+
createdByEmail: req?.user?.email,
|
|
83
|
+
createdByUserName: req?.user?.userName,
|
|
84
|
+
createdByRole: req?.user?.role,
|
|
85
|
+
mappingInfo: [],
|
|
86
|
+
};
|
|
87
|
+
const id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
|
|
88
|
+
const insertResult = await insertWithId( openSearch.footfallDirectory, id, record );
|
|
89
|
+
if ( insertResult && insertResult.statusCode === 201 ) {
|
|
90
|
+
return res.sendSuccess( 'Ticket raised successfully' );
|
|
91
|
+
}
|
|
92
|
+
} catch ( error ) {
|
|
93
|
+
const err = error.message || 'Internal Server Error';
|
|
94
|
+
logger.error( { error: error, funtion: 'createinternalTicket' } );
|
|
95
|
+
return res.sendError( err, 500 );
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function tangoReviewTicket( req, res ) {
|
|
99
|
+
try {
|
|
100
|
+
const inputData = req.body;
|
|
101
|
+
|
|
102
|
+
// get store info by the storeId into mongo db
|
|
103
|
+
const getstoreName = await findOneStore( { storeId: inputData.storeId, status: 'active' }, { storeId: 1, storeName: 1, clientId: 1 } );
|
|
104
|
+
|
|
105
|
+
if ( !getstoreName || getstoreName == null ) {
|
|
106
|
+
return res.sendError( 'The store ID is either inActive or not found', 400 );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// get the footfall count from opensearch
|
|
110
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
111
|
+
const dateString = `${inputData.storeId}_${inputData.dateString}`;
|
|
112
|
+
const getQuery = {
|
|
113
|
+
query: {
|
|
114
|
+
terms: {
|
|
115
|
+
_id: [ dateString ],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
_source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
119
|
+
sort: [
|
|
120
|
+
{
|
|
121
|
+
date_iso: {
|
|
122
|
+
order: 'desc',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const getFootfallCount = await getOpenSearchData( openSearch.footfall, getQuery );
|
|
129
|
+
const hits = getFootfallCount?.body?.hits?.hits || [];
|
|
130
|
+
if ( hits?.[0]?._source?.footfall_count <= 0 ) {
|
|
131
|
+
return res.sendError( 'You can’t create a ticket because this store has 0 footfall data' );
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// get category details from the client level configuration
|
|
135
|
+
const getConfig = await findOneClient( { clientId: getstoreName.clientId }, { footfallDirectoryConfigs: 1 } );
|
|
136
|
+
if ( !getConfig || getConfig == null ) {
|
|
137
|
+
return res.sendError( 'The Client ID is either not configured or not found', 400 );
|
|
138
|
+
}
|
|
139
|
+
let findQuery = {
|
|
140
|
+
size: 10000,
|
|
141
|
+
query: {
|
|
142
|
+
bool: {
|
|
143
|
+
must: [
|
|
144
|
+
{
|
|
145
|
+
term: {
|
|
146
|
+
'storeId.keyword': inputData.storeId,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
term: {
|
|
151
|
+
'dateString': inputData.dateString,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
|
|
159
|
+
let Ticket = findTicket.body?.hits?.hits;
|
|
160
|
+
|
|
161
|
+
if ( Ticket.length === 0 ) {
|
|
162
|
+
return res.sendError( 'Ticket not found', 400 );
|
|
163
|
+
}
|
|
164
|
+
const getTicket = {
|
|
165
|
+
size: 10000,
|
|
166
|
+
query: {
|
|
167
|
+
bool: {
|
|
168
|
+
must: [
|
|
169
|
+
{
|
|
170
|
+
term: {
|
|
171
|
+
'storeId.keyword': inputData.storeId,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
term: {
|
|
176
|
+
'dateString': inputData.dateString,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
if ( Ticket[0]?._source?.type != 'internal' ) {
|
|
185
|
+
getTicket.query.bool.must.push(
|
|
186
|
+
{
|
|
187
|
+
nested: {
|
|
188
|
+
path: 'mappingInfo',
|
|
189
|
+
query: {
|
|
190
|
+
bool: {
|
|
191
|
+
must: [
|
|
192
|
+
{
|
|
193
|
+
term: {
|
|
194
|
+
'mappingInfo.type': 'tangoreview',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const getFootfallticketData = await getOpenSearchData( openSearch.footfallDirectory, getTicket );
|
|
206
|
+
const ticketData = getFootfallticketData?.body?.hits?.hits;
|
|
207
|
+
|
|
208
|
+
if ( !ticketData || ticketData?.length == 0 ) {
|
|
209
|
+
return res.sendError( 'You don’t have any tagged images right now', 400 );
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const record = {
|
|
213
|
+
|
|
214
|
+
status: parseInt( inputData?.mappingInfo?.[0]?.revicedPerc || 0 ) < parseInt( getConfig?.footfallDirectoryConfigs?.tangoReview|| 0 ) ? 'Open - Accuracy Issue' : 'Closed',
|
|
215
|
+
revicedFootfall: inputData.mappingInfo?.[0]?.revicedFootfall,
|
|
216
|
+
revicedPerc: inputData.mappingInfo?.[0]?.revicedPerc,
|
|
217
|
+
mappingInfo: ticketData?.[0]?._source?.mappingInfo,
|
|
218
|
+
createdByEmail: req?.user?.email,
|
|
219
|
+
createdByUserName: req?.user?.userName,
|
|
220
|
+
createdByRole: req?.user?.role,
|
|
221
|
+
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
// Retrieve client footfallDirectoryConfigs revision
|
|
226
|
+
let isAutoCloseEnable = getConfig.footfallDirectoryConfigs.isAutoCloseEnable;
|
|
227
|
+
let autoCloseAccuracy = getConfig?.footfallDirectoryConfigs?.autoCloseAccuracy;
|
|
228
|
+
|
|
229
|
+
const getNumber = autoCloseAccuracy.split( '%' )[0];
|
|
230
|
+
|
|
231
|
+
let autoCloseAccuracyValue = parseFloat( ( autoCloseAccuracy || getNumber ).replace( '%', '' ) );
|
|
232
|
+
let revisedPercentage = inputData.mappingInfo?.revicedPerc;
|
|
233
|
+
const revised = Number( revisedPercentage?.split( '%' )[0] );
|
|
234
|
+
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
235
|
+
// If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
|
|
236
|
+
if (
|
|
237
|
+
isAutoCloseEnable === true &&
|
|
238
|
+
revisedPercentage >= autoCloseAccuracyValue
|
|
239
|
+
) {
|
|
240
|
+
record.status = 'Closed';
|
|
241
|
+
// Only keep or modify mappingInfo items with type "review"
|
|
242
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
243
|
+
const temp = record.mappingInfo
|
|
244
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
245
|
+
.map( ( item ) => ( {
|
|
246
|
+
...item,
|
|
247
|
+
|
|
248
|
+
mode: inputData.mappingInfo?.mode,
|
|
249
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
250
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
251
|
+
count: inputData.mappingInfo?.count,
|
|
252
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
253
|
+
status: 'Closed',
|
|
254
|
+
createdByEmail: req?.user?.email,
|
|
255
|
+
createdByUserName: req?.user?.userName,
|
|
256
|
+
createdByRole: req?.user?.role,
|
|
257
|
+
} ) );
|
|
258
|
+
|
|
259
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
260
|
+
...temp ];
|
|
261
|
+
// If updating the mapping config to mark [i].status as 'Closed'
|
|
262
|
+
// Make sure all relevant mappingInfo items of type 'approve' are set to status 'Closed'
|
|
263
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
264
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
265
|
+
return {
|
|
266
|
+
...item,
|
|
267
|
+
status: 'Closed',
|
|
268
|
+
};
|
|
269
|
+
} );
|
|
270
|
+
}
|
|
271
|
+
// If no review mapping existed, push a new one
|
|
272
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
273
|
+
// record.mappingInfo.push( {
|
|
274
|
+
// type: 'tangoreview',
|
|
275
|
+
// mode: inputData.mappingInfo?.mode,
|
|
276
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
277
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
278
|
+
// count: inputData.mappingInfo?.count,
|
|
279
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
280
|
+
// status: 'Closed',
|
|
281
|
+
// createdByEmail: req?.user?.email,
|
|
282
|
+
// createdByUserName: req?.user?.userName,
|
|
283
|
+
// createdByRole: req?.user?.role,
|
|
284
|
+
// } );
|
|
285
|
+
// }
|
|
286
|
+
}
|
|
287
|
+
record.mappingInfo.push(
|
|
288
|
+
{
|
|
289
|
+
type: 'finalRevision',
|
|
290
|
+
mode: inputData.mappingInfo?.mode,
|
|
291
|
+
revicedFootfall: revisedFootfall,
|
|
292
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
293
|
+
count: inputData.mappingInfo?.count,
|
|
294
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
295
|
+
status: 'Closed',
|
|
296
|
+
createdByEmail: req?.user?.email,
|
|
297
|
+
createdByUserName: req?.user?.userName,
|
|
298
|
+
createdByRole: req?.user?.role,
|
|
299
|
+
createdAt: new Date(),
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
} else if ( revised < tangoReview ) {
|
|
303
|
+
// If ticket is closed, do not proceed with revision mapping
|
|
304
|
+
|
|
305
|
+
record.status = 'Closed - Accuracy Issue';
|
|
306
|
+
// Only keep or modify mappingInfo items with type "review"
|
|
307
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
308
|
+
const temp = record.mappingInfo
|
|
309
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
310
|
+
.map( ( item ) => ( {
|
|
311
|
+
...item,
|
|
312
|
+
mode: inputData.mappingInfo?.mode,
|
|
313
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
314
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
315
|
+
count: inputData.mappingInfo?.count,
|
|
316
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
317
|
+
status: 'Closed',
|
|
318
|
+
createdByEmail: req?.user?.email,
|
|
319
|
+
createdByUserName: req?.user?.userName,
|
|
320
|
+
createdByRole: req?.user?.role,
|
|
321
|
+
} ) );
|
|
322
|
+
|
|
323
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
324
|
+
...temp ];
|
|
325
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
326
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
327
|
+
return {
|
|
328
|
+
...item,
|
|
329
|
+
status: 'Closed',
|
|
330
|
+
};
|
|
331
|
+
} );
|
|
332
|
+
}
|
|
333
|
+
// If no review mapping existed, push a new one
|
|
334
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
335
|
+
// record.mappingInfo.push( {
|
|
336
|
+
// type: 'tangoreview',
|
|
337
|
+
// mode: inputData.mappingInfo?.mode,
|
|
338
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
339
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
340
|
+
// count: inputData.mappingInfo?.count,
|
|
341
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
342
|
+
// status: 'Closed',
|
|
343
|
+
// createdByEmail: req?.user?.email,
|
|
344
|
+
// createdByUserName: req?.user?.userName,
|
|
345
|
+
// createdByRole: req?.user?.role,
|
|
346
|
+
// } );
|
|
347
|
+
// }
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
351
|
+
const temp = record.mappingInfo
|
|
352
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
353
|
+
.map( ( item ) => ( {
|
|
354
|
+
...item,
|
|
355
|
+
mode: inputData.mappingInfo?.mode,
|
|
356
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
357
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
358
|
+
count: inputData.mappingInfo?.count,
|
|
359
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
360
|
+
status: 'Closed',
|
|
361
|
+
createdByEmail: req?.user?.email,
|
|
362
|
+
createdByUserName: req?.user?.userName,
|
|
363
|
+
createdByRole: req?.user?.role,
|
|
364
|
+
} ) );
|
|
365
|
+
|
|
366
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
367
|
+
...temp ];
|
|
368
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
369
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
370
|
+
return {
|
|
371
|
+
...item,
|
|
372
|
+
status: 'Closed',
|
|
373
|
+
};
|
|
374
|
+
} );
|
|
375
|
+
}
|
|
376
|
+
// If no review mapping existed, push a new one
|
|
377
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
378
|
+
// record.mappingInfo.push( {
|
|
379
|
+
// type: 'tangoreview',
|
|
380
|
+
// mode: inputData.mappingInfo?.mode,
|
|
381
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
382
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
383
|
+
// count: inputData.mappingInfo?.count,
|
|
384
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
385
|
+
// status: 'Closed',
|
|
386
|
+
// createdByEmail: req?.user?.email,
|
|
387
|
+
// createdByUserName: req?.user?.userName,
|
|
388
|
+
// createdByRole: req?.user?.role,
|
|
389
|
+
// } );
|
|
390
|
+
// }
|
|
391
|
+
}
|
|
392
|
+
record.mappingInfo.push(
|
|
393
|
+
{
|
|
394
|
+
type: 'finalRevision',
|
|
395
|
+
mode: inputData.mappingInfo?.mode,
|
|
396
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
397
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
398
|
+
count: inputData.mappingInfo?.count,
|
|
399
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
400
|
+
status: 'Closed',
|
|
401
|
+
createdByEmail: req?.user?.email,
|
|
402
|
+
createdByUserName: req?.user?.userName,
|
|
403
|
+
createdByRole: req?.user?.role,
|
|
404
|
+
createdAt: new Date(),
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if ( Ticket[0]?._source?.type==='store' ) {
|
|
410
|
+
let findTagging = Ticket[0]?._source?.mappingInfo.filter( ( data ) => data.type==='tagging' );
|
|
411
|
+
if ( findTagging?.length>0&&findTagging[0].createdByEmail!='' ) {
|
|
412
|
+
let userData = await findOneUser( { email: findTagging[0]?.createdByEmail } );
|
|
413
|
+
let title = `Received response for the Footfall ticket raised.`;
|
|
414
|
+
let createdOn = dayjs( Ticket[0]?._source?.dateString ).format( 'DD MMM YYYY' );
|
|
415
|
+
let description = `Raised on ${createdOn}`;
|
|
416
|
+
|
|
417
|
+
let Data = {
|
|
418
|
+
'title': title,
|
|
419
|
+
'body': description,
|
|
420
|
+
'type': 'closed',
|
|
421
|
+
'date': Ticket[0]?._source?.dateString,
|
|
422
|
+
'storeId': Ticket[0]?._source?.storeId,
|
|
423
|
+
'clientId': Ticket[0]?._source?.clientId,
|
|
424
|
+
'ticketId': Ticket[0]?._source?.ticketId,
|
|
425
|
+
};
|
|
426
|
+
if ( userData && userData.fcmToken ) {
|
|
427
|
+
const fcmToken = userData.fcmToken;
|
|
428
|
+
await sendPushNotification( title, description, fcmToken, Data );
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// return;
|
|
433
|
+
|
|
434
|
+
let id = `${inputData.storeId}_${inputData.dateString}_footfall-directory-tagging`;
|
|
435
|
+
if ( inputData.ticketType === 'internal' ) {
|
|
436
|
+
id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
|
|
440
|
+
|
|
441
|
+
if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
|
|
442
|
+
return res.sendSuccess( 'Ticket closed successfully' );
|
|
443
|
+
} else {
|
|
444
|
+
return res.sendError( 'Internal Server Error', 500 );
|
|
445
|
+
}
|
|
446
|
+
} catch ( error ) {
|
|
447
|
+
const err = error.message || 'Internal Server Error';
|
|
448
|
+
logger.error( { error: err, funtion: 'tangoReviewTicket' } );
|
|
449
|
+
return res.sendError( err, 500 );
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function tangoReviewAccuracyClosedTicket( req, res ) {
|
|
454
|
+
try {
|
|
455
|
+
const inputData = req.body;
|
|
456
|
+
|
|
457
|
+
// get store info by the storeId into mongo db
|
|
458
|
+
const getstoreName = await findOneStore( { storeId: inputData.storeId, status: 'active' }, { storeId: 1, storeName: 1, clientId: 1 } );
|
|
459
|
+
|
|
460
|
+
if ( !getstoreName || getstoreName == null ) {
|
|
461
|
+
return res.sendError( 'The store ID is either inActive or not found', 400 );
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// get the footfall count from opensearch
|
|
465
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
466
|
+
const dateString = `${inputData.storeId}_${inputData.dateString}`;
|
|
467
|
+
const getQuery = {
|
|
468
|
+
query: {
|
|
469
|
+
terms: {
|
|
470
|
+
_id: [ dateString ],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
_source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
474
|
+
sort: [
|
|
475
|
+
{
|
|
476
|
+
date_iso: {
|
|
477
|
+
order: 'desc',
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const getFootfallCount = await getOpenSearchData( openSearch.footfall, getQuery );
|
|
484
|
+
const hits = getFootfallCount?.body?.hits?.hits || [];
|
|
485
|
+
if ( hits?.[0]?._source?.footfall_count <= 0 ) {
|
|
486
|
+
return res.sendError( 'You can’t create a ticket because this store has 0 footfall data' );
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// get category details from the client level configuration
|
|
490
|
+
const getConfig = await findOneClient( { clientId: getstoreName.clientId }, { footfallDirectoryConfigs: 1 } );
|
|
491
|
+
if ( !getConfig || getConfig == null ) {
|
|
492
|
+
return res.sendError( 'The Client ID is either not configured or not found', 400 );
|
|
493
|
+
}
|
|
494
|
+
let findQuery = {
|
|
495
|
+
size: 10000,
|
|
496
|
+
query: {
|
|
497
|
+
bool: {
|
|
498
|
+
must: [
|
|
499
|
+
{
|
|
500
|
+
term: {
|
|
501
|
+
'storeId.keyword': inputData.storeId,
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
term: {
|
|
506
|
+
'dateString': inputData.dateString,
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
|
|
514
|
+
let Ticket = findTicket.body?.hits?.hits;
|
|
515
|
+
|
|
516
|
+
if ( Ticket.length === 0 ) {
|
|
517
|
+
return res.sendError( 'Ticket not found', 400 );
|
|
518
|
+
}
|
|
519
|
+
const getTicket = {
|
|
520
|
+
size: 10000,
|
|
521
|
+
query: {
|
|
522
|
+
bool: {
|
|
523
|
+
must: [
|
|
524
|
+
{
|
|
525
|
+
term: {
|
|
526
|
+
'storeId.keyword': inputData.storeId,
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
term: {
|
|
531
|
+
'dateString': inputData.dateString,
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
if ( Ticket[0]?._source?.type != 'internal' ) {
|
|
540
|
+
getTicket.query.bool.must.push(
|
|
541
|
+
{
|
|
542
|
+
nested: {
|
|
543
|
+
path: 'mappingInfo',
|
|
544
|
+
query: {
|
|
545
|
+
bool: {
|
|
546
|
+
must: [
|
|
547
|
+
{
|
|
548
|
+
term: {
|
|
549
|
+
'mappingInfo.type': 'tangoreview',
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const getFootfallticketData = await getOpenSearchData( openSearch.footfallDirectory, getTicket );
|
|
561
|
+
const ticketData = getFootfallticketData?.body?.hits?.hits;
|
|
562
|
+
|
|
563
|
+
if ( !ticketData || ticketData?.length == 0 ) {
|
|
564
|
+
return res.sendError( 'You don’t have any tagged images right now', 400 );
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const record = {
|
|
568
|
+
|
|
569
|
+
status: parseInt( inputData?.mappingInfo?.[0]?.revicedPerc || 0 ) < parseInt( getConfig?.footfallDirectoryConfigs?.tangoReview|| 0 ) ? 'Open - Accuracy Issue' : 'Closed',
|
|
570
|
+
revicedFootfall: inputData.mappingInfo?.[0]?.revicedFootfall,
|
|
571
|
+
revicedPerc: inputData.mappingInfo?.[0]?.revicedPerc,
|
|
572
|
+
mappingInfo: ticketData?.[0]?._source?.mappingInfo,
|
|
573
|
+
createdByEmail: req?.user?.email,
|
|
574
|
+
createdByUserName: req?.user?.userName,
|
|
575
|
+
createdByRole: req?.user?.role,
|
|
576
|
+
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
// Retrieve client footfallDirectoryConfigs revision
|
|
581
|
+
let isAutoCloseEnable = getConfig.footfallDirectoryConfigs.isAutoCloseEnable;
|
|
582
|
+
let autoCloseAccuracy = getConfig?.footfallDirectoryConfigs?.autoCloseAccuracy;
|
|
583
|
+
|
|
584
|
+
const getNumber = autoCloseAccuracy.split( '%' )[0];
|
|
585
|
+
|
|
586
|
+
let autoCloseAccuracyValue = parseFloat( ( autoCloseAccuracy || getNumber ).replace( '%', '' ) );
|
|
587
|
+
let revisedPercentage = inputData.mappingInfo?.revicedPerc;
|
|
588
|
+
const revised = Number( revisedPercentage?.split( '%' )[0] );
|
|
589
|
+
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
590
|
+
// If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
|
|
591
|
+
if (
|
|
592
|
+
isAutoCloseEnable === true &&
|
|
593
|
+
revisedPercentage >= autoCloseAccuracyValue
|
|
594
|
+
) {
|
|
595
|
+
record.status = 'Closed';
|
|
596
|
+
// Only keep or modify mappingInfo items with type "review"
|
|
597
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
598
|
+
const temp = record.mappingInfo
|
|
599
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
600
|
+
.map( ( item ) => ( {
|
|
601
|
+
...item,
|
|
602
|
+
|
|
603
|
+
mode: inputData.mappingInfo?.mode,
|
|
604
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
605
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
606
|
+
count: inputData.mappingInfo?.count,
|
|
607
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
608
|
+
status: 'Closed',
|
|
609
|
+
createdByEmail: req?.user?.email,
|
|
610
|
+
createdByUserName: req?.user?.userName,
|
|
611
|
+
createdByRole: req?.user?.role,
|
|
612
|
+
} ) );
|
|
613
|
+
|
|
614
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
615
|
+
...temp ];
|
|
616
|
+
// If updating the mapping config to mark [i].status as 'Closed'
|
|
617
|
+
// Make sure all relevant mappingInfo items of type 'approve' are set to status 'Closed'
|
|
618
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
619
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
620
|
+
return {
|
|
621
|
+
...item,
|
|
622
|
+
status: 'Closed',
|
|
623
|
+
};
|
|
624
|
+
} );
|
|
625
|
+
}
|
|
626
|
+
// If no review mapping existed, push a new one
|
|
627
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
628
|
+
// record.mappingInfo.push( {
|
|
629
|
+
// type: 'tangoreview',
|
|
630
|
+
// mode: inputData.mappingInfo?.mode,
|
|
631
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
632
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
633
|
+
// count: inputData.mappingInfo?.count,
|
|
634
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
635
|
+
// status: 'Closed',
|
|
636
|
+
// createdByEmail: req?.user?.email,
|
|
637
|
+
// createdByUserName: req?.user?.userName,
|
|
638
|
+
// createdByRole: req?.user?.role,
|
|
639
|
+
// } );
|
|
640
|
+
// }
|
|
641
|
+
}
|
|
642
|
+
record.mappingInfo.push(
|
|
643
|
+
{
|
|
644
|
+
type: 'finalRevision',
|
|
645
|
+
mode: inputData.mappingInfo?.mode,
|
|
646
|
+
revicedFootfall: revisedFootfall,
|
|
647
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
648
|
+
count: inputData.mappingInfo?.count,
|
|
649
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
650
|
+
status: 'Closed',
|
|
651
|
+
createdByEmail: req?.user?.email,
|
|
652
|
+
createdByUserName: req?.user?.userName,
|
|
653
|
+
createdByRole: req?.user?.role,
|
|
654
|
+
createdAt: new Date(),
|
|
655
|
+
},
|
|
656
|
+
);
|
|
657
|
+
} else if ( revised < tangoReview ) {
|
|
658
|
+
// If ticket is closed, do not proceed with revision mapping
|
|
659
|
+
|
|
660
|
+
record.status = 'Closed - Accuracy Issue';
|
|
661
|
+
// Only keep or modify mappingInfo items with type "review"
|
|
662
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
663
|
+
const temp = record.mappingInfo
|
|
664
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
665
|
+
.map( ( item ) => ( {
|
|
666
|
+
...item,
|
|
667
|
+
mode: inputData.mappingInfo?.mode,
|
|
668
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
669
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
670
|
+
count: inputData.mappingInfo?.count,
|
|
671
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
672
|
+
status: 'Closed',
|
|
673
|
+
createdByEmail: req?.user?.email,
|
|
674
|
+
createdByUserName: req?.user?.userName,
|
|
675
|
+
createdByRole: req?.user?.role,
|
|
676
|
+
} ) );
|
|
677
|
+
|
|
678
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
679
|
+
...temp ];
|
|
680
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
681
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
682
|
+
return {
|
|
683
|
+
...item,
|
|
684
|
+
status: 'Closed',
|
|
685
|
+
};
|
|
686
|
+
} );
|
|
687
|
+
}
|
|
688
|
+
// If no review mapping existed, push a new one
|
|
689
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
690
|
+
// record.mappingInfo.push( {
|
|
691
|
+
// type: 'tangoreview',
|
|
692
|
+
// mode: inputData.mappingInfo?.mode,
|
|
693
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
694
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
695
|
+
// count: inputData.mappingInfo?.count,
|
|
696
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
697
|
+
// status: 'Closed',
|
|
698
|
+
// createdByEmail: req?.user?.email,
|
|
699
|
+
// createdByUserName: req?.user?.userName,
|
|
700
|
+
// createdByRole: req?.user?.role,
|
|
701
|
+
// } );
|
|
702
|
+
// }
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
706
|
+
const temp = record.mappingInfo
|
|
707
|
+
.filter( ( item ) => item.type === 'tangoreview' )
|
|
708
|
+
.map( ( item ) => ( {
|
|
709
|
+
...item,
|
|
710
|
+
mode: inputData.mappingInfo?.mode,
|
|
711
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
712
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
713
|
+
count: inputData.mappingInfo?.count,
|
|
714
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
715
|
+
status: 'Closed',
|
|
716
|
+
createdByEmail: req?.user?.email,
|
|
717
|
+
createdByUserName: req?.user?.userName,
|
|
718
|
+
createdByRole: req?.user?.role,
|
|
719
|
+
} ) );
|
|
720
|
+
|
|
721
|
+
record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
|
|
722
|
+
...temp ];
|
|
723
|
+
if ( Array.isArray( record.mappingInfo ) ) {
|
|
724
|
+
record.mappingInfo = record.mappingInfo.map( ( item ) => {
|
|
725
|
+
return {
|
|
726
|
+
...item,
|
|
727
|
+
status: 'Closed',
|
|
728
|
+
};
|
|
729
|
+
} );
|
|
730
|
+
}
|
|
731
|
+
// If no review mapping existed, push a new one
|
|
732
|
+
// if ( record.mappingInfo.length === 0 ) {
|
|
733
|
+
// record.mappingInfo.push( {
|
|
734
|
+
// type: 'tangoreview',
|
|
735
|
+
// mode: inputData.mappingInfo?.mode,
|
|
736
|
+
// revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
737
|
+
// revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
738
|
+
// count: inputData.mappingInfo?.count,
|
|
739
|
+
// revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
740
|
+
// status: 'Closed',
|
|
741
|
+
// createdByEmail: req?.user?.email,
|
|
742
|
+
// createdByUserName: req?.user?.userName,
|
|
743
|
+
// createdByRole: req?.user?.role,
|
|
744
|
+
// } );
|
|
745
|
+
// }
|
|
746
|
+
}
|
|
747
|
+
record.mappingInfo.push(
|
|
748
|
+
{
|
|
749
|
+
type: 'finalRevision',
|
|
750
|
+
mode: inputData.mappingInfo?.mode,
|
|
751
|
+
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
752
|
+
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
753
|
+
count: inputData.mappingInfo?.count,
|
|
754
|
+
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
755
|
+
status: 'Closed',
|
|
756
|
+
createdByEmail: req?.user?.email,
|
|
757
|
+
createdByUserName: req?.user?.userName,
|
|
758
|
+
createdByRole: req?.user?.role,
|
|
759
|
+
createdAt: new Date(),
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if ( Ticket[0]?._source?.type==='store' ) {
|
|
765
|
+
let findTagging = Ticket[0]?._source?.mappingInfo.filter( ( data ) => data.type==='tagging' );
|
|
766
|
+
if ( findTagging?.length>0&&findTagging[0].createdByEmail!='' ) {
|
|
767
|
+
let userData = await findOneUser( { email: findTagging[0]?.createdByEmail } );
|
|
768
|
+
let title = `Received response for the Footfall ticket raised.`;
|
|
769
|
+
let createdOn = dayjs( Ticket[0]?._source?.dateString ).format( 'DD MMM YYYY' );
|
|
770
|
+
let description = `Raised on ${createdOn}`;
|
|
771
|
+
|
|
772
|
+
let Data = {
|
|
773
|
+
'title': title,
|
|
774
|
+
'body': description,
|
|
775
|
+
'type': 'closed',
|
|
776
|
+
'date': Ticket[0]?._source?.dateString,
|
|
777
|
+
'storeId': Ticket[0]?._source?.storeId,
|
|
778
|
+
'clientId': Ticket[0]?._source?.clientId,
|
|
779
|
+
'ticketId': Ticket[0]?._source?.ticketId,
|
|
780
|
+
};
|
|
781
|
+
if ( userData && userData.fcmToken ) {
|
|
782
|
+
const fcmToken = userData.fcmToken;
|
|
783
|
+
await sendPushNotification( title, description, fcmToken, Data );
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
// return;
|
|
788
|
+
|
|
789
|
+
let id = `${inputData.storeId}_${inputData.dateString}_footfall-directory-tagging`;
|
|
790
|
+
if ( inputData.ticketType === 'internal' ) {
|
|
791
|
+
id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
|
|
795
|
+
|
|
796
|
+
if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
|
|
797
|
+
return res.sendSuccess( 'Ticket closed successfully' );
|
|
798
|
+
} else {
|
|
799
|
+
return res.sendError( 'Internal Server Error', 500 );
|
|
800
|
+
}
|
|
801
|
+
} catch ( error ) {
|
|
802
|
+
const err = error.message || 'Internal Server Error';
|
|
803
|
+
logger.error( { error: err, funtion: 'tangoReviewTicket' } );
|
|
804
|
+
return res.sendError( err, 500 );
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
63
808
|
async function bulkUpdateStatusToPending( indexName, inputData ) {
|
|
64
809
|
const bulkBody = [];
|
|
65
810
|
|
|
@@ -195,7 +940,6 @@ export async function ticketSummary1( req, res ) {
|
|
|
195
940
|
|
|
196
941
|
const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
|
|
197
942
|
const aggs = getData?.body?.aggregations;
|
|
198
|
-
logger.info( { aggs: aggs, body: getData?.body } );
|
|
199
943
|
|
|
200
944
|
const result = {
|
|
201
945
|
totalTickets: aggs.totalTicketCount.value,
|
|
@@ -220,10 +964,10 @@ export async function ticketSummary( req, res ) {
|
|
|
220
964
|
try {
|
|
221
965
|
let result = '';
|
|
222
966
|
const userInfo = req.user;
|
|
223
|
-
const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name =='reviewer' && ( m.isAdd==true || m.isEdit==true ) ) ) );
|
|
967
|
+
const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
|
|
224
968
|
|
|
225
|
-
if ( req.user.userType =='tango' ) {
|
|
226
|
-
result ={
|
|
969
|
+
if ( req.user.userType == 'tango' ) {
|
|
970
|
+
result = {
|
|
227
971
|
totalTickets: 0,
|
|
228
972
|
averageAccuracyOverAll: 0,
|
|
229
973
|
openTickets: 0,
|
|
@@ -234,30 +978,30 @@ export async function ticketSummary( req, res ) {
|
|
|
234
978
|
ticketAccuracyBelow: '0%',
|
|
235
979
|
};
|
|
236
980
|
} else {
|
|
237
|
-
result = req.user.role === 'superadmin'?
|
|
238
|
-
{
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} :
|
|
249
|
-
req.user.role === 'user'? 'NA':
|
|
250
|
-
ticketsFeature?
|
|
251
|
-
{
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}: 'NA';
|
|
981
|
+
result = req.user.role === 'superadmin' ?
|
|
982
|
+
{
|
|
983
|
+
totalTickets: 0,
|
|
984
|
+
openTickets: 0,
|
|
985
|
+
inprogress: 0,
|
|
986
|
+
closedTickets: 0,
|
|
987
|
+
dueToday: 0,
|
|
988
|
+
Expired: 0,
|
|
989
|
+
underTangoReview: 0,
|
|
990
|
+
avgTicket: '0%',
|
|
991
|
+
avgAccuracy: '0%',
|
|
992
|
+
} :
|
|
993
|
+
req.user.role === 'user' ? 'NA' :
|
|
994
|
+
ticketsFeature ?
|
|
995
|
+
{
|
|
996
|
+
totalTickets: 0,
|
|
997
|
+
openTickets: 0,
|
|
998
|
+
inprogress: 0,
|
|
999
|
+
closedTickets: 0,
|
|
1000
|
+
dueToday: 0,
|
|
1001
|
+
Expired: 0,
|
|
1002
|
+
avgTicket: '0%',
|
|
1003
|
+
avgAccuracy: '0%',
|
|
1004
|
+
} : 'NA';
|
|
261
1005
|
}
|
|
262
1006
|
|
|
263
1007
|
return res.sendSuccess( { result: result } );
|
|
@@ -293,7 +1037,7 @@ export async function ticketList1( req, res ) {
|
|
|
293
1037
|
terms: {
|
|
294
1038
|
'clientId.keyword': Array.isArray( inputData.clientId ) ?
|
|
295
1039
|
inputData.clientId :
|
|
296
|
-
|
|
1040
|
+
[ inputData.clientId ],
|
|
297
1041
|
},
|
|
298
1042
|
},
|
|
299
1043
|
];
|
|
@@ -470,12 +1214,12 @@ export async function ticketList1( req, res ) {
|
|
|
470
1214
|
'Ticket raised on': dayjs( item._source.createdAt ).format( 'DD MMM, YYYY' ),
|
|
471
1215
|
'Issue Date': dayjs( item._source.dateString ).format( 'DD MMM, YYYY' ),
|
|
472
1216
|
'Total Footfalls': item._source.footfallCount,
|
|
473
|
-
'Duplicates': item?._source?.status === 'closed'? item._source.duplicateACCount : item._source.duplicateCount,
|
|
474
|
-
'Employee/Staff': item?._source?.status === 'closed'? item._source.employeeACCount : item._source.employeeCount,
|
|
475
|
-
'HouseKeeping': item?._source?.status === 'closed'? item._source.houseKeepingACCount : item._source.houseKeepingCount,
|
|
476
|
-
'Junk': item?._source?.status === 'closed'? ( item._source.junkACCount || 0 ) : ( item._source.junkCount|| 0 ),
|
|
477
|
-
'Revised Footfall': item?._source?.status === 'closed'? item._source.footfallCount - ( item._source.duplicateACCount + item._source.employeeACCount + item._source.houseKeepingACCount +( item._source.junkACCount || 0 ) ) : item._source.footfallCount - ( item._source.duplicateCount + item._source.employeeCount + item._source.houseKeepingCount + ( item._source.junkCount || 0 ) ),
|
|
478
|
-
'Ticket%': item?._source?.status === 'closed'
|
|
1217
|
+
'Duplicates': item?._source?.status === 'closed' ? item._source.duplicateACCount : item._source.duplicateCount,
|
|
1218
|
+
'Employee/Staff': item?._source?.status === 'closed' ? item._source.employeeACCount : item._source.employeeCount,
|
|
1219
|
+
'HouseKeeping': item?._source?.status === 'closed' ? item._source.houseKeepingACCount : item._source.houseKeepingCount,
|
|
1220
|
+
'Junk': item?._source?.status === 'closed' ? ( item._source.junkACCount || 0 ) : ( item._source.junkCount || 0 ),
|
|
1221
|
+
'Revised Footfall': item?._source?.status === 'closed' ? item._source.footfallCount - ( item._source.duplicateACCount + item._source.employeeACCount + item._source.houseKeepingACCount + ( item._source.junkACCount || 0 ) ) : item._source.footfallCount - ( item._source.duplicateCount + item._source.employeeCount + item._source.houseKeepingCount + ( item._source.junkCount || 0 ) ),
|
|
1222
|
+
'Ticket%': item?._source?.status === 'closed' ? `${Math.round( ( ( item._source.duplicateACCount + item._source.employeeACCount + item._source.houseKeepingACCount + ( item?._source?.junkACCount || 0 ) ) / item._source.footfallCount ) * 100 ).toFixed( 0 )} %` : `${Math.round( ( ( item?._source?.duplicateCount + item?._source?.employeeCount + item?._source?.houseKeepingCount + ( item?._source?.junkCount || 0 ) ) / item?._source?.footfallCount ) * 100 ).toFixed( 0 )} %`,
|
|
479
1223
|
'Status': item._source.status,
|
|
480
1224
|
} );
|
|
481
1225
|
}
|
|
@@ -491,671 +1235,439 @@ export async function ticketList1( req, res ) {
|
|
|
491
1235
|
|
|
492
1236
|
export async function ticketList( req, res ) {
|
|
493
1237
|
try {
|
|
494
|
-
|
|
495
|
-
const inputData= req.query;
|
|
1238
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1239
|
+
const inputData = req.query;
|
|
496
1240
|
const userInfo = req.user;
|
|
497
|
-
const
|
|
1241
|
+
const limit = inputData?.limit || 10;
|
|
1242
|
+
const offset = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
|
|
1243
|
+
inputData.clientId = inputData?.clientId?.split( ',' ); // convert strig to array
|
|
498
1244
|
|
|
499
|
-
if ( req.user.userType =='tango' ) {
|
|
500
|
-
result =inputData.tangotype == 'store'?
|
|
501
1245
|
|
|
1246
|
+
const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
|
|
502
1247
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1248
|
+
const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
|
|
1249
|
+
|
|
1250
|
+
const searchQuery = {
|
|
1251
|
+
|
|
1252
|
+
size: limit, // or use parseInt(req.query.limit) for dynamic
|
|
1253
|
+
from: offset, // or use parseInt(req.query.offset) for dynamic
|
|
1254
|
+
sort: [ { 'createdAt': { order: 'desc' } } ],
|
|
1255
|
+
|
|
1256
|
+
query: {
|
|
1257
|
+
bool: {
|
|
1258
|
+
must: [
|
|
1259
|
+
{
|
|
1260
|
+
'range': {
|
|
1261
|
+
'dateString': {
|
|
1262
|
+
'gte': inputData.fromDate,
|
|
1263
|
+
'lte': inputData.toDate,
|
|
1264
|
+
'format': 'yyyy-MM-dd',
|
|
1265
|
+
},
|
|
1266
|
+
},
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
terms: {
|
|
1270
|
+
'clientId.keyword': Array.isArray( inputData.clientId ) ?
|
|
1271
|
+
inputData.clientId :
|
|
1272
|
+
[ inputData.clientId ],
|
|
1273
|
+
},
|
|
1274
|
+
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
531
1277
|
},
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
ticketId: 'TE_FDT_1763539990310',
|
|
562
|
-
storeId: '11-2000',
|
|
563
|
-
storeName: 'LKST2368',
|
|
564
|
-
ticketRaised: '2025-11-14',
|
|
565
|
-
issueDate: '2025-11-14',
|
|
566
|
-
dueDate: '2025-11-16',
|
|
567
|
-
footfall: 1300,
|
|
568
|
-
storeRevisedAccuracy: '96%',
|
|
569
|
-
reviewerRevisedAccuracy: '95%',
|
|
570
|
-
approverRevisedAccuracy: '96%',
|
|
571
|
-
tangoRevisedAccuracy: '95%',
|
|
572
|
-
status: 'Open',
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
ticketId: 'TE_FDT_1763539990311',
|
|
576
|
-
storeId: '11-2000',
|
|
577
|
-
storeName: 'LKST2368',
|
|
578
|
-
ticketRaised: '2025-11-15',
|
|
579
|
-
issueDate: '2025-11-15',
|
|
580
|
-
dueDate: '2025-11-17',
|
|
581
|
-
footfall: 1350,
|
|
582
|
-
storeRevisedAccuracy: '99%',
|
|
583
|
-
reviewerRevisedAccuracy: '98%',
|
|
584
|
-
approverRevisedAccuracy: '98%',
|
|
585
|
-
tangoRevisedAccuracy: '99%',
|
|
586
|
-
status: 'Closed',
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
ticketId: 'TE_FDT_1763539990312',
|
|
590
|
-
storeId: '11-2000',
|
|
591
|
-
storeName: 'LKST2368',
|
|
592
|
-
ticketRaised: '2025-11-16',
|
|
593
|
-
issueDate: '2025-11-16',
|
|
594
|
-
dueDate: '2025-11-18',
|
|
595
|
-
footfall: 1400,
|
|
596
|
-
storeRevisedAccuracy: '97%',
|
|
597
|
-
reviewerRevisedAccuracy: '96%',
|
|
598
|
-
approverRevisedAccuracy: '97%',
|
|
599
|
-
tangoRevisedAccuracy: '96%',
|
|
600
|
-
status: 'Closed',
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
ticketId: 'TE_FDT_1763539990313',
|
|
604
|
-
storeId: '11-10',
|
|
605
|
-
storeName: 'LKST80',
|
|
606
|
-
ticketRaised: '2023-11-14',
|
|
607
|
-
issueDate: '2023-11-14',
|
|
608
|
-
dueDate: '2023-11-16',
|
|
609
|
-
footfall: 900,
|
|
610
|
-
storeRevisedAccuracy: '95%',
|
|
611
|
-
reviewerRevisedAccuracy: '94%',
|
|
612
|
-
approverRevisedAccuracy: '95%',
|
|
613
|
-
tangoRevisedAccuracy: '95%',
|
|
614
|
-
status: 'Inprogress',
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
ticketId: 'TE_FDT_1763539990314',
|
|
618
|
-
storeId: '11-10',
|
|
619
|
-
storeName: 'LKST80',
|
|
620
|
-
ticketRaised: '2023-11-15',
|
|
621
|
-
issueDate: '2023-11-15',
|
|
622
|
-
dueDate: '2023-11-17',
|
|
623
|
-
footfall: 1000,
|
|
624
|
-
storeRevisedAccuracy: '98%',
|
|
625
|
-
reviewerRevisedAccuracy: '97%',
|
|
626
|
-
approverRevisedAccuracy: '97%',
|
|
627
|
-
tangoRevisedAccuracy: '98%',
|
|
628
|
-
status: 'Closed',
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
ticketId: 'TE_FDT_1763539990315',
|
|
632
|
-
storeId: '11-10',
|
|
633
|
-
storeName: 'LKST80',
|
|
634
|
-
ticketRaised: '2023-11-16',
|
|
635
|
-
issueDate: '2023-11-16',
|
|
636
|
-
dueDate: '2023-11-18',
|
|
637
|
-
footfall: 1050,
|
|
638
|
-
storeRevisedAccuracy: '96%',
|
|
639
|
-
reviewerRevisedAccuracy: '96%',
|
|
640
|
-
approverRevisedAccuracy: '96%',
|
|
641
|
-
tangoRevisedAccuracy: '97%',
|
|
642
|
-
status: 'Open',
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
ticketId: 'TE_FDT_1763539990316',
|
|
646
|
-
storeId: '11-10',
|
|
647
|
-
storeName: 'LKST80',
|
|
648
|
-
ticketRaised: '2023-11-17',
|
|
649
|
-
issueDate: '2023-11-17',
|
|
650
|
-
dueDate: '2023-11-19',
|
|
651
|
-
footfall: 1100,
|
|
652
|
-
storeRevisedAccuracy: '97%',
|
|
653
|
-
reviewerRevisedAccuracy: '97%',
|
|
654
|
-
approverRevisedAccuracy: '97%',
|
|
655
|
-
tangoRevisedAccuracy: '97%',
|
|
656
|
-
status: 'Closed',
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
ticketId: 'TE_FDT_1763539990317',
|
|
660
|
-
storeId: '12-1111',
|
|
661
|
-
storeName: 'LKST3030',
|
|
662
|
-
ticketRaised: '2025-10-12',
|
|
663
|
-
issueDate: '2025-10-12',
|
|
664
|
-
dueDate: '2025-10-14',
|
|
665
|
-
footfall: 1200,
|
|
666
|
-
storeRevisedAccuracy: '97%',
|
|
667
|
-
reviewerRevisedAccuracy: '97%',
|
|
668
|
-
approverRevisedAccuracy: '97%',
|
|
669
|
-
tangoRevisedAccuracy: '96%',
|
|
670
|
-
status: 'Open',
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
ticketId: 'TE_FDT_176353999018',
|
|
674
|
-
storeId: '12-1111',
|
|
675
|
-
storeName: 'LKST3030',
|
|
676
|
-
ticketRaised: '2025-10-13',
|
|
677
|
-
issueDate: '2025-10-13',
|
|
678
|
-
dueDate: '2025-10-15',
|
|
679
|
-
footfall: 1095,
|
|
680
|
-
storeRevisedAccuracy: '95%',
|
|
681
|
-
reviewerRevisedAccuracy: '96%',
|
|
682
|
-
approverRevisedAccuracy: '97%',
|
|
683
|
-
tangoRevisedAccuracy: '95%',
|
|
684
|
-
status: 'Open-Accuracy Issue',
|
|
685
|
-
},
|
|
686
|
-
{
|
|
687
|
-
ticketId: 'TE_FDT_1763539990319',
|
|
688
|
-
storeId: '12-1111',
|
|
689
|
-
storeName: 'LKST3030',
|
|
690
|
-
ticketRaised: '2025-10-14',
|
|
691
|
-
issueDate: '2025-10-14',
|
|
692
|
-
dueDate: '2025-10-16',
|
|
693
|
-
footfall: 987,
|
|
694
|
-
storeRevisedAccuracy: '98%',
|
|
695
|
-
reviewerRevisedAccuracy: '99%',
|
|
696
|
-
approverRevisedAccuracy: '99%',
|
|
697
|
-
tangoRevisedAccuracy: '99%',
|
|
698
|
-
status: 'closed-Accuracy Issue',
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
ticketId: 'TE_FDT_1763539990320',
|
|
702
|
-
storeId: '14-8002',
|
|
703
|
-
storeName: 'LKST4590',
|
|
704
|
-
ticketRaised: '2025-09-12',
|
|
705
|
-
issueDate: '2025-09-12',
|
|
706
|
-
dueDate: '2025-09-14',
|
|
707
|
-
footfall: 1080,
|
|
708
|
-
storeRevisedAccuracy: '98%',
|
|
709
|
-
reviewerRevisedAccuracy: '97%',
|
|
710
|
-
approverRevisedAccuracy: '99%',
|
|
711
|
-
tangoRevisedAccuracy: '99%',
|
|
712
|
-
status: 'Closed',
|
|
1278
|
+
},
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
if ( inputData.sortBy ) {
|
|
1282
|
+
let sortOrder = inputData.sortOrder === 1 ? 'asc' : 'desc';
|
|
1283
|
+
|
|
1284
|
+
// Remove default sort so we don't duplicate/conflict
|
|
1285
|
+
// INSERT_YOUR_CODE
|
|
1286
|
+
// If sortBy is present, check if the field needs ".keyword" (for string fields like storeName, storeId, ticketId)
|
|
1287
|
+
// This avoids OpenSearch errors about sorting on text fields.
|
|
1288
|
+
const stringKeywordFields = [ 'storeName', 'storeId', 'ticketId', 'status', 'type', 'clientId' ];
|
|
1289
|
+
let sortField = inputData.sortBy == 'footfall' ? 'footfallCount' : inputData.sortBy == 'issueDate' ? 'dateString' : inputData.sortBy;
|
|
1290
|
+
if ( stringKeywordFields.includes( sortField ) ) {
|
|
1291
|
+
sortField = `${sortField}.keyword`;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Remove default sort so we don't duplicate/conflict
|
|
1295
|
+
searchQuery.sort = [
|
|
1296
|
+
{ [sortField]: { order: sortOrder } },
|
|
1297
|
+
];
|
|
1298
|
+
}
|
|
1299
|
+
// Example: Filtering by storeId if present in the query
|
|
1300
|
+
if ( inputData.storeId ) {
|
|
1301
|
+
inputData.storeId = inputData?.storeId?.split( ',' );
|
|
1302
|
+
searchQuery.query.bool.must.push( {
|
|
1303
|
+
terms: {
|
|
1304
|
+
'storeId.keyword': Array.isArray( inputData.storeId ) ?
|
|
1305
|
+
inputData.storeId :
|
|
1306
|
+
[ inputData.storeId ],
|
|
713
1307
|
},
|
|
1308
|
+
} );
|
|
1309
|
+
}
|
|
1310
|
+
if ( inputData.status && inputData.status!=='' ) {
|
|
1311
|
+
inputData.status = inputData?.status?.split( ',' );
|
|
1312
|
+
if ( req.user.userType === 'tango' ) {
|
|
1313
|
+
searchQuery.query.bool.must.push( {
|
|
1314
|
+
terms: {
|
|
1315
|
+
'status': Array.isArray( inputData?.status ) ?
|
|
1316
|
+
inputData?.status :
|
|
1317
|
+
[ inputData?.status ],
|
|
1318
|
+
},
|
|
1319
|
+
} );
|
|
1320
|
+
} else if ( inputData?.permissionType === 'approve' ) {
|
|
1321
|
+
searchQuery.query.bool.must.push( {
|
|
1322
|
+
nested: {
|
|
1323
|
+
path: 'mappingInfo',
|
|
1324
|
+
query: {
|
|
1325
|
+
bool: {
|
|
1326
|
+
must: [
|
|
1327
|
+
{
|
|
1328
|
+
term: {
|
|
1329
|
+
'mappingInfo.type': 'approve',
|
|
1330
|
+
},
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
term: {
|
|
1334
|
+
'mappingInfo.status': inputData.status[0],
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
],
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1340
|
+
},
|
|
1341
|
+
} );
|
|
1342
|
+
} else if ( inputData?.permissionType === 'review' ) {
|
|
1343
|
+
searchQuery.query.bool.must.push( {
|
|
1344
|
+
nested: {
|
|
1345
|
+
path: 'mappingInfo',
|
|
1346
|
+
query: {
|
|
1347
|
+
bool: {
|
|
1348
|
+
must: [
|
|
1349
|
+
{
|
|
1350
|
+
term: {
|
|
1351
|
+
'mappingInfo.type': 'review',
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
term: {
|
|
1356
|
+
'mappingInfo.status': inputData.status[0],
|
|
1357
|
+
},
|
|
1358
|
+
},
|
|
1359
|
+
],
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1363
|
+
} );
|
|
1364
|
+
} else if ( ticketsFeature ) {
|
|
1365
|
+
searchQuery.query.bool.must.push( {
|
|
1366
|
+
nested: {
|
|
1367
|
+
path: 'mappingInfo',
|
|
1368
|
+
query: {
|
|
1369
|
+
bool: {
|
|
1370
|
+
must: [
|
|
1371
|
+
{
|
|
1372
|
+
term: {
|
|
1373
|
+
'mappingInfo.type': 'review',
|
|
1374
|
+
},
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
term: {
|
|
1378
|
+
'mappingInfo.status': inputData.status[0],
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
},
|
|
1383
|
+
},
|
|
1384
|
+
},
|
|
1385
|
+
} );
|
|
1386
|
+
} else if ( ticketsApproveFeature ) {
|
|
1387
|
+
searchQuery.query.bool.must.push( {
|
|
1388
|
+
nested: {
|
|
1389
|
+
path: 'mappingInfo',
|
|
1390
|
+
query: {
|
|
1391
|
+
bool: {
|
|
1392
|
+
must: [
|
|
1393
|
+
{
|
|
1394
|
+
term: {
|
|
1395
|
+
'mappingInfo.type': 'approve',
|
|
1396
|
+
},
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
term: {
|
|
1400
|
+
'mappingInfo.status': inputData.status[0],
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
],
|
|
1404
|
+
},
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
} );
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
714
1410
|
|
|
715
|
-
|
|
716
|
-
|
|
1411
|
+
if ( req?.user?.userType === 'tango' && inputData.tangoType !== 'internal' ) {
|
|
1412
|
+
searchQuery.query.bool.must.push(
|
|
1413
|
+
{
|
|
1414
|
+
term: {
|
|
1415
|
+
'type.keyword': 'store',
|
|
1416
|
+
},
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
nested: {
|
|
1420
|
+
path: 'mappingInfo',
|
|
1421
|
+
query: {
|
|
1422
|
+
bool: {
|
|
1423
|
+
must: [
|
|
1424
|
+
{
|
|
1425
|
+
term: {
|
|
1426
|
+
'mappingInfo.type': 'tangoreview',
|
|
1427
|
+
},
|
|
1428
|
+
},
|
|
1429
|
+
|
|
1430
|
+
],
|
|
1431
|
+
},
|
|
1432
|
+
},
|
|
1433
|
+
},
|
|
1434
|
+
|
|
1435
|
+
},
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
);
|
|
1439
|
+
} else if ( req?.user?.userType === 'client' && inputData.tangoType !== 'internal' ) {
|
|
1440
|
+
searchQuery.query.bool.must.push(
|
|
1441
|
+
{
|
|
1442
|
+
term: {
|
|
1443
|
+
'type.keyword': 'store',
|
|
1444
|
+
},
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
nested: {
|
|
1448
|
+
path: 'mappingInfo',
|
|
1449
|
+
query: {
|
|
1450
|
+
bool: {
|
|
1451
|
+
must: [
|
|
1452
|
+
{
|
|
1453
|
+
term: {
|
|
1454
|
+
'mappingInfo.type': ticketsFeature ? 'review' : ticketsApproveFeature ?
|
|
1455
|
+
'approve' : 'tagging',
|
|
1456
|
+
},
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1459
|
+
],
|
|
1460
|
+
},
|
|
1461
|
+
},
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
if ( inputData?.permissionType ) {
|
|
1469
|
+
searchQuery.query.bool.must.push(
|
|
1470
|
+
{
|
|
1471
|
+
nested: {
|
|
1472
|
+
path: 'mappingInfo',
|
|
1473
|
+
query: {
|
|
1474
|
+
bool: {
|
|
1475
|
+
must: [
|
|
1476
|
+
{
|
|
1477
|
+
term: {
|
|
1478
|
+
'mappingInfo.type': inputData?.permissionType == 'review' ? 'review' :
|
|
1479
|
+
'approve',
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
|
|
1483
|
+
],
|
|
1484
|
+
},
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
},
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
if ( inputData.searchValue && inputData.searchValue !== '' ) {
|
|
1492
|
+
searchQuery.query.bool['should'] = [];
|
|
1493
|
+
searchQuery.query.bool.should = [
|
|
717
1494
|
|
|
718
1495
|
{
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
footfall: 94,
|
|
725
|
-
type: 'store',
|
|
726
|
-
storeRevisedAccuracy: '95%',
|
|
727
|
-
reviewerRevisedAccuracy: '--',
|
|
728
|
-
approverRevisedAccuracy: '--',
|
|
729
|
-
tangoRevisedAccuracy: '--',
|
|
730
|
-
status: 'Open',
|
|
731
|
-
tangoStatus: 'Open',
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
ticketId: 'TE_FDT_1763539990307',
|
|
735
|
-
storeId: '11-1716',
|
|
736
|
-
storeName: 'LKST1916',
|
|
737
|
-
ticketRaised: '2025-11-13',
|
|
738
|
-
issueDate: '2025-11-13',
|
|
739
|
-
footfall: 1150,
|
|
740
|
-
type: 'store',
|
|
741
|
-
storeRevisedAccuracy: '99%',
|
|
742
|
-
reviewerRevisedAccuracy: '99%',
|
|
743
|
-
approverRevisedAccuracy: '98%',
|
|
744
|
-
tangoRevisedAccuracy: '97%',
|
|
745
|
-
status: 'Closed',
|
|
746
|
-
tangoStatus: 'open',
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
ticketId: 'TE_FDT_1763539990308',
|
|
750
|
-
storeId: '11-1716',
|
|
751
|
-
storeName: 'LKST1916',
|
|
752
|
-
ticketRaised: '2025-11-14',
|
|
753
|
-
issueDate: '2025-11-14',
|
|
754
|
-
footfall: 1100,
|
|
755
|
-
type: 'store',
|
|
756
|
-
storeRevisedAccuracy: '97%',
|
|
757
|
-
reviewerRevisedAccuracy: '96%',
|
|
758
|
-
approverRevisedAccuracy: '97%',
|
|
759
|
-
status: 'Closed',
|
|
760
|
-
tangoStatus: 'In-Progress',
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
ticketId: 'TE_FDT_1763539990309',
|
|
764
|
-
storeId: '11-2000',
|
|
765
|
-
storeName: 'LKST2368',
|
|
766
|
-
ticketRaised: '2025-11-13',
|
|
767
|
-
issueDate: '2025-11-13',
|
|
768
|
-
footfall: 1280,
|
|
769
|
-
type: 'internal',
|
|
770
|
-
storeRevisedAccuracy: '98%',
|
|
771
|
-
reviewerRevisedAccuracy: '98%',
|
|
772
|
-
approverRevisedAccuracy: '97%',
|
|
773
|
-
tangoRevisedAccuracy: '97%',
|
|
774
|
-
status: 'Closed-Accuracy Issue',
|
|
775
|
-
tangoStatus: 'Open',
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
ticketId: 'TE_FDT_1763539990310',
|
|
779
|
-
storeId: '11-2000',
|
|
780
|
-
storeName: 'LKST2368',
|
|
781
|
-
ticketRaised: '2025-11-14',
|
|
782
|
-
issueDate: '2025-11-14',
|
|
783
|
-
footfall: 300,
|
|
784
|
-
type: 'store',
|
|
785
|
-
storeRevisedAccuracy: '96%',
|
|
786
|
-
reviewerRevisedAccuracy: '95%',
|
|
787
|
-
approverRevisedAccuracy: '96%',
|
|
788
|
-
tangoRevisedAccuracy: '95%',
|
|
789
|
-
status: 'Closed',
|
|
790
|
-
tangoStatus: 'In-Progress',
|
|
791
|
-
},
|
|
792
|
-
{
|
|
793
|
-
ticketId: 'TE_FDT_1763539990311',
|
|
794
|
-
storeId: '11-2000',
|
|
795
|
-
storeName: 'LKST2368',
|
|
796
|
-
ticketRaised: '2025-11-15',
|
|
797
|
-
issueDate: '2025-11-15',
|
|
798
|
-
footfall: 350,
|
|
799
|
-
type: 'internal',
|
|
800
|
-
storeRevisedAccuracy: '99%',
|
|
801
|
-
reviewerRevisedAccuracy: '98%',
|
|
802
|
-
approverRevisedAccuracy: '98%',
|
|
803
|
-
tangoRevisedAccuracy: '99%',
|
|
804
|
-
status: 'Closed',
|
|
805
|
-
tangoStatus: 'In-Progress',
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
ticketId: 'TE_FDT_1763539990312',
|
|
809
|
-
storeId: '11-2000',
|
|
810
|
-
storeName: 'LKST2368',
|
|
811
|
-
ticketRaised: '2025-11-16',
|
|
812
|
-
issueDate: '2025-11-16',
|
|
813
|
-
footfall: 400,
|
|
814
|
-
type: 'internal',
|
|
815
|
-
storeRevisedAccuracy: '97%',
|
|
816
|
-
reviewerRevisedAccuracy: '96%',
|
|
817
|
-
approverRevisedAccuracy: '97%',
|
|
818
|
-
tangoRevisedAccuracy: '96%',
|
|
819
|
-
status: 'Open-Accuracy Issue',
|
|
820
|
-
tangoStatus: 'Open',
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
ticketId: 'TE_FDT_1763539990313',
|
|
824
|
-
storeId: '11-10',
|
|
825
|
-
storeName: 'LKST80',
|
|
826
|
-
ticketRaised: '2023-11-14',
|
|
827
|
-
issueDate: '2023-11-14',
|
|
828
|
-
footfall: 900,
|
|
829
|
-
type: 'internal',
|
|
830
|
-
storeRevisedAccuracy: '95%',
|
|
831
|
-
reviewerRevisedAccuracy: '94%',
|
|
832
|
-
approverRevisedAccuracy: '95%',
|
|
833
|
-
tangoRevisedAccuracy: '95%',
|
|
834
|
-
status: 'Closed',
|
|
835
|
-
tangoStatus: 'Closed',
|
|
1496
|
+
'wildcard': {
|
|
1497
|
+
'storeName.keyword': {
|
|
1498
|
+
'value': `*${inputData.searchValue}*`,
|
|
1499
|
+
},
|
|
1500
|
+
},
|
|
836
1501
|
},
|
|
837
1502
|
{
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
type: 'store',
|
|
844
|
-
footfall: 100,
|
|
845
|
-
storeRevisedAccuracy: '98%',
|
|
846
|
-
reviewerRevisedAccuracy: '97%',
|
|
847
|
-
approverRevisedAccuracy: '97%',
|
|
848
|
-
tangoRevisedAccuracy: '98%',
|
|
849
|
-
status: 'closed-Accuracy Issue',
|
|
850
|
-
tangoStatus: 'Closed',
|
|
1503
|
+
'wildcard': {
|
|
1504
|
+
'storeId.keyword': {
|
|
1505
|
+
'value': `*${inputData.searchValue}*`,
|
|
1506
|
+
},
|
|
1507
|
+
},
|
|
851
1508
|
},
|
|
852
1509
|
{
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
type: 'internal',
|
|
859
|
-
footfall: 80,
|
|
860
|
-
storeRevisedAccuracy: '98%',
|
|
861
|
-
reviewerRevisedAccuracy: '97%',
|
|
862
|
-
approverRevisedAccuracy: '99%',
|
|
863
|
-
tangoRevisedAccuracy: '99%',
|
|
864
|
-
status: 'Closed-Accuracy Issue',
|
|
865
|
-
tangoStatus: 'Closed',
|
|
1510
|
+
'wildcard': {
|
|
1511
|
+
'ticketId.keyword': {
|
|
1512
|
+
'value': `*${inputData.searchValue}*`,
|
|
1513
|
+
},
|
|
1514
|
+
},
|
|
866
1515
|
},
|
|
1516
|
+
|
|
1517
|
+
|
|
867
1518
|
];
|
|
1519
|
+
searchQuery.query.bool['minimum_should_match'] = 1;
|
|
1520
|
+
}
|
|
1521
|
+
// You can add more filters as needed
|
|
1522
|
+
const searchResult = await getOpenSearchData( openSearch.footfallDirectory, searchQuery );
|
|
1523
|
+
const count = searchResult?.body?.hits?.total?.value || 0;
|
|
1524
|
+
|
|
1525
|
+
if ( count === 0 ) {
|
|
1526
|
+
return res.sendError( 'no data found', 204 );
|
|
1527
|
+
}
|
|
1528
|
+
const ticketListData = searchResult?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
|
|
1529
|
+
|
|
1530
|
+
let temp = [];
|
|
1531
|
+
if ( req.user.userType === 'tango' ) {
|
|
1532
|
+
if ( inputData.tangoType === 'store' ) {
|
|
1533
|
+
for ( let item of ticketListData ) {
|
|
1534
|
+
temp.push( {
|
|
1535
|
+
|
|
1536
|
+
ticketId: item?.ticketId,
|
|
1537
|
+
storeId: item?.storeId,
|
|
1538
|
+
storeName: item?.storeName,
|
|
1539
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1540
|
+
issueDate: item?.dateString,
|
|
1541
|
+
dueDate: '',
|
|
1542
|
+
footfall: item?.footfallCount,
|
|
1543
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1544
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1545
|
+
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
1546
|
+
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1547
|
+
status: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
|
|
1548
|
+
|
|
1549
|
+
} );
|
|
1550
|
+
}
|
|
1551
|
+
} else {
|
|
1552
|
+
for ( let item of ticketListData ) {
|
|
1553
|
+
temp.push( {
|
|
1554
|
+
|
|
1555
|
+
ticketId: item?.ticketId,
|
|
1556
|
+
storeId: item?.storeId,
|
|
1557
|
+
storeName: item?.storeName,
|
|
1558
|
+
|
|
1559
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1560
|
+
issueDate: item?.dateString,
|
|
1561
|
+
footfall: item?.footfallCount,
|
|
1562
|
+
dueDate: '',
|
|
1563
|
+
type: item?.type || 'store',
|
|
1564
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1565
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1566
|
+
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
1567
|
+
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1568
|
+
status: item?.status,
|
|
1569
|
+
tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
|
|
1570
|
+
|
|
1571
|
+
} );
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
868
1574
|
} else {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
ticketId: 'TE_FDT_1763539990328',
|
|
963
|
-
storeId: '11-10',
|
|
964
|
-
storeName: 'LKST80',
|
|
965
|
-
ticketRaised: '2025-11-12',
|
|
966
|
-
issueDate: '2025-11-11',
|
|
967
|
-
dueDate: '2025-11-14',
|
|
968
|
-
footfall: 170,
|
|
969
|
-
storeRevisedAccuracy: '90%',
|
|
970
|
-
reviewerRevisedAccuracy: '90%',
|
|
971
|
-
approverRevisedAccuracy: '90%',
|
|
972
|
-
tangoRevisedAccuracy: '--',
|
|
973
|
-
status: 'Expired',
|
|
974
|
-
approvedBy: 'mu_mu@yopmail.com',
|
|
975
|
-
},
|
|
976
|
-
{
|
|
977
|
-
ticketId: 'TE_FDT_1763539990330',
|
|
978
|
-
storeId: '11-10',
|
|
979
|
-
storeName: 'LKST80',
|
|
980
|
-
ticketRaised: '2025-11-18',
|
|
981
|
-
issueDate: '2025-11-15',
|
|
982
|
-
dueDate: 'Due Today',
|
|
983
|
-
footfall: 230,
|
|
984
|
-
storeRevisedAccuracy: '90%',
|
|
985
|
-
reviewerRevisedAccuracy: '90%',
|
|
986
|
-
approverRevisedAccuracy: '90%',
|
|
987
|
-
tangoRevisedAccuracy: '90%',
|
|
988
|
-
status: 'Closed',
|
|
989
|
-
approvedBy: 'mu_mu@yopmail.com',
|
|
990
|
-
},
|
|
991
|
-
{
|
|
992
|
-
ticketId: 'TE_FDT_1763539990332',
|
|
993
|
-
storeId: '11-10',
|
|
994
|
-
storeName: 'LKST80',
|
|
995
|
-
ticketRaised: '2025-11-17',
|
|
996
|
-
issueDate: '2025-11-16',
|
|
997
|
-
dueDate: '2025-11-20',
|
|
998
|
-
footfall: 812,
|
|
999
|
-
storeRevisedAccuracy: '80%',
|
|
1000
|
-
reviewerRevisedAccuracy: '80%',
|
|
1001
|
-
approverRevisedAccuracy: '80%',
|
|
1002
|
-
tangoRevisedAccuracy: '--',
|
|
1003
|
-
status: 'Open',
|
|
1004
|
-
approvedBy: 'mu_mu@yopmail.com',
|
|
1005
|
-
},
|
|
1006
|
-
{
|
|
1007
|
-
ticketId: 'TE_FDT_176353999034',
|
|
1008
|
-
storeId: '11-2000',
|
|
1009
|
-
storeName: 'LKST2368',
|
|
1010
|
-
ticketRaised: '2025-11-15',
|
|
1011
|
-
issueDate: '2025-11-14',
|
|
1012
|
-
dueDate: '2025-11-19',
|
|
1013
|
-
footfall: '',
|
|
1014
|
-
storeRevisedAccuracy: '--',
|
|
1015
|
-
reviewerRevisedAccuracy: '--',
|
|
1016
|
-
approverRevisedAccuracy: '--',
|
|
1017
|
-
tangoRevisedAccuracy: '--',
|
|
1018
|
-
status: 'Open',
|
|
1019
|
-
approvedBy: '',
|
|
1020
|
-
},
|
|
1021
|
-
] :
|
|
1022
|
-
req.user.role === 'user'? 'NA':
|
|
1023
|
-
ticketsFeature?
|
|
1024
|
-
[
|
|
1025
|
-
{
|
|
1026
|
-
ticketId: 'TE_FDT_1763860421803',
|
|
1027
|
-
storeId: '11-1716',
|
|
1028
|
-
storeName: 'LKST1916',
|
|
1029
|
-
ticketRaised: '2025-11-21',
|
|
1030
|
-
issueDate: '2025-11-20',
|
|
1031
|
-
dueDate: 'Due Today',
|
|
1032
|
-
footfall: 90,
|
|
1033
|
-
storeRevisedAccuracy: '90%',
|
|
1034
|
-
reviewerRevisedAccuracy: '0%',
|
|
1035
|
-
status: 'Open',
|
|
1036
|
-
ReviewedBy: '',
|
|
1037
|
-
},
|
|
1038
|
-
{
|
|
1039
|
-
ticketId: 'TE_FDT_1763539990346',
|
|
1040
|
-
storeId: '11-2000',
|
|
1041
|
-
storeName: 'LKST2368',
|
|
1042
|
-
ticketRaised: '2025-11-21',
|
|
1043
|
-
issueDate: '2025-11-20',
|
|
1044
|
-
dueDate: '2025-11-26',
|
|
1045
|
-
footfall: 90,
|
|
1046
|
-
storeRevisedAccuracy: '90%',
|
|
1047
|
-
reviewerRevisedAccuracy: '--',
|
|
1048
|
-
status: 'In-Progress',
|
|
1049
|
-
ReviewedBy: 'mu_mu@yopmail.com',
|
|
1050
|
-
},
|
|
1051
|
-
{
|
|
1052
|
-
ticketId: 'TE_FDT_176353999048',
|
|
1053
|
-
storeId: '11-2000',
|
|
1054
|
-
storeName: 'LKST2368',
|
|
1055
|
-
ticketRaised: '2025-11-16',
|
|
1056
|
-
issueDate: '2025-11-15',
|
|
1057
|
-
dueDate: '2025-11-19',
|
|
1058
|
-
footfall: 100,
|
|
1059
|
-
storeRevisedAccuracy: '90%',
|
|
1060
|
-
reviewerRevisedAccuracy: '90%',
|
|
1061
|
-
status: 'Closed',
|
|
1062
|
-
ReviewedBy: 'ayyanar@yopmail.com',
|
|
1063
|
-
},
|
|
1064
|
-
{
|
|
1065
|
-
ticketId: 'TE_FDT_176353999048',
|
|
1066
|
-
storeId: '11-10',
|
|
1067
|
-
storeName: 'LKST80',
|
|
1068
|
-
ticketRaised: '2025-11-08',
|
|
1069
|
-
issueDate: '2025-11-06',
|
|
1070
|
-
dueDate: '2025-11-09',
|
|
1071
|
-
footfall: 120,
|
|
1072
|
-
storeRevisedAccuracy: '90%',
|
|
1073
|
-
reviewerRevisedAccuracy: '0%',
|
|
1074
|
-
status: 'Expired',
|
|
1075
|
-
ReviewedBy: '',
|
|
1076
|
-
},
|
|
1077
|
-
{
|
|
1078
|
-
ticketId: 'TE_FDT_1763539990341',
|
|
1079
|
-
storeId: '11-2000',
|
|
1080
|
-
storeName: 'LKST2368',
|
|
1081
|
-
ticketRaised: '2025-11-6',
|
|
1082
|
-
issueDate: '2025-11-15',
|
|
1083
|
-
dueDate: 'Due Today',
|
|
1084
|
-
footfall: 510,
|
|
1085
|
-
storeRevisedAccuracy: '90%',
|
|
1086
|
-
reviewerRevisedAccuracy: '--',
|
|
1087
|
-
status: 'Open',
|
|
1088
|
-
ReviewedBy: '',
|
|
1089
|
-
},
|
|
1090
|
-
{
|
|
1091
|
-
ticketId: 'TE_FDT_1763539990340',
|
|
1092
|
-
storeId: '11-10',
|
|
1093
|
-
storeName: 'LKST80',
|
|
1094
|
-
ticketRaised: '2025-11-14',
|
|
1095
|
-
issueDate: '2025-11-12',
|
|
1096
|
-
dueDate: '2025-11-15',
|
|
1097
|
-
footfall: 100,
|
|
1098
|
-
storeRevisedAccuracy: '0%',
|
|
1099
|
-
reviewerRevisedAccuracy: '0%',
|
|
1100
|
-
status: 'Expired',
|
|
1101
|
-
ReviewedBy: '',
|
|
1102
|
-
},
|
|
1103
|
-
{
|
|
1104
|
-
ticketId: 'TE_FDT_1763539990339',
|
|
1105
|
-
storeId: '11-2000',
|
|
1106
|
-
storeName: 'LKST2368',
|
|
1107
|
-
ticketRaised: '2025-11-16',
|
|
1108
|
-
issueDate: '2025-11-15',
|
|
1109
|
-
dueDate: '2025-11-17',
|
|
1110
|
-
footfall: 140,
|
|
1111
|
-
storeRevisedAccuracy: '90%',
|
|
1112
|
-
reviewerRevisedAccuracy: '90%',
|
|
1113
|
-
status: 'Closed',
|
|
1114
|
-
ReviewedBy: 'sornanithya@yopmail.com',
|
|
1115
|
-
},
|
|
1116
|
-
{
|
|
1117
|
-
ticketId: 'TE_FDT_1763539990337',
|
|
1118
|
-
storeId: '11-10',
|
|
1119
|
-
storeName: 'LKST80',
|
|
1120
|
-
ticketRaised: '2025-11-16',
|
|
1121
|
-
issueDate: '2025-11-15',
|
|
1122
|
-
dueDate: '2025-11-18',
|
|
1123
|
-
footfall: '',
|
|
1124
|
-
storeRevisedAccuracy: '90%',
|
|
1125
|
-
reviewerRevisedAccuracy: '--',
|
|
1126
|
-
status: 'Expired',
|
|
1127
|
-
ReviewedBy: '',
|
|
1128
|
-
},
|
|
1129
|
-
{
|
|
1130
|
-
ticketId: 'TE_FDT_1763539990338',
|
|
1131
|
-
storeId: '11-2000',
|
|
1132
|
-
storeName: 'LKST2368',
|
|
1133
|
-
ticketRaised: '2025-11-17',
|
|
1134
|
-
issueDate: '2025-11-16',
|
|
1135
|
-
dueDate: 'Due today',
|
|
1136
|
-
footfall: 110,
|
|
1137
|
-
storeRevisedAccuracy: '90%',
|
|
1138
|
-
reviewerRevisedAccuracy: '--',
|
|
1139
|
-
status: 'In-Progress',
|
|
1140
|
-
ReviewedBy: 'vinoth@yopmail.com',
|
|
1141
|
-
},
|
|
1142
|
-
{
|
|
1143
|
-
ticketId: 'TE_FDT_1763539990335',
|
|
1144
|
-
storeId: '11-2000',
|
|
1145
|
-
storeName: 'LKST2368',
|
|
1146
|
-
ticketRaised: '2025-11-17',
|
|
1147
|
-
issueDate: '2025-11-16',
|
|
1148
|
-
dueDate: 'Due today',
|
|
1149
|
-
footfall: 100,
|
|
1150
|
-
storeRevisedAccuracy: '90%',
|
|
1151
|
-
reviewerRevisedAccuracy: '0%',
|
|
1152
|
-
status: 'In-Progress',
|
|
1153
|
-
ReviewedBy: 'sornanithya@yopmail.com',
|
|
1154
|
-
},
|
|
1155
|
-
]: 'NA';
|
|
1575
|
+
if ( inputData?.permissionType === 'approve' ) {
|
|
1576
|
+
for ( let item of ticketListData ) {
|
|
1577
|
+
temp.push( {
|
|
1578
|
+
|
|
1579
|
+
ticketId: item?.ticketId,
|
|
1580
|
+
storeId: item?.storeId,
|
|
1581
|
+
storeName: item?.storeName,
|
|
1582
|
+
|
|
1583
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1584
|
+
issueDate: item?.dateString,
|
|
1585
|
+
dueDate: '',
|
|
1586
|
+
footfall: item?.footfallCount,
|
|
1587
|
+
|
|
1588
|
+
type: item.type || 'store',
|
|
1589
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1590
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1591
|
+
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
1592
|
+
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1593
|
+
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
1594
|
+
tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1595
|
+
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
1596
|
+
|
|
1597
|
+
} );
|
|
1598
|
+
}
|
|
1599
|
+
} else if ( inputData?.permissionType === 'review' ) {
|
|
1600
|
+
for ( let item of ticketListData ) {
|
|
1601
|
+
temp.push( {
|
|
1602
|
+
|
|
1603
|
+
ticketId: item?.ticketId,
|
|
1604
|
+
storeId: item?.storeId,
|
|
1605
|
+
storeName: item?.storeName,
|
|
1606
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1607
|
+
issueDate: item?.dateString,
|
|
1608
|
+
footfall: item?.footfallCount,
|
|
1609
|
+
dueDate: '',
|
|
1610
|
+
type: item.type || 'store',
|
|
1611
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1612
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1613
|
+
|
|
1614
|
+
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
1615
|
+
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
1616
|
+
|
|
1617
|
+
} );
|
|
1618
|
+
}
|
|
1619
|
+
} else if ( req.user.role === 'user' ) {
|
|
1620
|
+
temp = [];
|
|
1621
|
+
} else if ( ticketsFeature ) {
|
|
1622
|
+
for ( let item of ticketListData ) {
|
|
1623
|
+
temp.push( {
|
|
1624
|
+
|
|
1625
|
+
ticketId: item?.ticketId,
|
|
1626
|
+
storeId: item?.storeId,
|
|
1627
|
+
storeName: item?.storeName,
|
|
1628
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1629
|
+
issueDate: item?.dateString,
|
|
1630
|
+
footfall: item?.footfallCount,
|
|
1631
|
+
dueDate: '',
|
|
1632
|
+
type: item.type || 'store',
|
|
1633
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1634
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1635
|
+
|
|
1636
|
+
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
1637
|
+
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
1638
|
+
|
|
1639
|
+
} );
|
|
1640
|
+
}
|
|
1641
|
+
} else if ( ticketsApproveFeature ) {
|
|
1642
|
+
for ( let item of ticketListData ) {
|
|
1643
|
+
temp.push( {
|
|
1644
|
+
|
|
1645
|
+
ticketId: item?.ticketId,
|
|
1646
|
+
storeId: item?.storeId,
|
|
1647
|
+
storeName: item?.storeName,
|
|
1648
|
+
|
|
1649
|
+
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
|
|
1650
|
+
issueDate: item?.dateString,
|
|
1651
|
+
dueDate: '',
|
|
1652
|
+
footfall: item?.footfallCount,
|
|
1653
|
+
|
|
1654
|
+
type: item.type || 'store',
|
|
1655
|
+
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
1656
|
+
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
1657
|
+
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
1658
|
+
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1659
|
+
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
1660
|
+
tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
1661
|
+
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
1662
|
+
|
|
1663
|
+
} );
|
|
1664
|
+
}
|
|
1665
|
+
} else {
|
|
1666
|
+
temp = [];
|
|
1667
|
+
}
|
|
1156
1668
|
}
|
|
1157
1669
|
|
|
1158
|
-
return res.sendSuccess( { result:
|
|
1670
|
+
return res.sendSuccess( { result: temp, count: count } );
|
|
1159
1671
|
} catch ( error ) {
|
|
1160
1672
|
const err = error.message || 'Internal Server Error';
|
|
1161
1673
|
logger.error( { error: error, messgage: req.query } );
|
|
@@ -1167,149 +1679,19 @@ export async function getTickets( req, res ) {
|
|
|
1167
1679
|
try {
|
|
1168
1680
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1169
1681
|
const inputData = req.query;
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
logger.info( { inputData: inputData, limit: limit, skip: skip } );
|
|
1174
|
-
let source = [ 'storeId', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'duplicateCount', 'junkCount', 'junkACCount', 'comments', 'employee', 'houseKeeping', 'junk', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'email', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail' ];
|
|
1682
|
+
|
|
1683
|
+
|
|
1684
|
+
let source = [ 'storeId', 'type', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'duplicateCount', 'junkCount', 'junkACCount', 'comments', 'employee', 'houseKeeping', 'junk', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'email', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail', 'type' ];
|
|
1175
1685
|
let filter = [
|
|
1176
1686
|
|
|
1177
1687
|
{
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
gte: inputData.fromDate,
|
|
1181
|
-
lte: inputData.toDate,
|
|
1182
|
-
format: 'yyyy-MM-dd',
|
|
1183
|
-
},
|
|
1688
|
+
term: {
|
|
1689
|
+
'ticketId.keyword': inputData.ticketId,
|
|
1184
1690
|
},
|
|
1185
1691
|
},
|
|
1186
1692
|
];
|
|
1187
|
-
if ( inputData?.storeId ) {
|
|
1188
|
-
filter.push(
|
|
1189
|
-
{
|
|
1190
|
-
terms: {
|
|
1191
|
-
'storeId.keyword': Array.isArray( inputData.storeId ) ?
|
|
1192
|
-
inputData.storeId :
|
|
1193
|
-
inputData.storeId,
|
|
1194
|
-
},
|
|
1195
|
-
},
|
|
1196
|
-
);
|
|
1197
|
-
}
|
|
1198
|
-
if ( inputData?.dateString ) {
|
|
1199
|
-
filter.push(
|
|
1200
|
-
{
|
|
1201
|
-
term: {
|
|
1202
|
-
'dateString': inputData.dateString,
|
|
1203
|
-
},
|
|
1204
|
-
},
|
|
1205
|
-
);
|
|
1206
|
-
}
|
|
1207
|
-
if ( inputData.status ) {
|
|
1208
|
-
filter.push(
|
|
1209
|
-
{
|
|
1210
|
-
term: {
|
|
1211
|
-
'status.keyword': inputData.status,
|
|
1212
|
-
},
|
|
1213
|
-
},
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
if (
|
|
1217
|
-
inputData.revopsType
|
|
1218
|
-
) {
|
|
1219
|
-
inputData.revopsType === 'employee' ?
|
|
1220
|
-
filter.push( {
|
|
1221
|
-
range: {
|
|
1222
|
-
employeeCount: {
|
|
1223
|
-
gt: 0,
|
|
1224
|
-
},
|
|
1225
|
-
},
|
|
1226
|
-
} ) :
|
|
1227
|
-
inputData.revopsType === 'houseKeeping' ?
|
|
1228
|
-
filter.push( {
|
|
1229
|
-
range: {
|
|
1230
|
-
houseKeepingCount: {
|
|
1231
|
-
gt: 0,
|
|
1232
|
-
},
|
|
1233
|
-
},
|
|
1234
|
-
} ) :
|
|
1235
|
-
inputData.revopsType === 'junk' ?
|
|
1236
|
-
filter.push( {
|
|
1237
|
-
range: {
|
|
1238
|
-
junkCount: {
|
|
1239
|
-
gt: 0,
|
|
1240
|
-
},
|
|
1241
|
-
},
|
|
1242
|
-
} ) :
|
|
1243
|
-
filter.push( {
|
|
1244
|
-
range: {
|
|
1245
|
-
duplicateCount: {
|
|
1246
|
-
gt: 0,
|
|
1247
|
-
},
|
|
1248
|
-
},
|
|
1249
|
-
} );
|
|
1250
|
-
source = inputData.revopsType == 'employee' ? [ 'storeId', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'employeeCount', 'comments', 'employee', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'email', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'junkCount', 'junkStatus', 'junkACCount', 'approverRole', 'approverUserName', 'approverEmail' ] :
|
|
1251
|
-
inputData.revopsType == 'houseKeeping' ? [ 'storeId', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'houseKeepingCount', 'comments', 'houseKeeping', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'email', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'junkCount', 'junkStatus', 'junkACCount', 'approverRole', 'approverUserName', 'approverEmail' ] :
|
|
1252
|
-
inputData.revopsType == 'duplicateImages' ? [ 'storeId', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'duplicateCount', 'comments', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'email', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'junkCount', 'junkStatus', 'junkACCount', 'approverRole', 'approverUserName', 'approverEmail' ] :
|
|
1253
|
-
inputData.revopsType == 'junk' ? [ 'storeId', 'dateString', 'ticketName', 'revicedFootfall', 'revicedPerc', 'mappingInfo', 'footfallCount', 'duplicateCount', 'comments', 'junk', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'email', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'junkCount', 'junkACCount', 'junkStatus', 'approverRole', 'approverUserName', 'approverEmail' ] : [];
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
if ( inputData.action ) {
|
|
1257
|
-
filter.push( {
|
|
1258
|
-
bool: {
|
|
1259
|
-
should: [
|
|
1260
|
-
{
|
|
1261
|
-
constant_score: {
|
|
1262
|
-
filter: { term: { 'houseKeepingStatus.keyword': inputData.action } },
|
|
1263
|
-
boost: 1,
|
|
1264
|
-
_name: 'matched_housekeeping',
|
|
1265
|
-
},
|
|
1266
|
-
},
|
|
1267
|
-
{
|
|
1268
|
-
constant_score: {
|
|
1269
|
-
filter: { term: { 'employeeStatus.keyword': inputData.action } },
|
|
1270
|
-
boost: 1,
|
|
1271
|
-
_name: 'matched_employee',
|
|
1272
|
-
},
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
constant_score: {
|
|
1276
|
-
filter: { term: { 'duplicateStatus.keyword': inputData.action } },
|
|
1277
|
-
boost: 1,
|
|
1278
|
-
_name: 'matched_duplicate',
|
|
1279
|
-
},
|
|
1280
|
-
},
|
|
1281
|
-
{
|
|
1282
|
-
constant_score: {
|
|
1283
|
-
filter: { term: { 'junkStatus.keyword': inputData.action } },
|
|
1284
|
-
boost: 1,
|
|
1285
|
-
_name: 'matched_junk',
|
|
1286
|
-
},
|
|
1287
|
-
},
|
|
1288
|
-
],
|
|
1289
|
-
minimum_should_match: 1,
|
|
1290
|
-
},
|
|
1291
|
-
} );
|
|
1292
|
-
}
|
|
1293
1693
|
|
|
1294
1694
|
|
|
1295
|
-
let getRevCount = {};
|
|
1296
|
-
if ( inputData.revopsType ) {
|
|
1297
|
-
getRevCount = {
|
|
1298
|
-
size: 0,
|
|
1299
|
-
query: {
|
|
1300
|
-
bool: {
|
|
1301
|
-
filter: filter,
|
|
1302
|
-
},
|
|
1303
|
-
},
|
|
1304
|
-
aggs: {
|
|
1305
|
-
totalCount: {
|
|
1306
|
-
sum: {
|
|
1307
|
-
field: inputData.revopsType == 'employee' ? 'employeeCount' : inputData.revopsType == 'houseKeeping' ? 'houseKeepingCount' :inputData.revopsType == 'junk' ? 'junkCount': 'duplicateCount',
|
|
1308
|
-
},
|
|
1309
|
-
},
|
|
1310
|
-
},
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
1695
|
const getCount = {
|
|
1314
1696
|
query: {
|
|
1315
1697
|
bool: {
|
|
@@ -1320,20 +1702,14 @@ export async function getTickets( req, res ) {
|
|
|
1320
1702
|
|
|
1321
1703
|
|
|
1322
1704
|
const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if ( inputData.storeId?.length > 0 ) {
|
|
1328
|
-
const getStoreName = await findOneStore( { storeId: inputData.storeId }, { storeName: 1 } );
|
|
1329
|
-
return res.sendError( `No Pending items in the selected dates for the store ${getStoreName?.storeName || ''}`, 400 );
|
|
1330
|
-
}
|
|
1331
|
-
return res.sendError( 'No data found', 204 );
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
if ( !geteDataCount ) {
|
|
1708
|
+
return res.sendError( `No Pending items in the selected dates for the store`, 400 );
|
|
1332
1709
|
}
|
|
1333
1710
|
|
|
1334
1711
|
const getQuery = {
|
|
1335
|
-
size:
|
|
1336
|
-
from: skip,
|
|
1712
|
+
size: 1,
|
|
1337
1713
|
query: {
|
|
1338
1714
|
bool: {
|
|
1339
1715
|
filter: filter,
|
|
@@ -1343,14 +1719,9 @@ export async function getTickets( req, res ) {
|
|
|
1343
1719
|
};
|
|
1344
1720
|
|
|
1345
1721
|
const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
|
|
1346
|
-
|
|
1347
1722
|
const response = getData?.body?.hits?.hits;
|
|
1348
1723
|
if ( !response || response.length == 0 ) {
|
|
1349
|
-
|
|
1350
|
-
const getStoreName = await findOneStore( { storeId: inputData.storeId }, { storeName: 1 } );
|
|
1351
|
-
return res.sendError( `No Pending items in the selected dates for the store ${getStoreName?.storeName || ''}`, 400 );
|
|
1352
|
-
}
|
|
1353
|
-
return res.sendError( 'No data', 204 );
|
|
1724
|
+
return res.sendError( `No Pending items in the selected dates for the store`, 400 );
|
|
1354
1725
|
}
|
|
1355
1726
|
let temp = [];
|
|
1356
1727
|
if ( inputData?.action ) {
|
|
@@ -1388,56 +1759,307 @@ export async function getTickets( req, res ) {
|
|
|
1388
1759
|
approverUserName: hit?._source?.approverUserName,
|
|
1389
1760
|
approverEmail: hit?._source?.approverEmail,
|
|
1390
1761
|
approverRole: hit?._source?.approverRole,
|
|
1762
|
+
type: hit?._source?.type,
|
|
1391
1763
|
};
|
|
1392
1764
|
let result;
|
|
1393
1765
|
|
|
1766
|
+
|
|
1394
1767
|
const matched = hit.matched_queries;
|
|
1395
1768
|
// Add only matched data array
|
|
1396
|
-
if ( matched.includes( 'matched_employee' )&& ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
|
|
1769
|
+
if ( matched.includes( 'matched_employee' ) && ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
|
|
1397
1770
|
result = defaultData;
|
|
1398
1771
|
result.employee = hit?._source?.employee;
|
|
1399
1772
|
// result.type = 'employee';
|
|
1400
1773
|
result.matched = matched;
|
|
1401
1774
|
}
|
|
1402
|
-
if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
|
|
1775
|
+
if ( matched.includes( 'matched_housekeeping' ) && ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
|
|
1403
1776
|
result = defaultData;
|
|
1404
1777
|
result.houseKeeping = hit?._source?.houseKeeping;
|
|
1405
1778
|
result.matched = matched;
|
|
1406
1779
|
}
|
|
1407
|
-
if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
|
|
1780
|
+
if ( matched.includes( 'matched_duplicate' ) && ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
|
|
1408
1781
|
result = defaultData;
|
|
1409
1782
|
result.duplicateImages = hit?._source?.duplicateImages;
|
|
1410
1783
|
result.matched = matched;
|
|
1411
1784
|
}
|
|
1412
1785
|
|
|
1413
|
-
if ( matched.includes( 'matched_junk' )&& ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
|
|
1414
|
-
result = defaultData;
|
|
1415
|
-
result.junk = hit?._source?.junk;
|
|
1416
|
-
result.matched = matched;
|
|
1417
|
-
}
|
|
1786
|
+
if ( matched.includes( 'matched_junk' ) && ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
|
|
1787
|
+
result = defaultData;
|
|
1788
|
+
result.junk = hit?._source?.junk;
|
|
1789
|
+
result.matched = matched;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if ( result ) {
|
|
1793
|
+
const nested = [
|
|
1794
|
+
{
|
|
1795
|
+
_id: hit._id,
|
|
1796
|
+
_index: hit._index,
|
|
1797
|
+
_score: 0,
|
|
1798
|
+
_source: result,
|
|
1799
|
+
},
|
|
1800
|
+
];
|
|
1801
|
+
temp.push( ...nested );
|
|
1802
|
+
}
|
|
1803
|
+
} );
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
const finalResponse = inputData.action ? temp : response;
|
|
1807
|
+
const getRevopQuery = {
|
|
1808
|
+
size: 10000,
|
|
1809
|
+
query: {
|
|
1810
|
+
bool: {
|
|
1811
|
+
must: [
|
|
1812
|
+
{ term: { 'storeId.keyword': response?.[0]?._source?.storeId } }, // assuming inputData.storeId is an array
|
|
1813
|
+
{ term: { 'dateString': response?.[0]?._source?.dateString } },
|
|
1814
|
+
],
|
|
1815
|
+
},
|
|
1816
|
+
},
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
const revopResp = await getOpenSearchData( openSearch.revop, getRevopQuery );
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
// Map revopResp.body.hits.hits to revopSources
|
|
1824
|
+
const revopSources = Array.isArray( revopResp?.body?.hits?.hits ) ?
|
|
1825
|
+
revopResp?.body?.hits?.hits?.map( ( hit ) => hit._source ) :
|
|
1826
|
+
[];
|
|
1827
|
+
|
|
1828
|
+
// Create a map of revopSources by id for quick lookup
|
|
1829
|
+
const revopSourcesMap = new Map();
|
|
1830
|
+
revopSources.forEach( ( item ) => {
|
|
1831
|
+
if ( item?.id ) {
|
|
1832
|
+
revopSourcesMap.set( String( item.id ), item );
|
|
1833
|
+
}
|
|
1834
|
+
} );
|
|
1835
|
+
|
|
1836
|
+
// Process revopSources to replace duplicateImage entries with full objects from the array
|
|
1837
|
+
const processedRevopSources = revopSources.map( ( item ) => {
|
|
1838
|
+
// Check if this is a duplicate parent item
|
|
1839
|
+
if ( item?.revopsType === 'duplicate' && item?.isParent === true && Array.isArray( item?.duplicateImage ) ) {
|
|
1840
|
+
// Map each duplicateImage entry to the full object from revopSources
|
|
1841
|
+
const updatedDuplicateImage = item.duplicateImage.map( ( duplicateImg ) => {
|
|
1842
|
+
const duplicateId = String( duplicateImg?.id );
|
|
1843
|
+
// Find the full object in revopSources that matches this id
|
|
1844
|
+
const fullObject = revopSourcesMap.get( duplicateId );
|
|
1845
|
+
// Return the full object if found, otherwise return the original duplicateImg
|
|
1846
|
+
return fullObject || duplicateImg;
|
|
1847
|
+
} );
|
|
1848
|
+
|
|
1849
|
+
return {
|
|
1850
|
+
...item,
|
|
1851
|
+
duplicateImage: updatedDuplicateImage,
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
// Return item as-is if it doesn't meet the criteria
|
|
1855
|
+
return item;
|
|
1856
|
+
} );
|
|
1418
1857
|
|
|
1419
|
-
if ( result ) {
|
|
1420
|
-
const nested = [
|
|
1421
|
-
{
|
|
1422
|
-
_id: hit._id,
|
|
1423
|
-
_index: hit._index,
|
|
1424
|
-
_score: 0,
|
|
1425
|
-
_source: result,
|
|
1426
|
-
},
|
|
1427
|
-
];
|
|
1428
|
-
temp.push( ...nested );
|
|
1429
|
-
}
|
|
1430
|
-
} );
|
|
1431
|
-
}
|
|
1432
|
-
const finalResponse = inputData.action ? temp : response;
|
|
1433
1858
|
if ( finalResponse?.length == 0 || !finalResponse ) {
|
|
1434
1859
|
if ( inputData.storeId?.length > 0 ) {
|
|
1435
|
-
const getStoreName = await findOneStore( { storeId:
|
|
1860
|
+
const getStoreName = await findOneStore( { storeId: response?.[0]?._source?.storeId }, { storeName: 1 } );
|
|
1436
1861
|
return res.sendError( `No Pending items in the selected dates for the store ${getStoreName?.storeName || ''}`, 400 );
|
|
1437
1862
|
}
|
|
1438
1863
|
return res.sendError( 'No Data found', 204 );
|
|
1439
1864
|
}
|
|
1440
|
-
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
// replace the mappingInfo.revisedDetail with processedRevopSources
|
|
1868
|
+
if ( Array.isArray( finalResponse ) ) {
|
|
1869
|
+
for ( let item of finalResponse ) {
|
|
1870
|
+
if (
|
|
1871
|
+
item &&
|
|
1872
|
+
item._source &&
|
|
1873
|
+
item._source.mappingInfo
|
|
1874
|
+
|
|
1875
|
+
) {
|
|
1876
|
+
item._source.mappingInfo.revisedDetail = processedRevopSources;
|
|
1877
|
+
const vmsCommentsLogIndex = openSearch.vmsCommentsLog || 'vms-comments-log-dev';
|
|
1878
|
+
let commentsResponse = [];
|
|
1879
|
+
const commentsFilter = [
|
|
1880
|
+
{ term: { 'storeId.keyword': item?._source?.storeId } },
|
|
1881
|
+
{ term: { dateString: item?._source?.dateString } },
|
|
1882
|
+
|
|
1883
|
+
];
|
|
1884
|
+
|
|
1885
|
+
const commentsQuery = {
|
|
1886
|
+
size: 10000,
|
|
1887
|
+
sort: [
|
|
1888
|
+
{ createdAt: { order: 'desc' } }, // Sort descending by createdAt
|
|
1889
|
+
],
|
|
1890
|
+
query: {
|
|
1891
|
+
bool: {
|
|
1892
|
+
filter: commentsFilter,
|
|
1893
|
+
},
|
|
1894
|
+
},
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
const commentsRes = await getOpenSearchData( vmsCommentsLogIndex, commentsQuery );
|
|
1898
|
+
// If mappingInfo is an array, update revisedDetail for each mappingInfo object
|
|
1899
|
+
if ( Array.isArray( item._source.mappingInfo ) ) {
|
|
1900
|
+
item._source.mappingInfo.forEach( ( mappingObj ) => {
|
|
1901
|
+
commentsResponse = commentsRes?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
|
|
1902
|
+
|
|
1903
|
+
// Check if duplicate condition exists in commentsResponse
|
|
1904
|
+
|
|
1905
|
+
// Structure comments output
|
|
1906
|
+
let commentsDetails = [];
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
const types = [ 'tagging', 'review', 'approve' ];
|
|
1910
|
+
|
|
1911
|
+
// Process each type
|
|
1912
|
+
types.forEach( ( typeValue ) => {
|
|
1913
|
+
if ( typeValue === 'tagging' ) {
|
|
1914
|
+
// For tagging, group by category and create separate objects for each category
|
|
1915
|
+
const taggingComments = commentsResponse.filter( ( c ) => c.type === typeValue );
|
|
1916
|
+
|
|
1917
|
+
// Group by category
|
|
1918
|
+
const categoryGroups = {};
|
|
1919
|
+
taggingComments.forEach( ( c ) => {
|
|
1920
|
+
const category = c.category || 'other';
|
|
1921
|
+
if ( !categoryGroups[category] ) {
|
|
1922
|
+
categoryGroups[category] = [];
|
|
1923
|
+
}
|
|
1924
|
+
categoryGroups[category].push( c );
|
|
1925
|
+
} );
|
|
1926
|
+
|
|
1927
|
+
// Create separate objects for each category
|
|
1928
|
+
Object.keys( categoryGroups ).forEach( ( category ) => {
|
|
1929
|
+
const categoryComments = categoryGroups[category];
|
|
1930
|
+
let parent = null;
|
|
1931
|
+
|
|
1932
|
+
const comms = categoryComments.map( ( c ) => {
|
|
1933
|
+
if ( category === 'duplicate' ) {
|
|
1934
|
+
if ( !parent && c.parent ) {
|
|
1935
|
+
parent = c.parent;
|
|
1936
|
+
}
|
|
1937
|
+
return {
|
|
1938
|
+
createdByEmail: c.createdByEmail,
|
|
1939
|
+
createdByUserName: c.createdByUserName,
|
|
1940
|
+
createdByRole: c.createdByRole,
|
|
1941
|
+
message: c.message,
|
|
1942
|
+
};
|
|
1943
|
+
} else {
|
|
1944
|
+
return {
|
|
1945
|
+
id: c.id,
|
|
1946
|
+
tempId: c.tempId,
|
|
1947
|
+
timeRange: c.timeRange,
|
|
1948
|
+
entryTime: c.entryTime,
|
|
1949
|
+
exitTime: c.exitTime,
|
|
1950
|
+
filePath: c.filePath,
|
|
1951
|
+
isChecked: c.isChecked,
|
|
1952
|
+
createdAt: c.createdAt,
|
|
1953
|
+
message: c.message,
|
|
1954
|
+
createdByEmail: c.createdByEmail,
|
|
1955
|
+
createdByUserName: c.createdByUserName,
|
|
1956
|
+
createdByRole: c.createdByRole,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
} );
|
|
1960
|
+
|
|
1961
|
+
const taggingObj = {
|
|
1962
|
+
category: category,
|
|
1963
|
+
type: typeValue,
|
|
1964
|
+
comments: comms,
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
// Add parent only for duplicate category
|
|
1968
|
+
if ( category === 'duplicate' && parent !== null ) {
|
|
1969
|
+
taggingObj.parent = parent;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
commentsDetails.push( taggingObj );
|
|
1973
|
+
} );
|
|
1974
|
+
} else if ( typeValue === 'review' || typeValue === 'approve' ) {
|
|
1975
|
+
// For review and approve, keep existing structure
|
|
1976
|
+
const comms = commentsResponse
|
|
1977
|
+
.filter( ( c ) => c.type === typeValue )
|
|
1978
|
+
.map( ( c ) => {
|
|
1979
|
+
if ( c.category === 'duplicate' ) {
|
|
1980
|
+
return {
|
|
1981
|
+
parent: c?.taggedImages[0]?._source?.parent,
|
|
1982
|
+
category: c.category,
|
|
1983
|
+
taggedImages: Array.isArray( c.taggedImages ) ?
|
|
1984
|
+
c.taggedImages.map( ( img ) => ( {
|
|
1985
|
+
id: img?._source?.id,
|
|
1986
|
+
tempId: img?._source?.tempId,
|
|
1987
|
+
timeRange: img?._source?.timeRange,
|
|
1988
|
+
entryTime: img?._source?.entryTime,
|
|
1989
|
+
exitTime: img?._source?.exitTime,
|
|
1990
|
+
filePath: img?._source?.filePath,
|
|
1991
|
+
isChecked: img?._source?.isChecked,
|
|
1992
|
+
} ) ) :
|
|
1993
|
+
[],
|
|
1994
|
+
createdByEmail: c.createdByEmail,
|
|
1995
|
+
createdByUserName: c.createdByUserName,
|
|
1996
|
+
createdByRole: c.createdByRole,
|
|
1997
|
+
status: c.status,
|
|
1998
|
+
message: c.message,
|
|
1999
|
+
};
|
|
2000
|
+
} else {
|
|
2001
|
+
return {
|
|
2002
|
+
category: c.category,
|
|
2003
|
+
taggedImages: Array.isArray( c.taggedImages ) ?
|
|
2004
|
+
c.taggedImages.map( ( img ) => ( {
|
|
2005
|
+
id: img?._source?.id,
|
|
2006
|
+
tempId: img?._source?.tempId,
|
|
2007
|
+
timeRange: img?._source?.timeRange,
|
|
2008
|
+
entryTime: img?._source?.entryTime,
|
|
2009
|
+
exitTime: img?._source?.exitTime,
|
|
2010
|
+
filePath: img?._source?.filePath,
|
|
2011
|
+
isChecked: img?._source?.isChecked,
|
|
2012
|
+
} ) ) :
|
|
2013
|
+
[],
|
|
2014
|
+
createdByEmail: c.createdByEmail,
|
|
2015
|
+
createdByUserName: c.createdByUserName,
|
|
2016
|
+
createdByRole: c.createdByRole,
|
|
2017
|
+
status: c.status,
|
|
2018
|
+
message: c.message,
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
} );
|
|
2022
|
+
|
|
2023
|
+
// Only add if there are comments
|
|
2024
|
+
if ( comms.length > 0 ) {
|
|
2025
|
+
commentsDetails.push( {
|
|
2026
|
+
type: typeValue,
|
|
2027
|
+
comments: comms,
|
|
2028
|
+
} );
|
|
2029
|
+
} else {
|
|
2030
|
+
// Add empty comments array if no comments
|
|
2031
|
+
commentsDetails.push( {
|
|
2032
|
+
type: typeValue,
|
|
2033
|
+
comments: [],
|
|
2034
|
+
} );
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
} );
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
item._source.commentsDetails = commentsDetails;
|
|
2041
|
+
|
|
2042
|
+
if (
|
|
2043
|
+
Object.prototype.hasOwnProperty.call( mappingObj, 'revisedDetail' ) &&
|
|
2044
|
+
mappingObj.type !== 'tangoreview'
|
|
2045
|
+
) {
|
|
2046
|
+
mappingObj.revisedDetail = processedRevopSources;
|
|
2047
|
+
}
|
|
2048
|
+
} );
|
|
2049
|
+
} else {
|
|
2050
|
+
item._source.mappingInfo.revisedDetail = processedRevopSources;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
} else if (
|
|
2055
|
+
finalResponse &&
|
|
2056
|
+
finalResponse._source &&
|
|
2057
|
+
finalResponse._source.mappingInfo
|
|
2058
|
+
) {
|
|
2059
|
+
finalResponse._source.mappingInfo.revisedDetail = processedRevopSources;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
return res.sendSuccess( { result: finalResponse } );
|
|
1441
2063
|
} catch ( error ) {
|
|
1442
2064
|
const err = error.message || 'Internal Server Error';
|
|
1443
2065
|
logger.error( { error: error, messgage: req.query } );
|
|
@@ -1509,7 +2131,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
|
|
|
1509
2131
|
item.isChecked == true ? tempId.push( { tempId: item.tempId, timeRange: item.timeRange } ) : null;
|
|
1510
2132
|
bulkBody.push(
|
|
1511
2133
|
{ update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${item.timeRange}_${item.tempId}` } },
|
|
1512
|
-
{ doc: { isChecked: item.isChecked, status: item?.isChecked == true? 'approved':'rejected' } },
|
|
2134
|
+
{ doc: { isChecked: item.isChecked, status: item?.isChecked == true ? 'approved' : 'rejected' } },
|
|
1513
2135
|
);
|
|
1514
2136
|
} );
|
|
1515
2137
|
}
|
|
@@ -1524,11 +2146,11 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
|
|
|
1524
2146
|
updateData.employee = updatedEmployee;
|
|
1525
2147
|
await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: updateData } );
|
|
1526
2148
|
for ( let employee of updateData?.employee ) {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
2149
|
+
( employee.isChecked == true ) ? tempId.push( { tempId: employee.tempId, timeRange: employee.timeRange } ) : null;
|
|
2150
|
+
bulkBody.push(
|
|
2151
|
+
{ update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${employee.timeRange}_${employee.tempId}` } },
|
|
2152
|
+
{ doc: { isChecked: employee.isChecked, status: employee?.isChecked == true ? 'approved' : 'rejected' } },
|
|
2153
|
+
);
|
|
1532
2154
|
}
|
|
1533
2155
|
await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { employeeACCount: tempId?.length || 0 } } );
|
|
1534
2156
|
}
|
|
@@ -1547,7 +2169,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
|
|
|
1547
2169
|
houseKeeping.isChecked == true ? tempId.push( { tempId: houseKeeping.tempId, timeRange: houseKeeping.timeRange } ) : null;
|
|
1548
2170
|
bulkBody.push(
|
|
1549
2171
|
{ update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${houseKeeping.timeRange}_${houseKeeping.tempId}` } },
|
|
1550
|
-
{ doc: { isChecked: houseKeeping.isChecked, status: houseKeeping?.isChecked == true? 'approved':'rejected' } },
|
|
2172
|
+
{ doc: { isChecked: houseKeeping.isChecked, status: houseKeeping?.isChecked == true ? 'approved' : 'rejected' } },
|
|
1551
2173
|
);
|
|
1552
2174
|
}
|
|
1553
2175
|
await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { houseKeepingACCount: tempId?.length || 0 } } );
|
|
@@ -1568,7 +2190,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
|
|
|
1568
2190
|
junk.isChecked == true ? tempId.push( { tempId: junk.tempId, timeRange: junk.timeRange } ) : null;
|
|
1569
2191
|
bulkBody.push(
|
|
1570
2192
|
{ update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${junk.timeRange}_${junk.tempId}` } },
|
|
1571
|
-
{ doc: { isChecked: junk.isChecked, status: junk?.isChecked == true? 'approved':'rejected' } },
|
|
2193
|
+
{ doc: { isChecked: junk.isChecked, status: junk?.isChecked == true ? 'approved' : 'rejected' } },
|
|
1572
2194
|
);
|
|
1573
2195
|
}
|
|
1574
2196
|
await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { junkACCount: tempId?.length || 0 } } );
|
|
@@ -1593,7 +2215,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
|
|
|
1593
2215
|
let getUpdateExistingOne = await getOpenSearchById( openSearch.footfallDirectory, _id );
|
|
1594
2216
|
const tempIdList = await extractCheckedTempIds( getUpdateExistingOne?.body );
|
|
1595
2217
|
const isSendMessge = await sendSqsMessage( data, tempIdList, getStoreType, storeId );
|
|
1596
|
-
if ( isSendMessge ==true ) {
|
|
2218
|
+
if ( isSendMessge == true ) {
|
|
1597
2219
|
return true; // res.sendSuccess( 'Ticket has been updated successfully' );
|
|
1598
2220
|
} else {
|
|
1599
2221
|
return false; // res.sendError( 'No SQS message sent', 500 );
|
|
@@ -1656,7 +2278,7 @@ function updateEmployeeCheckFlags( existingEmployees, inputEmployees, status ) {
|
|
|
1656
2278
|
// Step 2: Loop through all existing and update isChecked accordingly
|
|
1657
2279
|
const updatedEmployees = existingEmployees.map( ( emp ) => ( {
|
|
1658
2280
|
...emp,
|
|
1659
|
-
isChecked: status === 'rejected'? !checkedTempIds.has( emp.tempId ):checkedTempIds.has( emp.tempId ),
|
|
2281
|
+
isChecked: status === 'rejected' ? !checkedTempIds.has( emp.tempId ) : checkedTempIds.has( emp.tempId ),
|
|
1660
2282
|
} ) );
|
|
1661
2283
|
|
|
1662
2284
|
return updatedEmployees;
|
|
@@ -1706,7 +2328,7 @@ function mergeDuplicateImagesWithUncheck( existingData, inputData, status ) {
|
|
|
1706
2328
|
export async function sendSqsMessage( inputData, tempId, getStoreType, storeId ) {
|
|
1707
2329
|
const sqs = JSON.parse( process.env.SQS );
|
|
1708
2330
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1709
|
-
const sqsName = getStoreType > 0? sqs.revopTrackTicket: sqs.revopTicket;
|
|
2331
|
+
const sqsName = getStoreType > 0 ? sqs.revopTrackTicket : sqs.revopTicket;
|
|
1710
2332
|
const sqsProduceQueue = getStoreType > 0 ? {
|
|
1711
2333
|
QueueUrl: `${sqs.url}${sqsName}`,
|
|
1712
2334
|
MessageBody: JSON.stringify( {
|
|
@@ -1734,12 +2356,11 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
1734
2356
|
|
|
1735
2357
|
} ),
|
|
1736
2358
|
};
|
|
1737
|
-
const sqsQueue = getStoreType > 0? await sendMessageToFIFOQueue( sqsProduceQueue ):
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
);
|
|
2359
|
+
const sqsQueue = getStoreType > 0 ? await sendMessageToFIFOQueue( sqsProduceQueue ) :
|
|
2360
|
+
await sendMessageToQueue(
|
|
2361
|
+
sqsProduceQueue.QueueUrl,
|
|
2362
|
+
sqsProduceQueue.MessageBody,
|
|
2363
|
+
);
|
|
1743
2364
|
if ( sqsQueue.statusCode ) {
|
|
1744
2365
|
logger.error( {
|
|
1745
2366
|
error: `${sqsQueue}`,
|
|
@@ -1747,7 +2368,7 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
1747
2368
|
} );
|
|
1748
2369
|
return false;
|
|
1749
2370
|
} else {
|
|
1750
|
-
const id
|
|
2371
|
+
const id = `${storeId}_${inputData.dateString.split( '-' ).reverse().join( '-' )}_${getStoreType > 0 ? 'live' : 'reduction'}_${Date.now()}`;
|
|
1751
2372
|
const logs = {
|
|
1752
2373
|
QueueUrl: sqsProduceQueue.QueueUrl,
|
|
1753
2374
|
MessageBody: sqsProduceQueue.MessageBody,
|
|
@@ -1797,13 +2418,7 @@ export async function getTaggedStores( req, res ) {
|
|
|
1797
2418
|
},
|
|
1798
2419
|
},
|
|
1799
2420
|
];
|
|
1800
|
-
|
|
1801
|
-
// filter.push(
|
|
1802
|
-
// {
|
|
1803
|
-
// terms: { 'storeId.keyword': req.stores },
|
|
1804
|
-
// },
|
|
1805
|
-
// );
|
|
1806
|
-
// }
|
|
2421
|
+
|
|
1807
2422
|
filter.push(
|
|
1808
2423
|
{
|
|
1809
2424
|
terms: { 'storeId.keyword': req?.stores || [] },
|
|
@@ -1918,8 +2533,8 @@ export async function downloadTickets( req, res ) {
|
|
|
1918
2533
|
{
|
|
1919
2534
|
terms: {
|
|
1920
2535
|
'storeId.keyword': Array.isArray( inputData.storeId ) ?
|
|
1921
|
-
|
|
1922
|
-
|
|
2536
|
+
inputData.storeId :
|
|
2537
|
+
inputData.storeId,
|
|
1923
2538
|
},
|
|
1924
2539
|
},
|
|
1925
2540
|
);
|
|
@@ -1967,26 +2582,26 @@ export async function downloadTickets( req, res ) {
|
|
|
1967
2582
|
},
|
|
1968
2583
|
|
|
1969
2584
|
} ) :
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2585
|
+
inputData.revopsType === 'junk' ?
|
|
2586
|
+
filter.push( {
|
|
2587
|
+
range: {
|
|
2588
|
+
junkCount: {
|
|
2589
|
+
gt: 0,
|
|
2590
|
+
},
|
|
1975
2591
|
},
|
|
1976
|
-
}
|
|
1977
|
-
} ):
|
|
2592
|
+
} ) :
|
|
1978
2593
|
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2594
|
+
filter.push( {
|
|
2595
|
+
range: {
|
|
2596
|
+
duplicateCount: {
|
|
2597
|
+
gt: 0,
|
|
2598
|
+
},
|
|
1983
2599
|
},
|
|
1984
|
-
}
|
|
1985
|
-
} );
|
|
2600
|
+
} );
|
|
1986
2601
|
source = inputData.revopsType == 'employee' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'comments', 'employee', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
|
|
1987
2602
|
inputData.revopsType == 'houseKeeping' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'houseKeepingCount', 'comments', 'houseKeeping', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
|
|
1988
2603
|
inputData.revopsType == 'duplicateImages' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'duplicateCount', 'comments', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
|
|
1989
|
-
|
|
2604
|
+
inputData.revopsType == 'junk' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'junkCount', 'comments', 'junk', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] : [];
|
|
1990
2605
|
}
|
|
1991
2606
|
|
|
1992
2607
|
if ( inputData.action ) {
|
|
@@ -2063,7 +2678,7 @@ export async function downloadTickets( req, res ) {
|
|
|
2063
2678
|
let temp = [];
|
|
2064
2679
|
if ( inputData?.action ) {
|
|
2065
2680
|
response.map( ( hit ) => {
|
|
2066
|
-
const defaultData ={
|
|
2681
|
+
const defaultData = {
|
|
2067
2682
|
storeId: hit._source.storeId,
|
|
2068
2683
|
dateString: hit?._source?.dateString,
|
|
2069
2684
|
ticketName: hit?._source?.ticketName,
|
|
@@ -2093,19 +2708,19 @@ export async function downloadTickets( req, res ) {
|
|
|
2093
2708
|
// result.type = 'employee';
|
|
2094
2709
|
result.matched = matched;
|
|
2095
2710
|
}
|
|
2096
|
-
if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
|
|
2711
|
+
if ( matched.includes( 'matched_housekeeping' ) && ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
|
|
2097
2712
|
logger.info( { revop: inputData.revopsType } );
|
|
2098
2713
|
result = defaultData;
|
|
2099
2714
|
result.houseKeeping = hit?._source?.houseKeeping;
|
|
2100
2715
|
result.matched = matched;
|
|
2101
2716
|
}
|
|
2102
|
-
if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
|
|
2717
|
+
if ( matched.includes( 'matched_duplicate' ) && ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
|
|
2103
2718
|
logger.info( { revop: inputData.revopsType } );
|
|
2104
2719
|
result = defaultData;
|
|
2105
2720
|
result.duplicateImages = hit?._source?.duplicateImages;
|
|
2106
2721
|
result.matched = matched;
|
|
2107
2722
|
}
|
|
2108
|
-
if ( matched.includes( 'matched_junk' )&& ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
|
|
2723
|
+
if ( matched.includes( 'matched_junk' ) && ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
|
|
2109
2724
|
result = defaultData;
|
|
2110
2725
|
result.junk = hit?._source?.junk;
|
|
2111
2726
|
result.matched = matched;
|
|
@@ -2149,7 +2764,7 @@ export async function downloadTickets( req, res ) {
|
|
|
2149
2764
|
revopsType: inputData?.revopsType,
|
|
2150
2765
|
type: 'get-tickets',
|
|
2151
2766
|
};
|
|
2152
|
-
const record={
|
|
2767
|
+
const record = {
|
|
2153
2768
|
stores: inputData?.storeId,
|
|
2154
2769
|
fromDate: inputData?.fromDate,
|
|
2155
2770
|
toDate: inputData?.toDate,
|
|
@@ -2167,7 +2782,7 @@ export async function downloadTickets( req, res ) {
|
|
|
2167
2782
|
const sqs = JSON.parse( process.env.SQS );
|
|
2168
2783
|
const sqsName = sqs.revopDownload;
|
|
2169
2784
|
|
|
2170
|
-
const sqsProduceQueue ={
|
|
2785
|
+
const sqsProduceQueue = {
|
|
2171
2786
|
QueueUrl: `${sqs.url}${sqsName}`,
|
|
2172
2787
|
MessageBody: JSON.stringify( {
|
|
2173
2788
|
_id: getId?._id,
|
|
@@ -2196,7 +2811,6 @@ export async function downloadTickets( req, res ) {
|
|
|
2196
2811
|
}
|
|
2197
2812
|
|
|
2198
2813
|
async function extractTempIds( document ) {
|
|
2199
|
-
logger.info( { document: document } );
|
|
2200
2814
|
const source = document?._source || {};
|
|
2201
2815
|
const result = [];
|
|
2202
2816
|
|
|
@@ -2246,7 +2860,7 @@ export async function reviewerList( req, res ) {
|
|
|
2246
2860
|
featureName: 'FootfallDirectory',
|
|
2247
2861
|
modules: {
|
|
2248
2862
|
$elemMatch: {
|
|
2249
|
-
name: '
|
|
2863
|
+
name: inputData?.type === 'review' ? 'reviewer' : 'approver',
|
|
2250
2864
|
$or: [ { isAdd: true }, { isEdit: true } ],
|
|
2251
2865
|
},
|
|
2252
2866
|
},
|
|
@@ -2255,7 +2869,7 @@ export async function reviewerList( req, res ) {
|
|
|
2255
2869
|
};
|
|
2256
2870
|
|
|
2257
2871
|
const getUserlist = await findUser( reviewerRoleQuery, { userName: 1, email: 1, role: 1 } );
|
|
2258
|
-
return res.sendSuccess( getUserlist|| [] );
|
|
2872
|
+
return res.sendSuccess( getUserlist || [] );
|
|
2259
2873
|
} catch ( error ) {
|
|
2260
2874
|
const err = error.message || 'Internal Server Error';
|
|
2261
2875
|
return res.sendError( err, 500 );
|
|
@@ -2277,6 +2891,16 @@ export async function openTicketList( req, res ) {
|
|
|
2277
2891
|
clientId: Array.isArray( clientId ) ? clientId : [ clientId ],
|
|
2278
2892
|
},
|
|
2279
2893
|
},
|
|
2894
|
+
{
|
|
2895
|
+
term: {
|
|
2896
|
+
'mappingInfo.type': inputData.type,
|
|
2897
|
+
},
|
|
2898
|
+
},
|
|
2899
|
+
{
|
|
2900
|
+
term: {
|
|
2901
|
+
'mappingInfo.status.keyword': 'Open',
|
|
2902
|
+
},
|
|
2903
|
+
},
|
|
2280
2904
|
{
|
|
2281
2905
|
range: {
|
|
2282
2906
|
dateString: {
|
|
@@ -2295,14 +2919,19 @@ export async function openTicketList( req, res ) {
|
|
|
2295
2919
|
filter: filter,
|
|
2296
2920
|
},
|
|
2297
2921
|
},
|
|
2298
|
-
_source: [ 'ticketId', 'storeName', 'revicedFootfall', 'footfallCount', 'revicedPerc' ],
|
|
2922
|
+
_source: [ 'ticketId', 'storeName', 'storeId', 'dateString', 'revicedFootfall', 'footfallCount', 'revicedPerc', 'type' ],
|
|
2299
2923
|
};
|
|
2924
|
+
// INSERT_YOUR_CODE
|
|
2925
|
+
// Add sorting by revicedPerc descending (highest revised accuracy first)
|
|
2926
|
+
openSearchQuery.sort = [
|
|
2927
|
+
{ 'revicedPerc.keyword': { order: inputData?.sortOrder === 1 ? 'asc' : 'desc' } },
|
|
2928
|
+
];
|
|
2300
2929
|
|
|
2301
2930
|
|
|
2302
2931
|
// Assuming getOpenSearchData and openSearch.footfallDirectoryTagging are available
|
|
2303
2932
|
const result = await getOpenSearchData( openSearch.footfallDirectory, openSearchQuery );
|
|
2304
2933
|
const getUserlist = result?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
|
|
2305
|
-
return res.sendSuccess( getUserlist|| [] );
|
|
2934
|
+
return res.sendSuccess( getUserlist || [] );
|
|
2306
2935
|
} catch ( error ) {
|
|
2307
2936
|
const err = error.message || 'Internal Server Error';
|
|
2308
2937
|
logger.error( { error: error, function: 'openTicketList' } );
|
|
@@ -2315,73 +2944,32 @@ export async function assignTicket( req, res ) {
|
|
|
2315
2944
|
const inputData = req.body;
|
|
2316
2945
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2317
2946
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
const { email, userName, role, actionType } = inputData;
|
|
2321
|
-
|
|
2322
|
-
// INSERT_YOUR_CODE
|
|
2323
|
-
|
|
2324
|
-
// Find and update mappingInfo fields for the provided ticketId and actionType
|
|
2325
|
-
// Requires ticketId in inputData
|
|
2326
|
-
const { ticketId } = inputData;
|
|
2327
|
-
if ( !ticketId ) {
|
|
2328
|
-
return res.sendError( 'ticketId is required', 400 );
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
// Build the OpenSearch update-by-query body
|
|
2332
|
-
const updateBody = {
|
|
2333
|
-
script: {
|
|
2334
|
-
source: `
|
|
2335
|
-
if (ctx._source.mappingInfo != null) {
|
|
2336
|
-
for (int i = 0; i < ctx._source.mappingInfo.length; i++) {
|
|
2337
|
-
if (ctx._source.mappingInfo[i].type == params.actionType) {
|
|
2338
|
-
ctx._source.mappingInfo[i].createdByEmail = params.email;
|
|
2339
|
-
ctx._source.mappingInfo[i].createdByUserName = params.userName;
|
|
2340
|
-
ctx._source.mappingInfo[i].createdByRole = params.role;
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
`,
|
|
2345
|
-
lang: 'painless',
|
|
2346
|
-
params: {
|
|
2347
|
-
email,
|
|
2348
|
-
userName,
|
|
2349
|
-
role,
|
|
2350
|
-
actionType,
|
|
2351
|
-
},
|
|
2352
|
-
},
|
|
2353
|
-
query: {
|
|
2354
|
-
bool: {
|
|
2355
|
-
must: [
|
|
2356
|
-
{ term: { 'ticketId.keyword': ticketId } },
|
|
2357
|
-
{
|
|
2358
|
-
nested: {
|
|
2359
|
-
path: 'mappingInfo',
|
|
2360
|
-
query: {
|
|
2361
|
-
bool: {
|
|
2362
|
-
must: [
|
|
2363
|
-
{ match: { 'mappingInfo.type': actionType } },
|
|
2364
|
-
],
|
|
2365
|
-
},
|
|
2366
|
-
},
|
|
2367
|
-
},
|
|
2368
|
-
},
|
|
2369
|
-
],
|
|
2370
|
-
},
|
|
2371
|
-
},
|
|
2372
|
-
};
|
|
2373
|
-
|
|
2374
|
-
// Call OpenSearch _update_by_query to update doc(s) where ticketId and mappingInfo[i].type == actionType
|
|
2375
|
-
const response = await upsertOpenSearchData(
|
|
2376
|
-
openSearch.footfallDirectory,
|
|
2377
|
-
'11-1716_2025-11-20_footfall-directory-tagging',
|
|
2378
|
-
updateBody, // custom arg to indicate passthrough for update-by-query, depends on helper implementation
|
|
2379
|
-
);
|
|
2380
|
-
|
|
2947
|
+
const { email, userName, role, storeId, dateString } = inputData;
|
|
2948
|
+
const _id = `${storeId}_${dateString}_footfall-directory-tagging`;
|
|
2381
2949
|
|
|
2382
|
-
|
|
2950
|
+
const getTicket = await getOpenSearchById( openSearch.footfallDirectory, _id );
|
|
2951
|
+
if ( !getTicket ) {
|
|
2952
|
+
return res.sendError( 'Ticket is not found', 400 );
|
|
2953
|
+
}
|
|
2954
|
+
const source = getTicket?.body?._source;
|
|
2955
|
+
const mappingInfo = Array.isArray( source.mappingInfo ) ? source.mappingInfo : [];
|
|
2956
|
+
if ( mappingInfo.length === 0 ) {
|
|
2957
|
+
return res.sendError( 'Ticket is not found', 400 );
|
|
2958
|
+
}
|
|
2959
|
+
const lastIndex = mappingInfo.length - 1;
|
|
2960
|
+
const lastEntry = mappingInfo[lastIndex];
|
|
2961
|
+
if ( String( lastEntry.status ) !== 'In-Progress' ) {
|
|
2962
|
+
return res.sendError( 'Ticket is not in progress', 400 );
|
|
2963
|
+
}
|
|
2383
2964
|
|
|
2384
|
-
|
|
2965
|
+
const currentTime = new Date();
|
|
2966
|
+
const updatedMappingInfo = [ ...mappingInfo ];
|
|
2967
|
+
updatedMappingInfo[lastIndex] = { ...lastEntry, createdByEmail: email, updatedAt: currentTime, createdByUserName: userName, createdByRole: role };
|
|
2968
|
+
const updateResult = await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { mappingInfo: updatedMappingInfo } } );
|
|
2969
|
+
if ( !updateResult ) {
|
|
2970
|
+
return res.sendError( 'Failed to update ticket', 400 );
|
|
2971
|
+
}
|
|
2972
|
+
return res.sendSuccess( { message: 'Ticket assigned successfully' } );
|
|
2385
2973
|
} catch ( error ) {
|
|
2386
2974
|
const err = error.message || 'Internal Server Error';
|
|
2387
2975
|
logger.error( { error: error, function: 'assignTicket' } );
|
|
@@ -2392,11 +2980,13 @@ export async function assignTicket( req, res ) {
|
|
|
2392
2980
|
export async function updateTempStatus( req, res ) {
|
|
2393
2981
|
try {
|
|
2394
2982
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2395
|
-
const
|
|
2983
|
+
const inputData = req.body;
|
|
2984
|
+
const { id, type, status, comments } = inputData;
|
|
2396
2985
|
|
|
2397
2986
|
// Use bulk update API via bucketing (batch update) -- fetch docs, then bulk-update
|
|
2398
2987
|
// 1. Search for all documents matching the ticket IDs
|
|
2399
2988
|
const searchBody = {
|
|
2989
|
+
size: 10000,
|
|
2400
2990
|
query: {
|
|
2401
2991
|
bool: {
|
|
2402
2992
|
must: [
|
|
@@ -2415,16 +3005,14 @@ export async function updateTempStatus( req, res ) {
|
|
|
2415
3005
|
openSearch.revop,
|
|
2416
3006
|
searchBody,
|
|
2417
3007
|
);
|
|
2418
|
-
|
|
3008
|
+
|
|
2419
3009
|
// Extract bulk IDs to update
|
|
2420
3010
|
const hits = searchResp?.body?.hits?.hits ?? [];
|
|
2421
|
-
|
|
3011
|
+
|
|
2422
3012
|
if ( !hits.length ) {
|
|
2423
3013
|
return res.sendError( 'no data', 204 );
|
|
2424
3014
|
}
|
|
2425
3015
|
|
|
2426
|
-
// 2. Build bulk update commands
|
|
2427
|
-
// Each doc: { update: { _id: ..., _index: ... } }, { doc: { status: status } }
|
|
2428
3016
|
|
|
2429
3017
|
// 1. Get all IDs from hits
|
|
2430
3018
|
const docIdToIndex = {};
|
|
@@ -2432,7 +3020,6 @@ export async function updateTempStatus( req, res ) {
|
|
|
2432
3020
|
docIdToIndex[doc._id] = doc._index;
|
|
2433
3021
|
} );
|
|
2434
3022
|
const docIds = hits.map( ( doc ) => doc._id );
|
|
2435
|
-
logger.info( { docIds } );
|
|
2436
3023
|
// 2. Fetch all docs by ID to get 'actions' (in chunks if large)
|
|
2437
3024
|
const getBody = [];
|
|
2438
3025
|
for ( const doc of hits ) {
|
|
@@ -2440,71 +3027,76 @@ export async function updateTempStatus( req, res ) {
|
|
|
2440
3027
|
}
|
|
2441
3028
|
|
|
2442
3029
|
let mgetResp;
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
3030
|
+
|
|
3031
|
+
mgetResp = await getOpenSearchData(
|
|
3032
|
+
openSearch.revop,
|
|
3033
|
+
{
|
|
3034
|
+
size: 10000,
|
|
3035
|
+
query: {
|
|
3036
|
+
ids: {
|
|
3037
|
+
values: docIds,
|
|
2451
3038
|
},
|
|
2452
|
-
_source: true,
|
|
2453
3039
|
},
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
mgetResp = undefined;
|
|
2458
|
-
}
|
|
2459
|
-
logger.info( { mgetResp } );
|
|
3040
|
+
_source: true,
|
|
3041
|
+
},
|
|
3042
|
+
);
|
|
2460
3043
|
// (If you have a utility for multi-get, you may want to use that. Else, you might need to fetch each by ID.)
|
|
2461
3044
|
// For fallback, fetch all source fields via another search
|
|
2462
|
-
let fullDocs = [];
|
|
2463
|
-
if ( mgetResp && mgetResp.body && mgetResp.body.docs && Array.isArray( mgetResp.body.docs ) ) {
|
|
2464
|
-
fullDocs = mgetResp.body.docs;
|
|
2465
|
-
} else if ( searchResp.body && searchResp.body.hits && searchResp.body.hits.hits ) {
|
|
2466
|
-
// fallback: use searchResp docs (request _source above)
|
|
2467
|
-
fullDocs = searchResp.body.hits.hits;
|
|
2468
|
-
}
|
|
3045
|
+
let fullDocs = mgetResp?.body?.hits?.hits || searchResp?.body?.hits?.hits || [];
|
|
2469
3046
|
|
|
2470
3047
|
// 3. Prepare the new actions array for each doc, and set up bulk update payloads
|
|
2471
3048
|
const reviewActions = [ 'approved', 'rejected' ];
|
|
2472
3049
|
const docsToUpdate = [];
|
|
2473
|
-
logger.info( { fullDocs: fullDocs } );
|
|
2474
3050
|
for ( const doc of fullDocs ) {
|
|
2475
3051
|
const source = doc._source || doc.fields || {}; // support mget and search hits
|
|
2476
3052
|
let actions = Array.isArray( source.actions ) ? [ ...source.actions ] : [];
|
|
2477
3053
|
if ( reviewActions.includes( status ) ) {
|
|
2478
3054
|
// for review: update or push 'review'
|
|
2479
3055
|
let found = false;
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
3056
|
+
switch ( type ) {
|
|
3057
|
+
case 'review':
|
|
3058
|
+
actions = actions.map( ( item ) => {
|
|
3059
|
+
if ( item.actionType === 'review' ) {
|
|
3060
|
+
found = true;
|
|
3061
|
+
return { ...item, action: status };
|
|
3062
|
+
}
|
|
3063
|
+
return item;
|
|
3064
|
+
} );
|
|
3065
|
+
if ( !found ) {
|
|
3066
|
+
actions.push( { actionType: 'review', action: status } );
|
|
3067
|
+
}
|
|
3068
|
+
break;
|
|
3069
|
+
case 'approve':
|
|
3070
|
+
actions = actions.map( ( item ) => {
|
|
3071
|
+
if ( item.actionType === 'approve' ) {
|
|
3072
|
+
found = true;
|
|
3073
|
+
return { ...item, action: status };
|
|
3074
|
+
}
|
|
3075
|
+
return item;
|
|
3076
|
+
} );
|
|
3077
|
+
if ( !found ) {
|
|
3078
|
+
actions.push( { actionType: 'approve', action: status } );
|
|
3079
|
+
}
|
|
3080
|
+
break;
|
|
3081
|
+
default:
|
|
3082
|
+
return res.sendError( 'wrong vaue', 400 );
|
|
2502
3083
|
}
|
|
2503
3084
|
}
|
|
3085
|
+
let isChecked = true;
|
|
3086
|
+
switch ( type ) {
|
|
3087
|
+
case 'review':
|
|
3088
|
+
isChecked = status === 'approved' ? true : false;
|
|
3089
|
+
break;
|
|
3090
|
+
case 'approve':
|
|
3091
|
+
isChecked = status === 'approved' ? doc?._source?.isChecked : !doc?._source?.isChecked;
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
2504
3094
|
docsToUpdate.push( {
|
|
2505
3095
|
_index: doc._index || docIdToIndex[doc._id],
|
|
2506
3096
|
_id: doc._id,
|
|
2507
3097
|
actions,
|
|
3098
|
+
isChecked: isChecked,
|
|
3099
|
+
comments: comments || '',
|
|
2508
3100
|
} );
|
|
2509
3101
|
}
|
|
2510
3102
|
const bulkPayload = [];
|
|
@@ -2514,11 +3106,9 @@ export async function updateTempStatus( req, res ) {
|
|
|
2514
3106
|
update: { _index: doc._index, _id: doc._id },
|
|
2515
3107
|
} );
|
|
2516
3108
|
bulkPayload.push( {
|
|
2517
|
-
doc: { actions: doc.actions },
|
|
3109
|
+
doc: { actions: doc.actions, isChecked: doc?.isChecked, comments: doc?.comments || '' },
|
|
2518
3110
|
} );
|
|
2519
3111
|
}
|
|
2520
|
-
logger.info( { bulkPayload: bulkPayload } );
|
|
2521
|
-
|
|
2522
3112
|
|
|
2523
3113
|
// 3. Execute bulk update
|
|
2524
3114
|
const bulkResp = await bulkUpdate( bulkPayload );
|
|
@@ -2526,8 +3116,50 @@ export async function updateTempStatus( req, res ) {
|
|
|
2526
3116
|
// Count successes
|
|
2527
3117
|
const updatedCount = bulkResp?.body?.items?.filter( ( item ) => item?.update?.result === 'updated' || item?.update?.result === 'noop' ).length ?? 0;
|
|
2528
3118
|
|
|
2529
|
-
|
|
3119
|
+
if ( inputData?.comments && inputData?.comments !== '' ) {
|
|
3120
|
+
const id = `${inputData.storeId}_${inputData.dateString}_${Date.now()}`;
|
|
3121
|
+
const searchBody1 = {
|
|
3122
|
+
size: 10000,
|
|
3123
|
+
query: {
|
|
3124
|
+
bool: {
|
|
3125
|
+
must: [
|
|
3126
|
+
{
|
|
3127
|
+
terms: {
|
|
3128
|
+
'_id': docIds,
|
|
3129
|
+
},
|
|
3130
|
+
},
|
|
3131
|
+
{
|
|
3132
|
+
term: {
|
|
3133
|
+
isParent: false,
|
|
3134
|
+
},
|
|
3135
|
+
},
|
|
3136
|
+
],
|
|
3137
|
+
},
|
|
3138
|
+
},
|
|
3139
|
+
};
|
|
3140
|
+
|
|
3141
|
+
const getSearchResp = await getOpenSearchData(
|
|
3142
|
+
openSearch.revop,
|
|
3143
|
+
searchBody1,
|
|
3144
|
+
);
|
|
3145
|
+
|
|
2530
3146
|
|
|
3147
|
+
const taggedImages = getSearchResp?.body?.hits?.hits?.length > 0 ? getSearchResp?.body?.hits?.hits : [];
|
|
3148
|
+
const logs = {
|
|
3149
|
+
type: inputData.type,
|
|
3150
|
+
storeId: taggedImages?.[0]?._source?.storeId,
|
|
3151
|
+
dateString: taggedImages?.[0]?._source?.dateString,
|
|
3152
|
+
category: taggedImages?.[0]?._source?.revopsType || '',
|
|
3153
|
+
taggedImages: taggedImages,
|
|
3154
|
+
status: inputData?.status,
|
|
3155
|
+
createdByEmail: req?.user?.email,
|
|
3156
|
+
createdByUserName: req?.user?.userName,
|
|
3157
|
+
createdByRole: req?.user?.role,
|
|
3158
|
+
message: inputData.comments || '',
|
|
3159
|
+
createdAt: new Date(),
|
|
3160
|
+
};
|
|
3161
|
+
await insertWithId( openSearch.vmsCommentsLog, id, logs );
|
|
3162
|
+
}
|
|
2531
3163
|
return res.sendSuccess( { updated: updatedCount } );
|
|
2532
3164
|
} catch ( error ) {
|
|
2533
3165
|
const err = error.message;
|
|
@@ -2536,3 +3168,249 @@ export async function updateTempStatus( req, res ) {
|
|
|
2536
3168
|
}
|
|
2537
3169
|
}
|
|
2538
3170
|
|
|
3171
|
+
export async function updateUserTicketStatus( req, res ) {
|
|
3172
|
+
try {
|
|
3173
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
3174
|
+
const { storeId, dateString } = req.body || {};
|
|
3175
|
+
|
|
3176
|
+
if ( !storeId || !dateString ) {
|
|
3177
|
+
return res.sendError( 'storeId and dateString are required', 400 );
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
const docId = `${storeId}_${dateString}_footfall-directory-tagging`;
|
|
3181
|
+
|
|
3182
|
+
// Fetch existing ticket so we can validate mappingInfo state
|
|
3183
|
+
const existingDoc = await getOpenSearchById( openSearch.footfallDirectory, docId );
|
|
3184
|
+
const ticketSource = existingDoc?.body?._source;
|
|
3185
|
+
|
|
3186
|
+
if ( !ticketSource ) {
|
|
3187
|
+
return res.sendError( 'Ticket not found', 404 );
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
const mappingInfo = Array.isArray( ticketSource.mappingInfo ) ? ticketSource.mappingInfo : [];
|
|
3191
|
+
if ( mappingInfo.length === 0 ) {
|
|
3192
|
+
return res.sendError( 'mappingInfo is missing for this ticket', 400 );
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
const lastIndex = mappingInfo.length - 1;
|
|
3196
|
+
const lastEntry = mappingInfo[lastIndex];
|
|
3197
|
+
|
|
3198
|
+
if ( !lastEntry ) {
|
|
3199
|
+
return res.sendError( 'Unable to determine current ticket status', 400 );
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
if ( String( lastEntry.status ).toLowerCase() !== 'open' ) {
|
|
3203
|
+
return res.sendError( 'Ticket is already picked by another user', 409 );
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
const currentTime = new Date();
|
|
3207
|
+
const updatedMappingInfo = [ ...mappingInfo ];
|
|
3208
|
+
updatedMappingInfo[lastIndex] = {
|
|
3209
|
+
...lastEntry,
|
|
3210
|
+
status: 'In-Progress',
|
|
3211
|
+
updatedAt: currentTime,
|
|
3212
|
+
createdByRole: req?.user?.role || '',
|
|
3213
|
+
createdByEmail: req?.user?.email || '',
|
|
3214
|
+
createdByUserName: req?.user?.userName || '',
|
|
3215
|
+
};
|
|
3216
|
+
|
|
3217
|
+
const updatePayload = {
|
|
3218
|
+
doc: {
|
|
3219
|
+
status: 'In-Progress',
|
|
3220
|
+
mappingInfo: updatedMappingInfo,
|
|
3221
|
+
updatedAt: currentTime,
|
|
3222
|
+
},
|
|
3223
|
+
};
|
|
3224
|
+
|
|
3225
|
+
const updateResult = await updateOpenSearchData(
|
|
3226
|
+
openSearch.footfallDirectory,
|
|
3227
|
+
docId,
|
|
3228
|
+
updatePayload,
|
|
3229
|
+
);
|
|
3230
|
+
|
|
3231
|
+
|
|
3232
|
+
if ( !updateResult || !( updateResult.statusCode === 200 || updateResult.statusCode === 201 ) ) {
|
|
3233
|
+
return res.sendError( 'Failed to update ticket status', 500 );
|
|
3234
|
+
}
|
|
3235
|
+
return res.sendSuccess( 'Ticket status updated successfully' );
|
|
3236
|
+
} catch ( error ) {
|
|
3237
|
+
const err = error.message;
|
|
3238
|
+
logger.info( { error: err, function: 'updateUserTicketStatus' } );
|
|
3239
|
+
return res.sendError( err, 500 );
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
export async function multiCloseTicket( req, res ) {
|
|
3244
|
+
try {
|
|
3245
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
3246
|
+
const inputData = req.body;
|
|
3247
|
+
|
|
3248
|
+
// inputData structure should include an array of items to close
|
|
3249
|
+
// Accept both array or single ticket update
|
|
3250
|
+
const tickets = Array.isArray( inputData.ticketList ) ? inputData.ticketList : [ inputData.ticketList ];
|
|
3251
|
+
// const mode = inputData.mode || '';
|
|
3252
|
+
|
|
3253
|
+
if ( !tickets.length ) {
|
|
3254
|
+
return res.sendError( 'No tickets provided', 400 );
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
const results = [];
|
|
3258
|
+
for ( const ticket of tickets ) {
|
|
3259
|
+
const { storeId, dateString } = ticket || {};
|
|
3260
|
+
if ( !storeId || !dateString ) {
|
|
3261
|
+
results.push( { storeId, dateString, success: false, error: 'Missing storeId or dateString' } );
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
// 1. Update the ticket document in footfallDirectory index
|
|
3265
|
+
const docId = `${storeId}_${dateString}_footfall-directory-tagging`;
|
|
3266
|
+
|
|
3267
|
+
// Fetch existing doc to update mappingInfo
|
|
3268
|
+
|
|
3269
|
+
const doc = await getOpenSearchById( openSearch.footfallDirectory, docId );
|
|
3270
|
+
|
|
3271
|
+
|
|
3272
|
+
const ticketSource = doc?.body?._source;
|
|
3273
|
+
if ( !ticketSource || !ticketSource.mappingInfo ) {
|
|
3274
|
+
results.push( { storeId, dateString, success: false, error: 'Ticket or mappingInfo missing' } );
|
|
3275
|
+
continue;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
let mappingInfoArray = Array.isArray( ticketSource.mappingInfo ) ? ticketSource.mappingInfo : [ ticketSource.mappingInfo ];
|
|
3279
|
+
// Find all mappingInfo items matching type 'approve'
|
|
3280
|
+
let updated = false;
|
|
3281
|
+
let newMappingInfoArray = mappingInfoArray.map( ( mi, i ) => {
|
|
3282
|
+
if ( mi?.type === 'approve' && mi?.status === 'Open' ) {
|
|
3283
|
+
updated = true;
|
|
3284
|
+
return {
|
|
3285
|
+
...mi,
|
|
3286
|
+
status: 'Approver-Closed',
|
|
3287
|
+
status: 'Closed', // following the user's instruction to set sttaus property (assumed typo, but explicitly used)
|
|
3288
|
+
updatedAt: new Date(),
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
return mi;
|
|
3292
|
+
} );
|
|
3293
|
+
|
|
3294
|
+
if ( !updated ) {
|
|
3295
|
+
// None found to update
|
|
3296
|
+
results.push( { storeId, dateString, success: false, error: `coudn't approve this store` } );
|
|
3297
|
+
continue;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
// Write update to footfallDirectory
|
|
3301
|
+
const ticketUpdatePayload = {
|
|
3302
|
+
doc: {
|
|
3303
|
+
mappingInfo: newMappingInfoArray,
|
|
3304
|
+
status: 'Approver-Closed', // status updated at top level as well
|
|
3305
|
+
updatedAt: new Date(),
|
|
3306
|
+
},
|
|
3307
|
+
};
|
|
3308
|
+
let ticketUpdateResult;
|
|
3309
|
+
try {
|
|
3310
|
+
ticketUpdateResult = await updateOpenSearchData( openSearch.footfallDirectory, docId, ticketUpdatePayload );
|
|
3311
|
+
if ( !( ticketUpdateResult && ( ticketUpdateResult.statusCode === 200 || ticketUpdateResult.statusCode === 201 ) ) ) {
|
|
3312
|
+
results.push( { storeId, dateString, success: false, error: 'Failed to update ticket' } );
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
} catch ( err ) {
|
|
3316
|
+
results.push( { storeId, dateString, success: false, error: 'Failed to update ticket' } );
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
|
|
3321
|
+
// For each ticket, update actions array for all matching image docs (storeId & dateString)
|
|
3322
|
+
|
|
3323
|
+
// Query to find all matching docs in revopTagging index with storeId and dateString
|
|
3324
|
+
const revopImageQuery = {
|
|
3325
|
+
size: 10000, // assume there won't be more than 1000 images per ticket
|
|
3326
|
+
query: {
|
|
3327
|
+
bool: {
|
|
3328
|
+
must: [
|
|
3329
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
3330
|
+
{ term: { dateString: dateString } },
|
|
3331
|
+
],
|
|
3332
|
+
},
|
|
3333
|
+
},
|
|
3334
|
+
};
|
|
3335
|
+
|
|
3336
|
+
// Fetch matching docs
|
|
3337
|
+
const searchRes = await getOpenSearchData( openSearch.revop, revopImageQuery );
|
|
3338
|
+
const revopHits = searchRes?.body?.hits?.hits || [];
|
|
3339
|
+
|
|
3340
|
+
// Optimized: batch and parallelize image-doc updates to avoid long sequential waits
|
|
3341
|
+
const BATCH_SIZE = 100;
|
|
3342
|
+
const now = new Date();
|
|
3343
|
+
|
|
3344
|
+
for ( let i = 0; i < revopHits.length; i += BATCH_SIZE ) {
|
|
3345
|
+
const batch = revopHits.slice( i, i + BATCH_SIZE );
|
|
3346
|
+
|
|
3347
|
+
const updatePromises = batch.map( async ( hit ) => {
|
|
3348
|
+
const imageDocId = hit._id;
|
|
3349
|
+
const imageSource = hit._source || {};
|
|
3350
|
+
const imageActionsArray = Array.isArray( imageSource.actions ) ? [ ...imageSource.actions ] : [];
|
|
3351
|
+
|
|
3352
|
+
imageActionsArray.push( {
|
|
3353
|
+
actionType: 'approve',
|
|
3354
|
+
action: 'approved',
|
|
3355
|
+
} );
|
|
3356
|
+
|
|
3357
|
+
const imageUpdatePayload = {
|
|
3358
|
+
doc: {
|
|
3359
|
+
actions: imageActionsArray,
|
|
3360
|
+
updatedAt: now,
|
|
3361
|
+
},
|
|
3362
|
+
};
|
|
3363
|
+
|
|
3364
|
+
return updateOpenSearchData( openSearch.revop, imageDocId, imageUpdatePayload );
|
|
3365
|
+
} );
|
|
3366
|
+
|
|
3367
|
+
// Wait for this batch to finish before starting the next one
|
|
3368
|
+
await Promise.all( updatePromises );
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
if ( results && results?.length > 0 ) {
|
|
3372
|
+
return res.sendError( results, 500 );
|
|
3373
|
+
}
|
|
3374
|
+
// Return batch summary
|
|
3375
|
+
} catch ( error ) {
|
|
3376
|
+
const err = error.message;
|
|
3377
|
+
logger.info( { error: err, function: 'multiCloseTicket' } );
|
|
3378
|
+
return res.sendError( err, 500 );
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
|
|
3383
|
+
export async function checkTicketExists( req, res ) {
|
|
3384
|
+
try {
|
|
3385
|
+
let inputData = req.body;
|
|
3386
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
3387
|
+
let findQuery = {
|
|
3388
|
+
size: 10000,
|
|
3389
|
+
query: {
|
|
3390
|
+
bool: {
|
|
3391
|
+
must: [
|
|
3392
|
+
{
|
|
3393
|
+
term: {
|
|
3394
|
+
'storeId.keyword': inputData.storeId,
|
|
3395
|
+
},
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
term: {
|
|
3399
|
+
'dateString': inputData.dateString,
|
|
3400
|
+
},
|
|
3401
|
+
},
|
|
3402
|
+
],
|
|
3403
|
+
},
|
|
3404
|
+
},
|
|
3405
|
+
};
|
|
3406
|
+
let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
|
|
3407
|
+
let Ticket = findTicket.body?.hits?.hits;
|
|
3408
|
+
|
|
3409
|
+
|
|
3410
|
+
res.sendSuccess( Ticket );
|
|
3411
|
+
} catch ( error ) {
|
|
3412
|
+
const err = error.message;
|
|
3413
|
+
logger.info( { error: err, function: 'checkTicketExists' } );
|
|
3414
|
+
return res.sendError( err, 500 );
|
|
3415
|
+
}
|
|
3416
|
+
}
|