tango-app-api-infra 3.8.1-beta.2 → 3.8.1-beta.21

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,7 +1,9 @@
1
- import { download, logger } from 'tango-app-api-middleware';
2
- import { bulkUpdate, getOpenSearchCount, getOpenSearchData, insertWithId, updateOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
1
+ import { chunkArray, download, logger, sendMessageToFIFOQueue, sendMessageToQueue } from 'tango-app-api-middleware';
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 dayjs from 'dayjs';
5
+ import { countDocumnetsCamera } from '../services/camera.service.js';
6
+ import { findOneRevopDownload, upsertRevopDownload } from '../services/revopDownload.service.js';
5
7
 
6
8
  export async function createTicket( req, res ) {
7
9
  try {
@@ -10,11 +12,12 @@ export async function createTicket( req, res ) {
10
12
  const getStoreName = await findOneStore( { storeId: inputData.storeId }, { storeName: 1, _id: 0 } );
11
13
  inputData.ticketId = 'TE_FDT_' + new Date().valueOf();
12
14
  inputData.clientId = inputData?.storeId?.split( '-' )[0];
13
- inputData.storeName =getStoreName?.storeName;
15
+ inputData.storeName = getStoreName?.storeName;
14
16
  inputData.createdAt = new Date();
15
17
  inputData.updatedAt = new Date();
16
- inputData.userName = req.user.userName;
17
- inputData.role = req.user.role;
18
+ inputData.userName = req?.user?.userName;
19
+ inputData.email = req?.user?.email;
20
+ inputData.role = req?.user?.role;
18
21
  inputData.status = 'open';
19
22
  if ( inputData.houseKeepingCount > 0 ) {
20
23
  inputData.houseKeepingStatus = 'pending';
@@ -67,12 +70,19 @@ async function bulkUpdateStatusToPending( indexName, inputData ) {
67
70
 
68
71
  // 3. Duplicate Images > data[]
69
72
  if ( inputData.duplicateCount > 0 ) {
73
+ let updatedDuplicateImages = [];
70
74
  for ( const dup of inputData.duplicateImages || [] ) {
71
75
  const id = `${inputData.storeId}_${inputData.dateString}_${dup.timeRange}_${dup.tempId}`;
72
- const updatedDuplicateImages = ( dup.data || [] ).map( ( item ) => ( {
73
- ...item,
74
- status: 'pending',
75
- } ) );
76
+ dup?.data?.map( ( item ) => {
77
+ updatedDuplicateImages.push( {
78
+ ...item,
79
+ status: 'pending',
80
+ } );
81
+ bulkBody.push(
82
+ { update: { _index: indexName, _id: `${inputData.storeId}_${inputData.dateString}_${item.timeRange}_${item.tempId}` } },
83
+ { doc: { status: 'pending' } },
84
+ );
85
+ } );
76
86
  bulkBody.push(
77
87
  { update: { _index: indexName, _id: id } },
78
88
  { doc: { status: 'pending', duplicateImage: updatedDuplicateImages } },
@@ -174,120 +184,211 @@ export async function ticketSummary( req, res ) {
174
184
 
175
185
  export async function ticketList( req, res ) {
176
186
  try {
177
- // let count = 0;
178
- // const array =[
179
- // { A: '3c88e8eb6d499a26d37ca075c390bd2d', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/101' },
180
- // { A: 'c3dd981f485ccd25221c7da704f35d24', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/101' },
181
- // { A: 'ee887e5546ab0e96ca0adbf8c2453c8d', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/1001' },
182
- // { A: 'a59ddeb30d158602d6c672994af2f79a', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/1001' },
183
- // { A: '1f3318280025f5456262b0322b806940', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/201' },
184
- // { A: '925a70751a0988793057148b20158186', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/201',
185
- // }, { A: 'd6d4ab301c322212fb612a7d9661b6be', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/301',
186
- // }, { A: 'cb09aeedfa5ade043922634eb1c35e46', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/301',
187
- // }, { A: '58f81e33f66ef7af15424c1dc44e7de1', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/401',
188
- // }, { A: '7ac8f73f02347d29f6f2bff3c52275f5', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/401',
189
- // }, { A: '9599acd19a48eb28957ad18e40a0a5b1', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/401',
190
- // }, { A: '36584a3ce215a5fbd1c0e6535201020c', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/501',
191
- // }, { A: 'cb7e91870e0a72d4eb691e2618aa5979', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/501',
192
- // }, { A: 'cd072627e6924df745cba004616ec3c9', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/601',
193
- // }, { A: 'a5891519a50f68a9d73a774539fe8496', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/601',
194
- // }, { A: '2008517125b03b1b9a66348e6d017523', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/701',
195
- // }, { A: '79fc93551b59f9ed07eab48d190ba7f4', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/701',
196
- // }, { A: '89365abcfc8dc663b26471d81a7f68eb', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/801',
197
- // }, { A: 'e9fb3b989a245994bd48b97287aad096', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/801',
198
- // }, { A: '4493fb6dc07611bab03630b154ca2483', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/801',
199
- // }, { A: 'e31caf56bb86f6e12377db006ac61756', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/901',
200
- // }, { A: 'fe66c637bc8da296485494e371f67b99', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/901' },
201
- // ];
202
-
203
- // for ( const item of array ) {
204
- // const a = await updateOneCamera( { streamName: item.A, clientId: '370' }, { RTSP: item.B } );
205
- // count++;
206
- // logger.info( { aaa: a, count: count } );
207
- // }
208
-
209
- // process.exit();
210
187
  const openSearch = JSON.parse( process.env.OPENSEARCH );
211
188
  const inputData = req.query;
212
189
  const limit = inputData.limit || 10;
213
- const skip= inputData.offset == 0? 0:( inputData.offset - 1 ) *limit || 0;
190
+ const offset = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
191
+ const order = inputData?.sortOrder || -1;
192
+
214
193
  inputData.clientId = inputData.clientId.split( ',' ); // convert strig to array
215
- logger.info( { inputData: inputData, limit: limit, skip: skip } );
216
- const getCount= {
217
- query: {
218
- bool: {
219
- filter: [
220
- { terms: { 'clientId.keyword': Array.isArray( inputData.clientId ) ?
221
- inputData.clientId :
222
- inputData.clientId } },
223
- {
224
- range: {
225
- dateString: {
226
- gte: inputData.fromDate,
227
- lte: inputData.toDate,
228
- format: 'yyyy-MM-dd',
229
- },
230
- },
231
- },
232
- ],
194
+
195
+
196
+ let filter = [
197
+ {
198
+ 'range': {
199
+ 'dateString': {
200
+ 'gte': inputData.fromDate,
201
+ 'lte': inputData.toDate,
202
+ 'format': 'yyyy-MM-dd',
203
+ },
233
204
  },
234
205
  },
235
- };
206
+ {
207
+ terms: {
208
+ 'clientId.keyword': Array.isArray( inputData.clientId ) ?
209
+ inputData.clientId :
210
+ [ inputData.clientId ],
211
+ },
212
+ },
213
+ ];
236
214
 
237
- const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
238
- if ( !geteDataCount || geteDataCount?.body?.count == 0 ) {
239
- return res.sendError( 'No data found', 204 );
215
+ if ( inputData?.storeId ) {
216
+ filter.push(
217
+ {
218
+ terms: {
219
+ 'storeId.keyword': Array.isArray( inputData.storeId ) ?
220
+ inputData.storeId :
221
+ [ inputData.storeId ],
222
+ },
223
+ },
224
+ );
240
225
  }
241
- const getQuery = {
242
- size: limit,
243
- from: skip,
244
- query: {
245
- bool: {
246
- filter: [
247
- { terms: { 'clientId.keyword': Array.isArray( inputData.clientId ) ?
248
- inputData.clientId :
249
- inputData.clientId } },
250
- {
251
- range: {
252
- dateString: {
253
- gte: inputData.fromDate,
254
- lte: inputData.toDate,
255
- format: 'yyyy-MM-dd',
256
- },
226
+
227
+ let search = {
228
+ 'must': filter,
229
+ };
230
+
231
+ if ( inputData.searchValue && inputData.searchValue !== '' ) {
232
+ search = {
233
+ 'must': filter,
234
+ 'should': [
235
+ {
236
+ 'wildcard': {
237
+ 'storeName.keyword': {
238
+ 'value': `*${inputData.searchValue}*`,
257
239
  },
258
240
  },
259
- ],
260
- },
241
+ },
242
+ {
243
+ 'wildcard': {
244
+ 'storeId.keyword': {
245
+ 'value': `*${inputData.searchValue}*`,
246
+ },
247
+ },
248
+ },
249
+ {
250
+ 'wildcard': {
251
+ 'ticketId.keyword': {
252
+ 'value': `*${inputData.searchValue}*`,
253
+ },
254
+ },
255
+ },
256
+ {
257
+ 'wildcard': {
258
+ 'status.keyword': {
259
+ 'value': `*${inputData.searchValue}*`,
260
+ },
261
+ },
262
+ },
263
+
264
+ ],
265
+ 'minimum_should_match': 1,
266
+ };
267
+ }
268
+
269
+ let searchQuery = {
270
+ '_source': [
271
+ 'storeName',
272
+ 'storeId',
273
+ 'ticketId',
274
+ 'createdAt',
275
+ 'updatedAt',
276
+ 'footfallCount',
277
+ 'duplicateCount',
278
+ 'employeeCount',
279
+ 'houseKeepingCount',
280
+ 'employeeACCount',
281
+ 'duplicateACCount',
282
+ 'houseKeepingACCount',
283
+ 'status',
284
+ 'dateString',
285
+ ],
286
+ 'from': offset,
287
+ 'size': limit,
288
+ 'query': {
289
+ 'bool': search,
261
290
  },
262
- _source: [ 'storeName', 'storeId', 'ticketId', 'createdAt', 'footfallCount', 'duplicateCount', 'employeeCount', 'houseKeepingCount', 'status', 'dateString' ],
291
+ 'sort': [
292
+ { dateString: { order: 'desc' } },
293
+ ],
263
294
  };
264
295
 
265
- const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
266
- const response = getData?.body?.hits?.hits;
267
- logger.info( { response: response, body: getData?.body, getData: getData, geteDataCount: geteDataCount } );
268
-
269
-
270
- if ( inputData.isExport=== true ) {
271
- const temp = [];
272
- for ( const item of response ) {
273
- temp.push( {
274
- 'Store Name': item.storeName,
275
- 'Store ID': item.storeId,
276
- 'Ticket ID': item.ticketId,
277
- 'Ticket raised on': dayjs( item.createdAt ).format( 'dd MMM, yyyy' ),
278
- 'Total Footfalls': item.footfallCount,
279
- 'Duplicates': item.duplicateCount,
280
- 'Employee/Staff': item.employeeCount,
281
- 'HouseKeeping': item.houseKeepingCount,
282
- 'Revised Footfalls': item.footfallCount-( item.duplicateCount+item.employeeCount+item.houseKeepingCount ),
283
- 'Status': item.status,
284
- } );
296
+ if ( inputData.sortBy && inputData.sortBy !== '' ) {
297
+ let sortByValue = '';
298
+
299
+ if ( [ 'storeName', 'storeId', 'ticketId', 'status' ].includes( inputData?.sortBy ) ) {
300
+ sortByValue = `${inputData.sortBy}.keyword`;
301
+ } else {
302
+ sortByValue = inputData.sortBy;
285
303
  }
286
- await download( temp, res );
287
- return;
304
+
305
+
306
+ searchQuery = {
307
+ '_source': [
308
+ 'storeName',
309
+ 'storeId',
310
+ 'ticketId',
311
+ 'createdAt',
312
+ 'updatedAt',
313
+ 'footfallCount',
314
+ 'duplicateCount',
315
+ 'employeeCount',
316
+ 'houseKeepingCount',
317
+ 'status',
318
+ 'employeeACCount',
319
+ 'duplicateACCount',
320
+ 'houseKeepingACCount',
321
+ 'dateString',
322
+ ],
323
+ 'from': offset,
324
+ 'size': limit,
325
+ 'query': {
326
+ 'bool': search,
327
+ },
328
+ 'sort': [
329
+ { [sortByValue]: { order: order === -1 ? 'desc' : 'asc' } },
330
+ ],
331
+ };
288
332
  }
289
333
 
290
- return res.sendSuccess( { result: response, count: geteDataCount?.body?.count } );
334
+ if ( inputData.isExport == true ) {
335
+ searchQuery = {
336
+ '_source': [
337
+ 'storeName',
338
+ 'storeId',
339
+ 'ticketId',
340
+ 'createdAt',
341
+ 'updatedAt',
342
+ 'footfallCount',
343
+ 'duplicateCount',
344
+ 'employeeACCount',
345
+ 'duplicateACCount',
346
+ 'employeeCount',
347
+ 'houseKeepingACCount',
348
+ 'houseKeepingCount',
349
+ 'status',
350
+ 'dateString',
351
+ ],
352
+ 'from': 0,
353
+ 'size': 10000,
354
+ 'query': {
355
+ 'bool': search,
356
+ },
357
+ 'sort': [
358
+ { 'storeName.keyword': { order: 'desc' } },
359
+ ],
360
+ };
361
+ }
362
+ const getData = await getOpenSearchData( openSearch.footfallDirectory, searchQuery );
363
+ const count = getData?.body?.hits?.total?.value;
364
+ if ( !count || count == 0 ) {
365
+ return res.sendError( 'No data found', 204 );
366
+ }
367
+ const searchValue = getData?.body?.hits?.hits;
368
+ if ( !searchValue || searchValue?.length == 0 ) {
369
+ return res.sendError( 'No data found', 204 );
370
+ }
371
+
372
+ if ( inputData.isExport == true ) {
373
+ const exportData = [];
374
+ for ( const item of searchValue ) {
375
+ exportData.push( {
376
+ 'Store Name': item._source.storeName || '--',
377
+ 'Store ID': item._source.storeId,
378
+ 'Ticket ID': item._source.ticketId,
379
+ 'Ticket raised on': dayjs( item._source.createdAt ).format( 'DD MMM, YYYY' ),
380
+ 'Total Footfalls': item._source.footfallCount,
381
+ 'Duplicates': item._source.duplicateCount,
382
+ 'Employee/Staff': item._source.employeeCount,
383
+ 'HouseKeeping': item._source.houseKeepingCount,
384
+ 'Revised Footfalls': item._source.footfallCount - ( item._source.duplicateCount + item._source.employeeCount + item._source.houseKeepingCount ),
385
+ 'Status': item._source.status,
386
+ 'Ticket%': item?._source?.status === 'closed'?Math.round( ( ( item._source.duplicateACCount + item._source.employeeACCount + item._source.houseKeepingACCount )/ item._source.footfallCount )*100 ).toFixed( 0 ) : 'NA',
387
+ } );
388
+ }
389
+ return await download( exportData, res );
390
+ }
391
+ return res.sendSuccess( { result: searchValue, count: count } );
291
392
  } catch ( error ) {
292
393
  const err = error.message || 'Internal Server Error';
293
394
  logger.error( { error: error, messgage: req.query } );
@@ -299,16 +400,13 @@ export async function getTickets( req, res ) {
299
400
  try {
300
401
  const openSearch = JSON.parse( process.env.OPENSEARCH );
301
402
  const inputData = req.query;
302
- const limit = inputData.limit || 10;
303
- const skip= inputData.offset == 0? 0:( inputData.offset - 1 ) *limit || 0;
403
+ const limit = inputData.limit;
404
+ const skip = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
304
405
  inputData.storeId = inputData.storeId.split( ',' ); // convert strig to array
305
406
  logger.info( { inputData: inputData, limit: limit, skip: skip } );
407
+ let source = [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'duplicateCount', 'comments', 'employee', 'houseKeeping', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail' ];
408
+ let filter = [
306
409
 
307
- let filter =[
308
- { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
309
- inputData.storeId :
310
- inputData.storeId },
311
- },
312
410
  {
313
411
  range: {
314
412
  dateString: {
@@ -319,6 +417,26 @@ export async function getTickets( req, res ) {
319
417
  },
320
418
  },
321
419
  ];
420
+ if ( inputData?.storeId ) {
421
+ filter.push(
422
+ {
423
+ terms: {
424
+ 'storeId.keyword': Array.isArray( inputData.storeId ) ?
425
+ inputData.storeId :
426
+ inputData.storeId,
427
+ },
428
+ },
429
+ );
430
+ }
431
+ if ( inputData?.dateString ) {
432
+ filter.push(
433
+ {
434
+ term: {
435
+ 'dateString': inputData.dateString,
436
+ },
437
+ },
438
+ );
439
+ }
322
440
  if ( inputData.status ) {
323
441
  filter.push(
324
442
  {
@@ -336,6 +454,32 @@ export async function getTickets( req, res ) {
336
454
  field: inputData.revopsType,
337
455
  },
338
456
  } );
457
+ inputData.revopsType === 'employee' ?
458
+ filter.push( {
459
+ range: {
460
+ employeeCount: {
461
+ gt: 0,
462
+ },
463
+ },
464
+ } ) :
465
+ inputData.revopsType === 'housekeeping' ?
466
+ filter.push( {
467
+ range: {
468
+ housekeepingCount: {
469
+ gt: 0,
470
+ },
471
+ },
472
+ } ) :
473
+ filter.push( {
474
+ range: {
475
+ duplicateCount: {
476
+ gt: 0,
477
+ },
478
+ },
479
+ } );
480
+ source = inputData.revopsType == 'employee' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'comments', 'employee', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCoun', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail' ] :
481
+ inputData.revopsType == 'houseKeeping' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'houseKeepingCount', 'comments', 'houseKeeping', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail' ] :
482
+ inputData.revopsType == 'duplicateImages' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'duplicateCount', 'comments', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus', 'houseKeepingACCount', 'houseKeepingCount', 'employeeCount', 'employeeACCount', 'duplicateCount', 'duplicateACCount', 'approverRole', 'approverUserName', 'approverEmail' ] : [];
339
483
  }
340
484
 
341
485
  if ( inputData.action ) {
@@ -343,27 +487,52 @@ export async function getTickets( req, res ) {
343
487
  bool: {
344
488
  should: [
345
489
  {
346
- term: {
347
- 'houseKeepingStatus.keyword': inputData.action,
490
+ constant_score: {
491
+ filter: { term: { 'houseKeepingStatus.keyword': inputData.action } },
492
+ boost: 1,
493
+ _name: 'matched_housekeeping',
348
494
  },
349
495
  },
350
496
  {
351
- term: {
352
- 'employeeStatus.keyword': inputData.action,
497
+ constant_score: {
498
+ filter: { term: { 'employeeStatus.keyword': inputData.action } },
499
+ boost: 1,
500
+ _name: 'matched_employee',
353
501
  },
354
502
  },
355
503
  {
356
- term: {
357
- 'duplicateStatus.keyword': inputData.action,
504
+ constant_score: {
505
+ filter: { term: { 'duplicateStatus.keyword': inputData.action } },
506
+ boost: 1,
507
+ _name: 'matched_duplicate',
358
508
  },
359
509
  },
360
510
  ],
361
- minimum_should_match: 1, // Ensures at least one should condition must match
511
+ minimum_should_match: 1,
362
512
  },
363
513
  } );
364
514
  }
365
515
 
366
- const getCount= {
516
+
517
+ let getRevCount = {};
518
+ if ( inputData.revopsType ) {
519
+ getRevCount = {
520
+ size: 0,
521
+ query: {
522
+ bool: {
523
+ filter: filter,
524
+ },
525
+ },
526
+ aggs: {
527
+ totalCount: {
528
+ sum: {
529
+ field: inputData.revopsType == 'employee' ? 'employeeCount' : inputData.revopsType == 'houseKeeping' ? 'houseKeepingCount' : 'duplicateCount',
530
+ },
531
+ },
532
+ },
533
+ };
534
+ }
535
+ const getCount = {
367
536
  query: {
368
537
  bool: {
369
538
  filter: filter,
@@ -371,8 +540,12 @@ export async function getTickets( req, res ) {
371
540
  },
372
541
  };
373
542
 
543
+
374
544
  const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
375
- if ( !geteDataCount || geteDataCount?.body?.count == 0 ) {
545
+ const geteRevDataCount = inputData?.revopsType ? await getOpenSearchData( openSearch.footfallDirectory, getRevCount ) : null;
546
+ const revCount = inputData?.revopsType ? geteRevDataCount?.body?.aggregations?.totalCount?.value : 0;
547
+ const count = geteDataCount?.body?.count;
548
+ if ( !geteDataCount || count == 0 ) {
376
549
  return res.sendError( 'No data found', 204 );
377
550
  }
378
551
 
@@ -384,34 +557,88 @@ export async function getTickets( req, res ) {
384
557
  filter: filter,
385
558
  },
386
559
  },
560
+ _source: source,
387
561
  };
388
562
 
389
563
  const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
564
+
390
565
  const response = getData?.body?.hits?.hits;
391
- logger.info( { response: response, body: getData?.body, getData: getData, geteDataCount: geteDataCount } );
392
-
393
-
394
- if ( inputData.isExport=== true ) {
395
- const temp = [];
396
- for ( const item of response ) {
397
- temp.push( {
398
- 'Store Name': item.storeName,
399
- 'Store ID': item.storeId,
400
- 'Ticket ID': item.ticketId,
401
- 'Ticket raised on': dayjs( item.createdAt ).format( 'dd MMM, yyyy' ),
402
- 'Total Footfalls': item.footfallCount,
403
- 'Duplicates': item.duplicateCount,
404
- 'Employee/Staff': item.employeeCount,
405
- 'HouseKeeping': item.houseKeepingCount,
406
- 'Revised Footfalls': item.footfallCount-( item.duplicateCount+item.employeeCount+item.houseKeepingCount ),
407
- 'Status': item.status,
408
- } );
409
- }
410
- await download( temp, res );
411
- return;
566
+ if ( !response || response.length == 0 ) {
567
+ return res.sendError( 'No data', 204 );
412
568
  }
569
+ let temp = [];
570
+ if ( inputData?.action ) {
571
+ response.map( ( hit ) => {
572
+ const defaultData = {
573
+ storeId: hit._source.storeId,
574
+ dateString: hit?._source?.dateString,
575
+ ticketName: hit?._source?.ticketName,
576
+ status: hit?._source?.status,
577
+ employeeStatus: hit?._source?.employeeStatus,
578
+ houseKeepingStatus: hit?._source?.houseKeepingStatus,
579
+ duplicateStatus: hit?._source?.duplicateStatus,
580
+ storeName: hit?._source?.storeName,
581
+ ticketId: hit?._source?.ticketId,
582
+ footfallCount: hit?._source?.footfallCount,
583
+ comments: hit?._source?.comments,
584
+ userName: hit?._source?.userName,
585
+ role: hit?._source?.role,
586
+ createdAt: hit?._source?.createdAt,
587
+ updatedAt: hit?._source?.updatedAt,
588
+ tempId: hit?._source?.tempId,
589
+ houseKeepingCount: hit?._source?.houseKeepingCount,
590
+ employeeCount: hit?._source?.employeeCount,
591
+ duplicateCount: hit?._source?.duplicateCount,
592
+ houseKeepingACCount: hit?._source?.houseKeepingACCount,
593
+ employeeACCount: hit?._source?.employeeACCount,
594
+ duplicateACCount: hit?._source?.duplicateACCount,
595
+ approverUserName: hit?._source?.approverUserName,
596
+ approverEmail: hit?._source?.approverEmail,
597
+ approverRole: hit?._source?.approverRole,
598
+
599
+
600
+ };
601
+ let result;
413
602
 
414
- return res.sendSuccess( { result: response, count: geteDataCount?.body?.count } );
603
+ const matched = hit.matched_queries;
604
+ // Add only matched data array
605
+ if ( matched.includes( 'matched_employee' )&& ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
606
+ result = defaultData;
607
+ result.employee = hit?._source?.employee;
608
+ // result.type = 'employee';
609
+ result.matched = matched;
610
+ }
611
+ if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
612
+ result = defaultData;
613
+ result.houseKeeping = hit?._source?.houseKeeping;
614
+ // result.type = 'houseKeeping';
615
+ result.matched = matched;
616
+ }
617
+ if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
618
+ result = defaultData;
619
+ result.duplicateImages = hit?._source?.duplicateImages;
620
+ // result.type = 'duplicateImages';
621
+ result.matched = matched;
622
+ }
623
+
624
+ if ( result ) {
625
+ const nested = [
626
+ {
627
+ _id: hit._id,
628
+ _index: hit._index,
629
+ _score: 0,
630
+ _source: result,
631
+ },
632
+ ];
633
+ temp.push( ...nested );
634
+ }
635
+ } );
636
+ }
637
+ const finalResponse = inputData.action ? temp : response;
638
+ if ( finalResponse?.length == 0 || !finalResponse ) {
639
+ return res.sendError( 'No Data found', 204 );
640
+ }
641
+ return res.sendSuccess( { result: finalResponse, count: count, revopCount: revCount } );
415
642
  } catch ( error ) {
416
643
  const err = error.message || 'Internal Server Error';
417
644
  logger.error( { error: error, messgage: req.query } );
@@ -421,45 +648,274 @@ export async function getTickets( req, res ) {
421
648
 
422
649
  export async function updateStatus( req, res ) {
423
650
  try {
651
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
424
652
  const inputData = req.body;
425
- const id = req.query._id;
426
- const splitId = id.split( '_' );
427
- const storeId = splitId[0];
428
- const dateString = splitId[1];
429
- if ( inputData.duplicateStatus === 'approved' && inputData.employeeStatus === 'approved' && inputData.houseKeepingStatus === 'approved' ) {
430
- inputData.status = 'closed';
431
- }
432
- await updateOpenSearchData( openSearch.footfallDirectory, id, { _doc: inputData } );
433
- let bulkBody = [];
434
- for ( let duplicate of inputData.duplicateImages ) {
653
+ let temp = [];
654
+ let result;
655
+ for ( let data of inputData?.data ) {
656
+ result = await updateTicketStatus( data, openSearch, temp, req.user );
657
+ }
658
+
659
+ if ( temp.length == inputData?.data?.length ) {
660
+ return res.sendSuccess( 'Ticket has been updated successfully' );
661
+ }
662
+ if ( result == false ) {
663
+ logger.info( { 'Note': `Queue Doesn't send` } );
664
+ }
665
+ } catch ( error ) {
666
+ const err = error.messgae || 'Internal Server Error';
667
+ logger.error( { error: error, data: req.body, function: 'infra-footfallDirectory-updateStatus' } );
668
+ return res.sendError( err, 500 );
669
+ }
670
+ }
671
+ export async function updateTicketStatus( data, openSearch, temp, user ) {
672
+ const id = data._id;
673
+ const splitId = id.split( '_' );
674
+ const storeId = splitId[0];
675
+ const dateString = splitId[1];
676
+ const {
677
+ duplicateStatus,
678
+ employeeStatus,
679
+ houseKeepingStatus,
680
+ } = data;
681
+ const statusList = [ duplicateStatus, employeeStatus, houseKeepingStatus ];
682
+ // Filter out undefined/null statuses
683
+ const existingStatuses = statusList.filter( ( status ) => status != null );
684
+
685
+ // Check if **none** of the existing statuses are 'pending'
686
+ const allNotPending = existingStatuses.every( ( status ) => status !== 'pending' );
687
+
688
+ if ( allNotPending && existingStatuses.length > 0 ) {
689
+ data.status = 'closed';
690
+ data.approverUserName = user?.userName || '';
691
+ data.approverEmail = user?.email || '';
692
+ data.approverRole = user?.role || '';
693
+ }
694
+ let tempId = [];
695
+ const { _id, ...updateData } = data;
696
+ let getExistingOne = await getOpenSearchById( openSearch.footfallDirectory, _id );
697
+ const source = getExistingOne?.body?._source;
698
+ let bulkBody = [];
699
+ if ( data?.duplicateImages?.length > 0 ) {
700
+ updateData.duplicateImages = await mergeDuplicateImagesWithUncheck( getExistingOne?.body?._source?.duplicateImages, data?.duplicateImages, data?.duplicateStatus );
701
+ logger.info( { updateData: updateData, existeg: getExistingOne?.body?._source?.duplicateImages, sttaus: data?.duplicateStatus } );
702
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: updateData } );
703
+ for ( let duplicate of updateData?.duplicateImages ) {
435
704
  bulkBody.push(
436
705
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${duplicate.timeRange}_${duplicate.tempId}` } },
437
- { doc: { duplicateImage: duplicate.duplicateImage } },
706
+ { doc: { duplicateImage: duplicate.duplicateImages } },
438
707
  );
439
- duplicate.map( ( item ) => {
708
+ duplicate?.data?.map( ( item ) => {
709
+ item.isChecked == true ? tempId.push( { tempId: item.tempId, timeRange: item.timeRange } ) : null;
440
710
  bulkBody.push(
441
711
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${item.timeRange}_${item.tempId}` } },
442
- { doc: { isChecked: item.isChecked } },
712
+ { doc: { isChecked: item.isChecked, status: item?.isChecked == true? 'approved':'rejected' } },
443
713
  );
444
714
  } );
445
715
  }
446
- for ( let employee of inputData.employee ) {
447
- bulkBody.push(
448
- { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${employee.timeRange}_${employee.tempId}` } },
449
- { doc: { isChecked: employee.isChecked } },
450
- );
716
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { duplicateACCount: tempId?.length || 0 } } );
717
+ }
718
+ if ( data?.employee?.length > 0 ) {
719
+ const updatedEmployee = updateEmployeeCheckFlags(
720
+ source.employee,
721
+ updateData.employee,
722
+ data.employeeStatus,
723
+ );
724
+ updateData.employee = updatedEmployee;
725
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: updateData } );
726
+ for ( let employee of updateData?.employee ) {
727
+ ( employee.isChecked == true ) ? tempId.push( { tempId: employee.tempId, timeRange: employee.timeRange } ) : null;
728
+ bulkBody.push(
729
+ { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${employee.timeRange}_${employee.tempId}` } },
730
+ { doc: { isChecked: employee.isChecked, status: employee?.isChecked == true? 'approved':'rejected' } },
731
+ );
451
732
  }
452
- for ( let houseKeeping of inputData.houseKeeping ) {
733
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { employeeACCount: tempId?.length || 0 } } );
734
+ }
735
+ if ( data?.houseKeeping?.length > 0 ) {
736
+ const updatedHouseKeeping = updateEmployeeCheckFlags(
737
+ source?.houseKeeping,
738
+ updateData?.houseKeeping,
739
+ data.houseKeepingStatus,
740
+ );
741
+ updateData.houseKeeping = updatedHouseKeeping;
742
+
743
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: updateData } );
744
+
745
+
746
+ for ( let houseKeeping of updateData?.houseKeeping ) {
747
+ houseKeeping.isChecked == true ? tempId.push( { tempId: houseKeeping.tempId, timeRange: houseKeeping.timeRange } ) : null;
453
748
  bulkBody.push(
454
749
  { update: { _index: openSearch.revop, _id: `${storeId}_${dateString}_${houseKeeping.timeRange}_${houseKeeping.tempId}` } },
455
- { doc: { isChecked: houseKeeping.isChecked } },
750
+ { doc: { isChecked: houseKeeping.isChecked, status: houseKeeping?.isChecked == true? 'approved':'rejected' } },
456
751
  );
457
752
  }
458
- return res.sendSuccess( 'ticket closed successfully' );
459
- } catch ( error ) {
460
- const err = error.messgae || 'Internal Server Error';
461
- logger.error( { error: error, data: req.body, function: 'infra-footfallDirectory-updateStatus' } );
462
- return res.sendError( err, 500 );
753
+ await updateOpenSearchData( openSearch.footfallDirectory, _id, { doc: { houseKeepingACCount: tempId?.length || 0 } } );
754
+ }
755
+ temp.push( storeId );
756
+ if ( bulkBody.length > 0 ) {
757
+ const res = await bulkUpdate( bulkBody );
758
+ if ( res?.errors ) {
759
+ logger.error( 'Bulk update errors:', res.items );
760
+ return { success: false, errors: res.items };
761
+ } else {
762
+ logger.error( 'Bulk status update successful.' );
763
+ // return { success: true };
764
+ }
765
+ }
766
+ if ( data.status == 'closed' ) {
767
+ const query = {
768
+ storeId: storeId,
769
+ isVideoStream: true,
770
+ };
771
+ const getStoreType = await countDocumnetsCamera( query );
772
+ const tempIdList = await extractCheckedTempIds( getExistingOne?.body );
773
+ const isSendMessge = await sendSqsMessage( data, tempIdList, getStoreType, storeId );
774
+ if ( isSendMessge ==true ) {
775
+ return true; // res.sendSuccess( 'Ticket has been updated successfully' );
776
+ } else {
777
+ return false; // res.sendError( 'No SQS message sent', 500 );
778
+ }
779
+ }
780
+ }
781
+
782
+ async function extractCheckedTempIds( document ) {
783
+ const source = document?._source || {};
784
+ const result = [];
785
+
786
+ // Employee
787
+ if ( source.employeeACCount > 0 && Array.isArray( source.employee ) ) {
788
+ for ( const emp of source.employee ) {
789
+ if ( emp.isChecked === true ) {
790
+ result.push( { tempId: emp.tempId, timeRange: emp.timeRange } );
791
+ }
792
+ }
793
+ }
794
+
795
+ // Housekeeping
796
+ if ( source.houseKeepingACCount > 0 && Array.isArray( source.houseKeeping ) ) {
797
+ for ( const hk of source.houseKeeping ) {
798
+ if ( hk.isChecked === true ) {
799
+ result.push( { tempId: hk.tempId, timeRange: hk.timeRange } );
800
+ }
801
+ }
802
+ }
803
+
804
+ // Duplicate Images
805
+ if ( source.duplicateACCount > 0 && Array.isArray( source.duplicateImages ) ) {
806
+ for ( const image of source.duplicateImages ) {
807
+ if ( Array.isArray( image.data ) ) {
808
+ for ( const dup of image.data ) {
809
+ if ( dup.isChecked === true ) {
810
+ result.push( { tempId: dup.tempId, timeRange: dup.timeRange } );
811
+ }
812
+ }
813
+ }
814
+ }
815
+ }
816
+
817
+ return result;
818
+ }
819
+
820
+
821
+ function updateEmployeeCheckFlags( existingEmployees, inputEmployees, status ) {
822
+ // Step 1: Create a Set of tempIds from input (which are all isChecked: true)
823
+ const checkedTempIds = new Set( inputEmployees.map( ( emp ) => emp.tempId ) );
824
+
825
+ // Step 2: Loop through all existing and update isChecked accordingly
826
+ const updatedEmployees = existingEmployees.map( ( emp ) => ( {
827
+ ...emp,
828
+ isChecked: status === 'rejected'? !checkedTempIds.has( emp.tempId ):checkedTempIds.has( emp.tempId ),
829
+ } ) );
830
+
831
+ return updatedEmployees;
832
+ }
833
+
834
+
835
+ function mergeDuplicateImagesWithUncheck( existingData, inputData, status ) {
836
+ const inputImageMap = new Map();
837
+
838
+ // Step 1: Build map of parentTempId -> Set of selected childTempIds
839
+ for ( const parent of inputData ) {
840
+ const selectedChildIds = new Set(
841
+ ( parent.data || [] )
842
+ .filter( ( child ) => child.selected )
843
+ .map( ( child ) => child.tempId ),
844
+ );
845
+ inputImageMap.set( parent.tempId, selectedChildIds );
846
+ }
847
+
848
+ // Step 2: Merge isChecked correctly per parent-child match
849
+ return existingData.map( ( existingParent ) => {
850
+ const parentTempId = existingParent.tempId;
851
+
852
+ // Get selected children for this specific parent
853
+ const selectedChildIds = inputImageMap.get( parentTempId ) || new Set();
854
+
855
+ const updatedData = ( existingParent.data || [] ).map( ( child ) => {
856
+ return {
857
+ ...child,
858
+ isChecked:
859
+ status === 'approved' ? selectedChildIds.has( child.tempId ) : false,
860
+ };
861
+ } );
862
+
863
+ // Also update the parent isChecked if all children are false
864
+ const anyChildChecked = updatedData.some( ( child ) => child.isChecked );
865
+
866
+ return {
867
+ ...existingParent,
868
+ isChecked: anyChildChecked,
869
+ data: updatedData,
870
+ };
871
+ } );
872
+ }
873
+
874
+
875
+ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId ) {
876
+ const sqs = JSON.parse( process.env.SQS );
877
+ const sqsName = getStoreType > 0? sqs.revopTrackTicket: sqs.revopTicket;
878
+ const sqsProduceQueue = getStoreType > 0 ? {
879
+ QueueUrl: `${sqs.url}${sqsName}`,
880
+ MessageBody: JSON.stringify( {
881
+ store_id: storeId,
882
+ store_date: inputData.dateString.split( '-' ).reverse().join( '-' ),
883
+ bucket_name: '',
884
+ zone_id: 'traffic_zone',
885
+ process_type: 'reduction',
886
+ revop_type: 'footfall',
887
+ temp_id: tempId,
888
+ time: Date.now(),
889
+ } ),
890
+ MessageGroupId: 'revops',
891
+ MessageDeduplicationId: Date.now().toString(),
892
+ } : {
893
+ QueueUrl: `${sqs.url}${sqsName}`,
894
+ MessageBody: JSON.stringify( {
895
+ store_id: storeId,
896
+ store_date: inputData.dateString.split( '-' ).reverse().join( '-' ),
897
+ bucket_name: '',
898
+ zone_id: 'traffic_zone',
899
+ process_type: 'reduction',
900
+ revop_type: 'footfall',
901
+ temp_id: tempId,
902
+
903
+ } ),
904
+ };
905
+ const sqsQueue = getStoreType > 0? await sendMessageToFIFOQueue( sqsProduceQueue ):
906
+ await sendMessageToQueue(
907
+ sqsProduceQueue.QueueUrl,
908
+ sqsProduceQueue.MessageBody,
909
+
910
+ );
911
+ if ( sqsQueue.statusCode ) {
912
+ logger.error( {
913
+ error: `${sqsQueue}`,
914
+ type: 'UPLOAD_ERROR',
915
+ } );
916
+ return false;
917
+ } else {
918
+ return true;
463
919
  }
464
920
  }
465
921
 
@@ -467,89 +923,442 @@ export async function getTaggedStores( req, res ) {
467
923
  try {
468
924
  const openSearch = JSON.parse( process.env.OPENSEARCH );
469
925
  const inputData = req.query;
470
- const limit = inputData.limit || 10;
471
- const skip= inputData.offset == 0? 0:( inputData.offset - 1 ) *limit || 0;
472
- inputData.storeId = inputData.storeId.split( ',' ); // convert strig to array
473
- logger.info( { inputData: inputData, limit: limit, skip: skip } );
474
- const getCount= {
926
+ inputData.clientId = inputData?.clientId?.split( ',' );
927
+ logger.info( { inputData: inputData.clientId } );
928
+ let filter = [
929
+ {
930
+ terms: { 'clientId.keyword': inputData.clientId },
931
+ },
932
+ {
933
+ range: {
934
+ dateString: {
935
+ gte: inputData.fromDate,
936
+ lte: inputData.toDate,
937
+ format: 'yyyy-MM-dd',
938
+ },
939
+ },
940
+ },
941
+
942
+ {
943
+ exists: {
944
+ field: 'storeId',
945
+ },
946
+ },
947
+ {
948
+ exists: {
949
+ field: 'storeName',
950
+ },
951
+ },
952
+ ];
953
+ // if ( req?.user?.userType == 'client' && req?.user?.role !== 'superadmin' && req?.stores?.length > 0 ) {
954
+ // filter.push(
955
+ // {
956
+ // terms: { 'storeId.keyword': req.stores },
957
+ // },
958
+ // );
959
+ // }
960
+ filter.push(
961
+ {
962
+ terms: { 'storeId.keyword': req?.stores || [] },
963
+ },
964
+ );
965
+
966
+ if ( inputData.searchValue && inputData.searchValue !== '' ) {
967
+ filter.push( {
968
+ regexp: {
969
+ 'storeName': {
970
+ value: `.*${inputData.searchValue.toLowerCase()}.*`,
971
+ flags: 'ALL',
972
+ },
973
+ },
974
+ } );
975
+ }
976
+ const getCount = {
977
+ size: 0,
475
978
  query: {
476
979
  bool: {
477
- filter: [
478
- { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
479
- inputData.storeId :
480
- inputData.storeId } },
481
- {
482
- range: {
483
- dateString: {
484
- gte: inputData.fromDate,
485
- lte: inputData.toDate,
486
- format: 'yyyy-MM-dd',
980
+ filter: filter,
981
+ },
982
+ },
983
+ aggs: {
984
+ unique_stores: {
985
+ composite: {
986
+ size: 1000,
987
+ sources: [
988
+ {
989
+ storeId: {
990
+ terms: { field: 'storeId.keyword' },
991
+ },
992
+ },
993
+ {
994
+ storeName: {
995
+ terms: { field: 'storeName.keyword' },
487
996
  },
488
997
  },
998
+ ],
999
+ },
1000
+ aggs: {
1001
+ totalCount: {
1002
+ sum: {
1003
+ script: {
1004
+ source: `
1005
+ return (doc.containsKey('duplicateCount') ? doc['duplicateCount'].value : 0)
1006
+ + (doc.containsKey('houseKeepingCount') ? doc['houseKeepingCount'].value : 0)
1007
+ + (doc.containsKey('employeeCount') ? doc['employeeCount'].value : 0);
1008
+ `,
1009
+ lang: 'painless',
1010
+ },
1011
+ },
1012
+ },
1013
+ },
1014
+ },
1015
+ },
1016
+ };
1017
+
1018
+ let temp = [];
1019
+
1020
+ const geteDataCount = await getOpenSearchData( openSearch.footfallDirectory, getCount );
1021
+ if ( !geteDataCount && geteDataCount?.body?.aggregations?.unique_stores?.buckets?.length > 0 ) {
1022
+ return res.sendError( 'No data found', 204 );
1023
+ }
1024
+
1025
+ const response = geteDataCount?.body?.aggregations?.unique_stores?.buckets;
1026
+ const sorted = sortByTotalCount( response, inputData.sortOrder );
1027
+ sorted?.map( ( item ) => temp.push( { storeId: item.key.storeId, storeName: item.key.storeName, totalCount: item?.totalCount?.value } ) );
1028
+
1029
+ if ( response?.length > 0 ) {
1030
+ return res.sendSuccess( { result: temp, count: response?.length } );
1031
+ } else {
1032
+ return res.sendError( 'NO Data', 204 );
1033
+ }
1034
+ } catch ( error ) {
1035
+ const err = error.message || 'Internal Server Error';
1036
+ logger.error( { error: error, messgage: req.query } );
1037
+ return res.sendSuccess( err, 500 );
1038
+ }
1039
+ }
1040
+
1041
+ function sortByTotalCount( response, sortOrder = -1 ) {
1042
+ return response.sort( ( a, b ) => {
1043
+ const aVal = a.totalCount.value;
1044
+ const bVal = b.totalCount.value;
1045
+
1046
+ return sortOrder === 1 ? aVal - bVal : bVal - aVal;
1047
+ } );
1048
+ }
1049
+
1050
+ export async function downloadTickets( req, res ) {
1051
+ try {
1052
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
1053
+ const inputData = req.query;
1054
+ const limit = inputData.limit;
1055
+ const skip = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
1056
+ inputData.storeId = inputData?.storeId?.split( ',' ); // convert strig to array
1057
+ logger.info( { inputData: inputData, limit: limit, skip: skip } );
1058
+ let source = [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'duplicateCount', 'comments', 'employee', 'houseKeeping', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus' ];
1059
+ let filter = [
1060
+
1061
+ {
1062
+ range: {
1063
+ dateString: {
1064
+ gte: inputData.fromDate,
1065
+ lte: inputData.toDate,
1066
+ format: 'yyyy-MM-dd',
1067
+ },
1068
+ },
1069
+ },
1070
+ ];
1071
+ if ( inputData?.storeId ) {
1072
+ filter.push(
1073
+ {
1074
+ terms: {
1075
+ 'storeId.keyword': Array.isArray( inputData.storeId ) ?
1076
+ inputData.storeId :
1077
+ inputData.storeId,
1078
+ },
1079
+ },
1080
+ );
1081
+ }
1082
+ if ( inputData?.dateString ) {
1083
+ filter.push(
1084
+ {
1085
+ term: {
1086
+ 'dateString': inputData.dateString,
1087
+ },
1088
+ },
1089
+ );
1090
+ }
1091
+ if ( inputData.status ) {
1092
+ filter.push(
1093
+ {
1094
+ term: {
1095
+ 'status.keyword': inputData.status,
1096
+ },
1097
+ },
1098
+ );
1099
+ }
1100
+ if (
1101
+ inputData.revopsType
1102
+ ) {
1103
+ filter.push( {
1104
+ exists: {
1105
+ field: inputData.revopsType,
1106
+ },
1107
+ } );
1108
+ inputData.revopsType === 'employee' ?
1109
+ filter.push( {
1110
+ range: {
1111
+ employeeCount: {
1112
+ gt: 0,
1113
+ },
1114
+ },
1115
+ } ) :
1116
+ inputData.revopsType === 'housekeeping' ?
1117
+ filter.push( {
1118
+ range: {
1119
+ housekeepingCount: {
1120
+ gt: 0,
1121
+ },
1122
+ },
1123
+ } ) :
1124
+ filter.push( {
1125
+ range: {
1126
+ duplicateCount: {
1127
+ gt: 0,
1128
+ },
1129
+ },
1130
+ } );
1131
+ source = inputData.revopsType == 'employee' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'employeeCount', 'comments', 'employee', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus' ] :
1132
+ inputData.revopsType == 'houseKeeping' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'houseKeepingCount', 'comments', 'houseKeeping', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus' ] :
1133
+ inputData.revopsType == 'duplicateImages' ? [ 'storeId', 'dateString', 'ticketName', 'footfallCount', 'duplicateCount', 'comments', 'duplicateImages', 'ticketId', 'clientId', 'storeName', 'createdAt', 'updatedAt', 'userName', 'role', 'status', 'employeeStatus', 'houseKeepingStatus', 'duplicateStatus' ] : [];
1134
+ }
1135
+
1136
+ if ( inputData.action ) {
1137
+ filter.push( {
1138
+ bool: {
1139
+ should: [
1140
+ {
1141
+ constant_score: {
1142
+ filter: { term: { 'houseKeepingStatus.keyword': inputData.action } },
1143
+ boost: 1,
1144
+ _name: 'matched_housekeeping',
1145
+ },
1146
+ },
1147
+ {
1148
+ constant_score: {
1149
+ filter: { term: { 'employeeStatus.keyword': inputData.action } },
1150
+ boost: 1,
1151
+ _name: 'matched_employee',
1152
+ },
1153
+ },
1154
+ {
1155
+ constant_score: {
1156
+ filter: { term: { 'duplicateStatus.keyword': inputData.action } },
1157
+ boost: 1,
1158
+ _name: 'matched_duplicate',
1159
+ },
489
1160
  },
490
1161
  ],
1162
+ minimum_should_match: 1,
1163
+ },
1164
+ } );
1165
+ }
1166
+
1167
+
1168
+ const getCount = {
1169
+ query: {
1170
+ bool: {
1171
+ filter: filter,
491
1172
  },
492
1173
  },
493
1174
  };
494
1175
 
1176
+
495
1177
  const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
496
- if ( !geteDataCount || geteDataCount?.body?.count == 0 ) {
1178
+ const count = geteDataCount?.body?.count;
1179
+ if ( !geteDataCount || count == 0 ) {
497
1180
  return res.sendError( 'No data found', 204 );
498
1181
  }
1182
+
499
1183
  const getQuery = {
500
1184
  size: limit,
501
1185
  from: skip,
502
1186
  query: {
503
1187
  bool: {
504
- filter: [
505
- { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
506
- inputData.storeId :
507
- inputData.storeId } },
508
- {
509
- range: {
510
- dateString: {
511
- gte: inputData.fromDate,
512
- lte: inputData.toDate,
513
- format: 'yyyy-MM-dd',
514
- },
515
- },
516
- },
517
- ],
1188
+ filter: filter,
518
1189
  },
519
1190
  },
520
- // _source: [ 'storeName', 'storeId', 'ticketId', 'createdAt', 'footfallCount', 'duplicateCount', 'employeeCount', 'houseKeepingCount', 'status', 'dateString' ],
1191
+ _source: source,
521
1192
  };
522
1193
 
523
1194
  const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
1195
+
524
1196
  const response = getData?.body?.hits?.hits;
525
- logger.info( { response: response, body: getData?.body, getData: getData, geteDataCount: geteDataCount } );
526
-
527
-
528
- if ( inputData.isExport=== true ) {
529
- const temp = [];
530
- for ( const item of response ) {
531
- temp.push( {
532
- 'Store Name': item.storeName,
533
- 'Store ID': item.storeId,
534
- 'Ticket ID': item.ticketId,
535
- 'Ticket raised on': dayjs( item.createdAt ).format( 'dd MMM, yyyy' ),
536
- 'Total Footfalls': item.footfallCount,
537
- 'Duplicates': item.duplicateCount,
538
- 'Employee/Staff': item.employeeCount,
539
- 'HouseKeeping': item.houseKeepingCount,
540
- 'Revised Footfalls': item.footfallCount-( item.duplicateCount+item.employeeCount+item.houseKeepingCount ),
541
- 'Status': item.status,
542
- } );
543
- }
544
- await download( temp, res );
545
- return;
1197
+ if ( !response || response.length == 0 ) {
1198
+ return res.sendError( 'No data', 204 );
1199
+ }
1200
+ let temp = [];
1201
+ if ( inputData?.action ) {
1202
+ response.map( ( hit ) => {
1203
+ const defaultData ={
1204
+ storeId: hit._source.storeId,
1205
+ dateString: hit?._source?.dateString,
1206
+ ticketName: hit?._source?.ticketName,
1207
+ status: hit?._source?.status,
1208
+ employeeStatus: hit?._source?.employeeStatus,
1209
+ houseKeepingStatus: hit?._source?.houseKeepingStatus,
1210
+ duplicateStatus: hit?._source?.duplicateStatus,
1211
+ storeName: hit?._source?.storeName,
1212
+ ticketId: hit?._source?.ticketId,
1213
+ footfallCount: hit?._source?.footfallCount,
1214
+ comments: hit?._source?.comments,
1215
+ userName: hit?._source?.userName,
1216
+ role: hit?._source?.role,
1217
+ createdAt: hit?._source?.createdAt,
1218
+ updatedAt: hit?._source?.updatedAt,
1219
+ tempId: hit?._source?.tempId,
1220
+ };
1221
+ let result;
1222
+
1223
+ const matched = hit.matched_queries;
1224
+ // Add only matched data array
1225
+ if ( matched.includes( 'matched_employee' ) && ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
1226
+ logger.info( { revop: inputData.revopsType } );
1227
+ result = defaultData;
1228
+ result.employee = hit?._source?.employee;
1229
+ // result.type = 'employee';
1230
+ result.matched = matched;
1231
+ }
1232
+ if ( matched.includes( 'matched_housekeeping' )&& ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
1233
+ logger.info( { revop: inputData.revopsType } );
1234
+ result = defaultData;
1235
+ result.houseKeeping = hit?._source?.houseKeeping;
1236
+ // result.type = 'houseKeeping';
1237
+ result.matched = matched;
1238
+ }
1239
+ if ( matched.includes( 'matched_duplicate' )&& ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
1240
+ logger.info( { revop: inputData.revopsType } );
1241
+ result = defaultData;
1242
+ result.duplicateImages = hit?._source?.duplicateImages;
1243
+ // result.type = 'duplicateImages';
1244
+ result.matched = matched;
1245
+ }
1246
+ if ( result ) {
1247
+ logger.info( { revop: inputData.revopsType, result: result } );
1248
+ const nested = [
1249
+ {
1250
+ _id: hit._id,
1251
+ _index: hit._index,
1252
+ _score: 0,
1253
+ _source: result,
1254
+ },
1255
+ ];
1256
+
1257
+ temp.push( ...nested );
1258
+ }
1259
+ } );
1260
+ }
1261
+
1262
+ const result = inputData.action ? temp : response;
1263
+
1264
+ if ( !result || result?.length == 0 ) {
1265
+ return res.sendError( 'no data', 204 );
546
1266
  }
1267
+ let allResults = [];
1268
+ const chunks = await chunkArray( result, 500 );
547
1269
 
548
- return res.sendSuccess( { result: response, count: geteDataCount?.body?.count } );
1270
+ for ( const chunk of chunks ) {
1271
+ const results = await Promise.all( chunk.map( extractTempIds ) );
1272
+ allResults.push( ...results.flat() );
1273
+ }
1274
+ if ( allResults?.length == 0 ) {
1275
+ return res.sendError( 'no data found', 204 );
1276
+ }
1277
+ const query = {
1278
+ stores: { $in: inputData.storeId },
1279
+ fromDate: inputData?.fromDate,
1280
+ toDate: inputData?.toDate,
1281
+ ticketStatus: inputData?.status,
1282
+ ticketAction: inputData?.action,
1283
+ revopsType: inputData?.revopsType,
1284
+ type: 'get-tickets',
1285
+ };
1286
+ const record={
1287
+ stores: inputData?.storeId,
1288
+ fromDate: inputData?.fromDate,
1289
+ toDate: inputData?.toDate,
1290
+ ticketStatus: inputData?.status,
1291
+ ticketAction: inputData?.action,
1292
+ revopsType: inputData?.revopsType,
1293
+ type: 'get-tickets',
1294
+ userEmail: req?.user?.email,
1295
+ };
1296
+ await upsertRevopDownload( query, record );
1297
+ const getId = await findOneRevopDownload( query, { _id: 1, status: 1 } );
1298
+ if ( !getId ) {
1299
+ return res.sendError( 'download record is missing', 400 );
1300
+ }
1301
+ const sqs = JSON.parse( process.env.SQS );
1302
+ const sqsName = getStoreType > 0? sqs.revopTrackTicket: sqs.revopTicket;
1303
+
1304
+ const sqsProduceQueue ={
1305
+ QueueUrl: `${sqs.url}${sqsName}`,
1306
+ MessageBody: JSON.stringify( {
1307
+ _id: getId?._id,
1308
+ data: allResults,
1309
+
1310
+ } ),
1311
+ };
1312
+ const sqsQueue = await sendMessageToQueue(
1313
+ sqsProduceQueue.QueueUrl,
1314
+ sqsProduceQueue.MessageBody,
1315
+
1316
+ );
1317
+ if ( sqsQueue.statusCode ) {
1318
+ logger.error( {
1319
+ error: `${sqsQueue}`,
1320
+ type: 'UPLOAD_ERROR',
1321
+ } );
1322
+ return false;
1323
+ }
1324
+ return res.sendSuccess( { result: { status: getId?.status, outputFilePath: getId?.outputFilePath } } );
549
1325
  } catch ( error ) {
550
1326
  const err = error.message || 'Internal Server Error';
551
- logger.error( { error: error, messgage: req.query } );
1327
+ logger.error( { error: error, messgage: req.query, function: 'downloadTickets-infra-footfallDirectory' } );
552
1328
  return res.sendSuccess( err, 500 );
553
1329
  }
554
1330
  }
555
1331
 
1332
+ async function extractTempIds( document ) {
1333
+ logger.info( { document: document } );
1334
+ const source = document?._source || {};
1335
+ const result = [];
1336
+
1337
+ // Employee
1338
+ if ( Array.isArray( source.employee ) ) {
1339
+ for ( const emp of source.employee ) {
1340
+ result.push( emp.filePath );
1341
+ }
1342
+ }
1343
+
1344
+ // Housekeeping
1345
+ if ( Array.isArray( source.houseKeeping ) ) {
1346
+ for ( const hk of source.houseKeeping ) {
1347
+ result.push( hk.filePath );
1348
+ }
1349
+ }
1350
+
1351
+ // Duplicate Images
1352
+ if ( Array.isArray( source.duplicateImages ) ) {
1353
+ for ( const image of source.duplicateImages ) {
1354
+ if ( Array.isArray( image.data ) ) {
1355
+ for ( const dup of image.data ) {
1356
+ result.push( dup.filePath );
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+ return result;
1362
+ }
1363
+
1364
+