tango-app-api-infra 3.9.5-vms.6 → 3.9.5-vms.61

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,5 +1,5 @@
1
1
  import { chunkArray, download, logger, sendMessageToFIFOQueue, sendMessageToQueue } from 'tango-app-api-middleware';
2
- import { bulkUpdate, getOpenSearchById, getOpenSearchCount, getOpenSearchData, insertWithId, updateOpenSearchData, upsertOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
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';
@@ -7,6 +7,7 @@ import dayjs from 'dayjs';
7
7
  import utc from 'dayjs/plugin/utc.js';
8
8
  import timezone from 'dayjs/plugin/timezone.js';
9
9
  import { findUser } from '../services/user.service.js';
10
+ import { findOneClient } from '../services/client.service.js';
10
11
 
11
12
  dayjs.extend( utc );
12
13
  dayjs.extend( timezone );
@@ -60,6 +61,355 @@ 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
+ if ( Ticket.length === 0 ) {
161
+ return res.sendError( 'Ticket not found', 400 );
162
+ }
163
+ const getTicket = {
164
+ size: 10000,
165
+ query: {
166
+ bool: {
167
+ must: [
168
+ {
169
+ term: {
170
+ 'storeId.keyword': inputData.storeId,
171
+ },
172
+ },
173
+ {
174
+ term: {
175
+ 'dateString': inputData.dateString,
176
+ },
177
+ },
178
+
179
+ ],
180
+ },
181
+ },
182
+ };
183
+ if ( Ticket[0]?._source?.type != 'internal' ) {
184
+ getTicket.query.bool.must.push( {
185
+ term: {
186
+ 'mappingInfo.type': 'tangoreview',
187
+ },
188
+ } );
189
+ }
190
+ const getFootfallticketData = await getOpenSearchData( openSearch.footfallDirectory, getTicket );
191
+ const ticketData = getFootfallticketData?.body?.hits?.hits;
192
+
193
+ if ( !ticketData || ticketData?.length == 0 ) {
194
+ return res.sendError( 'You don’t have any tagged images right now', 400 );
195
+ }
196
+
197
+ const record = {
198
+
199
+ status: 'Closed',
200
+ revicedFootfall: inputData.mappingInfo?.[0]?.revicedFootfall,
201
+ revicedPerc: inputData.mappingInfo?.[0]?.revicedPerc,
202
+ mappingInfo: ticketData?.[0]?._source?.mappingInfo,
203
+ createdByEmail: req?.user?.email,
204
+ createdByUserName: req?.user?.userName,
205
+ createdByRole: req?.user?.role,
206
+
207
+ };
208
+
209
+
210
+ // Retrieve client footfallDirectoryConfigs revision
211
+ let isAutoCloseEnable = getConfig.footfallDirectoryConfigs.isAutoCloseEnable;
212
+ let autoCloseAccuracy = getConfig?.footfallDirectoryConfigs?.autoCloseAccuracy;
213
+
214
+ const getNumber = autoCloseAccuracy.split( '%' )[0];
215
+
216
+ let autoCloseAccuracyValue = parseFloat( ( autoCloseAccuracy || getNumber ).replace( '%', '' ) );
217
+ let revisedPercentage = inputData.mappingInfo?.revicedPerc;
218
+ const revised = Number( revisedPercentage?.split( '%' )[0] );
219
+ const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
220
+ // If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
221
+ if (
222
+ isAutoCloseEnable === true &&
223
+ revisedPercentage >= autoCloseAccuracyValue
224
+ ) {
225
+ record.status = 'Closed';
226
+ // Only keep or modify mappingInfo items with type "review"
227
+ if ( Array.isArray( record.mappingInfo ) ) {
228
+ const temp = record.mappingInfo
229
+ .filter( ( item ) => item.type === 'tangoreview' )
230
+ .map( ( item ) => ( {
231
+ ...item,
232
+
233
+ mode: inputData.mappingInfo?.mode,
234
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
235
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
236
+ count: inputData.mappingInfo?.count,
237
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
238
+ status: 'Closed',
239
+ createdByEmail: req?.user?.email,
240
+ createdByUserName: req?.user?.userName,
241
+ createdByRole: req?.user?.role,
242
+ } ) );
243
+
244
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
245
+ ...temp ];
246
+ // If updating the mapping config to mark [i].status as 'Closed'
247
+ // Make sure all relevant mappingInfo items of type 'approve' are set to status 'Closed'
248
+ if ( Array.isArray( record.mappingInfo ) ) {
249
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
250
+ return {
251
+ ...item,
252
+ status: 'Closed',
253
+ };
254
+ } );
255
+ }
256
+ // If no review mapping existed, push a new one
257
+ if ( record.mappingInfo.length === 0 ) {
258
+ record.mappingInfo.push( {
259
+ type: 'tangoreview',
260
+ mode: inputData.mappingInfo?.mode,
261
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
262
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
263
+ count: inputData.mappingInfo?.count,
264
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
265
+ status: 'Closed',
266
+ createdByEmail: req?.user?.email,
267
+ createdByUserName: req?.user?.userName,
268
+ createdByRole: req?.user?.role,
269
+ } );
270
+ }
271
+ }
272
+ record.mappingInfo.push(
273
+ {
274
+ type: 'finalRevision',
275
+ mode: inputData.mappingInfo?.mode,
276
+ revicedFootfall: revisedFootfall,
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
+ createdAt: new Date(),
285
+ },
286
+ );
287
+ } else if ( revised < tangoReview ) {
288
+ // If ticket is closed, do not proceed with revision mapping
289
+
290
+ record.status = 'Closed - Accuracy Issue';
291
+ // Only keep or modify mappingInfo items with type "review"
292
+ if ( Array.isArray( record.mappingInfo ) ) {
293
+ const temp = record.mappingInfo
294
+ .filter( ( item ) => item.type === 'tangoreview' )
295
+ .map( ( item ) => ( {
296
+ ...item,
297
+ mode: inputData.mappingInfo?.mode,
298
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
299
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
300
+ count: inputData.mappingInfo?.count,
301
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
302
+ status: 'Closed',
303
+ createdByEmail: req?.user?.email,
304
+ createdByUserName: req?.user?.userName,
305
+ createdByRole: req?.user?.role,
306
+ } ) );
307
+
308
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
309
+ ...temp ];
310
+ if ( Array.isArray( record.mappingInfo ) ) {
311
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
312
+ return {
313
+ ...item,
314
+ status: 'Closed',
315
+ };
316
+ } );
317
+ }
318
+ // If no review mapping existed, push a new one
319
+ if ( record.mappingInfo.length === 0 ) {
320
+ record.mappingInfo.push( {
321
+ type: 'tangoreview',
322
+ mode: inputData.mappingInfo?.mode,
323
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
324
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
325
+ count: inputData.mappingInfo?.count,
326
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
327
+ status: 'Closed',
328
+ createdByEmail: req?.user?.email,
329
+ createdByUserName: req?.user?.userName,
330
+ createdByRole: req?.user?.role,
331
+ } );
332
+ }
333
+ }
334
+ } else {
335
+ if ( Array.isArray( record.mappingInfo ) ) {
336
+ const temp = record.mappingInfo
337
+ .filter( ( item ) => item.type === 'tangoreview' )
338
+ .map( ( item ) => ( {
339
+ ...item,
340
+ mode: inputData.mappingInfo?.mode,
341
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
342
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
343
+ count: inputData.mappingInfo?.count,
344
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
345
+ status: 'Closed',
346
+ createdByEmail: req?.user?.email,
347
+ createdByUserName: req?.user?.userName,
348
+ createdByRole: req?.user?.role,
349
+ } ) );
350
+
351
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
352
+ ...temp ];
353
+ if ( Array.isArray( record.mappingInfo ) ) {
354
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
355
+ return {
356
+ ...item,
357
+ status: 'Closed',
358
+ };
359
+ } );
360
+ }
361
+ // If no review mapping existed, push a new one
362
+ if ( record.mappingInfo.length === 0 ) {
363
+ record.mappingInfo.push( {
364
+ type: 'tangoreview',
365
+ mode: inputData.mappingInfo?.mode,
366
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
367
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
368
+ count: inputData.mappingInfo?.count,
369
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
370
+ status: 'Closed',
371
+ createdByEmail: req?.user?.email,
372
+ createdByUserName: req?.user?.userName,
373
+ createdByRole: req?.user?.role,
374
+ } );
375
+ }
376
+ }
377
+ record.mappingInfo.push(
378
+ {
379
+ type: 'finalRevision',
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
+ createdAt: new Date(),
390
+ },
391
+ );
392
+ }
393
+
394
+ let id = `${inputData.storeId}_${inputData.dateString}_footfall-directory-tagging`;
395
+ if ( inputData.ticketType === 'internal' ) {
396
+ id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
397
+ }
398
+
399
+ const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
400
+
401
+ if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
402
+ return res.sendSuccess( 'Ticket closed successfully' );
403
+ } else {
404
+ return res.sendError( 'Internal Server Error', 500 );
405
+ }
406
+ } catch ( error ) {
407
+ const err = error.message || 'Internal Server Error';
408
+ logger.error( { error: err, funtion: 'tangoReviewTicket' } );
409
+ return res.sendError( err, 500 );
410
+ }
411
+ }
412
+
63
413
  async function bulkUpdateStatusToPending( indexName, inputData ) {
64
414
  const bulkBody = [];
65
415
 
@@ -195,7 +545,6 @@ export async function ticketSummary1( req, res ) {
195
545
 
196
546
  const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
197
547
  const aggs = getData?.body?.aggregations;
198
- logger.info( { aggs: aggs, body: getData?.body } );
199
548
 
200
549
  const result = {
201
550
  totalTickets: aggs.totalTicketCount.value,
@@ -220,10 +569,10 @@ export async function ticketSummary( req, res ) {
220
569
  try {
221
570
  let result = '';
222
571
  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 ) ) ) );
572
+ const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
224
573
 
225
- if ( req.user.userType =='tango' ) {
226
- result ={
574
+ if ( req.user.userType == 'tango' ) {
575
+ result = {
227
576
  totalTickets: 0,
228
577
  averageAccuracyOverAll: 0,
229
578
  openTickets: 0,
@@ -234,30 +583,30 @@ export async function ticketSummary( req, res ) {
234
583
  ticketAccuracyBelow: '0%',
235
584
  };
236
585
  } else {
237
- result = req.user.role === 'superadmin'?
238
- {
239
- totalTickets: 0,
240
- openTickets: 0,
241
- inprogress: 0,
242
- closedTickets: 0,
243
- dueToday: 0,
244
- Expired: 0,
245
- underTangoReview: 0,
246
- avgTicket: '0%',
247
- avgAccuracy: '0%',
248
- } :
249
- req.user.role === 'user'? 'NA':
250
- ticketsFeature?
251
- {
252
- totalTickets: 0,
253
- openTickets: 0,
254
- inprogress: 0,
255
- closedTickets: 0,
256
- dueToday: 0,
257
- Expired: 0,
258
- avgTicket: '0%',
259
- avgAccuracy: '0%',
260
- }: 'NA';
586
+ result = req.user.role === 'superadmin' ?
587
+ {
588
+ totalTickets: 0,
589
+ openTickets: 0,
590
+ inprogress: 0,
591
+ closedTickets: 0,
592
+ dueToday: 0,
593
+ Expired: 0,
594
+ underTangoReview: 0,
595
+ avgTicket: '0%',
596
+ avgAccuracy: '0%',
597
+ } :
598
+ req.user.role === 'user' ? 'NA' :
599
+ ticketsFeature ?
600
+ {
601
+ totalTickets: 0,
602
+ openTickets: 0,
603
+ inprogress: 0,
604
+ closedTickets: 0,
605
+ dueToday: 0,
606
+ Expired: 0,
607
+ avgTicket: '0%',
608
+ avgAccuracy: '0%',
609
+ } : 'NA';
261
610
  }
262
611
 
263
612
  return res.sendSuccess( { result: result } );
@@ -293,7 +642,7 @@ export async function ticketList1( req, res ) {
293
642
  terms: {
294
643
  'clientId.keyword': Array.isArray( inputData.clientId ) ?
295
644
  inputData.clientId :
296
- [ inputData.clientId ],
645
+ [ inputData.clientId ],
297
646
  },
298
647
  },
299
648
  ];
@@ -470,12 +819,12 @@ export async function ticketList1( req, res ) {
470
819
  'Ticket raised on': dayjs( item._source.createdAt ).format( 'DD MMM, YYYY' ),
471
820
  'Issue Date': dayjs( item._source.dateString ).format( 'DD MMM, YYYY' ),
472
821
  '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'?`${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 )} %`,
822
+ 'Duplicates': item?._source?.status === 'closed' ? item._source.duplicateACCount : item._source.duplicateCount,
823
+ 'Employee/Staff': item?._source?.status === 'closed' ? item._source.employeeACCount : item._source.employeeCount,
824
+ 'HouseKeeping': item?._source?.status === 'closed' ? item._source.houseKeepingACCount : item._source.houseKeepingCount,
825
+ 'Junk': item?._source?.status === 'closed' ? ( item._source.junkACCount || 0 ) : ( item._source.junkCount || 0 ),
826
+ '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 ) ),
827
+ '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
828
  'Status': item._source.status,
480
829
  } );
481
830
  }
@@ -491,671 +840,296 @@ export async function ticketList1( req, res ) {
491
840
 
492
841
  export async function ticketList( req, res ) {
493
842
  try {
494
- let result = '';
495
- const inputData= req.query;
843
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
844
+ const inputData = req.query;
496
845
  const userInfo = req.user;
497
- const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name =='reviewer' && ( m.isAdd==true || m.isEdit==true ) ) ) );
846
+ const limit = inputData?.limit || 10;
847
+ const offset = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
848
+ inputData.clientId = inputData?.clientId?.split( ',' ); // convert strig to array
498
849
 
499
- if ( req.user.userType =='tango' ) {
500
- result =inputData.tangotyep == 'store'?
850
+ const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
501
851
 
852
+ const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
502
853
 
503
- [
504
- {
505
- ticketId: 'TE_FDT_1763539990306',
506
- storeId: '11-1716',
507
- storeName: 'LKST1916',
508
- ticketRaised: '2025-11-16',
509
- issueDate: '2025-11-16',
510
- dueDate: '2025-11-18',
511
- footfall: 1200,
512
- storeRevisedAccuracy: '98%',
513
- reviewerRevisedAccuracy: '97%',
514
- approverRevisedAccuracy: '98%',
515
- tangoRevisedAccuracy: '98%',
516
- status: 'Closed',
517
- },
518
- {
519
- ticketId: 'TE_FDT_1763860421803',
520
- storeId: '11-1716',
521
- storeName: 'LKST1916',
522
- ticketRaised: '2025-11-21',
523
- issueDate: '2025-11-20',
524
- dueDate: '2025-11-26',
525
- footfall: 94,
526
- storeRevisedAccuracy: '90%',
527
- reviewerRevisedAccuracy: '--',
528
- approverRevisedAccuracy: '--',
529
- tangoRevisedAccuracy: '--',
530
- status: 'Open',
531
- },
532
- {
533
- ticketId: 'TE_FDT_1763711403163',
534
- storeId: '11-1716',
535
- storeName: 'LKST1916',
536
- ticketRaised: '2025-11-20',
537
- issueDate: '2025-11-19',
538
- dueDate: 'Due Today',
539
- footfall: 94,
540
- storeRevisedAccuracy: '95%',
541
- reviewerRevisedAccuracy: '--',
542
- approverRevisedAccuracy: '--',
543
- tangoRevisedAccuracy: '--',
544
- status: 'Open',
545
- },
546
- {
547
- ticketId: 'TE_FDT_1763539990309',
548
- storeId: '11-2000',
549
- storeName: 'LKST2368',
550
- ticketRaised: '2025-11-13',
551
- issueDate: '2025-11-13',
552
- dueDate: '2025-11-15',
553
- footfall: 1280,
554
- storeRevisedAccuracy: '98%',
555
- reviewerRevisedAccuracy: '98%',
556
- approverRevisedAccuracy: '97%',
557
- tangoRevisedAccuracy: '97%',
558
- status: 'Closed',
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',
713
- },
854
+ const searchQuery = {
714
855
 
715
- ] :
716
- [
856
+ size: limit, // or use parseInt(req.query.limit) for dynamic
857
+ from: offset, // or use parseInt(req.query.offset) for dynamic
858
+ sort: [ { 'createdAt': { order: 'desc' } } ],
717
859
 
718
- {
719
- ticketId: 'TE_FDT_1763539990306',
720
- storeId: '11-1716',
721
- storeName: 'LKST1916',
722
- ticketRaised: '2025-11-16',
723
- issueDate: '2025-11-16',
724
- footfall: 200,
725
- type: 'internal',
726
- storeRevisedAccuracy: '98%',
727
- reviewerRevisedAccuracy: '97%',
728
- approverRevisedAccuracy: '98%',
729
- tangoRevisedAccuracy: '98%',
730
- status: 'Closed',
731
- tangoStatus: 'Open',
860
+ query: {
861
+ bool: {
862
+ must: [
863
+ {
864
+ 'range': {
865
+ 'dateString': {
866
+ 'gte': inputData.fromDate,
867
+ 'lte': inputData.toDate,
868
+ 'format': 'yyyy-MM-dd',
869
+ },
870
+ },
871
+ },
872
+ {
873
+ terms: {
874
+ 'clientId.keyword': Array.isArray( inputData.clientId ) ?
875
+ inputData.clientId :
876
+ [ inputData.clientId ],
877
+ },
878
+
879
+ },
880
+ ],
732
881
  },
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',
882
+ },
883
+ };
884
+
885
+ if ( inputData.sortBy ) {
886
+ let sortOrder = inputData.sortOrder === 1? 'asc': 'desc';
887
+
888
+ // Remove default sort so we don't duplicate/conflict
889
+ // INSERT_YOUR_CODE
890
+ // If sortBy is present, check if the field needs ".keyword" (for string fields like storeName, storeId, ticketId)
891
+ // This avoids OpenSearch errors about sorting on text fields.
892
+ const stringKeywordFields = [ 'storeName', 'storeId', 'ticketId', 'status', 'type', 'clientId' ];
893
+ let sortField = inputData.sortBy == 'footfall'? 'footfallCount' :inputData.sortBy == 'issueDate'?'dateString':inputData.sortBy;
894
+ if ( stringKeywordFields.includes( sortField ) ) {
895
+ sortField = `${sortField}.keyword`;
896
+ }
897
+
898
+ // Remove default sort so we don't duplicate/conflict
899
+ searchQuery.sort = [
900
+ { [sortField]: { order: sortOrder } },
901
+ ];
902
+ }
903
+ // Example: Filtering by storeId if present in the query
904
+ if ( inputData.storeId ) {
905
+ inputData.storeId = inputData?.storeId?.split( ',' );
906
+ searchQuery.query.bool.must.push( {
907
+ terms: {
908
+ 'storeId.keyword': Array.isArray( inputData.storeId ) ?
909
+ inputData.storeId :
910
+ [ inputData.storeId ],
747
911
  },
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',
912
+ } );
913
+ }
914
+
915
+ if ( req?.user?.userType == 'tango' && inputData.tangoType !== 'internal' ) {
916
+ searchQuery.query.bool.must.push( {
917
+ term: {
918
+ 'mappingInfo.type': 'tangoreview',
761
919
  },
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',
920
+ },
921
+ {
922
+ term: {
923
+ 'type.keyword': 'store',
776
924
  },
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',
925
+ },
926
+
927
+
928
+ );
929
+ } else if ( inputData.tangoType !== 'internal' ) {
930
+ searchQuery.query.bool.must.push( {
931
+ term: {
932
+ 'mappingInfo.type': ticketsFeature ? 'review' : ticketsApproveFeature ?
933
+ 'approve' :'tagging',
791
934
  },
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',
935
+ },
936
+ {
937
+ term: {
938
+ 'type.keyword': 'store',
806
939
  },
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',
940
+ } );
941
+ }
942
+
943
+ if ( inputData?.permissionType ) {
944
+ searchQuery.query.bool.must.push( {
945
+ term: {
946
+ 'mappingInfo.type': inputData?.permissionType == 'review' ? 'review' :
947
+ 'approve',
821
948
  },
949
+ } );
950
+ }
951
+
952
+ if ( inputData.searchValue && inputData.searchValue !== '' ) {
953
+ searchQuery.query.bool['should'] =[];
954
+ searchQuery.query.bool.should=[
955
+
822
956
  {
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',
957
+ 'wildcard': {
958
+ 'storeName.keyword': {
959
+ 'value': `*${inputData.searchValue}*`,
960
+ },
961
+ },
836
962
  },
837
963
  {
838
- ticketId: 'TE_FDT_1763539990314',
839
- storeId: '11-10',
840
- storeName: 'LKST80',
841
- ticketRaised: '2023-11-15',
842
- issueDate: '2023-11-15',
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',
964
+ 'wildcard': {
965
+ 'storeId.keyword': {
966
+ 'value': `*${inputData.searchValue}*`,
967
+ },
968
+ },
851
969
  },
852
970
  {
853
- ticketId: 'TE_FDT_1763539990320',
854
- storeId: '14-8002',
855
- storeName: 'LKST4590',
856
- ticketRaised: '2025-09-12',
857
- issueDate: '2025-09-12',
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',
971
+ 'wildcard': {
972
+ 'ticketId.keyword': {
973
+ 'value': `*${inputData.searchValue}*`,
974
+ },
975
+ },
866
976
  },
977
+
978
+
867
979
  ];
980
+ searchQuery.query.bool['minimum_should_match'] = 1;
981
+ }
982
+ // You can add more filters as needed
983
+ const searchResult = await getOpenSearchData( openSearch.footfallDirectory, searchQuery );
984
+ logger.info( { searchResult } );
985
+ const count = searchResult?.body?.hits?.total?.value || 0;
986
+
987
+ if ( count === 0 ) {
988
+ return res.sendError( 'no data found', 204 );
989
+ }
990
+ const ticketListData = searchResult?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
991
+
992
+ let temp = [];
993
+ if ( req.user.userType == 'tango' ) {
994
+ if ( inputData.tangotype == 'store' ) {
995
+ for ( let item of ticketListData ) {
996
+ temp.push( {
997
+
998
+ ticketId: item?.ticketId,
999
+ storeId: item?.storeId,
1000
+ storeName: item?.storeName,
1001
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1002
+ issueDate: item?.dateString,
1003
+ dueDate: '',
1004
+ footfall: item?.footfallCount,
1005
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1006
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1007
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1008
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1009
+ status: item?.status,
1010
+
1011
+ } );
1012
+ }
1013
+ } else {
1014
+ for ( let item of ticketListData ) {
1015
+ temp.push( {
1016
+
1017
+ ticketId: item?.ticketId,
1018
+ storeId: item?.storeId,
1019
+ storeName: item?.storeName,
1020
+
1021
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1022
+ issueDate: item?.dateString,
1023
+ footfall: item?.footfallCount,
1024
+ dueDate: '',
1025
+ type: item?.type || 'store',
1026
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1027
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1028
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1029
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1030
+ status: item?.status,
1031
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1032
+
1033
+ } );
1034
+ }
1035
+ }
868
1036
  } else {
869
- result = req.user.role === 'superadmin'?
870
- [
871
- {
872
- ticketId: 'TE_FDT_1763539990320',
873
- storeId: '11-1716',
874
- storeName: 'LKST1916',
875
- ticketRaised: '2025-11-13',
876
- issueDate: '2025-11-12',
877
- dueDate: '2025-11-14',
878
- footfall: 60,
879
- storeRevisedAccuracy: '45%',
880
- reviewerRevisedAccuracy: '67%',
881
- approverRevisedAccuracy: '87%',
882
- tangoRevisedAccuracy: '93%',
883
- status: 'Open',
884
- approvedBy: '',
885
- },
886
- {
887
- ticketId: 'TE_FDT_1763539990321',
888
- storeId: '11-1716',
889
- storeName: 'LKST1916',
890
- ticketRaised: '2025-11-11',
891
- issueDate: '2025-11-10',
892
- dueDate: '2025-11-12',
893
- footfall: 69,
894
- storeRevisedAccuracy: '79%',
895
- reviewerRevisedAccuracy: '80%',
896
- approverRevisedAccuracy: '90%',
897
- tangoRevisedAccuracy: '90%',
898
- status: 'In-Progress',
899
- approvedBy: 'mu_mu@yopmail.com',
900
- },
901
- {
902
- ticketId: 'TE_FDT_1763860421803',
903
- storeId: '11-1716',
904
- storeName: 'LKST1916',
905
- ticketRaised: '2025-11-21',
906
- issueDate: '2025-11-20',
907
- dueDate: '2025-11-26',
908
- footfall: 94,
909
- storeRevisedAccuracy: '90%',
910
- reviewerRevisedAccuracy: '90%',
911
- approverRevisedAccuracy: '90%',
912
- tangoRevisedAccuracy: '90%',
913
- status: 'Closed',
914
- approvedBy: 'mu_mu@yopmail.com',
915
- },
916
- {
917
- ticketId: 'TE_FDT_1763711403163',
918
- storeId: '11-1716',
919
- storeName: 'LKST1916',
920
- ticketRaised: '2025-11-20',
921
- issueDate: '2025-11-19',
922
- dueDate: 'Due Today',
923
- footfall: 94,
924
- storeRevisedAccuracy: '95%',
925
- reviewerRevisedAccuracy: '--',
926
- approverRevisedAccuracy: '--',
927
- tangoRevisedAccuracy: '--',
928
- status: 'In-Progress',
929
- approvedBy: 'mu_mu@yopmail.com',
930
- },
931
- {
932
- ticketId: 'TE_FDT_1763539990320',
933
- storeId: '11-1716',
934
- storeName: 'LKST1916',
935
- ticketRaised: '2025-11-15',
936
- issueDate: '2025-11-14',
937
- dueDate: '2025-11-17',
938
- footfall: 110,
939
- storeRevisedAccuracy: '90%',
940
- reviewerRevisedAccuracy: '--',
941
- approverRevisedAccuracy: '--',
942
- tangoRevisedAccuracy: '--',
943
- status: 'Open',
944
- approvedBy: '',
945
- },
946
- {
947
- ticketId: 'TE_FDT_1763539990323',
948
- storeId: '11-10',
949
- storeName: 'LKST80',
950
- ticketRaised: '2025-11-13',
951
- issueDate: '2025-11-12',
952
- dueDate: '2025-11-14',
953
- footfall: 170,
954
- storeRevisedAccuracy: '90%',
955
- reviewerRevisedAccuracy: '90%',
956
- approverRevisedAccuracy: '--',
957
- tangoRevisedAccuracy: '--',
958
- status: 'Open',
959
- approvedBy: '',
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_17635399903490',
1027
- storeId: '11-1716',
1028
- storeName: 'LKST1916',
1029
- ticketRaised: '2025-11-16',
1030
- issueDate: '2025-11-15',
1031
- dueDate: 'Due Today',
1032
- footfall: 213,
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-16',
1043
- issueDate: '2025-11-14',
1044
- dueDate: '2025-11-18',
1045
- footfall: 90,
1046
- storeRevisedAccuracy: '90%',
1047
- reviewerRevisedAccuracy: '--',
1048
- status: 'Expired',
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';
1037
+ if ( inputData?.permissionType ==='approve' ) {
1038
+ for ( let item of ticketListData ) {
1039
+ temp.push( {
1040
+
1041
+ ticketId: item?.ticketId,
1042
+ storeId: item?.storeId,
1043
+ storeName: item?.storeName,
1044
+
1045
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1046
+ issueDate: item?.dateString,
1047
+ dueDate: '',
1048
+ footfall: item?.footfallCount,
1049
+
1050
+ type: item.type || 'store',
1051
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1052
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1053
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1054
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1055
+ status: item?.status,
1056
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1057
+ approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
1058
+
1059
+ } );
1060
+ }
1061
+ } else if ( inputData?.permissionType ==='review' ) {
1062
+ for ( let item of ticketListData ) {
1063
+ temp.push( {
1064
+
1065
+ ticketId: item?.ticketId,
1066
+ storeId: item?.storeId,
1067
+ storeName: item?.storeName,
1068
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1069
+ issueDate: item?.dateString,
1070
+ footfall: item?.footfallCount,
1071
+ dueDate: '',
1072
+ type: item.type || 'store',
1073
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1074
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1075
+
1076
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
1077
+ ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
1078
+
1079
+ } );
1080
+ }
1081
+ } else if ( req.user.role === 'user' ) {
1082
+ temp = [];
1083
+ } else if ( ticketsFeature ) {
1084
+ for ( let item of ticketListData ) {
1085
+ temp.push( {
1086
+
1087
+ ticketId: item?.ticketId,
1088
+ storeId: item?.storeId,
1089
+ storeName: item?.storeName,
1090
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1091
+ issueDate: item?.dateString,
1092
+ footfall: item?.footfallCount,
1093
+ dueDate: '',
1094
+ type: item.type || 'store',
1095
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1096
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1097
+
1098
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
1099
+ ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
1100
+
1101
+ } );
1102
+ }
1103
+ } else if ( ticketsApproveFeature ) {
1104
+ for ( let item of ticketListData ) {
1105
+ temp.push( {
1106
+
1107
+ ticketId: item?.ticketId,
1108
+ storeId: item?.storeId,
1109
+ storeName: item?.storeName,
1110
+
1111
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1112
+ issueDate: item?.dateString,
1113
+ dueDate: '',
1114
+ footfall: item?.footfallCount,
1115
+
1116
+ type: item.type || 'store',
1117
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1118
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1119
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1120
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1121
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
1122
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1123
+ approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
1124
+
1125
+ } );
1126
+ }
1127
+ } else {
1128
+ temp = [];
1129
+ }
1156
1130
  }
1157
1131
 
1158
- return res.sendSuccess( { result: result } );
1132
+ return res.sendSuccess( { result: temp, count: count } );
1159
1133
  } catch ( error ) {
1160
1134
  const err = error.message || 'Internal Server Error';
1161
1135
  logger.error( { error: error, messgage: req.query } );
@@ -1167,149 +1141,19 @@ export async function getTickets( req, res ) {
1167
1141
  try {
1168
1142
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1169
1143
  const inputData = req.query;
1170
- const limit = inputData.limit;
1171
- const skip = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
1172
- inputData.storeId = inputData.storeId.split( ',' ); // convert strig to array
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' ];
1144
+
1145
+
1146
+ 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
1147
  let filter = [
1176
1148
 
1177
1149
  {
1178
- range: {
1179
- dateString: {
1180
- gte: inputData.fromDate,
1181
- lte: inputData.toDate,
1182
- format: 'yyyy-MM-dd',
1183
- },
1150
+ term: {
1151
+ 'ticketId.keyword': inputData.ticketId,
1184
1152
  },
1185
1153
  },
1186
1154
  ];
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
1155
 
1294
1156
 
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
1157
  const getCount = {
1314
1158
  query: {
1315
1159
  bool: {
@@ -1320,20 +1164,14 @@ export async function getTickets( req, res ) {
1320
1164
 
1321
1165
 
1322
1166
  const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
1323
- const geteRevDataCount = inputData?.revopsType ? await getOpenSearchData( openSearch.footfallDirectory, getRevCount ) : null;
1324
- const revCount = inputData?.revopsType ? geteRevDataCount?.body?.aggregations?.totalCount?.value : 0;
1325
- const count = geteDataCount?.body?.count;
1326
- if ( !geteDataCount || count == 0 ) {
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 );
1167
+
1168
+
1169
+ if ( !geteDataCount ) {
1170
+ return res.sendError( `No Pending items in the selected dates for the store`, 400 );
1332
1171
  }
1333
1172
 
1334
1173
  const getQuery = {
1335
- size: limit,
1336
- from: skip,
1174
+ size: 1,
1337
1175
  query: {
1338
1176
  bool: {
1339
1177
  filter: filter,
@@ -1343,14 +1181,9 @@ export async function getTickets( req, res ) {
1343
1181
  };
1344
1182
 
1345
1183
  const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
1346
-
1347
1184
  const response = getData?.body?.hits?.hits;
1348
1185
  if ( !response || response.length == 0 ) {
1349
- if ( inputData.storeId?.length > 0 ) {
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 );
1186
+ return res.sendError( `No Pending items in the selected dates for the store`, 400 );
1354
1187
  }
1355
1188
  let temp = [];
1356
1189
  if ( inputData?.action ) {
@@ -1388,29 +1221,31 @@ export async function getTickets( req, res ) {
1388
1221
  approverUserName: hit?._source?.approverUserName,
1389
1222
  approverEmail: hit?._source?.approverEmail,
1390
1223
  approverRole: hit?._source?.approverRole,
1224
+ type: hit?._source?.type,
1391
1225
  };
1392
1226
  let result;
1393
1227
 
1228
+
1394
1229
  const matched = hit.matched_queries;
1395
1230
  // Add only matched data array
1396
- if ( matched.includes( 'matched_employee' )&& ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
1231
+ if ( matched.includes( 'matched_employee' ) && ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
1397
1232
  result = defaultData;
1398
1233
  result.employee = hit?._source?.employee;
1399
1234
  // result.type = 'employee';
1400
1235
  result.matched = matched;
1401
1236
  }
1402
- if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
1237
+ if ( matched.includes( 'matched_housekeeping' ) && ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
1403
1238
  result = defaultData;
1404
1239
  result.houseKeeping = hit?._source?.houseKeeping;
1405
1240
  result.matched = matched;
1406
1241
  }
1407
- if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
1242
+ if ( matched.includes( 'matched_duplicate' ) && ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
1408
1243
  result = defaultData;
1409
1244
  result.duplicateImages = hit?._source?.duplicateImages;
1410
1245
  result.matched = matched;
1411
1246
  }
1412
1247
 
1413
- if ( matched.includes( 'matched_junk' )&& ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
1248
+ if ( matched.includes( 'matched_junk' ) && ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
1414
1249
  result = defaultData;
1415
1250
  result.junk = hit?._source?.junk;
1416
1251
  result.matched = matched;
@@ -1429,15 +1264,242 @@ export async function getTickets( req, res ) {
1429
1264
  }
1430
1265
  } );
1431
1266
  }
1267
+
1432
1268
  const finalResponse = inputData.action ? temp : response;
1269
+ const getRevopQuery = {
1270
+ size: 10000,
1271
+ query: {
1272
+ bool: {
1273
+ must: [
1274
+ { term: { 'storeId.keyword': response?.[0]?._source?.storeId } }, // assuming inputData.storeId is an array
1275
+ { term: { 'dateString': response?.[0]?._source?.dateString } },
1276
+ ],
1277
+ },
1278
+ },
1279
+ };
1280
+
1281
+
1282
+ const revopResp = await getOpenSearchData( openSearch.revop, getRevopQuery );
1283
+
1284
+
1285
+ // Map revopResp.body.hits.hits to revopSources
1286
+ const revopSources = Array.isArray( revopResp?.body?.hits?.hits ) ?
1287
+ revopResp?.body?.hits?.hits?.map( ( hit ) => hit._source ) :
1288
+ [];
1289
+
1290
+ // Create a map of revopSources by id for quick lookup
1291
+ const revopSourcesMap = new Map();
1292
+ revopSources.forEach( ( item ) => {
1293
+ if ( item?.id ) {
1294
+ revopSourcesMap.set( String( item.id ), item );
1295
+ }
1296
+ } );
1297
+
1298
+ // Process revopSources to replace duplicateImage entries with full objects from the array
1299
+ const processedRevopSources = revopSources.map( ( item ) => {
1300
+ // Check if this is a duplicate parent item
1301
+ if ( item?.revopsType === 'duplicate' && item?.isParent === true && Array.isArray( item?.duplicateImage ) ) {
1302
+ // Map each duplicateImage entry to the full object from revopSources
1303
+ const updatedDuplicateImage = item.duplicateImage.map( ( duplicateImg ) => {
1304
+ const duplicateId = String( duplicateImg?.id );
1305
+ // Find the full object in revopSources that matches this id
1306
+ const fullObject = revopSourcesMap.get( duplicateId );
1307
+ // Return the full object if found, otherwise return the original duplicateImg
1308
+ return fullObject || duplicateImg;
1309
+ } );
1310
+
1311
+ return {
1312
+ ...item,
1313
+ duplicateImage: updatedDuplicateImage,
1314
+ };
1315
+ }
1316
+ // Return item as-is if it doesn't meet the criteria
1317
+ return item;
1318
+ } );
1319
+
1433
1320
  if ( finalResponse?.length == 0 || !finalResponse ) {
1434
1321
  if ( inputData.storeId?.length > 0 ) {
1435
- const getStoreName = await findOneStore( { storeId: inputData.storeId }, { storeName: 1 } );
1322
+ const getStoreName = await findOneStore( { storeId: response?.[0]?._source?.storeId }, { storeName: 1 } );
1436
1323
  return res.sendError( `No Pending items in the selected dates for the store ${getStoreName?.storeName || ''}`, 400 );
1437
1324
  }
1438
1325
  return res.sendError( 'No Data found', 204 );
1439
1326
  }
1440
- return res.sendSuccess( { result: finalResponse, count: count, revopCount: revCount } );
1327
+
1328
+
1329
+ // replace the mappingInfo.revisedDetail with processedRevopSources
1330
+ if ( Array.isArray( finalResponse ) ) {
1331
+ for ( let item of finalResponse ) {
1332
+ if (
1333
+ item &&
1334
+ item._source &&
1335
+ item._source.mappingInfo
1336
+
1337
+ ) {
1338
+ item._source.mappingInfo.revisedDetail = processedRevopSources;
1339
+ const vmsCommentsLogIndex = openSearch.vmsCommentsLog || 'vms-comments-log-dev';
1340
+ let commentsResponse = [];
1341
+ const commentsFilter = [
1342
+ { term: { 'storeId.keyword': item?._source?.storeId } },
1343
+ { term: { dateString: item?._source?.dateString } },
1344
+
1345
+ ];
1346
+
1347
+ const commentsQuery = {
1348
+ size: 10000,
1349
+ sort: [
1350
+ { createdAt: { order: 'desc' } }, // Sort descending by createdAt
1351
+ ],
1352
+ query: {
1353
+ bool: {
1354
+ filter: commentsFilter,
1355
+ },
1356
+ },
1357
+ };
1358
+
1359
+ const commentsRes = await getOpenSearchData( vmsCommentsLogIndex, commentsQuery );
1360
+ // If mappingInfo is an array, update revisedDetail for each mappingInfo object
1361
+ if ( Array.isArray( item._source.mappingInfo ) ) {
1362
+ item._source.mappingInfo.forEach( ( mappingObj ) => {
1363
+ if ( mappingObj.status == 'In-Progress' ) {
1364
+ commentsResponse = commentsRes?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
1365
+
1366
+ // Check if duplicate condition exists in commentsResponse
1367
+ const isDuplicate = Array.isArray( commentsResponse ) &&
1368
+ commentsResponse.some( ( c ) => c.category === 'duplicate' );
1369
+
1370
+ // Structure comments output
1371
+ let commentsDetails = [];
1372
+ if ( isDuplicate ) {
1373
+ // Duplicate case - check from commentsResponse
1374
+ // Collect for each type (tagging, review, approve)
1375
+ const types = [ 'tagging', 'review', 'approve' ];
1376
+ commentsDetails = types.map( ( typeValue ) => {
1377
+ // parent value from original comment structure
1378
+ let parent = null;
1379
+ // Get all comments of this type (no filter by category)
1380
+ let comms = commentsResponse
1381
+ .filter( ( c ) => c.type === typeValue )
1382
+ .map( ( c ) => {
1383
+ if ( typeValue === 'tagging' ) {
1384
+ parent = c.parent;
1385
+ return {
1386
+ createdByEmail: c.createdByEmail,
1387
+ createdByUserName: c.createdByUserName,
1388
+ createdByRole: c.createdByRole,
1389
+ message: c.message,
1390
+ };
1391
+ }
1392
+ if ( typeValue === 'review' || typeValue === 'approve' ) {
1393
+ return {
1394
+ parent: c.parent,
1395
+ category: c.category,
1396
+ taggedImages: Array.isArray( c.taggedImages ) ?
1397
+ c.taggedImages.map( ( img ) => ( {
1398
+ id: img?._source?.id,
1399
+ tempId: img?._source?.tempId,
1400
+ timeRange: img?._source?.timeRange,
1401
+ entryTime: img?._source?.entryTime,
1402
+ exitTime: img?._source?.exitTime,
1403
+ filePath: img?._source?.filePath,
1404
+ isChecked: img?._source?.isChecked,
1405
+ } ) ) :
1406
+ [],
1407
+ createdByEmail: c.createdByEmail,
1408
+ createdByUserName: c.createdByUserName,
1409
+ createdByRole: c.createdByRole,
1410
+ status: c.status,
1411
+ message: c.message,
1412
+ };
1413
+ }
1414
+ return {};
1415
+ } );
1416
+ return {
1417
+ ...( typeValue === 'tagging' ? { category: 'duplicate' } : {} ),
1418
+ type: typeValue,
1419
+ ...( typeValue === 'tagging' ? { parent } : {} ),
1420
+ comments: comms,
1421
+ };
1422
+ } );
1423
+ } else {
1424
+ // For non-duplicate categories
1425
+ // Collect by type/tag (tagging/review/approve) and build similar structure
1426
+ const types = [ 'tagging', 'review', 'approve' ];
1427
+ commentsDetails = types.map( ( typeValue ) => {
1428
+ // parent for these non-duplicate is always null
1429
+ let comms = commentsResponse
1430
+ .filter( ( c ) => c.type === typeValue )
1431
+ .map( ( c ) => {
1432
+ if ( typeValue === 'tagging' ) {
1433
+ return {
1434
+ id: c.id,
1435
+ tempId: c.tempId,
1436
+ timeRange: c.timeRange,
1437
+ entryTime: c.entryTime,
1438
+ exitTime: c.exitTime,
1439
+ filePath: c.filePath,
1440
+ isChecked: c.isChecked,
1441
+ createdAt: c.createdAt,
1442
+ message: c.message,
1443
+ createdByEmail: c.createdByEmail,
1444
+ createdByUserName: c.createdByUserName,
1445
+ createdByRole: c.createdByRole,
1446
+ };
1447
+ }
1448
+ if ( typeValue === 'review' || typeValue === 'approve' ) {
1449
+ return {
1450
+ category: c.category,
1451
+ taggedImages: Array.isArray( c.taggedImages ) ?
1452
+ c.taggedImages.map( ( img ) => ( {
1453
+ id: img?._source?.id,
1454
+ tempId: img?._source?.tempId,
1455
+ timeRange: img?._source?.timeRange,
1456
+ entryTime: img?._source?.entryTime,
1457
+ exitTime: img?._source?.exitTime,
1458
+ filePath: img?._source?.filePath,
1459
+ isChecked: img?._source?.isChecked,
1460
+ } ) ) :
1461
+ [],
1462
+ createdByEmail: c.createdByEmail,
1463
+ createdByUserName: c.createdByUserName,
1464
+ createdByRole: c.createdByRole,
1465
+ status: c.status,
1466
+ message: c.message,
1467
+ };
1468
+ }
1469
+ return {};
1470
+ } );
1471
+ return {
1472
+ ...( typeValue === 'tagging' ? { category: 'duplicate' } : {} ),
1473
+ parent: null,
1474
+ type: typeValue,
1475
+ comments: comms,
1476
+ };
1477
+ } );
1478
+ }
1479
+
1480
+ item._source.commentsDetails = commentsDetails;
1481
+ }
1482
+ if (
1483
+ Object.prototype.hasOwnProperty.call( mappingObj, 'revisedDetail' ) &&
1484
+ mappingObj.type !== 'tangoreview'
1485
+ ) {
1486
+ mappingObj.revisedDetail = processedRevopSources;
1487
+ }
1488
+ } );
1489
+ } else {
1490
+ item._source.mappingInfo.revisedDetail = processedRevopSources;
1491
+ }
1492
+ }
1493
+ }
1494
+ } else if (
1495
+ finalResponse &&
1496
+ finalResponse._source &&
1497
+ finalResponse._source.mappingInfo
1498
+ ) {
1499
+ finalResponse._source.mappingInfo.revisedDetail = processedRevopSources;
1500
+ }
1501
+
1502
+ return res.sendSuccess( { result: finalResponse } );
1441
1503
  } catch ( error ) {
1442
1504
  const err = error.message || 'Internal Server Error';
1443
1505
  logger.error( { error: error, messgage: req.query } );
@@ -1509,7 +1571,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
1509
1571
  item.isChecked == true ? tempId.push( { tempId: item.tempId, timeRange: item.timeRange } ) : null;
1510
1572
  bulkBody.push(
1511
1573
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${item.timeRange}_${item.tempId}` } },
1512
- { doc: { isChecked: item.isChecked, status: item?.isChecked == true? 'approved':'rejected' } },
1574
+ { doc: { isChecked: item.isChecked, status: item?.isChecked == true ? 'approved' : 'rejected' } },
1513
1575
  );
1514
1576
  } );
1515
1577
  }
@@ -1524,11 +1586,11 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
1524
1586
  updateData.employee = updatedEmployee;
1525
1587
  await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: updateData } );
1526
1588
  for ( let employee of updateData?.employee ) {
1527
- ( employee.isChecked == true ) ? tempId.push( { tempId: employee.tempId, timeRange: employee.timeRange } ) : null;
1528
- bulkBody.push(
1529
- { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${employee.timeRange}_${employee.tempId}` } },
1530
- { doc: { isChecked: employee.isChecked, status: employee?.isChecked == true? 'approved':'rejected' } },
1531
- );
1589
+ ( employee.isChecked == true ) ? tempId.push( { tempId: employee.tempId, timeRange: employee.timeRange } ) : null;
1590
+ bulkBody.push(
1591
+ { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${employee.timeRange}_${employee.tempId}` } },
1592
+ { doc: { isChecked: employee.isChecked, status: employee?.isChecked == true ? 'approved' : 'rejected' } },
1593
+ );
1532
1594
  }
1533
1595
  await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { employeeACCount: tempId?.length || 0 } } );
1534
1596
  }
@@ -1547,7 +1609,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
1547
1609
  houseKeeping.isChecked == true ? tempId.push( { tempId: houseKeeping.tempId, timeRange: houseKeeping.timeRange } ) : null;
1548
1610
  bulkBody.push(
1549
1611
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${houseKeeping.timeRange}_${houseKeeping.tempId}` } },
1550
- { doc: { isChecked: houseKeeping.isChecked, status: houseKeeping?.isChecked == true? 'approved':'rejected' } },
1612
+ { doc: { isChecked: houseKeeping.isChecked, status: houseKeeping?.isChecked == true ? 'approved' : 'rejected' } },
1551
1613
  );
1552
1614
  }
1553
1615
  await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { houseKeepingACCount: tempId?.length || 0 } } );
@@ -1568,7 +1630,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
1568
1630
  junk.isChecked == true ? tempId.push( { tempId: junk.tempId, timeRange: junk.timeRange } ) : null;
1569
1631
  bulkBody.push(
1570
1632
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${junk.timeRange}_${junk.tempId}` } },
1571
- { doc: { isChecked: junk.isChecked, status: junk?.isChecked == true? 'approved':'rejected' } },
1633
+ { doc: { isChecked: junk.isChecked, status: junk?.isChecked == true ? 'approved' : 'rejected' } },
1572
1634
  );
1573
1635
  }
1574
1636
  await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { junkACCount: tempId?.length || 0 } } );
@@ -1593,7 +1655,7 @@ export async function updateTicketStatus( data, openSearch, temp, user ) {
1593
1655
  let getUpdateExistingOne = await getOpenSearchById( openSearch.footfallDirectory, _id );
1594
1656
  const tempIdList = await extractCheckedTempIds( getUpdateExistingOne?.body );
1595
1657
  const isSendMessge = await sendSqsMessage( data, tempIdList, getStoreType, storeId );
1596
- if ( isSendMessge ==true ) {
1658
+ if ( isSendMessge == true ) {
1597
1659
  return true; // res.sendSuccess( 'Ticket has been updated successfully' );
1598
1660
  } else {
1599
1661
  return false; // res.sendError( 'No SQS message sent', 500 );
@@ -1656,7 +1718,7 @@ function updateEmployeeCheckFlags( existingEmployees, inputEmployees, status ) {
1656
1718
  // Step 2: Loop through all existing and update isChecked accordingly
1657
1719
  const updatedEmployees = existingEmployees.map( ( emp ) => ( {
1658
1720
  ...emp,
1659
- isChecked: status === 'rejected'? !checkedTempIds.has( emp.tempId ):checkedTempIds.has( emp.tempId ),
1721
+ isChecked: status === 'rejected' ? !checkedTempIds.has( emp.tempId ) : checkedTempIds.has( emp.tempId ),
1660
1722
  } ) );
1661
1723
 
1662
1724
  return updatedEmployees;
@@ -1706,7 +1768,7 @@ function mergeDuplicateImagesWithUncheck( existingData, inputData, status ) {
1706
1768
  export async function sendSqsMessage( inputData, tempId, getStoreType, storeId ) {
1707
1769
  const sqs = JSON.parse( process.env.SQS );
1708
1770
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1709
- const sqsName = getStoreType > 0? sqs.revopTrackTicket: sqs.revopTicket;
1771
+ const sqsName = getStoreType > 0 ? sqs.revopTrackTicket : sqs.revopTicket;
1710
1772
  const sqsProduceQueue = getStoreType > 0 ? {
1711
1773
  QueueUrl: `${sqs.url}${sqsName}`,
1712
1774
  MessageBody: JSON.stringify( {
@@ -1734,12 +1796,11 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
1734
1796
 
1735
1797
  } ),
1736
1798
  };
1737
- const sqsQueue = getStoreType > 0? await sendMessageToFIFOQueue( sqsProduceQueue ):
1738
- await sendMessageToQueue(
1739
- sqsProduceQueue.QueueUrl,
1740
- sqsProduceQueue.MessageBody,
1741
-
1742
- );
1799
+ const sqsQueue = getStoreType > 0 ? await sendMessageToFIFOQueue( sqsProduceQueue ) :
1800
+ await sendMessageToQueue(
1801
+ sqsProduceQueue.QueueUrl,
1802
+ sqsProduceQueue.MessageBody,
1803
+ );
1743
1804
  if ( sqsQueue.statusCode ) {
1744
1805
  logger.error( {
1745
1806
  error: `${sqsQueue}`,
@@ -1747,7 +1808,7 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
1747
1808
  } );
1748
1809
  return false;
1749
1810
  } else {
1750
- const id =`${storeId}_${inputData.dateString.split( '-' ).reverse().join( '-' )}_${getStoreType > 0? 'live':'reduction'}_${Date.now()}`;
1811
+ const id = `${storeId}_${inputData.dateString.split( '-' ).reverse().join( '-' )}_${getStoreType > 0 ? 'live' : 'reduction'}_${Date.now()}`;
1751
1812
  const logs = {
1752
1813
  QueueUrl: sqsProduceQueue.QueueUrl,
1753
1814
  MessageBody: sqsProduceQueue.MessageBody,
@@ -1797,13 +1858,7 @@ export async function getTaggedStores( req, res ) {
1797
1858
  },
1798
1859
  },
1799
1860
  ];
1800
- // if ( req?.user?.userType == 'client' && req?.user?.role !== 'superadmin' && req?.stores?.length > 0 ) {
1801
- // filter.push(
1802
- // {
1803
- // terms: { 'storeId.keyword': req.stores },
1804
- // },
1805
- // );
1806
- // }
1861
+
1807
1862
  filter.push(
1808
1863
  {
1809
1864
  terms: { 'storeId.keyword': req?.stores || [] },
@@ -1918,8 +1973,8 @@ export async function downloadTickets( req, res ) {
1918
1973
  {
1919
1974
  terms: {
1920
1975
  'storeId.keyword': Array.isArray( inputData.storeId ) ?
1921
- inputData.storeId :
1922
- inputData.storeId,
1976
+ inputData.storeId :
1977
+ inputData.storeId,
1923
1978
  },
1924
1979
  },
1925
1980
  );
@@ -1967,26 +2022,26 @@ export async function downloadTickets( req, res ) {
1967
2022
  },
1968
2023
 
1969
2024
  } ) :
1970
- inputData.revopsType === 'junk' ?
1971
- filter.push( {
1972
- range: {
1973
- junkCount: {
1974
- gt: 0,
2025
+ inputData.revopsType === 'junk' ?
2026
+ filter.push( {
2027
+ range: {
2028
+ junkCount: {
2029
+ gt: 0,
2030
+ },
1975
2031
  },
1976
- },
1977
- } ):
2032
+ } ) :
1978
2033
 
1979
- filter.push( {
1980
- range: {
1981
- duplicateCount: {
1982
- gt: 0,
2034
+ filter.push( {
2035
+ range: {
2036
+ duplicateCount: {
2037
+ gt: 0,
2038
+ },
1983
2039
  },
1984
- },
1985
- } );
2040
+ } );
1986
2041
  source = inputData.revopsType == 'employee' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'comments', 'employee', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
1987
2042
  inputData.revopsType == 'houseKeeping' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'houseKeepingCount', 'comments', 'houseKeeping', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
1988
2043
  inputData.revopsType == 'duplicateImages' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'duplicateCount', 'comments', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] :
1989
- inputData.revopsType == 'junk' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'junkCount', 'comments', 'junk', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] : [];
2044
+ inputData.revopsType == 'junk' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'junkCount', 'comments', 'junk', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'junkStatus' ] : [];
1990
2045
  }
1991
2046
 
1992
2047
  if ( inputData.action ) {
@@ -2063,7 +2118,7 @@ export async function downloadTickets( req, res ) {
2063
2118
  let temp = [];
2064
2119
  if ( inputData?.action ) {
2065
2120
  response.map( ( hit ) => {
2066
- const defaultData ={
2121
+ const defaultData = {
2067
2122
  storeId: hit._source.storeId,
2068
2123
  dateString: hit?._source?.dateString,
2069
2124
  ticketName: hit?._source?.ticketName,
@@ -2093,19 +2148,19 @@ export async function downloadTickets( req, res ) {
2093
2148
  // result.type = 'employee';
2094
2149
  result.matched = matched;
2095
2150
  }
2096
- if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
2151
+ if ( matched.includes( 'matched_housekeeping' ) && ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
2097
2152
  logger.info( { revop: inputData.revopsType } );
2098
2153
  result = defaultData;
2099
2154
  result.houseKeeping = hit?._source?.houseKeeping;
2100
2155
  result.matched = matched;
2101
2156
  }
2102
- if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
2157
+ if ( matched.includes( 'matched_duplicate' ) && ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
2103
2158
  logger.info( { revop: inputData.revopsType } );
2104
2159
  result = defaultData;
2105
2160
  result.duplicateImages = hit?._source?.duplicateImages;
2106
2161
  result.matched = matched;
2107
2162
  }
2108
- if ( matched.includes( 'matched_junk' )&& ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
2163
+ if ( matched.includes( 'matched_junk' ) && ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
2109
2164
  result = defaultData;
2110
2165
  result.junk = hit?._source?.junk;
2111
2166
  result.matched = matched;
@@ -2149,7 +2204,7 @@ export async function downloadTickets( req, res ) {
2149
2204
  revopsType: inputData?.revopsType,
2150
2205
  type: 'get-tickets',
2151
2206
  };
2152
- const record={
2207
+ const record = {
2153
2208
  stores: inputData?.storeId,
2154
2209
  fromDate: inputData?.fromDate,
2155
2210
  toDate: inputData?.toDate,
@@ -2167,7 +2222,7 @@ export async function downloadTickets( req, res ) {
2167
2222
  const sqs = JSON.parse( process.env.SQS );
2168
2223
  const sqsName = sqs.revopDownload;
2169
2224
 
2170
- const sqsProduceQueue ={
2225
+ const sqsProduceQueue = {
2171
2226
  QueueUrl: `${sqs.url}${sqsName}`,
2172
2227
  MessageBody: JSON.stringify( {
2173
2228
  _id: getId?._id,
@@ -2246,7 +2301,7 @@ export async function reviewerList( req, res ) {
2246
2301
  featureName: 'FootfallDirectory',
2247
2302
  modules: {
2248
2303
  $elemMatch: {
2249
- name: 'Reviewer',
2304
+ name: inputData?.type === 'review' ? 'reviewer' : 'approver',
2250
2305
  $or: [ { isAdd: true }, { isEdit: true } ],
2251
2306
  },
2252
2307
  },
@@ -2255,7 +2310,7 @@ export async function reviewerList( req, res ) {
2255
2310
  };
2256
2311
 
2257
2312
  const getUserlist = await findUser( reviewerRoleQuery, { userName: 1, email: 1, role: 1 } );
2258
- return res.sendSuccess( getUserlist|| [] );
2313
+ return res.sendSuccess( getUserlist || [] );
2259
2314
  } catch ( error ) {
2260
2315
  const err = error.message || 'Internal Server Error';
2261
2316
  return res.sendError( err, 500 );
@@ -2277,6 +2332,16 @@ export async function openTicketList( req, res ) {
2277
2332
  clientId: Array.isArray( clientId ) ? clientId : [ clientId ],
2278
2333
  },
2279
2334
  },
2335
+ {
2336
+ term: {
2337
+ 'mappingInfo.type': inputData.type,
2338
+ },
2339
+ },
2340
+ {
2341
+ term: {
2342
+ 'mappingInfo.status.keyword': 'Open',
2343
+ },
2344
+ },
2280
2345
  {
2281
2346
  range: {
2282
2347
  dateString: {
@@ -2295,14 +2360,20 @@ export async function openTicketList( req, res ) {
2295
2360
  filter: filter,
2296
2361
  },
2297
2362
  },
2298
- _source: [ 'ticketId', 'storeName', 'revicedFootfall', 'footfallCount', 'revicedPerc' ],
2363
+ _source: [ 'ticketId', 'storeName', 'storeId', 'dateString', 'revicedFootfall', 'footfallCount', 'revicedPerc', 'type' ],
2299
2364
  };
2365
+ // INSERT_YOUR_CODE
2366
+ // Add sorting by revicedPerc descending (highest revised accuracy first)
2367
+ openSearchQuery.sort = [
2368
+ { 'revicedPerc.keyword': { order: inputData?.sortOrder === 1? 'asc':'desc' } },
2369
+ ];
2300
2370
 
2301
2371
 
2302
2372
  // Assuming getOpenSearchData and openSearch.footfallDirectoryTagging are available
2303
2373
  const result = await getOpenSearchData( openSearch.footfallDirectory, openSearchQuery );
2374
+ logger.info( { result } );
2304
2375
  const getUserlist = result?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
2305
- return res.sendSuccess( getUserlist|| [] );
2376
+ return res.sendSuccess( getUserlist || [] );
2306
2377
  } catch ( error ) {
2307
2378
  const err = error.message || 'Internal Server Error';
2308
2379
  logger.error( { error: error, function: 'openTicketList' } );
@@ -2310,82 +2381,479 @@ export async function openTicketList( req, res ) {
2310
2381
  }
2311
2382
  }
2312
2383
 
2313
- export async function updateiTcket( req, res ) {
2384
+ export async function assignTicket( req, res ) {
2314
2385
  try {
2315
2386
  const inputData = req.body;
2316
2387
  const openSearch = JSON.parse( process.env.OPENSEARCH );
2317
2388
 
2318
- // INSERT_YOUR_CODE
2319
- // Build the query to match storeId(s) and dateString range [fromDate, toDate], format: 'yyyy-mm-dd'
2320
- const { email, userName, role, actionType } = inputData;
2389
+ const { email, userName, role, actionType, storeId, dateString } = inputData;
2390
+ logger.info( { actionType } );
2391
+ const _id = `${storeId}_${dateString}_footfall-directory-tagging`;
2321
2392
 
2322
- // INSERT_YOUR_CODE
2393
+ const getTicket = await getOpenSearchById( openSearch.footfallDirectory, _id );
2394
+ if ( !getTicket ) {
2395
+ return res.sendError( 'Ticket is not found', 400 );
2396
+ }
2397
+ const source = getTicket?.body?._source;
2398
+ const mappingInfo = Array.isArray( source.mappingInfo ) ? source.mappingInfo : [];
2399
+ if ( mappingInfo.length === 0 ) {
2400
+ return res.sendError( 'Ticket is not found', 400 );
2401
+ }
2402
+ const lastIndex = mappingInfo.length - 1;
2403
+ const lastEntry = mappingInfo[lastIndex];
2404
+ if ( String( lastEntry.status ) !== 'In-Progress' ) {
2405
+ return res.sendError( 'Ticket is not in progress', 400 );
2406
+ }
2323
2407
 
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
- },
2408
+ const currentTime = new Date();
2409
+ const updatedMappingInfo = [ ...mappingInfo ];
2410
+ updatedMappingInfo[lastIndex] = { ...lastEntry, createdByEmail: email, updatedAt: currentTime, createdByUserName: userName, createdByRole: role };
2411
+ const updateResult = await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { mappingInfo: updatedMappingInfo } } );
2412
+ if ( !updateResult ) {
2413
+ return res.sendError( 'Failed to update ticket', 400 );
2414
+ }
2415
+ return res.sendSuccess( { message: 'Ticket assigned successfully' } );
2416
+ } catch ( error ) {
2417
+ const err = error.message || 'Internal Server Error';
2418
+ logger.error( { error: error, function: 'assignTicket' } );
2419
+ return res.sendError( err, 500 );
2420
+ }
2421
+ }
2422
+
2423
+ export async function updateTempStatus( req, res ) {
2424
+ try {
2425
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
2426
+ const inputData = req.body;
2427
+ const { id, type, status, comments } = inputData;
2428
+
2429
+ // Use bulk update API via bucketing (batch update) -- fetch docs, then bulk-update
2430
+ // 1. Search for all documents matching the ticket IDs
2431
+ const searchBody = {
2432
+ size: 10000,
2353
2433
  query: {
2354
2434
  bool: {
2355
2435
  must: [
2356
- { term: { 'ticketId.keyword': ticketId } },
2357
2436
  {
2358
- nested: {
2359
- path: 'mappingInfo',
2360
- query: {
2361
- bool: {
2362
- must: [
2363
- { match: { 'mappingInfo.type': actionType } },
2364
- ],
2365
- },
2366
- },
2437
+ terms: {
2438
+ 'id.keyword': id,
2367
2439
  },
2368
2440
  },
2369
2441
  ],
2370
2442
  },
2371
2443
  },
2444
+ _source: [ '_id' ], // Only bring _id for efficiency
2445
+ };
2446
+
2447
+ const searchResp = await getOpenSearchData(
2448
+ openSearch.revop,
2449
+ searchBody,
2450
+ );
2451
+
2452
+ // Extract bulk IDs to update
2453
+ const hits = searchResp?.body?.hits?.hits ?? [];
2454
+
2455
+ if ( !hits.length ) {
2456
+ return res.sendError( 'no data', 204 );
2457
+ }
2458
+
2459
+
2460
+ // 1. Get all IDs from hits
2461
+ const docIdToIndex = {};
2462
+ hits.forEach( ( doc ) => {
2463
+ docIdToIndex[doc._id] = doc._index;
2464
+ } );
2465
+ const docIds = hits.map( ( doc ) => doc._id );
2466
+ // 2. Fetch all docs by ID to get 'actions' (in chunks if large)
2467
+ const getBody = [];
2468
+ for ( const doc of hits ) {
2469
+ getBody.push( { _index: doc._index, _id: doc._id } );
2470
+ }
2471
+
2472
+ let mgetResp;
2473
+
2474
+ mgetResp = await getOpenSearchData(
2475
+ openSearch.revop,
2476
+ {
2477
+ size: 10000,
2478
+ query: {
2479
+ ids: {
2480
+ values: docIds,
2481
+ },
2482
+ },
2483
+ _source: true,
2484
+ },
2485
+ );
2486
+ // (If you have a utility for multi-get, you may want to use that. Else, you might need to fetch each by ID.)
2487
+ // For fallback, fetch all source fields via another search
2488
+ let fullDocs = mgetResp?.body?.hits?.hits || searchResp?.body?.hits?.hits || [];
2489
+
2490
+ // 3. Prepare the new actions array for each doc, and set up bulk update payloads
2491
+ const reviewActions = [ 'approved', 'rejected' ];
2492
+ const docsToUpdate = [];
2493
+ for ( const doc of fullDocs ) {
2494
+ const source = doc._source || doc.fields || {}; // support mget and search hits
2495
+ let actions = Array.isArray( source.actions ) ? [ ...source.actions ] : [];
2496
+ if ( reviewActions.includes( status ) ) {
2497
+ // for review: update or push 'review'
2498
+ let found = false;
2499
+ switch ( type ) {
2500
+ case 'review':
2501
+ actions = actions.map( ( item ) => {
2502
+ if ( item.actionType === 'review' ) {
2503
+ found = true;
2504
+ return { ...item, action: status };
2505
+ }
2506
+ return item;
2507
+ } );
2508
+ if ( !found ) {
2509
+ actions.push( { actionType: 'review', action: status } );
2510
+ }
2511
+ break;
2512
+ case 'approve':
2513
+ actions = actions.map( ( item ) => {
2514
+ if ( item.actionType === 'approve' ) {
2515
+ found = true;
2516
+ return { ...item, action: status };
2517
+ }
2518
+ return item;
2519
+ } );
2520
+ if ( !found ) {
2521
+ actions.push( { actionType: 'approve', action: status } );
2522
+ }
2523
+ break;
2524
+ default:
2525
+ return res.sendError( 'wrong vaue', 400 );
2526
+ }
2527
+ }
2528
+ let isChecked = true;
2529
+ switch ( type ) {
2530
+ case 'review':
2531
+ isChecked = status === 'approved' ? true : false;
2532
+ break;
2533
+ case 'approve':
2534
+ isChecked = status === 'approved' ? doc?._source?.isChecked : !doc?._source?.isChecked;
2535
+ break;
2536
+ }
2537
+ docsToUpdate.push( {
2538
+ _index: doc._index || docIdToIndex[doc._id],
2539
+ _id: doc._id,
2540
+ actions,
2541
+ isChecked: isChecked,
2542
+ comments: comments || '',
2543
+ } );
2544
+ }
2545
+ const bulkPayload = [];
2546
+ // 4. Build bulk update payload
2547
+ for ( const doc of docsToUpdate ) {
2548
+ bulkPayload.push( {
2549
+ update: { _index: doc._index, _id: doc._id },
2550
+ } );
2551
+ bulkPayload.push( {
2552
+ doc: { actions: doc.actions, isChecked: doc?.isChecked, comments: doc?.comments || '' },
2553
+ } );
2554
+ }
2555
+
2556
+ // 3. Execute bulk update
2557
+ const bulkResp = await bulkUpdate( bulkPayload );
2558
+
2559
+ // Count successes
2560
+ const updatedCount = bulkResp?.body?.items?.filter( ( item ) => item?.update?.result === 'updated' || item?.update?.result === 'noop' ).length ?? 0;
2561
+
2562
+ if ( inputData?.comments && inputData?.comments !== '' ) {
2563
+ const id = `${inputData.storeId}_${inputData.dateString}_${Date.now()}`;
2564
+ const searchBody1 = {
2565
+ size: 10000,
2566
+ query: {
2567
+ bool: {
2568
+ must: [
2569
+ {
2570
+ terms: {
2571
+ '_id': docIds,
2572
+ },
2573
+ },
2574
+ {
2575
+ term: {
2576
+ isParent: false,
2577
+ },
2578
+ },
2579
+ ],
2580
+ },
2581
+ },
2582
+ };
2583
+
2584
+ const getSearchResp = await getOpenSearchData(
2585
+ openSearch.revop,
2586
+ searchBody1,
2587
+ );
2588
+
2589
+
2590
+ const taggedImages = getSearchResp?.body?.hits?.hits?.length > 0? getSearchResp?.body?.hits?.hits : [];
2591
+ const logs = {
2592
+ type: inputData.type,
2593
+ storeId: taggedImages?.[0]?._source?.storeId,
2594
+ dateString: taggedImages?.[0]?._source?.dateString,
2595
+ category: taggedImages?.[0]?._source?.revopsType || '',
2596
+ taggedImages: taggedImages,
2597
+ status: inputData?.status,
2598
+ createdByEmail: req?.user?.email,
2599
+ createdByUserName: req?.user?.userName,
2600
+ createdByRole: req?.user?.role,
2601
+ message: inputData.comments || '',
2602
+ createdAt: new Date(),
2603
+ };
2604
+ await insertWithId( openSearch.vmsCommentsLog, id, logs );
2605
+ }
2606
+ return res.sendSuccess( { updated: updatedCount } );
2607
+ } catch ( error ) {
2608
+ const err = error.message;
2609
+ logger.info( { error: err, function: 'updateTempStatus' } );
2610
+ return res.sendError( err, 500 );
2611
+ }
2612
+ }
2613
+
2614
+ export async function updateUserTicketStatus( req, res ) {
2615
+ try {
2616
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
2617
+ const { storeId, dateString } = req.body || {};
2618
+
2619
+ if ( !storeId || !dateString ) {
2620
+ return res.sendError( 'storeId and dateString are required', 400 );
2621
+ }
2622
+
2623
+ const docId = `${storeId}_${dateString}_footfall-directory-tagging`;
2624
+
2625
+ // Fetch existing ticket so we can validate mappingInfo state
2626
+ const existingDoc = await getOpenSearchById( openSearch.footfallDirectory, docId );
2627
+ const ticketSource = existingDoc?.body?._source;
2628
+
2629
+ if ( !ticketSource ) {
2630
+ return res.sendError( 'Ticket not found', 404 );
2631
+ }
2632
+
2633
+ const mappingInfo = Array.isArray( ticketSource.mappingInfo ) ? ticketSource.mappingInfo : [];
2634
+ if ( mappingInfo.length === 0 ) {
2635
+ return res.sendError( 'mappingInfo is missing for this ticket', 400 );
2636
+ }
2637
+
2638
+ const lastIndex = mappingInfo.length - 1;
2639
+ const lastEntry = mappingInfo[lastIndex];
2640
+
2641
+ if ( !lastEntry ) {
2642
+ return res.sendError( 'Unable to determine current ticket status', 400 );
2643
+ }
2644
+
2645
+ if ( String( lastEntry.status ).toLowerCase() !== 'open' ) {
2646
+ return res.sendError( 'Ticket is already picked by another user', 409 );
2647
+ }
2648
+
2649
+ const currentTime = new Date();
2650
+ const updatedMappingInfo = [ ...mappingInfo ];
2651
+ updatedMappingInfo[lastIndex] = {
2652
+ ...lastEntry,
2653
+ status: 'In-Progress',
2654
+ updatedAt: currentTime,
2655
+ createdByRole: req?.user?.role || '',
2656
+ createdByEmail: req?.user?.email || '',
2657
+ createdByUserName: req?.user?.userName || '',
2658
+ };
2659
+
2660
+ const updatePayload = {
2661
+ doc: {
2662
+ status: 'In-Progress',
2663
+ mappingInfo: updatedMappingInfo,
2664
+ updatedAt: currentTime,
2665
+ },
2372
2666
  };
2373
2667
 
2374
- // Call OpenSearch _update_by_query to update doc(s) where ticketId and mappingInfo[i].type == actionType
2375
- const response = await upsertOpenSearchData(
2668
+ const updateResult = await updateOpenSearchData(
2376
2669
  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
2670
+ docId,
2671
+ updatePayload,
2379
2672
  );
2380
2673
 
2381
2674
 
2382
- logger.info( { response } );
2675
+ if ( !updateResult || !( updateResult.statusCode === 200 || updateResult.statusCode === 201 ) ) {
2676
+ return res.sendError( 'Failed to update ticket status', 500 );
2677
+ }
2678
+ return res.sendSuccess( 'Ticket status updated successfully' );
2679
+ } catch ( error ) {
2680
+ const err = error.message;
2681
+ logger.info( { error: err, function: 'updateUserTicketStatus' } );
2682
+ return res.sendError( err, 500 );
2683
+ }
2684
+ }
2685
+
2686
+ export async function multiCloseTicket( req, res ) {
2687
+ try {
2688
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
2689
+ const inputData = req.body;
2690
+
2691
+ // inputData structure should include an array of items to close
2692
+ // Accept both array or single ticket update
2693
+ const tickets = Array.isArray( inputData.ticketList ) ? inputData.ticketList : [ inputData.ticketList ];
2694
+ // const mode = inputData.mode || '';
2695
+
2696
+ if ( !tickets.length ) {
2697
+ return res.sendError( 'No tickets provided', 400 );
2698
+ }
2699
+
2700
+ const results = [];
2701
+ for ( const ticket of tickets ) {
2702
+ const { storeId, dateString } = ticket || {};
2703
+ if ( !storeId || !dateString ) {
2704
+ results.push( { storeId, dateString, success: false, error: 'Missing storeId or dateString' } );
2705
+ continue;
2706
+ }
2707
+ // 1. Update the ticket document in footfallDirectory index
2708
+ const docId = `${storeId}_${dateString}_footfall-directory-tagging`;
2709
+
2710
+ // Fetch existing doc to update mappingInfo
2711
+
2712
+ const doc = await getOpenSearchById( openSearch.footfallDirectory, docId );
2713
+
2714
+
2715
+ const ticketSource = doc?.body?._source;
2716
+ if ( !ticketSource || !ticketSource.mappingInfo ) {
2717
+ results.push( { storeId, dateString, success: false, error: 'Ticket or mappingInfo missing' } );
2718
+ continue;
2719
+ }
2720
+
2721
+ let mappingInfoArray = Array.isArray( ticketSource.mappingInfo ) ? ticketSource.mappingInfo : [ ticketSource.mappingInfo ];
2722
+ // Find all mappingInfo items matching type 'approve'
2723
+ let updated = false;
2724
+ let newMappingInfoArray = mappingInfoArray.map( ( mi, i ) => {
2725
+ if ( mi?.type === 'approve' && mi?.status === 'Open' ) {
2726
+ updated = true;
2727
+ return {
2728
+ ...mi,
2729
+ status: 'Approver-Closed',
2730
+ status: 'Closed', // following the user's instruction to set sttaus property (assumed typo, but explicitly used)
2731
+ updatedAt: new Date(),
2732
+ };
2733
+ }
2734
+ return mi;
2735
+ } );
2736
+
2737
+ if ( !updated ) {
2738
+ // None found to update
2739
+ results.push( { storeId, dateString, success: false, error: `coudn't approve this store` } );
2740
+ continue;
2741
+ }
2742
+
2743
+ // Write update to footfallDirectory
2744
+ const ticketUpdatePayload = {
2745
+ doc: {
2746
+ mappingInfo: newMappingInfoArray,
2747
+ status: 'Approver-Closed', // status updated at top level as well
2748
+ updatedAt: new Date(),
2749
+ },
2750
+ };
2751
+ let ticketUpdateResult;
2752
+ try {
2753
+ ticketUpdateResult = await updateOpenSearchData( openSearch.footfallDirectory, docId, ticketUpdatePayload );
2754
+ if ( !( ticketUpdateResult && ( ticketUpdateResult.statusCode === 200 || ticketUpdateResult.statusCode === 201 ) ) ) {
2755
+ results.push( { storeId, dateString, success: false, error: 'Failed to update ticket' } );
2756
+ continue;
2757
+ }
2758
+ } catch ( err ) {
2759
+ results.push( { storeId, dateString, success: false, error: 'Failed to update ticket' } );
2760
+ continue;
2761
+ }
2762
+
2763
+
2764
+ // For each ticket, update actions array for all matching image docs (storeId & dateString)
2765
+
2766
+ // Query to find all matching docs in revopTagging index with storeId and dateString
2767
+ const revopImageQuery = {
2768
+ size: 10000, // assume there won't be more than 1000 images per ticket
2769
+ query: {
2770
+ bool: {
2771
+ must: [
2772
+ { term: { 'storeId.keyword': storeId } },
2773
+ { term: { dateString: dateString } },
2774
+ ],
2775
+ },
2776
+ },
2777
+ };
2778
+
2779
+ // Fetch matching docs
2780
+ const searchRes = await getOpenSearchData( openSearch.revop, revopImageQuery );
2781
+ const revopHits = searchRes?.body?.hits?.hits || [];
2782
+
2783
+ // Optimized: batch and parallelize image-doc updates to avoid long sequential waits
2784
+ const BATCH_SIZE = 100;
2785
+ const now = new Date();
2786
+
2787
+ for ( let i = 0; i < revopHits.length; i += BATCH_SIZE ) {
2788
+ const batch = revopHits.slice( i, i + BATCH_SIZE );
2789
+
2790
+ const updatePromises = batch.map( async ( hit ) => {
2791
+ const imageDocId = hit._id;
2792
+ const imageSource = hit._source || {};
2793
+ const imageActionsArray = Array.isArray( imageSource.actions ) ? [ ...imageSource.actions ] : [];
2794
+
2795
+ imageActionsArray.push( {
2796
+ actionType: 'approve',
2797
+ action: 'approved',
2798
+ } );
2799
+
2800
+ const imageUpdatePayload = {
2801
+ doc: {
2802
+ actions: imageActionsArray,
2803
+ updatedAt: now,
2804
+ },
2805
+ };
2806
+
2807
+ return updateOpenSearchData( openSearch.revop, imageDocId, imageUpdatePayload );
2808
+ } );
2383
2809
 
2384
- return res.sendSuccess( { updated: response?.body?.updated ?? 0 } );
2810
+ // Wait for this batch to finish before starting the next one
2811
+ await Promise.all( updatePromises );
2812
+ }
2813
+ }
2814
+ if ( results && results?.length > 0 ) {
2815
+ return res.sendError( results, 500 );
2816
+ }
2817
+ // Return batch summary
2385
2818
  } catch ( error ) {
2386
- const err = error.message || 'Internal Server Error';
2387
- logger.error( { error: error, function: 'updateticket' } );
2819
+ const err = error.message;
2820
+ logger.info( { error: err, function: 'multiCloseTicket' } );
2388
2821
  return res.sendError( err, 500 );
2389
2822
  }
2390
2823
  }
2391
2824
 
2825
+
2826
+ export async function checkTicketExists( req, res ) {
2827
+ try {
2828
+ let inputData = req.body;
2829
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
2830
+ let findQuery = {
2831
+ size: 10000,
2832
+ query: {
2833
+ bool: {
2834
+ must: [
2835
+ {
2836
+ term: {
2837
+ 'storeId.keyword': inputData.storeId,
2838
+ },
2839
+ },
2840
+ {
2841
+ term: {
2842
+ 'dateString': inputData.dateString,
2843
+ },
2844
+ },
2845
+ ],
2846
+ },
2847
+ },
2848
+ };
2849
+ let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
2850
+ let Ticket = findTicket.body?.hits?.hits;
2851
+
2852
+
2853
+ res.sendSuccess( Ticket );
2854
+ } catch ( error ) {
2855
+ const err = error.message;
2856
+ logger.info( { error: err, function: 'checkTicketExists' } );
2857
+ return res.sendError( err, 500 );
2858
+ }
2859
+ }