tango-app-api-report 3.4.0-beta.5 → 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.5",
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"
@@ -361,6 +361,7 @@ export async function generateReport( req, res ) {
361
361
  const sqs = JSON.parse( process.env.SQS );
362
362
  const openSearch = JSON.parse( process.env.OPENSEARCH );
363
363
  const inputData = req.body;
364
+ const revopType = req.report?.reportConfigs?.reportType || 'automate';
364
365
  const sqsProduceQueue = {
365
366
  QueueUrl: `${sqs.url}${sqs.generateReport}`,
366
367
  MessageBody: JSON.stringify( {
@@ -368,6 +369,7 @@ export async function generateReport( req, res ) {
368
369
  client_name: req.report?.reportConfigs?.reportName,
369
370
  client_id: Number( inputData.clientId ),
370
371
  message: 'good to trigger',
372
+ reportType: revopType,
371
373
  } ),
372
374
  };
373
375
  const sqsQueue = await sendMessageToQueue( sqsProduceQueue.QueueUrl, sqsProduceQueue.MessageBody );
@@ -382,16 +384,17 @@ export async function generateReport( req, res ) {
382
384
  reportName: req.report?.reportConfigs?.reportName,
383
385
  clientId: inputData.clientId,
384
386
  sqsMessageId: sqsQueue.MessageId,
387
+ reportType: revopType,
385
388
  },
386
389
  createdAt: Date.now(),
387
390
  };
388
391
  logger.info( { auditLog: openSearch.auditLog, openSearch: openSearch } );
389
392
  await insertOpenSearchData( openSearch.auditLog, log );
390
393
 
391
- 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' } );
392
395
  }
393
396
  } catch ( error ) {
394
- const err = error.code || 'Internal Server Error';
397
+ const err = error.message || 'Internal Server Error';
395
398
  logger.error( { error: error, message: req.body, function: 'generateReport' } );
396
399
  return res.sendError( err, 500 );
397
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
+