tango-app-api-analysis-traffic 3.8.9 → 3.8.11
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 +2 -2
- package/src/controllers/nob.controllers.js +102 -29
- package/src/controllers/revop.controller.js +1493 -71
- package/src/controllers/tangoTrafficV3.controllers.js +27 -26
- package/src/dtos/nob.dtos.js +13 -0
- package/src/dtos/revop.dtos.js +16 -9
- package/src/dtos/validation.dtos.js +11 -0
- package/src/routes/nob.routes.js +5 -1
- package/src/routes/revop.routes.js +7 -6
- package/src/routes/traffic.routes.js +3 -1
- package/src/services/nob.service.js +1 -0
- package/src/services/vmsStoreRequest.service.js +5 -0
- package/src/validations/nob.validations.js +1 -0
- package/src/validations/revop.validation.js +240 -107
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData } from 'tango-app-api-middleware';
|
|
2
2
|
import { findOnerevopConfig } from '../services/revopConfig.service.js';
|
|
3
3
|
import * as clientService from '../services/clients.services.js';
|
|
4
|
-
import { bulkUpdate, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
4
|
+
import { bulkUpdate, insertWithId, scrollResponse, searchOpenSearchData, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
5
|
+
import { findOneVmsStoreRequest } from '../services/vmsStoreRequest.service.js';
|
|
5
6
|
import dayjs from 'dayjs';
|
|
6
7
|
// Lamda Service Call //
|
|
7
8
|
async function LamdaServiceCall( url, data ) {
|
|
@@ -16,7 +17,6 @@ async function LamdaServiceCall( url, data ) {
|
|
|
16
17
|
const response = await fetch( url, requestOptions );
|
|
17
18
|
if ( !response.ok ) {
|
|
18
19
|
throw new Error( `Response status: ${response.status}` );
|
|
19
|
-
return false;
|
|
20
20
|
}
|
|
21
21
|
const json = await response.json();
|
|
22
22
|
return json;
|
|
@@ -69,7 +69,7 @@ export async function revoptagging( req, res ) {
|
|
|
69
69
|
|
|
70
70
|
let respo= await getOpenSearchData( openSearch.revops, searchQuery );
|
|
71
71
|
const revopData = respo?.body?.hits?.hits;
|
|
72
|
-
if ( revopData&& revopData.length>0 ) {
|
|
72
|
+
if ( revopData && revopData.length>0 ) {
|
|
73
73
|
await updateOpenSearchData( openSearch.revops, revopData[0]._id, { doc: item } );
|
|
74
74
|
} else {
|
|
75
75
|
item.createdAt = new Date();
|
|
@@ -92,7 +92,7 @@ async function getClientConfig( clientId ) {
|
|
|
92
92
|
}
|
|
93
93
|
return getClientData;
|
|
94
94
|
} catch ( error ) {
|
|
95
|
-
logger.error( { error: error, message:
|
|
95
|
+
logger.error( { error: error, message: clientId, function: 'getClientConfig' } );
|
|
96
96
|
return false;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -126,7 +126,7 @@ export async function getrevoptagging( req, res ) {
|
|
|
126
126
|
};
|
|
127
127
|
let respo= await getOpenSearchData( openSearch.revops, searchQuery );
|
|
128
128
|
const revopData = respo?.body?.hits?.hits;
|
|
129
|
-
if ( revopData.length>0 ) {
|
|
129
|
+
if ( revopData && revopData.length>0 ) {
|
|
130
130
|
return res.sendSuccess( revopData[0]._source );
|
|
131
131
|
} else {
|
|
132
132
|
return res.sendError( 'no data found', 204 );
|
|
@@ -136,6 +136,690 @@ export async function getrevoptagging( req, res ) {
|
|
|
136
136
|
return res.sendError( { error: error }, 500 );
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
export async function migrateRevopIndex( req, res ) {
|
|
141
|
+
try {
|
|
142
|
+
const { storeId, dateString, size = 100 } = req.body;
|
|
143
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
144
|
+
|
|
145
|
+
const query = {
|
|
146
|
+
size: size,
|
|
147
|
+
query: {
|
|
148
|
+
bool: {
|
|
149
|
+
must: [
|
|
150
|
+
// {
|
|
151
|
+
// range: {
|
|
152
|
+
// createdAt: {
|
|
153
|
+
// gte: '2025-10-01T00:00:00.000Z',
|
|
154
|
+
// lte: '2025-10-31T23:59:59.000Z',
|
|
155
|
+
// },
|
|
156
|
+
// },
|
|
157
|
+
// },
|
|
158
|
+
{
|
|
159
|
+
term: {
|
|
160
|
+
'type.keyword': 'tagging-reflect',
|
|
161
|
+
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if ( storeId ) {
|
|
170
|
+
query.query.bool.must.push( {
|
|
171
|
+
term: {
|
|
172
|
+
'storeId.keyword': storeId,
|
|
173
|
+
},
|
|
174
|
+
} );
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ( dateString ) {
|
|
178
|
+
query.query.bool.must.push( {
|
|
179
|
+
terms: {
|
|
180
|
+
dateString: Array.isArray( dateString ) ? dateString : `${dateString}`.split( ',' ),
|
|
181
|
+
},
|
|
182
|
+
} );
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// const response = await getOpenSearchData( openSearch.revop, query );
|
|
186
|
+
// const hits = response?.body?.hits?.hits || [];
|
|
187
|
+
|
|
188
|
+
// Use OpenSearch scroll API to retrieve up to 60000 records efficiently
|
|
189
|
+
let allHits = [];
|
|
190
|
+
let scrollId = null;
|
|
191
|
+
let totalFetched = 0;
|
|
192
|
+
let firstResponse = await searchOpenSearchData( openSearch.revop, query );
|
|
193
|
+
// Collect first batch
|
|
194
|
+
let hitsBatch = firstResponse?.body?.hits?.hits || [];
|
|
195
|
+
if ( hitsBatch.length > 0 ) {
|
|
196
|
+
allHits.push( ...hitsBatch );
|
|
197
|
+
totalFetched += hitsBatch.length;
|
|
198
|
+
scrollId = firstResponse.body._scroll_id;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const bulkBody = [];
|
|
202
|
+
for ( const hit of hitsBatch ) {
|
|
203
|
+
const src = hit._source || {};
|
|
204
|
+
const statusValue = ( src.status || '' ).toLowerCase();
|
|
205
|
+
// const parentValue = src.parent;
|
|
206
|
+
|
|
207
|
+
// Get ticket sattasu from the footfalldirectory index matching src.storeId and src.dateString
|
|
208
|
+
let ticketStatus = null;
|
|
209
|
+
|
|
210
|
+
const footfallQuery = {
|
|
211
|
+
size: 1,
|
|
212
|
+
query: {
|
|
213
|
+
bool: {
|
|
214
|
+
must: [
|
|
215
|
+
{ term: { 'storeId.keyword': src.storeId } },
|
|
216
|
+
{ term: { 'dateString': src.dateString } },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const footfallResp = await getOpenSearchData( openSearch.oldFootfallDirectory, footfallQuery );
|
|
222
|
+
ticketStatus = footfallResp?.body?.hits?.hits?.[0]?._source?.status || null;
|
|
223
|
+
if ( src?.duplicateImage?.length > 0 ) {
|
|
224
|
+
src.duplicateImage = src.duplicateImage.map( ( item ) => ( {
|
|
225
|
+
...item,
|
|
226
|
+
id: `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${item.tempId || ''}`,
|
|
227
|
+
actions: ( ticketStatus === 'closed' && item.isChecked === true ) ? [
|
|
228
|
+
{
|
|
229
|
+
actionType: 'tagging',
|
|
230
|
+
action: 'submitted',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
actionType: 'review',
|
|
234
|
+
action: 'approved',
|
|
235
|
+
},
|
|
236
|
+
]: ( ticketStatus === 'closed' && item.isChecked === false )?
|
|
237
|
+
[
|
|
238
|
+
{
|
|
239
|
+
actionType: 'tagging',
|
|
240
|
+
action: 'submitted',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
actionType: 'review',
|
|
244
|
+
action: 'rejected',
|
|
245
|
+
},
|
|
246
|
+
]:
|
|
247
|
+
[
|
|
248
|
+
{
|
|
249
|
+
actionType: 'tagging',
|
|
250
|
+
action: 'submitted',
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
// Include relevant action, assuming 'actions' will be determined below
|
|
254
|
+
} ) );
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
const idValue = `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${src.tempId || ''}`;
|
|
259
|
+
let actions = [
|
|
260
|
+
{
|
|
261
|
+
actionType: 'tagging',
|
|
262
|
+
action: 'submitted',
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
if ( statusValue === 'approved' ) {
|
|
267
|
+
actions = [
|
|
268
|
+
{
|
|
269
|
+
actionType: 'tagging',
|
|
270
|
+
action: 'submitted',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
actionType: 'review',
|
|
274
|
+
action: 'approved',
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
} else if ( statusValue === 'rejected' ) {
|
|
278
|
+
actions = [
|
|
279
|
+
{
|
|
280
|
+
actionType: 'tagging',
|
|
281
|
+
action: 'submitted',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
actionType: 'review',
|
|
285
|
+
action: 'rejected',
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
const doc = {
|
|
292
|
+
...src,
|
|
293
|
+
id: idValue,
|
|
294
|
+
revopsType: src?.revopsType === 'house-keeping'? 'houseKeeping' : src?.revopsType,
|
|
295
|
+
isParent: src?.duplicateImage?.length > 0? true : false,
|
|
296
|
+
actions,
|
|
297
|
+
ticketStatus: src.status,
|
|
298
|
+
// updatedAt: new Date(),
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
bulkBody.push(
|
|
302
|
+
{ update: { _index: openSearch.newRevop, _id: hit._id } },
|
|
303
|
+
{ doc: doc, doc_as_upsert: true },
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const bulkRes = await bulkUpdate( bulkBody );
|
|
308
|
+
|
|
309
|
+
if ( bulkRes?.errors ) {
|
|
310
|
+
logger.error( 'Bulk migration errors:', bulkRes.items );
|
|
311
|
+
return res.sendError( 'Failed to migrate some records', 500 );
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
while ( hitsBatch.length > 0 && scrollId ) {
|
|
315
|
+
// Fetch next batch using scroll_id
|
|
316
|
+
const nextScrollRes = await scrollResponse( scrollId );
|
|
317
|
+
|
|
318
|
+
hitsBatch = nextScrollRes?.body?.hits?.hits || [];
|
|
319
|
+
if ( hitsBatch.length === 0 ) break;
|
|
320
|
+
logger.info( { hitsBatch: hitsBatch?.length } );
|
|
321
|
+
const bulkBody = [];
|
|
322
|
+
for ( const hit of hitsBatch ) {
|
|
323
|
+
const src = hit._source || {};
|
|
324
|
+
const statusValue = ( src.status || '' ).toLowerCase();
|
|
325
|
+
// const parentValue = src.parent;
|
|
326
|
+
|
|
327
|
+
// Get ticket sattasu from the footfalldirectory index matching src.storeId and src.dateString
|
|
328
|
+
let ticketStatus = null;
|
|
329
|
+
|
|
330
|
+
const footfallQuery = {
|
|
331
|
+
size: 1,
|
|
332
|
+
query: {
|
|
333
|
+
bool: {
|
|
334
|
+
must: [
|
|
335
|
+
{ term: { 'storeId.keyword': src.storeId } },
|
|
336
|
+
{ term: { 'dateString': src.dateString } },
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const footfallResp = await getOpenSearchData( openSearch.oldFootfallDirectory, footfallQuery );
|
|
342
|
+
ticketStatus = footfallResp?.body?.hits?.hits?.[0]?._source?.status || null;
|
|
343
|
+
if ( src?.duplicateImage?.length > 0 ) {
|
|
344
|
+
src.duplicateImage = src.duplicateImage.map( ( item ) => ( {
|
|
345
|
+
...item,
|
|
346
|
+
id: `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${item.tempId || ''}`,
|
|
347
|
+
actions: ( ticketStatus === 'closed' && item.isChecked === true ) ? [
|
|
348
|
+
{
|
|
349
|
+
actionType: 'tagging',
|
|
350
|
+
action: 'submitted',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
actionType: 'review',
|
|
354
|
+
action: 'approved',
|
|
355
|
+
},
|
|
356
|
+
]: ( ticketStatus === 'closed' && item.isChecked === false )?
|
|
357
|
+
[
|
|
358
|
+
{
|
|
359
|
+
actionType: 'tagging',
|
|
360
|
+
action: 'submitted',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
actionType: 'review',
|
|
364
|
+
action: 'rejected',
|
|
365
|
+
},
|
|
366
|
+
]:
|
|
367
|
+
[
|
|
368
|
+
{
|
|
369
|
+
actionType: 'tagging',
|
|
370
|
+
action: 'submitted',
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
// Include relevant action, assuming 'actions' will be determined below
|
|
374
|
+
} ) );
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
const idValue = `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${src.tempId || ''}`;
|
|
379
|
+
logger.info( { idValue } );
|
|
380
|
+
let actions = [
|
|
381
|
+
{
|
|
382
|
+
actionType: 'tagging',
|
|
383
|
+
action: 'submitted',
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
if ( statusValue === 'approved' ) {
|
|
388
|
+
actions = [
|
|
389
|
+
{
|
|
390
|
+
actionType: 'tagging',
|
|
391
|
+
action: 'submitted',
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
actionType: 'review',
|
|
395
|
+
action: 'approved',
|
|
396
|
+
},
|
|
397
|
+
];
|
|
398
|
+
} else if ( statusValue === 'rejected' ) {
|
|
399
|
+
actions = [
|
|
400
|
+
{
|
|
401
|
+
actionType: 'tagging',
|
|
402
|
+
action: 'submitted',
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
actionType: 'review',
|
|
406
|
+
action: 'rejected',
|
|
407
|
+
},
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
const doc = {
|
|
413
|
+
...src,
|
|
414
|
+
id: idValue,
|
|
415
|
+
revopsType: src?.revopsType === 'house-keeping'? 'houseKeeping' : src?.revopsType,
|
|
416
|
+
isParent: src?.duplicateImage?.length > 0? true : false,
|
|
417
|
+
actions,
|
|
418
|
+
ticketStatus: src.status,
|
|
419
|
+
// updatedAt: new Date(),
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
bulkBody.push(
|
|
423
|
+
{ update: { _index: openSearch.newRevop, _id: hit._id } },
|
|
424
|
+
{ doc: doc, doc_as_upsert: true },
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const bulkRes = await bulkUpdate( bulkBody );
|
|
429
|
+
|
|
430
|
+
if ( bulkRes?.errors ) {
|
|
431
|
+
logger.error( 'Bulk migration errors:', bulkRes.items );
|
|
432
|
+
return res.sendError( 'Failed to migrate some records', 500 );
|
|
433
|
+
}
|
|
434
|
+
allHits.push( ...hitsBatch );
|
|
435
|
+
totalFetched += hitsBatch.length;
|
|
436
|
+
logger.info( { totalFetched } );
|
|
437
|
+
// Protect against exceeding limit
|
|
438
|
+
|
|
439
|
+
scrollId = nextScrollRes.body._scroll_id;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// For downstream logic, use allHits instead of hits
|
|
443
|
+
const hits = allHits;
|
|
444
|
+
|
|
445
|
+
if ( hits.length === 0 ) {
|
|
446
|
+
return res.sendSuccess( { message: 'No records found for migration', updated: 0 } );
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
// for ( const hit of hits ) {
|
|
451
|
+
// const src = hit._source || {};
|
|
452
|
+
// const statusValue = ( src.status || '' ).toLowerCase();
|
|
453
|
+
// // const parentValue = src.parent;
|
|
454
|
+
|
|
455
|
+
// // Get ticket sattasu from the footfalldirectory index matching src.storeId and src.dateString
|
|
456
|
+
// let ticketStatus = null;
|
|
457
|
+
|
|
458
|
+
// const footfallQuery = {
|
|
459
|
+
// size: 1,
|
|
460
|
+
// query: {
|
|
461
|
+
// bool: {
|
|
462
|
+
// must: [
|
|
463
|
+
// { term: { 'storeId.keyword': src.storeId } },
|
|
464
|
+
// { term: { 'dateString': src.dateString } },
|
|
465
|
+
// ],
|
|
466
|
+
// },
|
|
467
|
+
// },
|
|
468
|
+
// };
|
|
469
|
+
// const footfallResp = await getOpenSearchData( openSearch.oldFootfallDirectory, footfallQuery );
|
|
470
|
+
// ticketStatus = footfallResp?.body?.hits?.hits?.[0]?._source?.status || null;
|
|
471
|
+
// if ( src?.duplicateImage?.length > 0 ) {
|
|
472
|
+
// src.duplicateImage = src.duplicateImage.map( ( item ) => ( {
|
|
473
|
+
// ...item,
|
|
474
|
+
// id: `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${item.tempId || ''}`,
|
|
475
|
+
// actions: ( ticketStatus === 'closed' && item.isChecked === true ) ? [
|
|
476
|
+
// {
|
|
477
|
+
// actionType: 'tagging',
|
|
478
|
+
// action: 'submitted',
|
|
479
|
+
// },
|
|
480
|
+
// {
|
|
481
|
+
// actionType: 'review',
|
|
482
|
+
// action: 'approved',
|
|
483
|
+
// },
|
|
484
|
+
// ]: ( ticketStatus === 'closed' && item.isChecked === false )?
|
|
485
|
+
// [
|
|
486
|
+
// {
|
|
487
|
+
// actionType: 'tagging',
|
|
488
|
+
// action: 'submitted',
|
|
489
|
+
// },
|
|
490
|
+
// {
|
|
491
|
+
// actionType: 'review',
|
|
492
|
+
// action: 'rejected',
|
|
493
|
+
// },
|
|
494
|
+
// ]:
|
|
495
|
+
// [
|
|
496
|
+
// {
|
|
497
|
+
// actionType: 'tagging',
|
|
498
|
+
// action: 'submitted',
|
|
499
|
+
// },
|
|
500
|
+
// ],
|
|
501
|
+
// // Include relevant action, assuming 'actions' will be determined below
|
|
502
|
+
// } ) );
|
|
503
|
+
// }
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
// const idValue = `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${src.tempId || ''}`;
|
|
507
|
+
|
|
508
|
+
// let actions = [
|
|
509
|
+
// {
|
|
510
|
+
// actionType: 'tagging',
|
|
511
|
+
// action: 'submitted',
|
|
512
|
+
// },
|
|
513
|
+
// ];
|
|
514
|
+
|
|
515
|
+
// if ( statusValue === 'approved' ) {
|
|
516
|
+
// actions = [
|
|
517
|
+
// {
|
|
518
|
+
// actionType: 'tagging',
|
|
519
|
+
// action: 'submitted',
|
|
520
|
+
// },
|
|
521
|
+
// {
|
|
522
|
+
// actionType: 'review',
|
|
523
|
+
// action: 'approved',
|
|
524
|
+
// },
|
|
525
|
+
// ];
|
|
526
|
+
// } else if ( statusValue === 'rejected' ) {
|
|
527
|
+
// actions = [
|
|
528
|
+
// {
|
|
529
|
+
// actionType: 'tagging',
|
|
530
|
+
// action: 'submitted',
|
|
531
|
+
// },
|
|
532
|
+
// {
|
|
533
|
+
// actionType: 'review',
|
|
534
|
+
// action: 'rejected',
|
|
535
|
+
// },
|
|
536
|
+
// ];
|
|
537
|
+
// }
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
// const doc = {
|
|
541
|
+
// ...src,
|
|
542
|
+
// id: idValue,
|
|
543
|
+
// revopsType: src?.revopsType === 'house-keeping'? 'houseKeeping' : src?.revopsType,
|
|
544
|
+
// isParent: src?.duplicateImage?.length > 0? true : false,
|
|
545
|
+
// actions,
|
|
546
|
+
// ticketStatus: src.status,
|
|
547
|
+
// // updatedAt: new Date(),
|
|
548
|
+
// };
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
// bulkBody.push(
|
|
552
|
+
// { update: { _index: openSearch.newRevop, _id: hit._id } },
|
|
553
|
+
// { doc: doc, doc_as_upsert: true },
|
|
554
|
+
// );
|
|
555
|
+
// }
|
|
556
|
+
// Implement batch by batch update
|
|
557
|
+
// const BATCH_SIZE = 10000; // You can adjust the batch size as needed
|
|
558
|
+
|
|
559
|
+
// for ( let i = 0; i < bulkBody.length; i += BATCH_SIZE ) {
|
|
560
|
+
// const batch = bulkBody.slice( i, i + BATCH_SIZE );
|
|
561
|
+
// const bulkRes = await bulkUpdate( batch );
|
|
562
|
+
|
|
563
|
+
// if ( bulkRes?.errors ) {
|
|
564
|
+
// logger.error( 'Bulk migration errors:', bulkRes.items );
|
|
565
|
+
// return res.sendError( 'Failed to migrate some records', 500 );
|
|
566
|
+
// }
|
|
567
|
+
// }
|
|
568
|
+
|
|
569
|
+
// const bulkRes = await bulkUpdate( bulkBody );
|
|
570
|
+
|
|
571
|
+
// if ( bulkRes?.errors ) {
|
|
572
|
+
// logger.error( 'Bulk migration errors:', bulkRes.items );
|
|
573
|
+
// return res.sendError( 'Failed to migrate some records', 500 );
|
|
574
|
+
// }
|
|
575
|
+
|
|
576
|
+
return res.sendSuccess( { message: 'Migration completed', updated: hits.length } );
|
|
577
|
+
} catch ( error ) {
|
|
578
|
+
logger.error( { error: error, message: req.body, function: 'migrateRevopIndex' } );
|
|
579
|
+
return res.sendError( { error: error }, 500 );
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export async function expireReviewStatus( req, res ) {
|
|
584
|
+
try {
|
|
585
|
+
const {
|
|
586
|
+
thresholdDate,
|
|
587
|
+
batchSize = 500,
|
|
588
|
+
storeId,
|
|
589
|
+
dateString,
|
|
590
|
+
} = req.body;
|
|
591
|
+
const cutoffDate = new Date( thresholdDate );
|
|
592
|
+
// Convert cutoffDate to "2026-01-12T23:59:59.000Z"
|
|
593
|
+
|
|
594
|
+
cutoffDate.setUTCHours( 23, 59, 59, 0 );
|
|
595
|
+
|
|
596
|
+
console.log( cutoffDate );
|
|
597
|
+
if ( Number.isNaN( cutoffDate.getTime() ) ) {
|
|
598
|
+
return res.sendError( 'Invalid thresholdDate', 400 );
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
603
|
+
const query = {
|
|
604
|
+
size: batchSize,
|
|
605
|
+
query: {
|
|
606
|
+
bool: {
|
|
607
|
+
must: [
|
|
608
|
+
{ term: { 'ticketName.keyword': 'footfall-directory' } },
|
|
609
|
+
{ term: { 'type.keyword': 'store' } },
|
|
610
|
+
],
|
|
611
|
+
must_not: [
|
|
612
|
+
{ terms: { 'status.keyword': [ 'Closed' ] } },
|
|
613
|
+
],
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
if ( storeId ) {
|
|
620
|
+
query.query.bool.must.push( { term: { 'storeId.keyword': storeId } } );
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
if ( dateString ) {
|
|
625
|
+
query.query.bool.must.push( {
|
|
626
|
+
terms: {
|
|
627
|
+
dateString: Array.isArray( dateString ) ? dateString : `${dateString}`.split( ',' ),
|
|
628
|
+
},
|
|
629
|
+
} );
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
let totalUpdated = 0;
|
|
634
|
+
let scrollId = null;
|
|
635
|
+
|
|
636
|
+
let firstResponse = await searchOpenSearchData( openSearch.footfallDirectory, query );
|
|
637
|
+
let hitsBatch = firstResponse?.body?.hits?.hits || [];
|
|
638
|
+
scrollId = firstResponse?.body?._scroll_id;
|
|
639
|
+
|
|
640
|
+
while ( hitsBatch.length > 0 ) {
|
|
641
|
+
const bulkBody = [];
|
|
642
|
+
|
|
643
|
+
for ( const hit of hitsBatch ) {
|
|
644
|
+
const src = hit._source || {};
|
|
645
|
+
const mappingInfo = Array.isArray( src.mappingInfo ) ? src.mappingInfo : [];
|
|
646
|
+
let changed = false;
|
|
647
|
+
let updatedMapping = mappingInfo.map( ( item ) => {
|
|
648
|
+
if ( item?.type === 'review' && item?.status !== 'Closed' && item?.dueDate ) {
|
|
649
|
+
const due = new Date( item.dueDate );
|
|
650
|
+
if ( !Number.isNaN( due.getTime() ) && due < cutoffDate ) {
|
|
651
|
+
changed = true;
|
|
652
|
+
|
|
653
|
+
return { ...item, status: 'Expired' };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return item;
|
|
658
|
+
} );
|
|
659
|
+
if ( changed ) {
|
|
660
|
+
updatedMapping = updatedMapping.map( ( item ) => {
|
|
661
|
+
if ( item?.type === 'tagging' ) {
|
|
662
|
+
return { ...item, status: 'Expired' };
|
|
663
|
+
}
|
|
664
|
+
return item;
|
|
665
|
+
} );
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if ( changed ) {
|
|
669
|
+
const doc = {
|
|
670
|
+
mappingInfo: updatedMapping,
|
|
671
|
+
status: 'Reviewer-Expired',
|
|
672
|
+
};
|
|
673
|
+
logger.info( { updatedMapping } );
|
|
674
|
+
bulkBody.push(
|
|
675
|
+
{ update: { _index: openSearch.footfallDirectory, _id: hit._id } },
|
|
676
|
+
{ doc: doc, doc_as_upsert: true },
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if ( bulkBody.length > 0 ) {
|
|
682
|
+
const bulkRes = await bulkUpdate( bulkBody );
|
|
683
|
+
if ( bulkRes?.errors ) {
|
|
684
|
+
logger.error( { message: 'Bulk expire errors', items: bulkRes.items } );
|
|
685
|
+
}
|
|
686
|
+
totalUpdated += bulkBody.length / 2;
|
|
687
|
+
}
|
|
688
|
+
logger.info( { totalUpdated, msg: '........9' } );
|
|
689
|
+
if ( !scrollId ) break;
|
|
690
|
+
const nextRes = await scrollResponse( scrollId );
|
|
691
|
+
hitsBatch = nextRes?.body?.hits?.hits || [];
|
|
692
|
+
scrollId = nextRes?.body?._scroll_id;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return res.sendSuccess( { message: 'Expired review status updated', updated: totalUpdated } );
|
|
696
|
+
} catch ( error ) {
|
|
697
|
+
logger.error( { error: error, message: req.body, function: 'expireReviewStatus' } );
|
|
698
|
+
return res.sendError( { error: error }, 500 );
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
export async function expireApproveStatus( req, res ) {
|
|
704
|
+
try {
|
|
705
|
+
const {
|
|
706
|
+
thresholdDate = '2026-01-21',
|
|
707
|
+
batchSize = 500,
|
|
708
|
+
storeId,
|
|
709
|
+
dateString,
|
|
710
|
+
} = req.body;
|
|
711
|
+
const cutoffDate = new Date( thresholdDate );
|
|
712
|
+
if ( Number.isNaN( cutoffDate.getTime() ) ) {
|
|
713
|
+
return res.sendError( 'Invalid thresholdDate', 400 );
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
718
|
+
const query = {
|
|
719
|
+
size: batchSize,
|
|
720
|
+
query: {
|
|
721
|
+
bool: {
|
|
722
|
+
must: [
|
|
723
|
+
{ term: { 'ticketName.keyword': 'footfall-directory' } },
|
|
724
|
+
{ term: { 'type.keyword': 'store' } },
|
|
725
|
+
],
|
|
726
|
+
must_not: [
|
|
727
|
+
{ terms: { 'status.keyword': [ 'Closed' ] } },
|
|
728
|
+
],
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
if ( storeId ) {
|
|
735
|
+
query.query.bool.must.push( { term: { 'storeId.keyword': storeId } } );
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
if ( dateString ) {
|
|
740
|
+
query.query.bool.must.push( {
|
|
741
|
+
terms: {
|
|
742
|
+
dateString: Array.isArray( dateString ) ? dateString : `${dateString}`.split( ',' ),
|
|
743
|
+
},
|
|
744
|
+
} );
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
let totalUpdated = 0;
|
|
749
|
+
let scrollId = null;
|
|
750
|
+
|
|
751
|
+
let firstResponse = await searchOpenSearchData( openSearch.footfallDirectory, query );
|
|
752
|
+
let hitsBatch = firstResponse?.body?.hits?.hits || [];
|
|
753
|
+
scrollId = firstResponse?.body?._scroll_id;
|
|
754
|
+
|
|
755
|
+
while ( hitsBatch.length > 0 ) {
|
|
756
|
+
const bulkBody = [];
|
|
757
|
+
|
|
758
|
+
for ( const hit of hitsBatch ) {
|
|
759
|
+
const src = hit._source || {};
|
|
760
|
+
const mappingInfo = Array.isArray( src.mappingInfo ) ? src.mappingInfo : [];
|
|
761
|
+
let changed = false;
|
|
762
|
+
let updatedMapping = mappingInfo.map( ( item ) => {
|
|
763
|
+
if ( item?.type === 'approve' && item?.status !== 'Closed' && item?.dueDate ) {
|
|
764
|
+
const due = new Date( item.dueDate );
|
|
765
|
+
logger.info( { item, due, msg: '..........1', cutoffDate } );
|
|
766
|
+
if ( !Number.isNaN( due.getTime() ) && due < cutoffDate ) {
|
|
767
|
+
changed = true;
|
|
768
|
+
|
|
769
|
+
return { ...item, status: 'Expired' };
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return item;
|
|
774
|
+
} );
|
|
775
|
+
logger.info( { updatedMapping, msh: '.......12' } );
|
|
776
|
+
if ( changed ) {
|
|
777
|
+
logger.info( { changed, msg: '.......2' } );
|
|
778
|
+
updatedMapping = updatedMapping.map( ( item ) => {
|
|
779
|
+
logger.info( { item, msg: '.......3' } );
|
|
780
|
+
if ( item?.type === 'tagging' ) {
|
|
781
|
+
logger.info( { item: item?.type, msg: '.......4' } );
|
|
782
|
+
return { ...item, status: 'Expired' };
|
|
783
|
+
}
|
|
784
|
+
return item;
|
|
785
|
+
} );
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
logger.info( { updatedMapping, msh: '.......13' } );
|
|
789
|
+
if ( changed ) {
|
|
790
|
+
const doc = {
|
|
791
|
+
mappingInfo: updatedMapping,
|
|
792
|
+
status: 'Expired',
|
|
793
|
+
};
|
|
794
|
+
logger.info( { updatedMapping } );
|
|
795
|
+
bulkBody.push(
|
|
796
|
+
{ update: { _index: openSearch.footfallDirectory, _id: hit._id } },
|
|
797
|
+
{ doc: doc, doc_as_upsert: true },
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if ( bulkBody.length > 0 ) {
|
|
803
|
+
const bulkRes = await bulkUpdate( bulkBody );
|
|
804
|
+
if ( bulkRes?.errors ) {
|
|
805
|
+
logger.error( { message: 'Bulk expire errors', items: bulkRes.items } );
|
|
806
|
+
}
|
|
807
|
+
totalUpdated += bulkBody.length / 2;
|
|
808
|
+
}
|
|
809
|
+
logger.info( { totalUpdated, msg: '........9' } );
|
|
810
|
+
if ( !scrollId ) break;
|
|
811
|
+
const nextRes = await scrollResponse( scrollId );
|
|
812
|
+
hitsBatch = nextRes?.body?.hits?.hits || [];
|
|
813
|
+
scrollId = nextRes?.body?._scroll_id;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return res.sendSuccess( { message: 'Expired approve status updated', updated: totalUpdated } );
|
|
817
|
+
} catch ( error ) {
|
|
818
|
+
logger.error( { error: error, message: req.body, function: 'expireApproveStatus' } );
|
|
819
|
+
return res.sendError( { error: error }, 500 );
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
139
823
|
export async function revoptaggingcount( req, res ) {
|
|
140
824
|
try {
|
|
141
825
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
@@ -211,44 +895,180 @@ export async function storeProcessedData( req, res ) {
|
|
|
211
895
|
try {
|
|
212
896
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
213
897
|
const inputData = req.query;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
898
|
+
const { fromDate, toDate, storeId } = inputData;
|
|
899
|
+
|
|
900
|
+
// Multi-date range handling for a single store
|
|
901
|
+
if ( fromDate && toDate && storeId ) {
|
|
902
|
+
const dayjs = ( await import( 'dayjs' ) ).default;
|
|
903
|
+
const isSameOrBefore = ( await import( 'dayjs/plugin/isSameOrBefore.js' ) ).default;
|
|
904
|
+
dayjs.extend( isSameOrBefore );
|
|
905
|
+
|
|
906
|
+
let start = dayjs( fromDate );
|
|
907
|
+
// get start value from the before day one
|
|
908
|
+
// Move start one day back so we can access "day before" when looping
|
|
909
|
+
start = start.subtract( 1, 'day' );
|
|
910
|
+
let end = dayjs( toDate );
|
|
911
|
+
|
|
912
|
+
if ( !start.isValid() || !end.isValid() ) {
|
|
913
|
+
return res.sendError( 'Invalid date range supplied', 400 );
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if ( end.isBefore( start ) ) {
|
|
917
|
+
[ start, end ] = [ end, start ];
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const allDateStrings = [];
|
|
921
|
+
const orderedDates = [];
|
|
922
|
+
|
|
923
|
+
while ( start.isSameOrBefore( end ) ) {
|
|
924
|
+
const formatted = start.format( 'YYYY-MM-DD' );
|
|
925
|
+
orderedDates.push( formatted );
|
|
926
|
+
allDateStrings.push( `${storeId}_${formatted}` );
|
|
927
|
+
start = start.add( 1, 'day' );
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if ( allDateStrings.length === 0 ) {
|
|
931
|
+
return res.sendSuccess( [] );
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const footfallQuery = {
|
|
935
|
+
query: {
|
|
936
|
+
terms: {
|
|
937
|
+
_id: allDateStrings,
|
|
228
938
|
},
|
|
229
939
|
},
|
|
230
|
-
|
|
231
|
-
|
|
940
|
+
_source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
941
|
+
sort: [
|
|
942
|
+
{ date_iso: { order: 'asc' } },
|
|
943
|
+
],
|
|
944
|
+
size: allDateStrings.length,
|
|
945
|
+
};
|
|
232
946
|
|
|
233
|
-
|
|
234
|
-
|
|
947
|
+
const multiGet = await getOpenSearchData( openSearch.footfall, footfallQuery );
|
|
948
|
+
const multiHits = multiGet?.body?.hits?.hits || [];
|
|
949
|
+
const hitsMap = new Map();
|
|
950
|
+
multiHits.forEach( ( hit ) => {
|
|
951
|
+
hitsMap.set( hit?._id, hit?._source || null );
|
|
952
|
+
} );
|
|
235
953
|
|
|
236
|
-
|
|
237
|
-
const previousData = hits.find( ( d ) => d._id === dateStringPrevious )?._source || null;
|
|
954
|
+
const responseArray = [];
|
|
238
955
|
|
|
239
|
-
|
|
956
|
+
for ( let i = 1; i < orderedDates.length; i++ ) {
|
|
957
|
+
const currentDate = orderedDates[i];
|
|
958
|
+
const currentId = `${storeId}_${currentDate}`;
|
|
959
|
+
logger.info( { currentId, currentDate } );
|
|
960
|
+
const processedData = hitsMap.get( currentId );
|
|
961
|
+
logger.info( { processedData } );
|
|
962
|
+
if ( !processedData ) {
|
|
963
|
+
responseArray.push( {
|
|
964
|
+
date: currentDate,
|
|
965
|
+
footfallCount: 0,
|
|
966
|
+
footfallCountTrend: 0,
|
|
967
|
+
downtime: 0,
|
|
968
|
+
} );
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
240
971
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
972
|
+
const prevDate = dayjs( currentDate ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
|
|
973
|
+
const prevId = `${storeId}_${prevDate}`;
|
|
974
|
+
logger.info( { prevId, prevDate } );
|
|
975
|
+
const previousData = hitsMap.get( prevId );
|
|
246
976
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
977
|
+
let footfallCountTrend = 0;
|
|
978
|
+
logger.info( { previousData, previoucubr1: previousData?.footfall_count } );
|
|
979
|
+
if ( previousData && previousData.footfall_count ) {
|
|
980
|
+
logger.info( { previousData, previoucubr: previousData?.footfall_count } );
|
|
981
|
+
footfallCountTrend = Math.round(
|
|
982
|
+
( ( processedData.footfall_count - previousData?.footfall_count ) / previousData.footfall_count ) * 100,
|
|
983
|
+
);
|
|
984
|
+
logger.info( { footfallCountTrend } );
|
|
985
|
+
}
|
|
986
|
+
// Add ticket status from openSearch.footfallDirectory (_source.status)
|
|
987
|
+
let ticketStatus = null;
|
|
988
|
+
let receivedfootfall = null;
|
|
989
|
+
// Try to find a matching footfallDirectory record for this date+storeId
|
|
990
|
+
// const ticketKey = `${storeId}_${currentDate}`;
|
|
991
|
+
const footfallDirQuery = {
|
|
992
|
+
query: {
|
|
993
|
+
bool: {
|
|
994
|
+
must: [
|
|
995
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
996
|
+
{ term: { 'dateString': currentDate } },
|
|
997
|
+
{ term: { 'ticketName.keyword': 'footfall-directory' } },
|
|
998
|
+
{
|
|
999
|
+
'term': {
|
|
1000
|
+
'type.keyword': 'store',
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
1003
|
+
],
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
size: 1,
|
|
1007
|
+
_source: [ 'status', 'mappingInfo', 'footfallCount' ],
|
|
1008
|
+
};
|
|
1009
|
+
try {
|
|
1010
|
+
const footfallDirRes = await getOpenSearchData( openSearch.footfallDirectory, footfallDirQuery );
|
|
1011
|
+
const hit = footfallDirRes?.body?.hits?.hits?.[0];
|
|
1012
|
+
receivedfootfall = hit._source.footfallCount;
|
|
1013
|
+
if ( hit?._source?.mappingInfo && Array.isArray( hit._source.mappingInfo ) ) {
|
|
1014
|
+
for ( let i = 0; i < hit._source.mappingInfo.length; i++ ) {
|
|
1015
|
+
if ( hit._source.mappingInfo[i].type === 'tagging' ) {
|
|
1016
|
+
ticketStatus = hit._source.mappingInfo[i].status;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
} catch ( err ) {
|
|
1022
|
+
logger.warn( { message: 'Could not get ticket status from footfallDirectory', error: err } );
|
|
1023
|
+
}
|
|
1024
|
+
// Check if request status ("raised") should be enabled or disabled by querying MongoDB config
|
|
1025
|
+
// We'll assume findOnerevopConfig can fetch the record with status for this storeId and dateString
|
|
1026
|
+
let raisedStatusEnabled = 'reset'; // default: enabled
|
|
1027
|
+
try {
|
|
1028
|
+
const mongoConfig = await findOneVmsStoreRequest( { storeId: storeId, dateString: currentDate } );
|
|
1029
|
+
if ( mongoConfig && mongoConfig.status ) {
|
|
1030
|
+
raisedStatusEnabled = mongoConfig.status;
|
|
1031
|
+
}
|
|
1032
|
+
} catch ( err ) {
|
|
1033
|
+
logger.warn( { message: 'Could not get request status from MongoDB', error: err } );
|
|
1034
|
+
// Leave raisedStatusEnabled as default
|
|
1035
|
+
}
|
|
1036
|
+
let statusArray=[ 'Closed', 'Open - Accuracy Issue', 'Closed - Accuracy Issue' ];
|
|
1037
|
+
|
|
1038
|
+
responseArray.push( {
|
|
1039
|
+
date: processedData?.date_string || currentDate,
|
|
1040
|
+
footfallCount: processedData?.footfall_count || 0,
|
|
1041
|
+
footfallCountTrend,
|
|
1042
|
+
downtime: processedData?.down_time || 0,
|
|
1043
|
+
ticketStatus,
|
|
1044
|
+
raisedStatusEnabled,
|
|
1045
|
+
footfallticketCount: statusArray.includes( ticketStatus )?receivedfootfall:processedData?.footfall_count,
|
|
1046
|
+
} );
|
|
1047
|
+
console.log( '🚀 ~ storeProcessedData ~ ticketStatus:', ticketStatus );
|
|
1048
|
+
|
|
1049
|
+
if ( raisedStatusEnabled === 'block' ) {
|
|
1050
|
+
// Calculate the number of days from currentDate + 1 to the end of the month
|
|
1051
|
+
// Assume currentDate is in format 'YYYY-MM-DD'
|
|
1052
|
+
const currentDateObj = new Date( currentDate );
|
|
1053
|
+
// Move to next day
|
|
1054
|
+
const nextDay = new Date( currentDateObj );
|
|
1055
|
+
nextDay.setDate( currentDateObj.getDate() + 1 );
|
|
1056
|
+
|
|
1057
|
+
// Get the last date of the current month
|
|
1058
|
+
const endOfMonth = new Date( currentDateObj.getFullYear(), currentDateObj.getMonth() + 1, 0 );
|
|
1059
|
+
|
|
1060
|
+
// Calculate number of days (inclusive of end date, exclusive of nextDay)
|
|
1061
|
+
let noOfBlockedDays = Math.floor( ( endOfMonth - nextDay ) / ( 1000 * 60 * 60 * 24 ) ) + 1;
|
|
1062
|
+
if ( noOfBlockedDays < 0 ) noOfBlockedDays = 0;
|
|
1063
|
+
|
|
1064
|
+
// Add to response
|
|
1065
|
+
responseArray[responseArray.length - 1].noOfBlockedDays = noOfBlockedDays;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
return res.sendSuccess( responseArray );
|
|
1070
|
+
}
|
|
1071
|
+
return res.sendError( 'Required parameters missing', 400 );
|
|
252
1072
|
} catch ( error ) {
|
|
253
1073
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
254
1074
|
const err = error.message || 'Internal Server Error';
|
|
@@ -284,43 +1104,183 @@ export async function footFallImages( req, res ) {
|
|
|
284
1104
|
'ticketName.keyword': 'footfall-directory',
|
|
285
1105
|
},
|
|
286
1106
|
},
|
|
1107
|
+
{
|
|
1108
|
+
'term': {
|
|
1109
|
+
'type.keyword': 'store',
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
287
1112
|
],
|
|
288
1113
|
},
|
|
289
1114
|
},
|
|
290
|
-
'_source': [ 'dateString', 'storeId', '
|
|
1115
|
+
'_source': [ 'dateString', 'storeId', 'mappingInfo', 'revicedFootfall', 'revicedPerc', 'reviced', 'createdAt', 'updatedAt', 'footfallCount' ],
|
|
291
1116
|
|
|
292
1117
|
};
|
|
293
1118
|
|
|
294
1119
|
const getData = await getOpenSearchData( opensearch.footfallDirectory, query );
|
|
295
1120
|
const ticketDetails = getData?.body?.hits?.hits[0];
|
|
296
1121
|
let temp = [];
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
1122
|
+
const footfallValue = ticketDetails?._source?.footfallCount ?? 0;
|
|
1123
|
+
const mappingInfoArray = ticketDetails?._source?.mappingInfo ?? [];
|
|
1124
|
+
// Helper to get mappingInfo for an actionType
|
|
1125
|
+
function getMappingForType( type ) {
|
|
1126
|
+
return mappingInfoArray.find( ( m ) => m?.type === type ) || {};
|
|
1127
|
+
}
|
|
1128
|
+
const getTagging = getMappingForType( 'tagging' );
|
|
1129
|
+
const getFinal = getMappingForType( 'finalRevision' );
|
|
1130
|
+
const taggingRevised = getTagging.revicedFootfall ?? 0;
|
|
1131
|
+
const finalRevised = getFinal.revicedFootfall ?? 0;
|
|
1132
|
+
// List of actionTypes to process in sequence
|
|
1133
|
+
|
|
1134
|
+
if ( req.user.userType !== 'tango' && req.user.role !== 'superadmin' ) {
|
|
1135
|
+
switch ( req.user.role ) {
|
|
1136
|
+
case 'user':
|
|
1137
|
+
const actionTypesUser = [ 'tagging', 'finalRevision' ];
|
|
1138
|
+
|
|
1139
|
+
temp = [];
|
|
1140
|
+
actionTypesUser.forEach( ( type ) => {
|
|
1141
|
+
const mapping = getMappingForType( type );
|
|
1142
|
+
if ( type === 'tagging' ) {
|
|
1143
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
1144
|
+
const revisedPerc =
|
|
1145
|
+
footfallValue > 0 ?
|
|
1146
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
1147
|
+
'0';
|
|
1148
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1149
|
+
temp.push( {
|
|
1150
|
+
actionType: type,
|
|
1151
|
+
footfall: footfallValue,
|
|
1152
|
+
revicedFootfall: revisedFootfall,
|
|
1153
|
+
revicedPerc: revisedPerc,
|
|
1154
|
+
count: countObj,
|
|
1155
|
+
createdAt: mapping.createdAt ?? '',
|
|
1156
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1157
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1158
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1159
|
+
isUp: false,
|
|
1160
|
+
} );
|
|
1161
|
+
} else if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
1162
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
1163
|
+
const revisedPerc =
|
|
1164
|
+
footfallValue > 0 ?
|
|
1165
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
1166
|
+
'0';
|
|
1167
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1168
|
+
temp.push( {
|
|
1169
|
+
actionType: type,
|
|
1170
|
+
footfall: footfallValue,
|
|
1171
|
+
revicedFootfall: revisedFootfall,
|
|
1172
|
+
revicedPerc: revisedPerc,
|
|
1173
|
+
count: countObj,
|
|
1174
|
+
createdAt: mapping.createdAt ?? '',
|
|
1175
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1176
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1177
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1178
|
+
isUp: finalRevised > taggingRevised? true : false,
|
|
1179
|
+
} );
|
|
1180
|
+
}
|
|
1181
|
+
} );
|
|
1182
|
+
break;
|
|
1183
|
+
case 'admin':
|
|
1184
|
+
const actionTypesAdmin = [ 'tagging', 'review', 'finalRevision' ];
|
|
1185
|
+
temp = [];
|
|
1186
|
+
actionTypesAdmin.forEach( ( type ) => {
|
|
1187
|
+
const mapping = getMappingForType( type );
|
|
1188
|
+
if ( type === 'tagging' ) {
|
|
1189
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
1190
|
+
const revisedPerc =
|
|
1191
|
+
footfallValue > 0 ?
|
|
1192
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
1193
|
+
'0';
|
|
1194
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1195
|
+
temp.push( {
|
|
1196
|
+
actionType: type,
|
|
1197
|
+
footfall: footfallValue,
|
|
1198
|
+
revicedFootfall: revisedFootfall,
|
|
1199
|
+
revicedPerc: revisedPerc,
|
|
1200
|
+
count: countObj,
|
|
1201
|
+
createdAt: mapping.createdAt ?? '',
|
|
1202
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1203
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1204
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1205
|
+
isUp: false,
|
|
1206
|
+
} );
|
|
1207
|
+
} else if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
1208
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
1209
|
+
const revisedPerc =
|
|
1210
|
+
footfallValue > 0 ?
|
|
1211
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
1212
|
+
'0';
|
|
1213
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1214
|
+
temp.push( {
|
|
1215
|
+
actionType: type,
|
|
1216
|
+
footfall: footfallValue,
|
|
1217
|
+
revicedFootfall: revisedFootfall,
|
|
1218
|
+
revicedPerc: revisedPerc,
|
|
1219
|
+
count: countObj,
|
|
1220
|
+
createdAt: mapping.createdAt ?? '',
|
|
1221
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1222
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1223
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1224
|
+
isUp: finalRevised > taggingRevised? true : false,
|
|
1225
|
+
} );
|
|
1226
|
+
}
|
|
1227
|
+
} );
|
|
1228
|
+
}
|
|
1229
|
+
} else {
|
|
1230
|
+
const actionTypes = [ 'tagging', 'review', 'approve', 'tangoreview', 'finalRevision' ];
|
|
1231
|
+
|
|
1232
|
+
// Dynamically add to temp only if actionType matches and status is 'closed',
|
|
1233
|
+
// except for 'tagging' where status must be 'raised'
|
|
1234
|
+
temp = [];
|
|
1235
|
+
actionTypes.forEach( ( type ) => {
|
|
1236
|
+
const mapping = getMappingForType( type );
|
|
1237
|
+
if ( type === 'tagging' ) {
|
|
1238
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1239
|
+
temp.push( {
|
|
1240
|
+
actionType: type,
|
|
1241
|
+
footfall: footfallValue,
|
|
1242
|
+
revicedFootfall: mapping.revicedFootfall ?? 0,
|
|
1243
|
+
revicedPerc: mapping?.reviced?.toString() ?? '--',
|
|
1244
|
+
count: countObj,
|
|
1245
|
+
createdAt: mapping.createdAt ?? '',
|
|
1246
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1247
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1248
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1249
|
+
isUp: false,
|
|
1250
|
+
} );
|
|
1251
|
+
}
|
|
1252
|
+
if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
1253
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
1254
|
+
temp.push( {
|
|
1255
|
+
actionType: type,
|
|
1256
|
+
footfall: footfallValue,
|
|
1257
|
+
revicedFootfall: mapping.revicedFootfall ?? 0,
|
|
1258
|
+
revicedPerc: mapping?.reviced?.toString() ?? '--',
|
|
1259
|
+
count: countObj,
|
|
1260
|
+
createdAt: mapping.createdAt ?? '',
|
|
1261
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
1262
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
1263
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
1264
|
+
isUp: finalRevised > taggingRevised? true : false,
|
|
1265
|
+
} );
|
|
1266
|
+
}
|
|
1267
|
+
} );
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
const LamdaURL = revop.getImages;
|
|
1272
|
+
let resultData = await LamdaServiceCall( LamdaURL, inputData );
|
|
1273
|
+
if ( resultData ) {
|
|
1274
|
+
// temp.length? temp[0].status = 'open': null;
|
|
1275
|
+
|
|
1276
|
+
if ( resultData.status_code == '200' ) {
|
|
1277
|
+
return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0 && ticketDetails? temp : null, config: req?.store?.footfallDirectoryConfigs } );
|
|
1278
|
+
} else {
|
|
1279
|
+
return res.sendError( 'No Content', 204 );
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
return res.sendError( 'No Content', 204 );
|
|
1283
|
+
}
|
|
324
1284
|
} catch ( error ) {
|
|
325
1285
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
326
1286
|
const err = error.message || 'Internal Server Error';
|
|
@@ -330,12 +1290,20 @@ export async function footFallImages( req, res ) {
|
|
|
330
1290
|
|
|
331
1291
|
export async function tagTempId( req, res ) {
|
|
332
1292
|
try {
|
|
333
|
-
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
334
1293
|
const inputData = req.body;
|
|
1294
|
+
const today = dayjs();
|
|
1295
|
+
const diff = today.diff( inputData.dateString, 'day' );
|
|
1296
|
+
const taggingDueDate = req?.client?.footfallDirectoryConfigs?.allowTicketCreation || 0;
|
|
1297
|
+
if ( diff > taggingDueDate ) {
|
|
1298
|
+
return res.sendError( `Tagging is not allowed for a period exceeding ${taggingDueDate} days`, 400 );
|
|
1299
|
+
}
|
|
1300
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1301
|
+
|
|
335
1302
|
const upsertRecord = {
|
|
336
1303
|
clientId: inputData.storeId.split( '-' )[0],
|
|
337
1304
|
storeId: inputData.storeId,
|
|
338
1305
|
tempId: inputData.tempId,
|
|
1306
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${inputData?.tempId}`,
|
|
339
1307
|
dateString: inputData.dateString,
|
|
340
1308
|
timeRange: inputData.timeRange,
|
|
341
1309
|
processType: inputData.processType,
|
|
@@ -344,10 +1312,31 @@ export async function tagTempId( req, res ) {
|
|
|
344
1312
|
exitTime: inputData.exitTime,
|
|
345
1313
|
filePath: inputData.filePath,
|
|
346
1314
|
status: inputData?.revopsType == 'non-tagging' ?'':'submitted',
|
|
347
|
-
description: '',
|
|
348
|
-
isChecked:
|
|
349
|
-
|
|
1315
|
+
description: inputData.comments || '',
|
|
1316
|
+
isChecked: null,
|
|
1317
|
+
// Add id to each object in duplicateImage if it exists and is an array
|
|
1318
|
+
duplicateImage: Array.isArray( inputData?.duplicateImage ) ?
|
|
1319
|
+
inputData.duplicateImage.map( ( img ) => ( {
|
|
1320
|
+
...img,
|
|
1321
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${img?.tempId}`,
|
|
1322
|
+
actions: [
|
|
1323
|
+
{
|
|
1324
|
+
actionType: 'tagging',
|
|
1325
|
+
action: 'submitted',
|
|
1326
|
+
},
|
|
1327
|
+
],
|
|
1328
|
+
} ) ) :
|
|
1329
|
+
[],
|
|
350
1330
|
type: 'tagging-reflect',
|
|
1331
|
+
ticketStatus: 'submitted',
|
|
1332
|
+
isParent: inputData?.revopsType === 'duplicate'? true : false,
|
|
1333
|
+
actions: [
|
|
1334
|
+
{
|
|
1335
|
+
actionType: 'tagging',
|
|
1336
|
+
action: 'submitted',
|
|
1337
|
+
},
|
|
1338
|
+
],
|
|
1339
|
+
comments: inputData.comments || '',
|
|
351
1340
|
createdAt: new Date(),
|
|
352
1341
|
updatedAt: new Date(),
|
|
353
1342
|
|
|
@@ -363,8 +1352,10 @@ export async function tagTempId( req, res ) {
|
|
|
363
1352
|
ctx._source.description = params.description;
|
|
364
1353
|
ctx._source.isChecked = params.isChecked;
|
|
365
1354
|
ctx._source.duplicateImage = params.duplicateImage;
|
|
1355
|
+
ctx._source.isParent = params.isParent;
|
|
366
1356
|
ctx._source.updatedAt = params.updatedAt;
|
|
367
1357
|
ctx._source.timeRange = params.timeRange;
|
|
1358
|
+
ctx._source.comments = params.comments;
|
|
368
1359
|
if (ctx._source.createdAt == null) {
|
|
369
1360
|
ctx._source.createdAt = params.createdAt;
|
|
370
1361
|
ctx._source.clientId = params.clientId;
|
|
@@ -379,6 +1370,32 @@ export async function tagTempId( req, res ) {
|
|
|
379
1370
|
};
|
|
380
1371
|
const id = `${inputData.storeId}_${inputData.dateString}_${inputData.timeRange}_${inputData.tempId}`;
|
|
381
1372
|
await upsertWithScript( openSearch.revop, id, { script, upsert: upsertRecord } );
|
|
1373
|
+
if ( inputData?.comments && inputData?.comments !== '' ) {
|
|
1374
|
+
const id = `${inputData.storeId}_${inputData.dateString}_${Date.now()}`;
|
|
1375
|
+
const logs = {
|
|
1376
|
+
type: 'tagging',
|
|
1377
|
+
parent: inputData?.revopsType == 'duplicate'? inputData.tempId : null,
|
|
1378
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${inputData?.tempId}`,
|
|
1379
|
+
tempId: inputData.tempId,
|
|
1380
|
+
timeRange: inputData.timeRange,
|
|
1381
|
+
storeId: inputData.storeId,
|
|
1382
|
+
dateString: inputData.dateString,
|
|
1383
|
+
processType: inputData.processType,
|
|
1384
|
+
category: inputData.revopsType,
|
|
1385
|
+
entryTime: inputData.entryTime,
|
|
1386
|
+
exitTime: inputData.exitTime,
|
|
1387
|
+
filePath: inputData.filePath,
|
|
1388
|
+
status: inputData?.revopsType == 'non-tagging' ?'':'submitted',
|
|
1389
|
+
description: inputData.comments || '',
|
|
1390
|
+
isChecked: null,
|
|
1391
|
+
createdByEmail: req?.user?.email,
|
|
1392
|
+
createdByUserName: req?.user?.userName,
|
|
1393
|
+
createdByRole: req?.user?.role,
|
|
1394
|
+
message: inputData.comments || '',
|
|
1395
|
+
createdAt: new Date(),
|
|
1396
|
+
};
|
|
1397
|
+
await insertWithId( openSearch.vmsCommentsLog, id, logs );
|
|
1398
|
+
}
|
|
382
1399
|
if ( inputData?.duplicateImage?.length> 0 ) {
|
|
383
1400
|
let bulkBody = [];
|
|
384
1401
|
for ( let item of inputData?.duplicateImage ) {
|
|
@@ -386,9 +1403,10 @@ export async function tagTempId( req, res ) {
|
|
|
386
1403
|
clientId: inputData.storeId.split( '-' )[0],
|
|
387
1404
|
storeId: inputData.storeId,
|
|
388
1405
|
tempId: item.tempId,
|
|
1406
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${item?.tempId}`,
|
|
389
1407
|
dateString: inputData.dateString,
|
|
390
1408
|
timeRange: item.timeRange,
|
|
391
|
-
isChecked:
|
|
1409
|
+
isChecked: null,
|
|
392
1410
|
processType: inputData.processType,
|
|
393
1411
|
revopsType: item.revopsType,
|
|
394
1412
|
entryTime: item.entryTime,
|
|
@@ -397,7 +1415,16 @@ export async function tagTempId( req, res ) {
|
|
|
397
1415
|
status: item?.revopsType == 'non-tagging' ?'':'submitted',
|
|
398
1416
|
description: '',
|
|
399
1417
|
duplicateImage: [],
|
|
1418
|
+
isParent: false,
|
|
400
1419
|
type: 'tagging-reflect',
|
|
1420
|
+
ticketStatus: 'submitted',
|
|
1421
|
+
comments: inputData.comments || '',
|
|
1422
|
+
actions: [
|
|
1423
|
+
{
|
|
1424
|
+
actionType: 'tagging',
|
|
1425
|
+
action: 'submitted',
|
|
1426
|
+
},
|
|
1427
|
+
],
|
|
401
1428
|
createdAt: new Date(),
|
|
402
1429
|
updatedAt: new Date(),
|
|
403
1430
|
|
|
@@ -419,12 +1446,20 @@ export async function tagTempId( req, res ) {
|
|
|
419
1446
|
return { success: false, errors: res1.items };
|
|
420
1447
|
} else {
|
|
421
1448
|
logger.info( { msg: 'res1' } );
|
|
422
|
-
return res.sendSuccess( `ID tagged as
|
|
1449
|
+
return res.sendSuccess( `ID tagged as Duplicates` );
|
|
423
1450
|
// return { success: true };
|
|
424
1451
|
}
|
|
425
1452
|
}
|
|
426
1453
|
} else {
|
|
427
|
-
|
|
1454
|
+
// Convert camelCase revopsType to space-separated and capitalize first letter of each word
|
|
1455
|
+
function camelCaseToTitle( str ) {
|
|
1456
|
+
if ( !str ) return '';
|
|
1457
|
+
return str.replace( /([A-Z])/g, ' $1' ).replace( /^./, ( s ) => s.toUpperCase() );
|
|
1458
|
+
}
|
|
1459
|
+
const titleRevopsType = camelCaseToTitle( inputData?.revopsType );
|
|
1460
|
+
const message = inputData?.revopsType == 'non-tagging' ?
|
|
1461
|
+
'ID removed from tagging' :
|
|
1462
|
+
`ID tagged as ${titleRevopsType}`;
|
|
428
1463
|
return res.sendSuccess( message );
|
|
429
1464
|
}
|
|
430
1465
|
} catch ( error ) {
|
|
@@ -451,3 +1486,390 @@ export async function getCategorizedImages( req, res ) {
|
|
|
451
1486
|
return res.sendError( err, 500 );
|
|
452
1487
|
}
|
|
453
1488
|
}
|
|
1489
|
+
|
|
1490
|
+
export async function vmsDataMigration( req, res ) {
|
|
1491
|
+
try {
|
|
1492
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1493
|
+
const inputData = req.body;
|
|
1494
|
+
const { storeId, dateString, limit = 10000 } = inputData;
|
|
1495
|
+
|
|
1496
|
+
// Build query to fetch old structure documents
|
|
1497
|
+
const query = {
|
|
1498
|
+
query: {
|
|
1499
|
+
bool: {
|
|
1500
|
+
must: [
|
|
1501
|
+
{
|
|
1502
|
+
term: {
|
|
1503
|
+
'ticketName.keyword': 'footfall-directory',
|
|
1504
|
+
},
|
|
1505
|
+
},
|
|
1506
|
+
// {
|
|
1507
|
+
// range: {
|
|
1508
|
+
// createdAt: {
|
|
1509
|
+
// gte: '2025-12-01T00:00:00.000Z',
|
|
1510
|
+
// lte: '2025-12-03T00:00:00.000Z',
|
|
1511
|
+
// },
|
|
1512
|
+
// },
|
|
1513
|
+
// },
|
|
1514
|
+
],
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
size: parseInt( limit ),
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
// Add storeId filter if provided
|
|
1521
|
+
if ( storeId ) {
|
|
1522
|
+
query.query.bool.must.push( {
|
|
1523
|
+
term: {
|
|
1524
|
+
'storeId.keyword': storeId,
|
|
1525
|
+
},
|
|
1526
|
+
} );
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// Add dateString filter if provided
|
|
1530
|
+
if ( dateString ) {
|
|
1531
|
+
query.query.bool.must.push( {
|
|
1532
|
+
terms: {
|
|
1533
|
+
dateString: dateString?.split( ',' ),
|
|
1534
|
+
},
|
|
1535
|
+
} );
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// Exclude documents that already have the new structure (have mappingInfo or type: 'store')
|
|
1539
|
+
query.query.bool.must_not = [
|
|
1540
|
+
{
|
|
1541
|
+
exists: {
|
|
1542
|
+
field: 'mappingInfo',
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
term: {
|
|
1547
|
+
'type.keyword': 'store',
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
];
|
|
1551
|
+
|
|
1552
|
+
const getData = await getOpenSearchData( openSearch.oldFootfallDirectory, query );
|
|
1553
|
+
logger.info( { getData } );
|
|
1554
|
+
const hits = getData?.body?.hits?.hits || [];
|
|
1555
|
+
|
|
1556
|
+
if ( hits.length === 0 ) {
|
|
1557
|
+
return res.sendSuccess( { message: 'No documents found to migrate', migrated: 0 } );
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
let migratedCount = 0;
|
|
1561
|
+
const errors = [];
|
|
1562
|
+
|
|
1563
|
+
for ( const hit of hits ) {
|
|
1564
|
+
try {
|
|
1565
|
+
const oldSource = hit._source;
|
|
1566
|
+
const documentId = hit._id;
|
|
1567
|
+
|
|
1568
|
+
// Calculate revicedFootfall (sum of AC counts)
|
|
1569
|
+
const tempFootfall =oldSource?.status === 'open' ?
|
|
1570
|
+
( oldSource.duplicateCount || 0 ) +
|
|
1571
|
+
( oldSource.employeeCount || 0 ) +
|
|
1572
|
+
( oldSource.houseKeepingCount || 0 ) +
|
|
1573
|
+
( oldSource.junkCount || 0 ):
|
|
1574
|
+
( oldSource.duplicateACCount || 0 ) +
|
|
1575
|
+
( oldSource.employeeACCount || 0 ) +
|
|
1576
|
+
( oldSource.houseKeepingACCount || 0 ) +
|
|
1577
|
+
( oldSource.junkACCount || 0 );
|
|
1578
|
+
|
|
1579
|
+
// Calculate revicedPerc
|
|
1580
|
+
const footfallCount = oldSource.footfallCount || 0;
|
|
1581
|
+
const revicedFootfall = footfallCount - tempFootfall;
|
|
1582
|
+
const revicedPerc = footfallCount > 0 ?
|
|
1583
|
+
Math.round( ( revicedFootfall / footfallCount ) * 100 ) :
|
|
1584
|
+
0;
|
|
1585
|
+
// Calculate reviced
|
|
1586
|
+
const reviced = parseInt( revicedPerc );
|
|
1587
|
+
|
|
1588
|
+
// Transform arrays to revisedDetail format
|
|
1589
|
+
const revisedDetail = [];
|
|
1590
|
+
|
|
1591
|
+
// Transform duplicateImages
|
|
1592
|
+
if ( Array.isArray( oldSource.duplicateImages ) ) {
|
|
1593
|
+
for ( const duplicate of oldSource.duplicateImages ) {
|
|
1594
|
+
const parentId = `${oldSource.storeId}_${oldSource.dateString}_${duplicate.tempId}`;
|
|
1595
|
+
const parentDetail = {
|
|
1596
|
+
id: parentId,
|
|
1597
|
+
clientId: oldSource.clientId,
|
|
1598
|
+
storeId: oldSource.storeId,
|
|
1599
|
+
tempId: duplicate.tempId,
|
|
1600
|
+
dateString: oldSource.dateString,
|
|
1601
|
+
timeRange: duplicate.timeRange,
|
|
1602
|
+
processType: 'footfall',
|
|
1603
|
+
revopsType: 'duplicate',
|
|
1604
|
+
entryTime: duplicate.entryTime,
|
|
1605
|
+
exitTime: duplicate.exitTime,
|
|
1606
|
+
filePath: duplicate.filePath,
|
|
1607
|
+
status: oldSource.duplicateStatus || 'submitted',
|
|
1608
|
+
description: '',
|
|
1609
|
+
isChecked: duplicate.isChecked !== undefined ? duplicate.isChecked : false,
|
|
1610
|
+
type: 'tagging-reflect',
|
|
1611
|
+
parent: null,
|
|
1612
|
+
isParent: true,
|
|
1613
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1614
|
+
updatedAt: oldSource.updatedAt || new Date(),
|
|
1615
|
+
duplicateImage: [],
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
// Add child duplicate images
|
|
1619
|
+
if ( Array.isArray( duplicate.data ) ) {
|
|
1620
|
+
parentDetail.duplicateImage = duplicate.data.map( ( child ) => ( {
|
|
1621
|
+
id: `${oldSource.storeId}_${oldSource.dateString}_${child.tempId}`,
|
|
1622
|
+
tempId: child.tempId,
|
|
1623
|
+
timeRange: child.timeRange,
|
|
1624
|
+
entryTime: child.entryTime,
|
|
1625
|
+
exitTime: child.exitTime,
|
|
1626
|
+
filePath: child.filePath,
|
|
1627
|
+
isChecked: child.isChecked !== undefined ? child.isChecked : true,
|
|
1628
|
+
} ) );
|
|
1629
|
+
|
|
1630
|
+
// Add child details to revisedDetail
|
|
1631
|
+
for ( const child of duplicate.data ) {
|
|
1632
|
+
revisedDetail.push( {
|
|
1633
|
+
id: `${oldSource.storeId}_${oldSource.dateString}_${child.tempId}`,
|
|
1634
|
+
clientId: oldSource.clientId,
|
|
1635
|
+
storeId: oldSource.storeId,
|
|
1636
|
+
tempId: child.tempId,
|
|
1637
|
+
dateString: oldSource.dateString,
|
|
1638
|
+
timeRange: child.timeRange,
|
|
1639
|
+
processType: 'footfall',
|
|
1640
|
+
revopsType: 'duplicate',
|
|
1641
|
+
entryTime: child.entryTime,
|
|
1642
|
+
exitTime: child.exitTime,
|
|
1643
|
+
filePath: child.filePath,
|
|
1644
|
+
status: oldSource.duplicateStatus || 'submitted',
|
|
1645
|
+
description: '',
|
|
1646
|
+
isChecked: child.isChecked !== undefined ? child.isChecked : false,
|
|
1647
|
+
type: 'tagging-reflect',
|
|
1648
|
+
parent: duplicate.tempId,
|
|
1649
|
+
isParent: false,
|
|
1650
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1651
|
+
updatedAt: oldSource.updatedAt || new Date(),
|
|
1652
|
+
duplicateImage: [],
|
|
1653
|
+
} );
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
revisedDetail.push( parentDetail );
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// Transform houseKeeping
|
|
1662
|
+
if ( Array.isArray( oldSource.houseKeeping ) ) {
|
|
1663
|
+
for ( const item of oldSource.houseKeeping ) {
|
|
1664
|
+
revisedDetail.push( {
|
|
1665
|
+
id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
|
|
1666
|
+
clientId: oldSource.clientId,
|
|
1667
|
+
storeId: oldSource.storeId,
|
|
1668
|
+
tempId: item.tempId,
|
|
1669
|
+
dateString: oldSource.dateString,
|
|
1670
|
+
timeRange: item.timeRange,
|
|
1671
|
+
processType: 'footfall',
|
|
1672
|
+
revopsType: 'houseKeeping',
|
|
1673
|
+
entryTime: item.entryTime,
|
|
1674
|
+
exitTime: item.exitTime,
|
|
1675
|
+
filePath: item.filePath,
|
|
1676
|
+
status: oldSource.houseKeepingStatus || 'submitted',
|
|
1677
|
+
description: '',
|
|
1678
|
+
isChecked: item.isChecked !== undefined ? item.isChecked : false,
|
|
1679
|
+
type: 'tagging-reflect',
|
|
1680
|
+
parent: null,
|
|
1681
|
+
isParent: false,
|
|
1682
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1683
|
+
updatedAt: oldSource.updatedAt || new Date(),
|
|
1684
|
+
duplicateImage: [],
|
|
1685
|
+
} );
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// Transform employee
|
|
1690
|
+
if ( Array.isArray( oldSource.employee ) ) {
|
|
1691
|
+
for ( const item of oldSource.employee ) {
|
|
1692
|
+
revisedDetail.push( {
|
|
1693
|
+
id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
|
|
1694
|
+
clientId: oldSource.clientId,
|
|
1695
|
+
storeId: oldSource.storeId,
|
|
1696
|
+
tempId: item.tempId,
|
|
1697
|
+
dateString: oldSource.dateString,
|
|
1698
|
+
timeRange: item.timeRange,
|
|
1699
|
+
processType: 'footfall',
|
|
1700
|
+
revopsType: 'employee',
|
|
1701
|
+
entryTime: item.entryTime,
|
|
1702
|
+
exitTime: item.exitTime,
|
|
1703
|
+
filePath: item.filePath,
|
|
1704
|
+
status: oldSource.employeeStatus || 'submitted',
|
|
1705
|
+
description: '',
|
|
1706
|
+
isChecked: item.isChecked !== undefined ? item.isChecked : false,
|
|
1707
|
+
type: 'tagging-reflect',
|
|
1708
|
+
parent: null,
|
|
1709
|
+
isParent: false,
|
|
1710
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1711
|
+
updatedAt: oldSource.updatedAt || new Date(),
|
|
1712
|
+
duplicateImage: [],
|
|
1713
|
+
} );
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Transform junk
|
|
1718
|
+
if ( Array.isArray( oldSource.junk ) ) {
|
|
1719
|
+
for ( const item of oldSource.junk ) {
|
|
1720
|
+
revisedDetail.push( {
|
|
1721
|
+
id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
|
|
1722
|
+
clientId: oldSource.clientId,
|
|
1723
|
+
storeId: oldSource.storeId,
|
|
1724
|
+
tempId: item.tempId,
|
|
1725
|
+
dateString: oldSource.dateString,
|
|
1726
|
+
timeRange: item.timeRange,
|
|
1727
|
+
processType: 'footfall',
|
|
1728
|
+
revopsType: 'junk',
|
|
1729
|
+
entryTime: item.entryTime,
|
|
1730
|
+
exitTime: item.exitTime,
|
|
1731
|
+
filePath: item.filePath,
|
|
1732
|
+
status: 'submitted',
|
|
1733
|
+
description: '',
|
|
1734
|
+
isChecked: item.isChecked !== undefined ? item.isChecked : false,
|
|
1735
|
+
type: 'tagging-reflect',
|
|
1736
|
+
parent: null,
|
|
1737
|
+
isParent: false,
|
|
1738
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1739
|
+
updatedAt: oldSource.updatedAt || new Date(),
|
|
1740
|
+
duplicateImage: [],
|
|
1741
|
+
} );
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// Create count array
|
|
1746
|
+
const count = [
|
|
1747
|
+
{
|
|
1748
|
+
name: 'Duplicate',
|
|
1749
|
+
value: oldSource.duplicateCount || 0,
|
|
1750
|
+
key: 'duplicateCount',
|
|
1751
|
+
type: 'duplicate',
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
name: 'Employee',
|
|
1755
|
+
value: oldSource.employeeCount || 0,
|
|
1756
|
+
key: 'employeeCount',
|
|
1757
|
+
type: 'employee',
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
name: 'House keeping',
|
|
1761
|
+
value: oldSource.houseKeepingCount || 0,
|
|
1762
|
+
key: 'houseKeepingCount',
|
|
1763
|
+
type: 'houseKeeping',
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
name: 'Junk',
|
|
1767
|
+
value: oldSource.junkCount || 0,
|
|
1768
|
+
key: 'junkCount',
|
|
1769
|
+
type: 'junk',
|
|
1770
|
+
},
|
|
1771
|
+
];
|
|
1772
|
+
|
|
1773
|
+
// Create mappingInfo array
|
|
1774
|
+
const mappingInfo = oldSource.status === 'open' ?
|
|
1775
|
+
[
|
|
1776
|
+
{
|
|
1777
|
+
type: 'tagging',
|
|
1778
|
+
mode: 'mobile',
|
|
1779
|
+
revicedFootfall,
|
|
1780
|
+
revicedPerc: `${revicedPerc}%`,
|
|
1781
|
+
reviced,
|
|
1782
|
+
count,
|
|
1783
|
+
revisedDetail,
|
|
1784
|
+
status: oldSource.status === 'open' ? 'Raised' : oldSource.status || 'Raised',
|
|
1785
|
+
createdByEmail: oldSource.email || '',
|
|
1786
|
+
createdByUserName: oldSource.userName || '',
|
|
1787
|
+
createdByRole: oldSource.role || 'user',
|
|
1788
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
type: 'review',
|
|
1792
|
+
count,
|
|
1793
|
+
revisedDetail,
|
|
1794
|
+
status: oldSource.status === 'open' ? 'Open' : oldSource.status || 'Open',
|
|
1795
|
+
dueDate: oldSource.updatedAt ? new Date( new Date( oldSource.updatedAt ).getTime() + 3 * 24 * 60 * 60 * 1000 ) : new Date( Date.now() + 3 * 24 * 60 * 60 * 1000 ),
|
|
1796
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1797
|
+
},
|
|
1798
|
+
]:
|
|
1799
|
+
|
|
1800
|
+
[
|
|
1801
|
+
{
|
|
1802
|
+
type: 'tagging',
|
|
1803
|
+
mode: 'web',
|
|
1804
|
+
revicedFootfall,
|
|
1805
|
+
revicedPerc: `${revicedPerc}%`,
|
|
1806
|
+
reviced,
|
|
1807
|
+
count,
|
|
1808
|
+
revisedDetail,
|
|
1809
|
+
status: oldSource.status === 'closed' ? 'Closed' : oldSource.status || 'Closed',
|
|
1810
|
+
createdByEmail: oldSource.email || '',
|
|
1811
|
+
createdByUserName: oldSource.userName || '',
|
|
1812
|
+
createdByRole: oldSource.role || 'user',
|
|
1813
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
type: 'review',
|
|
1817
|
+
mode: 'web',
|
|
1818
|
+
revicedFootfall,
|
|
1819
|
+
revicedPerc: `${revicedPerc}%`,
|
|
1820
|
+
reviced,
|
|
1821
|
+
count,
|
|
1822
|
+
revisedDetail,
|
|
1823
|
+
status: oldSource.status === 'closed' ? 'Closed' : oldSource.status || 'Closed',
|
|
1824
|
+
dueDate: oldSource.createdAt ? new Date( new Date( oldSource.createdAt ).getTime() + 3 * 24 * 60 * 60 * 1000 ) : new Date( Date.now() + 3 * 24 * 60 * 60 * 1000 ),
|
|
1825
|
+
createdAt: oldSource.createdAt || new Date(),
|
|
1826
|
+
createdByEmail: oldSource.approverEmail || '',
|
|
1827
|
+
createdByUserName: oldSource.approverUserName || '',
|
|
1828
|
+
createdByRole: oldSource.approverRole || 'user',
|
|
1829
|
+
},
|
|
1830
|
+
];
|
|
1831
|
+
// Create new structure
|
|
1832
|
+
const newSource = {
|
|
1833
|
+
storeId: oldSource.storeId,
|
|
1834
|
+
type: 'store',
|
|
1835
|
+
dateString: oldSource.dateString,
|
|
1836
|
+
storeName: oldSource.storeName,
|
|
1837
|
+
ticketName: oldSource.ticketName,
|
|
1838
|
+
footfallCount: oldSource.footfallCount,
|
|
1839
|
+
clientId: oldSource.clientId,
|
|
1840
|
+
ticketId: oldSource.ticketId,
|
|
1841
|
+
createdAt: oldSource.createdAt,
|
|
1842
|
+
updatedAt: oldSource.updatedAt,
|
|
1843
|
+
status: oldSource.status === 'open' ? 'Raised' : ( oldSource.status === 'closed' ? 'Closed' : 'Raised' ),
|
|
1844
|
+
comments: oldSource.comments || '',
|
|
1845
|
+
revicedFootfall,
|
|
1846
|
+
revicedPerc: `${revicedPerc}%`,
|
|
1847
|
+
reviced,
|
|
1848
|
+
mappingInfo,
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
// Update document in OpenSearch
|
|
1852
|
+
const updatedData = await updateOpenSearchData( openSearch.footfallDirectory, documentId, { doc: newSource, doc_as_upsert: true } );
|
|
1853
|
+
logger.info( { updatedData } );
|
|
1854
|
+
migratedCount++;
|
|
1855
|
+
|
|
1856
|
+
logger.info( { message: 'Document migrated successfully', newSource, documentId, storeId: oldSource.storeId, dateString: oldSource.dateString } );
|
|
1857
|
+
} catch ( error ) {
|
|
1858
|
+
const errorMsg = `Error migrating document ${hit._id}: ${error.message}`;
|
|
1859
|
+
errors.push( errorMsg );
|
|
1860
|
+
logger.error( { error: error, documentId: hit._id, function: 'vmsDataMigration' } );
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
return res.sendSuccess( {
|
|
1865
|
+
message: `Migration completed. ${migratedCount} document(s) migrated.`,
|
|
1866
|
+
migrated: migratedCount,
|
|
1867
|
+
total: hits.length,
|
|
1868
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
1869
|
+
} );
|
|
1870
|
+
} catch ( error ) {
|
|
1871
|
+
logger.error( { error: error, message: req.query, function: 'vmsDataMigration' } );
|
|
1872
|
+
const err = error.message || 'Internal Server Error';
|
|
1873
|
+
return res.sendError( err, 500 );
|
|
1874
|
+
}
|
|
1875
|
+
}
|