tango-app-api-infra 3.8.1-beta.0 → 3.8.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-infra",
3
- "version": "3.8.1-beta.0",
3
+ "version": "3.8.1-beta.1",
4
4
  "description": "infra",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -303,23 +303,70 @@ export async function getTickets( req, res ) {
303
303
  const skip= inputData.offset == 0? 0:( inputData.offset - 1 ) *limit || 0;
304
304
  inputData.storeId = inputData.storeId.split( ',' ); // convert strig to array
305
305
  logger.info( { inputData: inputData, limit: limit, skip: skip } );
306
- const getCount= {
307
- query: {
308
- bool: {
309
- filter: [
310
- { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
306
+
307
+ let filter =[
308
+ { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
311
309
  inputData.storeId :
312
- inputData.storeId } },
310
+ inputData.storeId },
311
+ },
312
+ {
313
+ range: {
314
+ dateString: {
315
+ gte: inputData.fromDate,
316
+ lte: inputData.toDate,
317
+ format: 'yyyy-MM-dd',
318
+ },
319
+ },
320
+ },
321
+ ];
322
+ if ( inputData.status ) {
323
+ filter.push(
324
+ {
325
+ term: {
326
+ 'status.keyword': inputData.status,
327
+ },
328
+ },
329
+ );
330
+ }
331
+ if (
332
+ inputData.revopsType
333
+ ) {
334
+ filter.push( {
335
+ exists: {
336
+ field: inputData.revopsType,
337
+ },
338
+ } );
339
+ }
340
+
341
+ if ( inputData.action ) {
342
+ filter.push( {
343
+ bool: {
344
+ should: [
313
345
  {
314
- range: {
315
- dateString: {
316
- gte: inputData.fromDate,
317
- lte: inputData.toDate,
318
- format: 'yyyy-MM-dd',
319
- },
346
+ term: {
347
+ 'houseKeepingStatus.keyword': inputData.action,
348
+ },
349
+ },
350
+ {
351
+ term: {
352
+ 'employeeStatus.keyword': inputData.action,
353
+ },
354
+ },
355
+ {
356
+ term: {
357
+ 'duplicateStatus.keyword': inputData.action,
320
358
  },
321
359
  },
322
360
  ],
361
+ minimum_should_match: 1, // Ensures at least one should condition must match
362
+ },
363
+ } );
364
+ }
365
+
366
+ const getCount= {
367
+ query: {
368
+ bool: {
369
+ filter: filter,
323
370
  },
324
371
  },
325
372
  };
@@ -328,28 +375,15 @@ export async function getTickets( req, res ) {
328
375
  if ( !geteDataCount || geteDataCount?.body?.count == 0 ) {
329
376
  return res.sendError( 'No data found', 204 );
330
377
  }
378
+
331
379
  const getQuery = {
332
380
  size: limit,
333
381
  from: skip,
334
382
  query: {
335
383
  bool: {
336
- filter: [
337
- { terms: { 'storeId.keyword': Array.isArray( inputData.storeId ) ?
338
- inputData.storeId :
339
- inputData.storeId } },
340
- {
341
- range: {
342
- dateString: {
343
- gte: inputData.fromDate,
344
- lte: inputData.toDate,
345
- format: 'yyyy-MM-dd',
346
- },
347
- },
348
- },
349
- ],
384
+ filter: filter,
350
385
  },
351
386
  },
352
- // _source: [ 'storeName', 'storeId', 'ticketId', 'createdAt', 'footfallCount', 'duplicateCount', 'employeeCount', 'houseKeepingCount', 'status', 'dateString' ],
353
387
  };
354
388
 
355
389
  const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
@@ -429,3 +463,93 @@ export async function updateStatus( req, res ) {
429
463
  }
430
464
  }
431
465
 
466
+ export async function getTaggedStores( req, res ) {
467
+ try {
468
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
469
+ 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= {
475
+ query: {
476
+ 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',
487
+ },
488
+ },
489
+ },
490
+ ],
491
+ },
492
+ },
493
+ };
494
+
495
+ const geteDataCount = await getOpenSearchCount( openSearch.footfallDirectory, getCount );
496
+ if ( !geteDataCount || geteDataCount?.body?.count == 0 ) {
497
+ return res.sendError( 'No data found', 204 );
498
+ }
499
+ const getQuery = {
500
+ size: limit,
501
+ from: skip,
502
+ query: {
503
+ 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
+ ],
518
+ },
519
+ },
520
+ // _source: [ 'storeName', 'storeId', 'ticketId', 'createdAt', 'footfallCount', 'duplicateCount', 'employeeCount', 'houseKeepingCount', 'status', 'dateString' ],
521
+ };
522
+
523
+ const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
524
+ 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;
546
+ }
547
+
548
+ return res.sendSuccess( { result: response, count: geteDataCount?.body?.count } );
549
+ } catch ( error ) {
550
+ const err = error.message || 'Internal Server Error';
551
+ logger.error( { error: error, messgage: req.query } );
552
+ return res.sendSuccess( err, 500 );
553
+ }
554
+ }
555
+
@@ -143,6 +143,24 @@ export const footfallDirectoryDocs = {
143
143
  scema: j2s( getTicketsSchema ).swagger,
144
144
  required: true,
145
145
  },
146
+ {
147
+ in: 'query',
148
+ name: 'revopsType',
149
+ scema: j2s( getTicketsSchema ).swagger,
150
+ required: false,
151
+ },
152
+ {
153
+ in: 'query',
154
+ name: 'status',
155
+ scema: j2s( getTicketsSchema ).swagger,
156
+ required: false,
157
+ },
158
+ {
159
+ in: 'query',
160
+ name: 'action',
161
+ scema: j2s( getTicketsSchema ).swagger,
162
+ required: false,
163
+ },
146
164
  ],
147
165
  responses: {
148
166
  200: { description: 'Successful' },
@@ -162,7 +180,7 @@ export const footfallDirectoryDocs = {
162
180
  parameters: [
163
181
  {
164
182
  in: 'query',
165
- name: 'storeId',
183
+ name: '_id',
166
184
  scema: j2s( updateStatusQuerySchema ).swagger,
167
185
  required: true,
168
186
  },
@@ -185,7 +185,6 @@ export const ticketListValid = {
185
185
 
186
186
  export const getTicketsSchema = Joi.object().keys( {
187
187
  storeId: Joi.string().required(),
188
-
189
188
  fromDate: Joi.string()
190
189
  .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
191
190
  .required()
@@ -221,6 +220,12 @@ export const getTicketsSchema = Joi.object().keys( {
221
220
 
222
221
  return value;
223
222
  } ),
223
+ status: Joi.string().optional(),
224
+ action: Joi.string().optional(),
225
+ revopsType: Joi.string().optional(),
226
+ limit: Joi.number().optional(),
227
+ offset: Joi.number().optional(),
228
+
224
229
  } ).custom( ( value, helpers ) => {
225
230
  const from = dayjs( value.fromDate );
226
231
  const to = dayjs( value.toDate );
@@ -301,3 +306,65 @@ export const updateStatusValid = {
301
306
  query: updateStatusQuerySchema,
302
307
  body: updateStatusSchemea,
303
308
  };
309
+
310
+
311
+ export const getTaggedStoresSchema = Joi.object().keys( {
312
+ clientId: Joi.string().required(),
313
+
314
+ fromDate: Joi.string()
315
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
316
+ .required()
317
+ .messages( {
318
+ 'string.pattern.name': `'fromDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
319
+ 'string.empty': `'fromDate' is required.`,
320
+ } )
321
+ .custom( ( value, helpers ) => {
322
+ const from = dayjs( value );
323
+ if ( !from.isValid() ) {
324
+ return helpers.error( 'any.invalid', { message: 'Invalid fromDate' } );
325
+ }
326
+ return value;
327
+ } ),
328
+
329
+ toDate: Joi.string()
330
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
331
+ .required()
332
+ .messages( {
333
+ 'string.pattern.name': `'toDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
334
+ 'string.empty': `'toDate' is required.`,
335
+ } )
336
+ .custom( ( value, helpers ) => {
337
+ const to = dayjs( value );
338
+ const today = dayjs();
339
+
340
+ if ( !to.isValid() ) {
341
+ return helpers.error( 'any.invalid', { message: 'Invalid toDate' } );
342
+ }
343
+ if ( to.isAfter( today, 'day' ) ) {
344
+ return helpers.error( 'any.invalid', { message: 'toDate cannot be in the future' } );
345
+ }
346
+
347
+ return value;
348
+ } ),
349
+ } ).custom( ( value, helpers ) => {
350
+ const from = dayjs( value.fromDate );
351
+ const to = dayjs( value.toDate );
352
+
353
+ if ( !from.isValid() || !to.isValid() ) {
354
+ return helpers.error( 'any.invalid', { message: 'Invalid dates' } );
355
+ }
356
+
357
+ if ( from.isAfter( to ) ) {
358
+ return helpers.error( 'any.invalid', { message: 'fromDate cannot be after toDate' } );
359
+ }
360
+
361
+ if ( to.diff( from, 'day' ) > 90 ) {
362
+ return helpers.error( 'any.invalid', { message: 'Date range cannot exceed 90 days' } );
363
+ }
364
+
365
+ return value;
366
+ } );
367
+
368
+ export const getTaggedStoresValid = {
369
+ query: getTaggedStoresSchema,
370
+ };
@@ -1,7 +1,7 @@
1
1
  import express from 'express';
2
2
  import { isExist } from '../validations/footfallDirectory.validation.js';
3
- import { createTicket, getTickets, ticketList, ticketSummary, updateStatus } from '../controllers/footfallDirectory.controllers.js';
4
- import { createTicketValid, getTicketsValid, ticketListValid, ticketSummaryValid, updateStatusValid } from '../dtos/footfallDirectory.dtos.js';
3
+ import { createTicket, getTaggedStores, getTickets, ticketList, ticketSummary, updateStatus } from '../controllers/footfallDirectory.controllers.js';
4
+ import { createTicketValid, getTaggedStoresValid, getTicketsValid, ticketListValid, ticketSummaryValid, updateStatusValid } from '../dtos/footfallDirectory.dtos.js';
5
5
  import { bulkValidate, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
6
6
 
7
7
  export const footfallDirectoryRouter = express.Router();
@@ -11,6 +11,7 @@ footfallDirectoryRouter.get( '/ticket-summary', isAllowedSessionHandler, bulkVal
11
11
 
12
12
  footfallDirectoryRouter.get( '/ticket-list', isAllowedSessionHandler, bulkValidate( ticketListValid ), ticketList );
13
13
  footfallDirectoryRouter.get( '/get-tickets', isAllowedSessionHandler, bulkValidate( getTicketsValid ), getTickets );
14
+ footfallDirectoryRouter.get( '/get-tagged-stores', bulkValidate( getTaggedStoresValid ), getTaggedStores );
14
15
 
15
16
  footfallDirectoryRouter.put( '/update-status', bulkValidate( updateStatusValid ), updateStatus );
16
17