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
|
@@ -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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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: '
|
|
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
|
|