tango-app-api-analysis-traffic 3.0.0-alpha.54 → 3.0.0-alpha.56
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-analysis-traffic",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.56",
|
|
4
4
|
"description": "Traffic Analysis",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"nodemon": "^3.1.4",
|
|
25
25
|
"swagger-ui-express": "^5.0.1",
|
|
26
26
|
"tango-api-schema": "^2.2.7",
|
|
27
|
-
"tango-app-api-middleware": "3.1.
|
|
27
|
+
"tango-app-api-middleware": "^3.1.46",
|
|
28
28
|
"winston": "^3.13.1",
|
|
29
29
|
"winston-daily-rotate-file": "^5.0.0"
|
|
30
30
|
},
|
|
@@ -10,7 +10,6 @@ export async function storeList( req, res ) {
|
|
|
10
10
|
if ( req.user.userType !== 'superadmin' && inputData?.assignedStores?.length == 0 ) {
|
|
11
11
|
return res.sendSuccess( { result: [] } );
|
|
12
12
|
}
|
|
13
|
-
logger.info( { assignedStores: inputData?.assignedStores } );
|
|
14
13
|
let filter = [
|
|
15
14
|
{
|
|
16
15
|
clientId: { $eq: req.clientId },
|
|
@@ -61,101 +60,26 @@ export async function storeList( req, res ) {
|
|
|
61
60
|
export async function addBills( req, res ) {
|
|
62
61
|
try {
|
|
63
62
|
let resData=[];
|
|
64
|
-
let errorData=[];
|
|
65
63
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
66
|
-
const inputData = req.
|
|
67
|
-
if ( req.user.role !== 'superadmin' && req.user.userType !== 'tango'&& ( inputData?.assignedStores?.length == 0 || !inputData?.assignedStores ) ) {
|
|
68
|
-
return res.sendError( 'access forbidden', 403 );
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let filter = [];
|
|
72
|
-
if ( req.user.role !== 'superadmin' && req.user.userType !== 'tango' ) {
|
|
73
|
-
filter.push( { storeId: { $in: inputData?.assignedStores } } );
|
|
74
|
-
}
|
|
75
|
-
for ( let i=0; i<inputData?.bills?.length; i++ ) {
|
|
76
|
-
filter = [
|
|
77
|
-
{ clientId: { $eq: req.clientId } },
|
|
78
|
-
{ status: { $eq: 'active' } },
|
|
79
|
-
{
|
|
80
|
-
$or: [
|
|
81
|
-
{ 'storeId': inputData?.bills[i]?.storeCode },
|
|
82
|
-
{ 'storeProfile.storeCode': inputData?.bills[i]?.storeCode },
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
];
|
|
86
|
-
const query = [
|
|
87
|
-
{
|
|
88
|
-
$match: {
|
|
89
|
-
$and: filter,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
$project: {
|
|
94
|
-
storeId: 1,
|
|
95
|
-
storeName: 1,
|
|
96
|
-
storeCode: '$storeProfile.storeCode',
|
|
64
|
+
const inputData = req.tempInserData;
|
|
97
65
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
66
|
+
for ( let i=0; i<inputData?.length; i++ ) {
|
|
67
|
+
await updateOneNobBilling( inputData[i]?.query, inputData[i]?.data );
|
|
68
|
+
const getData = await findOneNobBilling( inputData[i]?.query, { _id: 0 } );
|
|
69
|
+
if ( !inputData[i]?.isUpdated ) {
|
|
70
|
+
await insertOpenSearchData( openSearch.nob, getData );
|
|
71
|
+
resData.push( { code: 200, tangoCode: getData.storeId, storeId: inputData[i]?.data?.storeCode, message: 'Data Inserted Successfully' } );
|
|
104
72
|
} else {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'query': {
|
|
108
|
-
'bool': {
|
|
109
|
-
'must': [
|
|
110
|
-
{
|
|
111
|
-
'term': {
|
|
112
|
-
'storeId.keyword': storeData[0].storeId,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
'term': {
|
|
117
|
-
'nobDate': dayjs( inputData?.bills[i]?.nobDate ).format( 'YYYY-MM-DD' ),
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
let searchData=await getOpenSearchData( openSearch.nob, searchQuery );
|
|
125
|
-
let nobDateIso =new Date( inputData?.bills[i]?.nobDate );
|
|
126
|
-
nobDateIso.setUTCHours( 0, 0, 0, 0 );
|
|
127
|
-
const storeName = storeData[0]?.storeName;
|
|
128
|
-
let inserData={
|
|
129
|
-
clientId: req?.clientId,
|
|
130
|
-
storeId: storeData[0]?.storeId,
|
|
131
|
-
storeCode: storeData[0]?.storeCode,
|
|
132
|
-
storeName: storeName.toLowerCase(),
|
|
133
|
-
nobDate: nobDateIso,
|
|
134
|
-
nobCount: inputData?.bills[i]?.nobCount,
|
|
135
|
-
dateString: inputData?.bills[i]?.nobDate,
|
|
136
|
-
nobAmount: 1.0,
|
|
137
|
-
};
|
|
138
|
-
const query ={ storeId: storeData[0]?.storeId, nobDate: inputData?.bills[i]?.nobDate };
|
|
139
|
-
await updateOneNobBilling( query, inserData );
|
|
140
|
-
const getData = await findOneNobBilling( query, { _id: 0 } );
|
|
141
|
-
if ( searchData?.body?.hits?.hits?.length==0 || searchData?.body==undefined ) {
|
|
142
|
-
await insertOpenSearchData( openSearch.nob, getData );
|
|
143
|
-
resData.push( { code: 200, tangoCode: getData.storeId, storeId: inputData?.bills[i]?.storeCode, message: 'Data Inserted Successfully' } );
|
|
144
|
-
} else {
|
|
145
|
-
await updateOpenSearchData( openSearch.nob, searchData.body.hits.hits[0]._id, { doc: getData } );
|
|
146
|
-
resData.push( { code: 200, tangoCode: storeData[0]?.storeId, storeId: inputData?.bills[i]?.storeCode, message: 'Data Updated Successfully' } );
|
|
147
|
-
}
|
|
73
|
+
await updateOpenSearchData( openSearch.nob, inputData[i]?.searchData?.body?.hits?.hits[0]?._id, { doc: getData } );
|
|
74
|
+
resData.push( { code: 200, tangoCode: inputData[i]?.data?.storeId, storeId: inputData[i]?.data?.storeCode, message: 'Data Updated Successfully' } );
|
|
148
75
|
}
|
|
149
76
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
logger.info( { resData: resData, errorData: errorData, function: 'nob-addbills' } );
|
|
154
|
-
return res.sendSuccess( 'Data has been inserted successfully' );
|
|
77
|
+
logger.info( { resData: resData } );
|
|
78
|
+
return res.sendSuccess( 'Data has been inserted/updated successfully' );
|
|
155
79
|
} catch ( error ) {
|
|
156
80
|
const err= error.message || 'Internal Server Error';
|
|
157
81
|
logger.error( { error: error, message: req.body, function: 'nob-addBills' } );
|
|
158
|
-
res.sendError( err, 500 );
|
|
82
|
+
return res.sendError( err, 500 );
|
|
159
83
|
}
|
|
160
84
|
}
|
|
161
85
|
|
|
@@ -193,7 +117,6 @@ export async function getNobData( req, res ) {
|
|
|
193
117
|
|
|
194
118
|
if ( inputData.searchValue && inputData.searchValue!== '' ) {
|
|
195
119
|
const searchValue = escapeSpecialChars( inputData.searchValue );
|
|
196
|
-
logger.info( { searchvalue: inputData.searchValue, searchValue: searchValue } );
|
|
197
120
|
temp = {
|
|
198
121
|
|
|
199
122
|
'must': filter,
|
|
@@ -238,24 +161,7 @@ export async function getNobData( req, res ) {
|
|
|
238
161
|
const getNobData=await getOpenSearchData( openSearch.nob, nobQuery );
|
|
239
162
|
const nobData = getNobData?.body?.hits?.hits;
|
|
240
163
|
if ( !nobData ||nobData?.length == 0 ) {
|
|
241
|
-
|
|
242
|
-
'size': 1,
|
|
243
|
-
'query': {
|
|
244
|
-
'bool': {
|
|
245
|
-
'must': [
|
|
246
|
-
{
|
|
247
|
-
'term': {
|
|
248
|
-
'clientId.keyword': req.clientId,
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
const getInitialData=await getOpenSearchData( openSearch.nob, initialQuery );
|
|
256
|
-
const initialData = getInitialData?.body?.hits?.hits;
|
|
257
|
-
logger.info( { initialData: initialData, clientId: req.clientId } );
|
|
258
|
-
if ( initialData && initialData.length > 0 ) {
|
|
164
|
+
if ( inputData.searchValue && inputData.searchValue!== '' ) {
|
|
259
165
|
return res.sendError( 'No Data Found', 204 );
|
|
260
166
|
} else {
|
|
261
167
|
return res.sendSuccess( { initialInsert: false } );
|
|
@@ -263,6 +169,9 @@ export async function getNobData( req, res ) {
|
|
|
263
169
|
}
|
|
264
170
|
|
|
265
171
|
const footfallQuery={
|
|
172
|
+
'_source': {
|
|
173
|
+
'exclude': [ 'storeName' ], // Specify the fields to exclude
|
|
174
|
+
},
|
|
266
175
|
'query': {
|
|
267
176
|
'bool': {
|
|
268
177
|
'must': [
|
|
@@ -282,18 +191,19 @@ export async function getNobData( req, res ) {
|
|
|
282
191
|
|
|
283
192
|
const getFootfall= await getOpenSearchData( openSearch.footfall, footfallQuery );
|
|
284
193
|
const footfall = getFootfall?.body?.hits?.hits;
|
|
194
|
+
logger.info( { footfall: footfall, nobData: nobData } );
|
|
285
195
|
let result=[];
|
|
286
196
|
nobData.map( ( data ) => {
|
|
287
197
|
let count = 0;
|
|
288
198
|
let temp=[];
|
|
289
199
|
|
|
290
200
|
data._source.isUpdated = data._source.createdAt !== data._source.updatedAt? true : false;
|
|
291
|
-
|
|
292
201
|
footfall.filter( ( item ) => {
|
|
293
|
-
if ( ( data._source.
|
|
202
|
+
if ( ( data._source.dateString === item._source.date_string ) && ( data._source.storeId === item._source.store_id ) ) {
|
|
294
203
|
count = 1;
|
|
295
204
|
temp =[
|
|
296
205
|
{
|
|
206
|
+
storeName: item._source.store_name,
|
|
297
207
|
footfallCount: item._source.footfall_count,
|
|
298
208
|
engagersCount: item._source.engagers_count,
|
|
299
209
|
potentialBuyers: item._source.potentialBuyers_count,
|
|
@@ -303,7 +213,6 @@ export async function getNobData( req, res ) {
|
|
|
303
213
|
} );
|
|
304
214
|
|
|
305
215
|
let conversionCount;
|
|
306
|
-
|
|
307
216
|
if ( count === 1 ) {
|
|
308
217
|
switch ( getClient?.featureConfigs?.conversionCalculation ) {
|
|
309
218
|
case 'footfall-count':
|
|
@@ -321,10 +230,11 @@ export async function getNobData( req, res ) {
|
|
|
321
230
|
result.push( {
|
|
322
231
|
|
|
323
232
|
...data._source,
|
|
233
|
+
storeName: temp[0]?.storeName,
|
|
324
234
|
footfallCount: temp[0]?.footfallCount || null,
|
|
325
235
|
engagersCount: temp[0]?.engagersCount || null,
|
|
326
236
|
potentialBuyers: temp[0]?.potentialBuyers || null,
|
|
327
|
-
conversionRate: conversionCount == null ? null
|
|
237
|
+
conversionRate: conversionCount == null ? null :`${Math.round( conversionCount )} %`,
|
|
328
238
|
|
|
329
239
|
} );
|
|
330
240
|
} else {
|
|
@@ -368,7 +278,6 @@ export async function getNobData( req, res ) {
|
|
|
368
278
|
}
|
|
369
279
|
element.conversionRate? exportData[index]['Conversion Rate'] = `${element.conversionRate} %` : exportData[index]['Conversion Rate'] =null;
|
|
370
280
|
} );
|
|
371
|
-
logger.info( { exportData: exportData } );
|
|
372
281
|
return exportData;
|
|
373
282
|
} );
|
|
374
283
|
const mappedArrays = await Promise.all( promises );
|
package/src/dtos/nob.dtos.js
CHANGED
|
@@ -7,15 +7,15 @@ export const addBillsSchema = joi.object( {
|
|
|
7
7
|
storeCode: joi.string().required().messages( {
|
|
8
8
|
'string.empty': 'Please enter a valid Store ID',
|
|
9
9
|
'any.required': 'Store ID is required',
|
|
10
|
-
} ),
|
|
10
|
+
} ).allow( null ),
|
|
11
11
|
nobDate: joi.string().required().messages( {
|
|
12
12
|
'string.empty': 'Please enter a valid NOB Date',
|
|
13
13
|
'any.required': 'NOB Date is required',
|
|
14
|
-
} ),
|
|
14
|
+
} ).allow( null ),
|
|
15
15
|
nobCount: joi.number().required().messages( {
|
|
16
16
|
'string.empty': 'Please enter a valid NOB Count',
|
|
17
17
|
'any.required': 'NOB Count is required',
|
|
18
|
-
} ),
|
|
18
|
+
} ).allow( null ),
|
|
19
19
|
} ),
|
|
20
20
|
).required(),
|
|
21
21
|
|
package/src/routes/nob.routes.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { accessVerification, getAssinedStore, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
2
|
+
import { accessVerification, bulkValidate, getAssinedStore, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
3
3
|
import { addBillsValid, getNobDataValid, storeListValid } from '../dtos/nob.dtos.js';
|
|
4
4
|
import { addBills, getNobData, storeList } from '../controllers/nob.controllers.js';
|
|
5
|
-
import { clientValidations, roleVerification } from '../validations/nob.validations.js';
|
|
5
|
+
import { clientValidations, fieldValidation, roleVerification } from '../validations/nob.validations.js';
|
|
6
6
|
|
|
7
7
|
const nobRouter=express.Router();
|
|
8
8
|
|
|
9
9
|
// store list
|
|
10
10
|
nobRouter.get( '/store-list', isAllowedSessionHandler, accessVerification( { userType: [ 'client', 'tango' ] } ), validate( storeListValid ), clientValidations, getAssinedStore, storeList );
|
|
11
11
|
|
|
12
|
-
nobRouter.post( '/add-bills', isAllowedSessionHandler, accessVerification( { userType: [ 'client', 'tango' ] } ),
|
|
12
|
+
nobRouter.post( '/add-bills', isAllowedSessionHandler, accessVerification( { userType: [ 'client', 'tango' ] } ), bulkValidate( addBillsValid ), roleVerification, clientValidations, fieldValidation, getAssinedStore, addBills );
|
|
13
13
|
|
|
14
14
|
nobRouter.post( '/get-nob-data', isAllowedSessionHandler, accessVerification( { userType: [ 'client', 'tango' ] } ), validate( getNobDataValid ), clientValidations, getAssinedStore, getNobData );
|
|
15
15
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { getOpenSearchData, logger } from 'tango-app-api-middleware';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { aggregateStore } from '../services/stores.service.js';
|
|
4
|
+
|
|
1
5
|
export async function clientValidations( req, res, next ) {
|
|
2
6
|
try {
|
|
3
7
|
const inputData = req.method === 'POST' ? req.body : req.query;
|
|
@@ -26,3 +30,115 @@ export async function roleVerification( req, res, next ) {
|
|
|
26
30
|
return res.sendError( error, 500 );
|
|
27
31
|
}
|
|
28
32
|
}
|
|
33
|
+
|
|
34
|
+
export async function fieldValidation( req, res, next ) {
|
|
35
|
+
try {
|
|
36
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
37
|
+
const inputData = req.body;
|
|
38
|
+
if ( req.user.role !== 'superadmin' && req.user.userType !== 'tango'&& ( inputData?.assignedStores?.length == 0 || !inputData?.assignedStores ) ) {
|
|
39
|
+
return res.sendError( 'access forbidden', 403 );
|
|
40
|
+
}
|
|
41
|
+
let tempInserData = [];
|
|
42
|
+
let filter = [];
|
|
43
|
+
if ( req.user.role !== 'superadmin' && req.user.userType !== 'tango' ) {
|
|
44
|
+
filter.push( { storeId: { $in: inputData?.assignedStores } } );
|
|
45
|
+
}
|
|
46
|
+
const inputFilter = inputData?.bills.filter( ( item ) => ( item.storeCode !== null && item.nobDate !== null && item.nobCount !== null ) );
|
|
47
|
+
// Find duplicates
|
|
48
|
+
const seen = new Map();
|
|
49
|
+
logger.info( { inputFilter: inputFilter } );
|
|
50
|
+
for ( let i=0; i<inputFilter?.length&& inputFilter.length !== 0; i++ ) {
|
|
51
|
+
const key = `${inputFilter[i]?.storeCode}-${inputFilter[i]?.nobDate}`; // Combine `storeCode` and `nobDate` to create a unique key
|
|
52
|
+
logger.info( { key: key } );
|
|
53
|
+
if ( seen.has( key ) ) {
|
|
54
|
+
logger.info( { message: 'deupkilicare' } );
|
|
55
|
+
// duplicates.push( { ...item, originalIndex: index } );
|
|
56
|
+
return res.sendError( `Error in index ${i+1}: Dublicate record exist`, 403 );
|
|
57
|
+
} else {
|
|
58
|
+
seen.set( key, i );
|
|
59
|
+
}
|
|
60
|
+
filter = [
|
|
61
|
+
{ clientId: { $eq: req.clientId } },
|
|
62
|
+
{ status: { $eq: 'active' } },
|
|
63
|
+
{
|
|
64
|
+
$or: [
|
|
65
|
+
{ 'storeId': inputFilter[i]?.storeCode },
|
|
66
|
+
{ 'storeProfile.storeCode': inputFilter[i]?.storeCode },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
const query = [
|
|
71
|
+
{
|
|
72
|
+
$match: {
|
|
73
|
+
$and: filter,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
$project: {
|
|
78
|
+
storeId: 1,
|
|
79
|
+
storeName: 1,
|
|
80
|
+
storeCode: '$storeProfile.storeCode',
|
|
81
|
+
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
let storeData= await aggregateStore( query );
|
|
86
|
+
if ( storeData?.length ==0 ) {
|
|
87
|
+
return res.sendError( `Error in index ${i+1}: Store code is not mapped with tango`, 403 );
|
|
88
|
+
} else {
|
|
89
|
+
let searchQuery={
|
|
90
|
+
'size': 1,
|
|
91
|
+
'query': {
|
|
92
|
+
'bool': {
|
|
93
|
+
'must': [
|
|
94
|
+
{
|
|
95
|
+
'term': {
|
|
96
|
+
'storeId.keyword': storeData[0].storeId,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
'term': {
|
|
101
|
+
'nobDate': dayjs( inputFilter[i]?.nobDate ).format( 'YYYY-MM-DD' ),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
let searchData=await getOpenSearchData( openSearch.nob, searchQuery );
|
|
109
|
+
if ( searchData?.body?.hits?.hits.length > 0 && searchData?.body?.hits?.hits[0]?._source?.createdAt !== searchData?.body?.hits?.hits[0]?._source?.updatedAt ) {
|
|
110
|
+
return res.sendError( `Error in index ${i+1}: Access Forbidden.You are trying to re-upload/edit more than one time `, 403 );
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let nobDateIso =new Date( inputFilter[i]?.nobDate );
|
|
114
|
+
nobDateIso.setUTCHours( 0, 0, 0, 0 );
|
|
115
|
+
const storeName = storeData[0]?.storeName;
|
|
116
|
+
let inserData={
|
|
117
|
+
clientId: req?.clientId,
|
|
118
|
+
storeId: storeData[0]?.storeId,
|
|
119
|
+
storeCode: storeData[0]?.storeCode,
|
|
120
|
+
storeName: storeName.toLowerCase(),
|
|
121
|
+
nobDate: nobDateIso,
|
|
122
|
+
nobCount: inputFilter[i]?.nobCount,
|
|
123
|
+
dateString: inputFilter[i]?.nobDate,
|
|
124
|
+
};
|
|
125
|
+
const query ={ storeId: storeData[0]?.storeId, nobDate: inputFilter[i]?.nobDate };
|
|
126
|
+
|
|
127
|
+
tempInserData.push( { data: inserData, query: query, searchData: searchData, isUpdated: ( searchData?.body?.hits?.hits?.length==0 || searchData?.body==undefined )? false:true } );
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ( inputData?.bills?.length == i+1 ) {
|
|
131
|
+
if ( tempInserData.length > 0 ) {
|
|
132
|
+
req.tempInserData = tempInserData;
|
|
133
|
+
return next();
|
|
134
|
+
} else {
|
|
135
|
+
return res.sendError( 'Bad Request', 400 );
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return res.sendError( 'Bad Request', 400 );
|
|
140
|
+
} catch ( error ) {
|
|
141
|
+
logger.error( { error: error, message: req.body, function: 'nob-roleVerification' } );
|
|
142
|
+
return res.sendError( error, 500 );
|
|
143
|
+
}
|
|
144
|
+
}
|