tango-app-api-report 3.4.0-beta.4 → 3.5.0

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,11 +1,11 @@
1
1
  {
2
2
  "name": "tango-app-api-report",
3
- "version": "3.4.0-beta.4",
3
+ "version": "3.5.0",
4
4
  "description": "report",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "start": "nodemon --exec \"eslint --fix . && node index.js\""
8
+ "start": "nodemon --exec \"eslint --fix . && node app.js\""
9
9
  },
10
10
  "engines": {
11
11
  "node": ">=18.10.0"
@@ -25,7 +25,7 @@
25
25
  "nodemailer": "^6.9.13",
26
26
  "nodemon": "^3.1.0",
27
27
  "swagger-ui-express": "^5.0.0",
28
- "tango-api-schema": "^2.1.58",
28
+ "tango-api-schema": "^2.4.7",
29
29
  "tango-app-api-middleware": "^3.1.45",
30
30
  "winston": "^3.12.0",
31
31
  "winston-daily-rotate-file": "^5.0.0"
@@ -266,10 +266,10 @@ export async function clientReportList( req, res ) {
266
266
  return res.sendError( 'No Data Found', 204 );
267
267
  }
268
268
  let paginatedData = count;
269
+
269
270
  if ( inputData?.reportStatus==='' ) {
270
271
  paginatedData = await paginate( count, offset, limit );
271
272
  }
272
-
273
273
  if ( inputData.reportStatus&&inputData.reportStatus!=='' ) {
274
274
  paginatedData = await splitArray( paginatedData );
275
275
  paginatedData= paginatedData.filter( ( ele ) => {
@@ -291,7 +291,7 @@ export async function clientReportList( req, res ) {
291
291
  }
292
292
 
293
293
  export async function mapFunction( array ) {
294
- for ( let [ client ] of array.entries() ) {
294
+ for ( let client of array ) {
295
295
  const openSearch = JSON.parse( process.env.OPENSEARCH );
296
296
  const reportLog = await getOpenSearchData( openSearch.auditLog,
297
297
  {
@@ -326,8 +326,10 @@ export async function mapFunction( array ) {
326
326
  },
327
327
 
328
328
  } );
329
+
329
330
  client.reportStatus='notsent';
330
- if ( reportLog.body.hits.hits.length > 0 ) {
331
+
332
+ if ( reportLog?.body?.hits?.hits?.length > 0 ) {
331
333
  const sourcesArray = reportLog.body.hits.hits.map( ( hit ) => hit._source );
332
334
  if ( sourcesArray.length>0 ) {
333
335
  client.reportStatus='sent';
@@ -349,9 +351,8 @@ export async function splitArray( array ) {
349
351
  }
350
352
 
351
353
  export async function paginate( array, pageNumber, pageSize ) {
352
- const startIndex = pageNumber * pageSize;
354
+ const startIndex = pageNumber;
353
355
  const endIndex = startIndex + pageSize;
354
-
355
356
  return array.slice( startIndex, endIndex );
356
357
  }
357
358
 
@@ -360,6 +361,7 @@ export async function generateReport( req, res ) {
360
361
  const sqs = JSON.parse( process.env.SQS );
361
362
  const openSearch = JSON.parse( process.env.OPENSEARCH );
362
363
  const inputData = req.body;
364
+ const revopType = req.report?.reportConfigs?.reportType || 'automate';
363
365
  const sqsProduceQueue = {
364
366
  QueueUrl: `${sqs.url}${sqs.generateReport}`,
365
367
  MessageBody: JSON.stringify( {
@@ -367,6 +369,7 @@ export async function generateReport( req, res ) {
367
369
  client_name: req.report?.reportConfigs?.reportName,
368
370
  client_id: Number( inputData.clientId ),
369
371
  message: 'good to trigger',
372
+ reportType: revopType,
370
373
  } ),
371
374
  };
372
375
  const sqsQueue = await sendMessageToQueue( sqsProduceQueue.QueueUrl, sqsProduceQueue.MessageBody );
@@ -381,16 +384,17 @@ export async function generateReport( req, res ) {
381
384
  reportName: req.report?.reportConfigs?.reportName,
382
385
  clientId: inputData.clientId,
383
386
  sqsMessageId: sqsQueue.MessageId,
387
+ reportType: revopType,
384
388
  },
385
389
  createdAt: Date.now(),
386
390
  };
387
391
  logger.info( { auditLog: openSearch.auditLog, openSearch: openSearch } );
388
392
  await insertOpenSearchData( openSearch.auditLog, log );
389
393
 
390
- return res.sendSuccess( { result: 'Report has been Initiated Sucessfully' } );
394
+ return res.sendSuccess( { result: revopType !== 'manual'? 'The report has been initiated and will be sent automatically.':'The report has been initiated Successfully' } );
391
395
  }
392
396
  } catch ( error ) {
393
- const err = error.code || 'Internal Server Error';
397
+ const err = error.message || 'Internal Server Error';
394
398
  logger.error( { error: error, message: req.body, function: 'generateReport' } );
395
399
  return res.sendError( err, 500 );
396
400
  }
@@ -3,7 +3,7 @@ import express from 'express';
3
3
  import { accessVerification, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
4
4
  import { addReportValid, clientListTableValid, deleteReportValid, downloadReportValid, generateReportValid, getReportListValid, getReportLogValid, getReportValid, getSingleReportValid, sendReportValid, updateReportValid } from '../dtos/report.dtos.js';
5
5
  import { clientReportList, createReport, deleteReport, dowloadReport, generateReport, getReport, getReportList, getReportLog, getSingleReport, sendReport, updateBasePrice, updateReport, uploadManualReport } from '../controllers/report.controllers.js';
6
- import { isFileExist, isFolderExist, isReportEnable, isSendReportEnable } from '../validations/report.validations.js';
6
+ import { getQueueCount, isAuditCompleted, isFeatureRunning, isFileExist, isFolderExist, isReportEnable, isSendReportEnable } from '../validations/report.validations.js';
7
7
 
8
8
  export const reportRouter = express.Router();
9
9
  // report setting configurations
@@ -30,7 +30,7 @@ reportRouter.post( '/generate-report', isAllowedSessionHandler,
30
30
  accessVerification( { userType: [ 'tango' ], access: [
31
31
  { featureName: 'TangoAdmin', name: 'Reports', permissions: [ 'isEdit' ] },
32
32
  ] } ),
33
- validate( generateReportValid ), isReportEnable, generateReport );
33
+ validate( generateReportValid ), isReportEnable, getQueueCount, isAuditCompleted, isFeatureRunning, generateReport );
34
34
 
35
35
  reportRouter.post( '/upload-manual-report', isAllowedSessionHandler,
36
36
  accessVerification( { userType: [ 'tango' ], access: [
@@ -0,0 +1,6 @@
1
+ import auditStoreDataModel from 'tango-api-schema/schema/auditStoreData.model.js';
2
+
3
+
4
+ export function aggregateAuditStoreData( query ) {
5
+ return auditStoreDataModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
6
+ }
@@ -0,0 +1,6 @@
1
+ import storeAuditModel from 'tango-api-schema/schema/storeAudit.model.js';
2
+
3
+
4
+ export function aggregateStoreAudit( query ) {
5
+ return storeAuditModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
6
+ }
@@ -1,11 +1,14 @@
1
1
  import { checkFileExist, logger } from 'tango-app-api-middleware';
2
2
  import { findOneClient } from '../service/client.service.js';
3
+ import { aggregateStoreAudit } from '../service/storeAudit.service.js';
4
+ import { getQueueUrl, receiveMessage } from 'tango-app-api-middleware/src/utils/aws/sqs.js';
5
+ import axios from 'axios';
3
6
 
4
7
 
5
8
  export async function isReportEnable( req, res, next ) {
6
9
  try {
7
10
  const inputData = req.method !== 'GET'? req.body: req.query;
8
- const getReportName = await findOneClient( { 'clientId': inputData.clientId, 'status': { $in: [ 'active', 'hold' ] }, 'reportConfigs.report': true }, { '_id': 0, 'reportConfigs.reportName': 1, 'clientName': 1 } );
11
+ const getReportName = await findOneClient( { 'clientId': inputData.clientId, 'status': { $in: [ 'active', 'hold' ] }, 'reportConfigs.report': true }, { '_id': 0, 'reportConfigs': 1, 'clientName': 1, 'auditConfigs': 1 } );
9
12
  if ( !getReportName ) {
10
13
  return res.sendError( 'This action was forbidden', 403 );
11
14
  }
@@ -74,3 +77,211 @@ export async function isFileExist( req, res, next ) {
74
77
  return res.sendError( error, 500 );
75
78
  }
76
79
  }
80
+
81
+
82
+ export async function isAuditCompleted( req, res, next ) {
83
+ try {
84
+ const inputData = req.body;
85
+ if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
86
+ logger.info( { inputData: inputData } );
87
+
88
+ const getStoreAuditQuery = [
89
+ {
90
+ $match: {
91
+ $and: [
92
+ {
93
+ fileDate: { $eq: inputData.fileDate },
94
+ },
95
+ {
96
+ clientId: { $eq: inputData.clientId },
97
+ },
98
+ {
99
+ status: { $ne: 'completed' },
100
+ },
101
+ ],
102
+ },
103
+ },
104
+ {
105
+ $group: {
106
+ _id: null,
107
+ storeId: { $addToSet: '$storeId' },
108
+ },
109
+ },
110
+ {
111
+ $project: {
112
+ storeId: 1,
113
+ },
114
+ },
115
+ ];
116
+
117
+ const getStoreAudit = await aggregateStoreAudit( getStoreAuditQuery );
118
+ if ( getStoreAudit && getStoreAudit?.[0]?.storeId?.length >0 ) {
119
+ return res.sendError(
120
+ 'Some files are not completed in the audit level for the selected date. Please try again later.',
121
+ 400,
122
+ );
123
+ } else {
124
+ return next();
125
+ }
126
+ } else {
127
+ return next();
128
+ }
129
+ } catch ( error ) {
130
+ logger.error( { error: error, message: req.body, function: 'isAuditCompleted' } );
131
+ return res.sendError( error.message || 'Internal Server Error', 500 );
132
+ }
133
+ }
134
+ // -----------------------------
135
+ // getQueueCount endpoint
136
+ // -----------------------------
137
+ export async function getQueueCount( req, res, next ) {
138
+ try {
139
+ if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
140
+ if ( !req?.report?.auditConfigs ) return next();
141
+
142
+ const temp = req.report.auditConfigs;
143
+
144
+ // Config flags
145
+ const auditConfig = {
146
+ track: temp.audit,
147
+ traffic: temp.trackAudit,
148
+ };
149
+
150
+
151
+ // Mapping of config flags to queue-check functions
152
+ const queueChecks = {
153
+ track: [ () => checkTrackingQueueCount( temp?.trackQueueName ) ],
154
+ traffic: [
155
+ () => checkTrafficQueueCount( temp?.trafficQueueName ),
156
+ () => checkZoneQueueCount( temp?.zoneQueueName ),
157
+ ],
158
+ };
159
+
160
+ // Run all enabled checks, stop on first true
161
+ const isProcessing = await runQueueChecks( auditConfig, queueChecks );
162
+
163
+ if ( isProcessing ) {
164
+ return res.sendError(
165
+ 'This client is already being processed in the Queue. Please try again later.',
166
+ 400,
167
+ );
168
+ }
169
+
170
+ return next();
171
+ } else {
172
+ return next();
173
+ }
174
+ } catch ( error ) {
175
+ logger.error( { error, message: req.body, function: 'getQueueCount' } );
176
+ return res.sendError( error.message || 'Internal Server Error', 500 );
177
+ }
178
+ }
179
+
180
+
181
+ // -----------------------------
182
+ // Run queue checks in parallel, stop on first true
183
+ // -----------------------------
184
+ async function runQueueChecks( config, queueChecks ) {
185
+ const tasks = Object.entries( queueChecks )
186
+ .filter( ( [ flag ] ) => config[flag] )
187
+ .flatMap( ( [ , funcs ] ) => funcs.map( ( fn ) => fn() ) );
188
+
189
+ if ( tasks.length === 0 ) return false;
190
+
191
+ try {
192
+ const result = await Promise.any( tasks );
193
+ return result === true;
194
+ } catch {
195
+ return false; // all checks returned false
196
+ }
197
+ }
198
+
199
+ // -----------------------------
200
+ // Helper: add timeout to promises
201
+ // -----------------------------
202
+ function withTimeout( promise, ms, fallback = false ) {
203
+ return Promise.race( [
204
+ promise,
205
+ new Promise( ( resolve ) => setTimeout( () => resolve( fallback ), ms ) ),
206
+ ] );
207
+ }
208
+
209
+ // -----------------------------
210
+ // Generic safe queue check
211
+ // -----------------------------
212
+ async function safeQueueCheck( queueName ) {
213
+ if ( !queueName ) return false;
214
+
215
+ const [ queue, count ] = await Promise.all( [
216
+ withTimeout( getQueueUrl( queueName ), 5000 ),
217
+ withTimeout( receiveMessage( queueName ), 5000 ),
218
+ ] );
219
+
220
+ if ( queue?.statusCode ) return false;
221
+
222
+ return !!count && count !== 'null';
223
+ }
224
+
225
+ // -----------------------------
226
+ // Queue check functions
227
+ // -----------------------------
228
+ async function checkTrackingQueueCount( queueName ) {
229
+ return safeQueueCheck( queueName );
230
+ }
231
+
232
+ async function checkTrafficQueueCount( queueName ) {
233
+ return safeQueueCheck( queueName );
234
+ }
235
+
236
+ async function checkZoneQueueCount( queueName ) {
237
+ return safeQueueCheck( queueName );
238
+ }
239
+
240
+ export async function isFeatureRunning( req, res, next ) {
241
+ try {
242
+ const inputData = req.body;
243
+ if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
244
+ const url = JSON.parse( process.env.URL );
245
+ const REIDURL = url.REIDStatus;
246
+ const FeatureURL = url.FeatureStatus;
247
+ const data = { 'client_id': inputData.clientId, 'store_date': inputData?.fileDate };
248
+
249
+ const isFeature = await callLambdaWithData( data, FeatureURL );
250
+ const isREID = await callLambdaWithData( data, REIDURL );
251
+ if ( isFeature?.status_code !== '200' ||isREID?.status_code !== '200' ) {
252
+ return res.sendError(
253
+ 'This client is already being processed in REID/Feature layer for the selected date. Please try again later.',
254
+ 400,
255
+ );
256
+ } else if ( isFeature?.data === false || isREID?.data === false ) {
257
+ return res.sendError(
258
+ 'This client is already being processed in REID/Feature layer for the selected date. Please try again later.',
259
+ 400,
260
+ );
261
+ } else {
262
+ return next();
263
+ }
264
+ } else {
265
+ return next();
266
+ }
267
+ } catch ( error ) {
268
+ logger.error( { error: error, message: req.query, function: 'isFeatureRunning' } );
269
+ return res.sendError( error.message || 'Iternal Server Error', 500 );
270
+ }
271
+ }
272
+
273
+
274
+ export const callLambdaWithData = async ( data, LAMBDA_URL ) => {
275
+ try {
276
+ const response = await axios.post( LAMBDA_URL, data, {
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ },
280
+ } );
281
+ return response.data;
282
+ } catch ( error ) {
283
+ console.error( 'Error calling Lambda with data:', error );
284
+ throw error;
285
+ }
286
+ };
287
+