tango-app-api-store-builder 1.0.1-alpha → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/Stranger Things - Tango.xlsx +0 -0
  2. package/data/Bitz_Final_Store_List_28.08.25.xlsx +0 -0
  3. package/data/Coastline 3.0 Tango.xlsx +0 -0
  4. package/data/Fixture capacity.xlsx +0 -0
  5. package/data/JJ_OD New Launch_Tango.xlsx +0 -0
  6. package/data/LKST 98 - Inventory Analysis.xlsx +0 -0
  7. package/data/OE_Vs_NON_OE_UPDATED.xlsx +0 -0
  8. package/data/Sale, Non sale stores.xlsx +0 -0
  9. package/data/Updated IVM New Fixture Flow-v8.xlsx +0 -0
  10. package/data/VM_logic.xlsx +0 -0
  11. package/data/euro_center_stores_tentpole.xls +0 -0
  12. package/data/ivmLogic.json +6058 -0
  13. package/data/logs.json +3 -0
  14. package/data/missing_stores.json +3 -0
  15. package/data/response.json +2119 -0
  16. package/index.js +7 -1
  17. package/package.json +15 -4
  18. package/src/controllers/fixtureTemplate.controller.js +1767 -0
  19. package/src/controllers/managePlano.controller.js +1986 -0
  20. package/src/controllers/planoLibrary.controller.js +1487 -0
  21. package/src/controllers/script.controller.js +14677 -0
  22. package/src/controllers/storeBuilder.controller.js +6920 -27
  23. package/src/controllers/task.controller.js +1149 -0
  24. package/src/dtos/validation.dtos.js +277 -1
  25. package/src/routes/fixtureTemplate.routes.js +30 -0
  26. package/src/routes/managePlano.routes.js +29 -0
  27. package/src/routes/planoLibrary.routes.js +42 -0
  28. package/src/routes/script.routes.js +44 -0
  29. package/src/routes/storeBuilder.routes.js +54 -6
  30. package/src/routes/task.routes.js +20 -0
  31. package/src/service/assignService.service.js +11 -0
  32. package/src/service/checklist.service.js +7 -0
  33. package/src/service/fixtureConfig.service.js +52 -0
  34. package/src/service/fixtureConfigDuplicate.service.js +52 -0
  35. package/src/service/fixtureShelf.service.js +53 -0
  36. package/src/service/fixtureShelfDuplicate.service.js +53 -0
  37. package/src/service/planoCompliance.service.js +33 -0
  38. package/src/service/planoDuplicateModel.service.js +41 -0
  39. package/src/service/planoGlobalComment.service.js +25 -0
  40. package/src/service/planoLibrary.service.js +45 -0
  41. package/src/service/planoLibraryDuplicate.service.js +45 -0
  42. package/src/service/planoMapping.service.js +44 -0
  43. package/src/service/planoMappingDuplicate.service.js +44 -0
  44. package/src/service/planoProduct.service.js +42 -0
  45. package/src/service/planoProductDuplicate.service.js +42 -0
  46. package/src/service/planoQrConversionRequest.service.js +32 -0
  47. package/src/service/planoRevision.service.js +15 -0
  48. package/src/service/planoStaticData.service.js +11 -0
  49. package/src/service/planoTask.service.js +39 -0
  50. package/src/service/planoVm.service.js +49 -0
  51. package/src/service/planoVmDuplicate.service.js +49 -0
  52. package/src/service/planogram.service.js +8 -0
  53. package/src/service/planoproductCategory.service.js +47 -0
  54. package/src/service/processedTaskservice.js +29 -0
  55. package/src/service/processedchecklist.service.js +17 -0
  56. package/src/service/storeBuilder.service.js +20 -0
  57. package/src/service/storeBuilderDuplicate.service.js +53 -0
  58. package/src/service/storeFixture.service.js +83 -0
  59. package/src/service/storeFixtureDuplicate.service.js +69 -0
  60. package/src/service/task.service.js +6 -0
  61. package/src/service/templateLog.service.js +10 -0
  62. package/src/service/user.service.js +14 -0
  63. package/src/service/vmType.service.js +33 -0
@@ -0,0 +1,1487 @@
1
+ import { logger, fileUpload } from 'tango-app-api-middleware';
2
+ import * as planoLibraryService from '../service/planoLibrary.service.js';
3
+ import * as fixtureTemplateService from '../service/fixtureConfig.service.js';
4
+ import * as vmTypeService from '../service/vmType.service.js';
5
+ import * as planoProductService from '../service/planoproductCategory.service.js';
6
+ import * as planoStaticService from '../service/planoStaticData.service.js';
7
+ import * as vmService from '../service/planoVm.service.js';
8
+ import * as storeFixtureService from '../service/storeFixture.service.js';
9
+ import * as planoService from '../service/planogram.service.js';
10
+ import ExcelJS from 'exceljs';
11
+ import mongoose from 'mongoose';
12
+ import dayjs from 'dayjs';
13
+ const ObjectId = mongoose.Types.ObjectId;
14
+ import path from 'path';
15
+
16
+ export async function fixtureBulkUpload( req, res ) {
17
+ try {
18
+ let inputData = req.body;
19
+ let groupedData = inputData.fixtureData.reduce( ( acc, ele ) => {
20
+ if ( !acc[ele.fixtureName] ) {
21
+ acc[ele.fixtureName] = {
22
+ clientId: inputData.clientId,
23
+ fixtureCategory: ele.fixtureName,
24
+ fixtureType: ele.fixtureType,
25
+ fixtureLength: {
26
+ value: ele.height,
27
+ unit: 'ft',
28
+ },
29
+ fixtureWidth: {
30
+ value: ele.width,
31
+ unit: 'ft',
32
+ },
33
+ fixtureLibCode: ele?.fixLibCode || '',
34
+ header: {
35
+ height: {
36
+ value: ele.headerHeight,
37
+ unit: 'ft',
38
+ },
39
+ },
40
+ footer: {
41
+ height: {
42
+ value: ele.footerHeight,
43
+ unit: 'ft',
44
+ },
45
+ },
46
+ shelfConfig: [
47
+ {
48
+ shelfNumber: ele.shelfNumber,
49
+ shelfType: ele.shelfType,
50
+ trayRows: ele.shelfType =='shelf' ? 0 : ele.trayRows,
51
+ productPerShelf: ele.productPerShelf,
52
+ label: ele.shelfName,
53
+ },
54
+ ],
55
+ ...( typeof ele?.isEdit == undefined ) ? { 'status': ele?.fixLibCode ? inputData.updateFixtureStatus : inputData.newFixtureStatus } :{},
56
+ fixtureCapacity: ele.shelfType =='shelf' ? ele.productPerShelf : ele.trayRows * ele.productPerShelf,
57
+ };
58
+ } else {
59
+ acc[ele.fixtureName].fixtureCapacity = acc[ele.fixtureName].fixtureCapacity + ( ele.shelfType =='shelf' ? ele.productPerShelf : ele.trayRows * ele.productPerShelf );
60
+ acc[ele.fixtureName].shelfConfig.push(
61
+ {
62
+ shelfNumber: ele.shelfNumber,
63
+ shelfType: ele.shelfType,
64
+ trayRows: ele.shelfType =='shelf' ? 0 : ele.trayRows,
65
+ productPerShelf: ele.productPerShelf,
66
+ label: ele.shelfName,
67
+ },
68
+ );
69
+ }
70
+ return acc;
71
+ }, {} );
72
+ // let fixtureData = [];
73
+ for ( let ele of Object.keys( groupedData ) ) {
74
+ // if ( groupedData[ele]?.fixtureLibCode && !groupedData[ele]?.status ) {
75
+ // await planoLibraryService.updateOne( { fixtureLibCode: groupedData[ele]?.fixtureLibCode }, groupedData[ele] );
76
+ // } else {
77
+ let FixLibCode = await getMaxFixtureLibCode();
78
+ groupedData[ele].fixtureLibCode = FixLibCode;
79
+ // fixtureData.push( groupedData[ele] );
80
+ await planoLibraryService.upsertOne( { 'fixtureCategory': groupedData[ele].fixtureCategory, 'fixtureWidth.value': groupedData[ele].fixtureWidth.value, 'fixtureWidth.unit': groupedData[ele].fixtureWidth.unit }, groupedData[ele] );
81
+ // }
82
+ }
83
+ // let deleteList = inputData.deleteFixtureList.map( ( ele ) => new ObjectId( ele ) );
84
+ // if ( deleteList.length ) {
85
+ // await planoLibraryService.deleteMany( { _id: { $in: deleteList } } );
86
+ // }
87
+ // await planoLibraryService.insertMany( fixtureData );
88
+ return res.sendSuccess( 'Fixture library created successfully' );
89
+ } catch ( e ) {
90
+ console.log( e );
91
+ logger.error( { functionName: 'fixtureBulkUpload', error: e } );
92
+ return res.sendError( e, 500 );
93
+ }
94
+ }
95
+
96
+ async function getMaxFixtureLibCode() {
97
+ try {
98
+ let getFixtureLibDetails = await planoLibraryService.find( { fixtureLibCode: { $exists: true } }, { fixtureLibCode: 1 } );
99
+ if ( !getFixtureLibDetails.length ) {
100
+ return 'FX01';
101
+ } else {
102
+ let numList = getFixtureLibDetails.map( ( ele ) => ele.fixtureLibCode.substring( 2 ) );
103
+ let missingNum = [];
104
+ for ( let i=1; i<=getFixtureLibDetails.length; i++ ) {
105
+ let numPad = String( i ).padStart( 2, '0' );
106
+ if ( !numList.includes( numPad ) ) {
107
+ missingNum.push( numPad );
108
+ }
109
+ }
110
+ if ( missingNum.length ) {
111
+ return 'FX'+ missingNum[0];
112
+ } else {
113
+ return 'FX'+ String( parseInt( getFixtureLibDetails.length+1 ) ).padStart( 2, '0' );
114
+ }
115
+ }
116
+ } catch ( e ) {
117
+ console.log( e );
118
+ logger.error( { functionName: 'getMaxFixtureLibCode', error: e } );
119
+ return false;
120
+ }
121
+ }
122
+
123
+ export async function createFixture( req, res ) {
124
+ try {
125
+ let FixLibCode = await getMaxFixtureLibCode();
126
+ let data ={
127
+ clientId: req.body.clientId,
128
+ fixtureCategory: req.body.fixtureCategory,
129
+ fixtureType: req.body.fixtureType,
130
+ fixtureLibCode: FixLibCode,
131
+ fixtureStaticLength: {
132
+ 'value': 1524,
133
+ 'unit': 'mm',
134
+ },
135
+ fixtureStaticWidth: {
136
+ 'value': 1220,
137
+ 'unit': 'mm',
138
+ },
139
+ };
140
+ let fixtLibraryDetails = await planoLibraryService.create( data );
141
+ return res.sendSuccess( fixtLibraryDetails );
142
+ } catch ( e ) {
143
+ logger.error( { functionName: 'createFixture', error: e } );
144
+ return res.sendError( e, 500 );
145
+ }
146
+ }
147
+
148
+ export async function updateFixture( req, res ) {
149
+ try {
150
+ let fixtureLibraryDetails = await planoLibraryService.findOne( { _id: req.params.fixtureId } );
151
+ if ( !fixtureLibraryDetails ) {
152
+ return res.sendError( 'No data found', 204 );
153
+ }
154
+ let templateMapped = await fixtureTemplateService.find( { fixtureLibraryId: req.params.fixtureId } );
155
+ if ( templateMapped.length ) {
156
+ return res.sendError( 'Fixture library is mapped with template', 400 );
157
+ }
158
+ let query = [
159
+ {
160
+ $addFields: {
161
+ fixtureCategoryLower: { $toLower: '$fixtureCategory' },
162
+ },
163
+ },
164
+ {
165
+ $match: {
166
+ 'clientId': req.body.clientId,
167
+ 'fixtureCategoryLower': req.body.fixtureCategory.toLowerCase(),
168
+ 'fixtureWidth.value': req.body.fixtureWidth.value,
169
+ '_id': { $ne: new ObjectId( req.params.fixtureId ) },
170
+ },
171
+ },
172
+ ];
173
+ let fixLibDetails = await planoLibraryService.aggregate( query );
174
+ if ( fixLibDetails.length ) {
175
+ return res.sendError( `${req.body.fixtureCategory} is already exists`, 400 );
176
+ }
177
+
178
+ let fixtureData = {
179
+ ...req.body,
180
+ updatedAt: new Date(),
181
+ };
182
+ await planoLibraryService.updateOne( { _id: req.params.fixtureId }, fixtureData );
183
+ return res.sendSuccess( 'Fixture library details updated successfully' );
184
+ } catch ( e ) {
185
+ logger.error( { functionName: 'updateFixture', error: e } );
186
+ return res.sendError( e, 500 );
187
+ }
188
+ }
189
+
190
+ export async function getFixture( req, res ) {
191
+ try {
192
+ if ( !req.query?.fixtureId ) {
193
+ return res.sendError( 'FixtureId is required', 400 );
194
+ }
195
+ let fixtureLibDetails = await planoLibraryService.findOne( { _id: req.query?.fixtureId } );
196
+ if ( !fixtureLibDetails ) {
197
+ return res.sendError( 'No data found', 204 );
198
+ }
199
+ let fixtureTemplateDetails = await fixtureTemplateService.find( { fixtureLibraryId: req.query?.fixtureId } );
200
+ let fixtureDetails = await storeFixtureService.findOne( { fixtureLibraryId: req.query?.fixtureId }, { planoId: 1 } );
201
+ if ( fixtureDetails ) {
202
+ let planoStatus = await planoService.findOne( { planoId: fixtureDetails.planoId }, { status: 1 } );
203
+ if ( planoStatus ) {
204
+ fixtureLibDetails.status = planoStatus.status == 'complete' ? 'active' :'inactive';
205
+ }
206
+ } else {
207
+ fixtureLibDetails.status = fixtureLibDetails.status == 'draft' ? 'draft' : 'inactive';
208
+ }
209
+ fixtureLibDetails = {
210
+ ...fixtureLibDetails.toObject(),
211
+ templateId: fixtureTemplateDetails.length,
212
+ };
213
+ return res.sendSuccess( fixtureLibDetails );
214
+ } catch ( e ) {
215
+ logger.error( { functionName: 'getFixture', error: e } );
216
+ return res.sendError( e, 500 );
217
+ }
218
+ }
219
+
220
+ export async function FixtureLibraryList( req, res ) {
221
+ try {
222
+ let limit = req.body?.limit || 10;
223
+ let page = req.body?.offset || 1;
224
+ let skip = limit * ( page - 1 );
225
+
226
+ const matchStage = {
227
+ clientId: req.body.clientId,
228
+ ...( req.body?.searchValue ?
229
+ { $or: [ { fixtureCategory: { $regex: req.body.searchValue, $options: 'i' } }, { fixtureCategorySize: { $regex: req.body.searchValue, $options: 'i' } } ] } :
230
+ {} ),
231
+ ...( req.body?.filter?.type?.length ?
232
+ { fixtureType: { $in: req.body.filter.type } } :
233
+ {} ),
234
+ ...( req.body?.filter?.size?.length ?
235
+ { 'fixtureWidth.value': { $in: req.body.filter.size } } :
236
+ {} ),
237
+ };
238
+
239
+ const query = [
240
+ {
241
+ $addFields: {
242
+ fixtureCategorySize: {
243
+ $concat: [ '$fixtureCategory', ' - ', { $toString: '$fixtureWidth.value' }, { $toString: '$fixtureWidth.unit' } ],
244
+ },
245
+ },
246
+ },
247
+ { $match: matchStage },
248
+ {
249
+ $lookup: {
250
+ from: 'fixtureconfigs',
251
+ localField: '_id',
252
+ foreignField: 'fixtureLibraryId',
253
+ as: 'fixtureTemplate',
254
+ },
255
+ },
256
+ {
257
+ $set: {
258
+ templateId: {
259
+ $setUnion: [
260
+ { $map: { input: '$fixtureTemplate', as: 'fc', in: '$$fc._id' } },
261
+ [],
262
+ ],
263
+ },
264
+ },
265
+ },
266
+ {
267
+ $lookup: {
268
+ from: 'storefixtures',
269
+ localField: '_id',
270
+ foreignField: 'fixtureLibraryId',
271
+ as: 'storeFixtureDetails',
272
+ },
273
+ },
274
+ {
275
+ $set: {
276
+ planoId: {
277
+ $setUnion: [
278
+ { $map: { input: '$storeFixtureDetails', as: 'sf', in: '$$sf.planoId' } },
279
+ [],
280
+ ],
281
+ },
282
+ },
283
+ },
284
+ {
285
+ $lookup: {
286
+ from: 'planograms',
287
+ let: { planoIds: '$planoId' },
288
+ pipeline: [
289
+ {
290
+ $match: {
291
+ $expr: {
292
+ $in: [ '$_id', { $ifNull: [ '$$planoIds', [] ] } ],
293
+ },
294
+ },
295
+ },
296
+ {
297
+ $group: {
298
+ _id: null,
299
+ statusList: { $push: '$status' },
300
+ },
301
+ },
302
+ ],
303
+ as: 'planoStatus',
304
+ },
305
+ },
306
+ {
307
+ $project: {
308
+ fixtureWidth: 1,
309
+ fixtureHeight: 1,
310
+ fixtureLength: 1,
311
+ fixtureType: 1,
312
+ shelfConfig: 1,
313
+ shelfCount: { $size: '$shelfConfig' },
314
+ fixtureCategory: 1,
315
+ clientId: 1,
316
+ planoId: 1,
317
+ templateId: 1,
318
+ header: 1,
319
+ footer: 1,
320
+ templateCount: { $size: '$templateId' },
321
+ fixtureLibCode: 1,
322
+ planoStatus: { $ifNull: [ { $arrayElemAt: [ '$planoStatus.statusList', 0 ] }, [] ] },
323
+ status: {
324
+ $cond: {
325
+ if: { $and: [ { $in: [ 'completed', { $ifNull: [ { $arrayElemAt: [ '$planoStatus.statusList', 0 ] }, [] ] } ] }, { $gt: [ { $size: '$templateId' }, 0 ] } ] },
326
+ then: 'active',
327
+ else: {
328
+ $cond: {
329
+ if: {
330
+ $eq: [ '$status', 'draft' ],
331
+ },
332
+ then: 'draft',
333
+ else: 'inactive',
334
+ },
335
+ },
336
+ },
337
+ },
338
+ },
339
+ },
340
+ ];
341
+
342
+ if ( req.body.filter.status.length ) {
343
+ query.push( {
344
+ $match: {
345
+ status: { $in: req.body.filter.status },
346
+ },
347
+ } );
348
+ }
349
+
350
+ if ( req.body?.sortColumnName && req.body?.sortBy ) {
351
+ if ( req.body?.sortColumnName == 'shelfConfig' ) {
352
+ req.body.sortColumnName = 'shelfCount';
353
+ }
354
+ query.push( {
355
+ $sort: {
356
+ [req.body.sortColumnName]: req.body.sortBy,
357
+ },
358
+ },
359
+ );
360
+ } else {
361
+ query.push( {
362
+ $sort: { _id: -1 },
363
+ } );
364
+ }
365
+ query.push(
366
+ {
367
+ $facet: {
368
+ ...( !req.body?.export ) ? {
369
+ fixtureData: [ { $skip: skip }, { $limit: limit } ],
370
+ } : { fixtureData: [ { $skip: skip } ] },
371
+ count: [ { $count: 'total' } ],
372
+ },
373
+ },
374
+ );
375
+ let fixtureDetails = await planoLibraryService.aggregate( query );
376
+ if ( !req.body.export && !fixtureDetails[0]?.fixtureData.length ) {
377
+ return res.sendError( 'No data found', 204 );
378
+ }
379
+ let result = {
380
+ count: fixtureDetails[0]?.count?.[0]?.total || 0,
381
+ data: fixtureDetails[0]?.fixtureData || [],
382
+ };
383
+ if ( !req.body.export ) {
384
+ return res.sendSuccess( result );
385
+ } else {
386
+ const workbook = new ExcelJS.Workbook();
387
+ const sheet = workbook.addWorksheet( 'Fixture Library' );
388
+
389
+ sheet.getRow( 1 ).values = [ 'Fixture Name', 'Fixture Type', 'Fixture Height(ft)', 'Fixture Width(ft)', 'Fixture Header Height(ft)', 'Fixture Footer Height(ft)', 'Shelf Number', 'Shelf Type', 'Tray Rows', 'Product Per Shelf/Tray', 'Panel Name' ];
390
+
391
+ let rowStart = 2;
392
+ let lockedRowNumber = [];
393
+
394
+ if ( req.body.emptyDownload ) {
395
+ result.data = [];
396
+ }
397
+ for ( let i=0; i<result.data.length; i++ ) {
398
+ let height = 0;
399
+ let width = 0;
400
+ let headerHeight = 0;
401
+ let footerHeight = 0;
402
+ if ( result.data[i]?.fixtureLength ) {
403
+ height = result.data[i].fixtureLength.value;
404
+ }
405
+ if ( result.data[i]?.fixtureWidth ) {
406
+ width = result.data[i].fixtureWidth.value;
407
+ }
408
+ if ( result.data[i]?.header?.height?.value ) {
409
+ headerHeight = result.data[i].header.height.value;
410
+ }
411
+ if ( result.data[i]?.footer?.height?.value ) {
412
+ footerHeight = result.data[i].footer.height.value;
413
+ }
414
+ if ( !result.data[i].shelfConfig.length ) {
415
+ // result.data[i]?.fixtureLibCode || '',
416
+ sheet.getRow( rowStart ).values = [ result.data[i]?.fixtureCategory || '', result.data[i]?.fixtureType || 'Wall', height, width, headerHeight, footerHeight, 0, '', 0, 0, '' ];
417
+ if ( result.data[i].status == 'active' || result.data[i].templateId.length ) {
418
+ lockedRowNumber.push( rowStart );
419
+ }
420
+ rowStart = rowStart + 1;
421
+ }
422
+
423
+ result.data[i].shelfConfig.forEach( ( shelf ) => {
424
+ sheet.getRow( rowStart ).values = [ result.data[i]?.fixtureCategory || '', result.data[i]?.fixtureType || 'Wall', height, width, headerHeight, footerHeight, shelf?.shelfNumber || 0, shelf?.shelfType || '', shelf?.trayRows || 0, shelf?.productPerShelf || 0, shelf?.label || '' ];
425
+ if ( result.data[i].status == 'active' || result.data[i].templateId.length ) {
426
+ lockedRowNumber.push( rowStart );
427
+ }
428
+ rowStart = rowStart + 1;
429
+ } );
430
+ }
431
+
432
+
433
+ const maxRows = 1048576;
434
+
435
+ // let unlockCellValues = 20000;
436
+ // let splitLoop = [];
437
+
438
+ // for ( let i=1; i<=unlockCellValues; i+=2000 ) {
439
+ // splitLoop.push( { start: i, end: i + 1999 } );
440
+ // }
441
+
442
+ // await Promise.all( splitLoop.map( ( item ) => {
443
+ // for ( let i=item.start; i<=item.end; i++ ) {
444
+ // const row = sheet.getRow( i );
445
+ // if ( i > rowStart - 1 ) {
446
+ // row.values = [ '', '', '', '', '', '', '', '', '', '', '', '' ];
447
+ // }
448
+ // if ( !lockedRowNumber.includes( i ) && i != 1 ) {
449
+ // row.eachCell( ( cell ) => {
450
+ // const columnLetter = cell.address.replace( /[0-9]/g, '' );
451
+ // if ( columnLetter != 'A' ) {
452
+ // cell.protection = { locked: false };
453
+ // }
454
+ // } );
455
+ // }
456
+ // }
457
+ // } ) );
458
+
459
+
460
+ let dropDownRange = [ { key: `B2:B${maxRows}`, optionList: [ '"wall,floor"' ] }, { key: `H2:H${maxRows}`, optionList: [ '"shelf,tray"' ] } ];
461
+
462
+ dropDownRange.forEach( ( ele ) => {
463
+ sheet.dataValidations.add( ele.key, {
464
+ type: 'list',
465
+ allowBlank: true,
466
+ formulae: ele.optionList,
467
+ showErrorMessage: true,
468
+ errorTitle: 'Invalid Choice',
469
+ error: 'Please select from the dropdown list.',
470
+ } );
471
+ } );
472
+
473
+
474
+ // await sheet.protect( 'password123', {
475
+ // selectLockedCells: false,
476
+ // selectUnlockedCells: true,
477
+ // } );
478
+
479
+ sheet.columns.forEach( ( column ) => {
480
+ let maxLength = 10;
481
+ column.eachCell( { includeEmpty: true }, ( cell ) => {
482
+ const cellValue = cell.value ? cell.value.toString() : '';
483
+ if ( cellValue.length > maxLength ) {
484
+ maxLength = cellValue.length;
485
+ }
486
+ } );
487
+ column.width = maxLength + 2;
488
+ } );
489
+ const buffer = await workbook.xlsx.writeBuffer();
490
+ res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' );
491
+ res.setHeader( 'Content-Disposition', 'attachment; filename="Fixture Library.xlsx"' );
492
+ return res.send( buffer );
493
+ }
494
+ } catch ( e ) {
495
+ console.log( e );
496
+ logger.error( { functionName: 'FixtureLibraryList', error: e } );
497
+ return res.sendError( e, 500 );
498
+ }
499
+ }
500
+
501
+ export async function duplicateFixture( req, res ) {
502
+ try {
503
+ if ( !req.body?.fixtureId ) {
504
+ return res.sendError( 'FixtureId is required', 400 );
505
+ }
506
+ let fixtureLibDetails = await planoLibraryService.findOne( { _id: req.body?.fixtureId }, { createdAt: 0, updatedAt: 0 } );
507
+ if ( !fixtureLibDetails ) {
508
+ return res.sendError( 'No data found', 204 );
509
+ }
510
+ fixtureLibDetails = fixtureLibDetails.toObject();
511
+ delete fixtureLibDetails._id;
512
+ let getAllLibraryList = await planoLibraryService.findAndSort( { fixtureCategory: { $regex: fixtureLibDetails.fixtureCategory.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ), $options: 'i' } }, { fixtureCategory: 1 }, { fixtureCategory: -1 } );
513
+ let counter = 1;
514
+ let newFixName = fixtureLibDetails.fixtureCategory + ` (${counter})`;
515
+ if ( getAllLibraryList?.length ) {
516
+ let fixtureNameList = getAllLibraryList.map( ( ele ) => ele.fixtureCategory );
517
+ while ( fixtureNameList.includes( newFixName ) ) {
518
+ newFixName = fixtureLibDetails.fixtureCategory + ` (${counter})`;
519
+ counter++;
520
+ }
521
+ }
522
+ fixtureLibDetails.fixtureCategory = newFixName;
523
+ let FixLibCode = await getMaxFixtureLibCode();
524
+ fixtureLibDetails.fixtureLibCode = FixLibCode;
525
+ fixtureLibDetails.status = 'draft';
526
+ let duplicateData = await planoLibraryService.create( fixtureLibDetails );
527
+ return res.sendSuccess( { message: 'Fixture duplicated successfully', id: duplicateData._id } );
528
+ } catch ( e ) {
529
+ logger.error( { functionName: 'duplicateFixture', error: e } );
530
+ return res.sendError( e, 500 );
531
+ }
532
+ }
533
+
534
+ export async function deleteFixture( req, res ) {
535
+ try {
536
+ if ( !req.body?.fixtureId ) {
537
+ return res.sendError( 'FixtureId is required', 400 );
538
+ }
539
+ let fixtureLibDetails = await planoLibraryService.findOne( { _id: req.body?.fixtureId } );
540
+ if ( !fixtureLibDetails ) {
541
+ return res.sendError( 'No data found', 204 );
542
+ }
543
+ let fixtureTemplate = await fixtureTemplateService.count( { fixtureLibraryId: req.body?.fixtureId } );
544
+ if ( fixtureTemplate ) {
545
+ return res.sendError( `Fixture is mapped with ${fixtureTemplate} templates`, 400 );
546
+ }
547
+ await planoLibraryService.deleteOne( { _id: req.body?.fixtureId } );
548
+ return res.sendSuccess( 'Fixture deleted successfully' );
549
+ } catch ( e ) {
550
+ logger.error( { functionName: 'deleteFixture', error: e } );
551
+ return res.sendError( e, 500 );
552
+ }
553
+ }
554
+
555
+ export async function getFixLibWidth( req, res ) {
556
+ try {
557
+ if ( !req.query?.clientId ) {
558
+ return res.sendError( 'Client id is required', 400 );
559
+ }
560
+ let getLibDetails = await planoLibraryService.find( { clientId: req.query.clientId }, { fixtureWidth: 1 } );
561
+ if ( !getLibDetails ) {
562
+ return res.sendError( 'No data content', 204 );
563
+ }
564
+
565
+ getLibDetails = [ ...new Set( getLibDetails.map( ( item ) => item.fixtureWidth.value ) ) ];
566
+ return res.sendSuccess( getLibDetails );
567
+ } catch ( e ) {
568
+ logger.error( { functionName: 'getFixLibWidth', error: e } );
569
+ return res.sendError( e, 500 );
570
+ }
571
+ }
572
+
573
+ export async function addVmType( req, res ) {
574
+ try {
575
+ let inputData = req.body;
576
+ let vmTypeList = [];
577
+ let error = [];
578
+ inputData.vmData.forEach( ( ele ) => {
579
+ ele.clientId = inputData.clientId;
580
+ if ( vmTypeList.includes( ele.vmType.toLowerCase() ) ) {
581
+ error.push( ele.vmType );
582
+ }
583
+ vmTypeList.push( ele.vmType.toLowerCase() );
584
+ } );
585
+ if ( error.length ) {
586
+ return res.sendError( `${error.toString()} - Vm types are duplicated.`, 400 );
587
+ }
588
+ await vmTypeService.deleteMany( { clientId: inputData.clientId, _id: { $ne: req.body.mappedTypeList } } );
589
+ await vmTypeService.insertMany( inputData.vmData );
590
+ return res.sendSuccess( 'Vm type is created successfully' );
591
+ } catch ( e ) {
592
+ logger.error( { functionName: 'addVmType', error: e } );
593
+ return res.sendError( e, 500 );
594
+ }
595
+ }
596
+
597
+ export async function deleteVmType( req, res ) {
598
+ try {
599
+ if ( !req.body?.vmId ) {
600
+ return res.sendError( 'Vm id is required', 400 );
601
+ }
602
+ let vmtypeDetails = await vmTypeService.findOne( { _id: req.body.vmId } );
603
+ if ( !vmtypeDetails ) {
604
+ return res.sendError( 'No data found', 204 );
605
+ }
606
+ let mappedType = await vmService.findOne( { vmType: vmtypeDetails.vmType } );
607
+ if ( mappedType ) {
608
+ return res.sendError( `${vmtypeDetails.vmType}-vmtype is mapped with Vmlibrary`, 400 );
609
+ }
610
+ await vmTypeService.deleteOne( { _id: req.body.vmId } );
611
+ return res.sendSuccess( 'Vmtype is deleted successfully' );
612
+ } catch ( e ) {
613
+ logger.error( { functionName: 'deleteVmType', error: e } );
614
+ return res.sendError( e, 500 );
615
+ }
616
+ }
617
+
618
+ export async function uploadVmImage( req, res ) {
619
+ try {
620
+ if ( !req.files?.file ) {
621
+ return res.sendError( 'file is required', 400 );
622
+ }
623
+ if ( !req.params?.vmId ) {
624
+ return res.sendError( 'id is required', 400 );
625
+ }
626
+ let params = {
627
+ Bucket: JSON.parse( process.env.BUCKET ).storeBuilder,
628
+ Key: `vmType/${req.params.vmId}/`,
629
+ fileName: Date.now() +'.'+ req.files?.file?.name?.split( '.' )?.slice( -1 )?.[0],
630
+ ContentType: req.files.file.mimeType,
631
+ body: req.files.file.data,
632
+ };
633
+ let fileRes = await fileUpload( params );
634
+ if ( fileRes.Key ) {
635
+ await vmTypeService.updateKeys( { _id: req.params.vmId }, { $push: { imageUrls: fileRes.Key } } );
636
+ return res.sendSuccess( { url: fileRes.Key } );
637
+ }
638
+ } catch ( e ) {
639
+ logger.error( { functionName: 'updateVmType', error: e } );
640
+ return res.sendError( e, 500 );
641
+ }
642
+ }
643
+
644
+ export async function getVmTypeList( req, res ) {
645
+ try {
646
+ let vmDetails = await vmTypeService.find( { ...( req.query.clientId ) ? { clientId: req.query.clientId } : {}, ...( req.query.id ) ? { _id: req.query.id } : {} }, { createdAt: 0, updatedAt: 0 } );
647
+ vmDetails = await Promise.all( vmDetails.map( async ( ele ) => {
648
+ ele= { ...ele.toObject(), isUsed: false };
649
+ let mappedDetails = await vmService.findOne( { vmType: ele.vmType } );
650
+ if ( mappedDetails ) {
651
+ ele['isUsed'] = true;
652
+ }
653
+ return ele;
654
+ } ) );
655
+ return res.sendSuccess( vmDetails );
656
+ } catch ( e ) {
657
+ logger.error( { functionName: 'getVmTypeList', error: e } );
658
+ return res.sendError( e, 500 );
659
+ }
660
+ }
661
+
662
+ export async function deletevmTypeImage( req, res ) {
663
+ try {
664
+ let getVmDetails = await vmTypeService.findOne( { _id: req.body.vmId } );
665
+ if ( !getVmDetails ) {
666
+ return res.sendError( 'No data found', 204 );
667
+ }
668
+ getVmDetails.imageUrls.splice( req.body.index, 1 );
669
+ getVmDetails.save();
670
+ return res.sendSuccess( 'Image removed successfullly' );
671
+ } catch ( e ) {
672
+ logger.error( { functionName: 'getVmTypeList', error: e } );
673
+ return res.sendError( e, 500 );
674
+ }
675
+ }
676
+
677
+ export async function getBrandList( req, res ) {
678
+ try {
679
+ let getBrandDetails = await planoProductService.find( { clientId: req.body.clientId }, { createdAt: 0, updatedAt: 0 } );
680
+
681
+ getBrandDetails = await Promise.all( getBrandDetails.map( async ( ele ) => {
682
+ ele = { ...ele.toObject(), isUsed: false };
683
+ let mappedDetails = await vmService.findOne( { vmBrand: ele.brandName } );
684
+ if ( mappedDetails ) {
685
+ ele.isUsed = true;
686
+ }
687
+ return ele;
688
+ } ) );
689
+ if ( !req?.body?.export ) {
690
+ return res.sendSuccess( getBrandDetails );
691
+ } else {
692
+ const workbook = new ExcelJS.Workbook();
693
+ const sheet = workbook.addWorksheet( 'Brand Details' );
694
+
695
+ sheet.getRow( 1 ).values = [ 'Brand Name', 'Brand Category', 'Brand SubCategory' ];
696
+
697
+ // let rowStart = 2;
698
+ // let lockedRowNumber = [];
699
+ if ( req.body.emptyDownload ) {
700
+ getBrandDetails = [];
701
+ }
702
+ // getBrandDetails.forEach( ( ele ) => {
703
+ // sheet.getRow( rowStart ).values = [ ele.brandName, ele.category.toString(), ele.subCategory.toString() ];
704
+ // if ( ele.isUsed ) {
705
+ // lockedRowNumber.push( rowStart );
706
+ // }
707
+ // rowStart = rowStart + 1;
708
+ // } );
709
+
710
+ // let unlockCellValues = 20000;
711
+ // let splitLoop = [];
712
+
713
+ // for ( let i=1; i<=unlockCellValues; i+=2000 ) {
714
+ // splitLoop.push( { start: i, end: i + 1999 } );
715
+ // }
716
+
717
+ // await Promise.all( splitLoop.map( ( item ) => {
718
+ // for ( let i=item.start; i<=item.end; i++ ) {
719
+ // const row = sheet.getRow( i );
720
+ // if ( i > rowStart - 1 ) {
721
+ // row.values = [ '', '', '', '', '', '', '', '', '', '' ];
722
+ // }
723
+ // if ( !lockedRowNumber.includes( i ) && i != 1 ) {
724
+ // row.eachCell( ( cell ) => {
725
+ // cell.protection = { locked: false };
726
+ // } );
727
+ // }
728
+ // }
729
+ // } ) );
730
+
731
+ // await sheet.protect( 'password123', {
732
+ // selectLockedCells: false,
733
+ // selectUnlockedCells: true,
734
+ // } );
735
+
736
+ sheet.columns.forEach( ( column ) => {
737
+ let maxLength = 10;
738
+ column.eachCell( { includeEmpty: true }, ( cell ) => {
739
+ const cellValue = cell.value ? cell.value.toString() : '';
740
+ if ( cellValue.length > maxLength ) {
741
+ maxLength = cellValue.length;
742
+ }
743
+ } );
744
+ column.width = maxLength + 2;
745
+ } );
746
+ const buffer = await workbook.xlsx.writeBuffer();
747
+ res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' );
748
+ res.setHeader( 'Content-Disposition', 'attachment; filename="Brand Details.xlsx"' );
749
+
750
+ return res.send( buffer );
751
+ }
752
+ } catch ( e ) {
753
+ logger.error( { functionName: 'getBrandList', error: e } );
754
+ return res.sendError( e, 500 );
755
+ }
756
+ }
757
+
758
+ export async function addUpdateBrandList( req, res ) {
759
+ try {
760
+ let inputData = req.body;
761
+ inputData.brandUsedList = inputData.brandUsedList.map( ( ele ) => new ObjectId( ele ) );
762
+ let brandData = [];
763
+ inputData.brandData.forEach( ( ele ) => {
764
+ if ( !ele?.isUsed ) {
765
+ brandData.push( {
766
+ clientId: inputData.clientId,
767
+ brandName: ele.brand,
768
+ category: [ ...new Set( ele?.category?.map( ( ele ) => ele ) ) ],
769
+ subCategory: [ ...new Set( ele?.subCategory?.map( ( ele ) => ele ) ) ],
770
+ } );
771
+ }
772
+ } );
773
+ await planoProductService.deleteMany( { clientId: inputData.clientId, _id: { $nin: inputData.brandUsedList } } );
774
+ await planoProductService.insertMany( brandData );
775
+ return res.sendSuccess( 'Brand Details updated successfully' );
776
+ } catch ( e ) {
777
+ logger.error( { functionName: 'addBrandList', error: e } );
778
+ return res.sendError( e, 500 );
779
+ }
780
+ }
781
+
782
+ export async function uploadBrandList( req, res ) {
783
+ try {
784
+ let inputData = req.body;
785
+
786
+ let brandData = inputData.brandData.reduce( ( acc, ele ) => {
787
+ let category = ele?.['Brand Category'].split( ',' );
788
+ let subCategory = ele?.['Brand SubCategory'].split( ',' );
789
+ if ( !acc[ele['Brand Name']] ) {
790
+ acc[ele['Brand Name']] = {
791
+ brandName: ele['Brand Name'],
792
+ clientId: inputData.clientId,
793
+ category: [ ...new Set( category?.filter( ( ele ) => ele.trim() ).map( ( ele ) => ele.trim() ) ) ],
794
+ subCategory: [ ...new Set( subCategory?.filter( ( ele ) => ele.trim() ).map( ( ele ) => ele.trim() ) ) ],
795
+ };
796
+ } else {
797
+ category.forEach( ( cat ) => {
798
+ if ( !acc[ele['Brand Name']].category.includes( cat ) ) {
799
+ acc[ele['Brand Name']].category.push( cat );
800
+ }
801
+ } );
802
+ if ( subCategory.length ) {
803
+ subCategory.forEach( ( cat ) => {
804
+ if ( !acc[ele['Brand Name']].subCategory.includes( cat ) ) {
805
+ acc[ele['Brand Name']].subCategory.push( cat );
806
+ }
807
+ } );
808
+ }
809
+ }
810
+ return acc;
811
+ }, {} );
812
+ await Promise.all( Object.keys( brandData ).map( async ( brand ) => {
813
+ let brandLower = brand.toLowerCase();
814
+ let query = [
815
+ {
816
+ $addFields: {
817
+ brandLower: { $toLower: '$brandName' },
818
+ },
819
+ },
820
+ {
821
+ $match: {
822
+ brandLower: brandLower,
823
+ clientId: inputData.clientId,
824
+ },
825
+ },
826
+ ];
827
+ let brandDetails = await planoProductService.aggregate( query );
828
+ if ( brandDetails.length ) {
829
+ let getCategory = brandDetails[0]?.category;
830
+ let getsubCategory = brandDetails[0]?.subCategory;
831
+ brandData[brand].category.forEach( ( ele ) => {
832
+ if ( !getCategory.includes( ele ) ) {
833
+ getCategory.push( ele );
834
+ }
835
+ } );
836
+ brandData[brand].subCategory.forEach( ( ele ) => {
837
+ if ( !getsubCategory.includes( ele ) ) {
838
+ getsubCategory.push( ele );
839
+ }
840
+ } );
841
+ await planoProductService.updateOne( { _id: brandDetails[0]._id }, { category: getCategory, subCategory: getsubCategory } );
842
+ } else {
843
+ await planoProductService.create( brandData[brand] );
844
+ }
845
+ } ) );
846
+
847
+ return res.sendSuccess( 'Brand details upload successfully' );
848
+ } catch ( e ) {
849
+ logger.error( { functionName: 'uploadBrandList', error: e } );
850
+ return res.sendError( e, 500 );
851
+ }
852
+ }
853
+
854
+ export async function getTaskConfig( req, res ) {
855
+ try {
856
+ let taskConfigDetails = await planoStaticService.findOne( { clientId: req.query.clientId, type: 'task' }, { updatedAt: 0, createdAt: 0 } );
857
+ if ( !taskConfigDetails ) {
858
+ return res.sendError( 'No data found', 204 );
859
+ }
860
+ taskConfigDetails = { ...taskConfigDetails.toObject() };
861
+ taskConfigDetails.dueTime = dayjs( taskConfigDetails.dueTime, 'hh:mm A' ).format( 'HH:mm' );
862
+ return res.sendSuccess( taskConfigDetails );
863
+ } catch ( e ) {
864
+ logger.error( { functionName: 'getTaskConfig', error: e } );
865
+ return res.sendError( e, 500 );
866
+ }
867
+ }
868
+
869
+ export async function updateTaskConfig( req, res ) {
870
+ try {
871
+ let inputData = req.body;
872
+ inputData.type = 'task';
873
+ inputData.dueTime = dayjs( inputData.dueTime, 'HH:mm' ).format( 'hh:mm A' );
874
+ await planoStaticService.updateOne( { clientId: req.body.clientId, type: 'task' }, inputData );
875
+ return res.sendSuccess( 'Task config updated successfully' );
876
+ } catch ( e ) {
877
+ logger.error( { functionName: 'updateTaskConfig', error: e } );
878
+ return res.sendError( e, 500 );
879
+ }
880
+ }
881
+
882
+ export async function addUpdateVm( req, res ) {
883
+ try {
884
+ let query = [
885
+ {
886
+ $addFields: {
887
+ name: { $toLower: '$vmName' },
888
+ },
889
+ },
890
+ {
891
+ $match: {
892
+ clientId: req.body.clientId,
893
+ name: req.body.vmName.toLowerCase(),
894
+ ...( req.body._id ) ? { _id: { $ne: new ObjectId( req.body._id ) } } : {},
895
+ },
896
+ },
897
+ ];
898
+ let checkVmExists = await vmService.aggregate( query );
899
+ if ( checkVmExists.length ) {
900
+ return res.sendError( 'VmName is already exists', 400 );
901
+ }
902
+ // if ( req.body?.vmImageUrl && ( req.body?.vmImageUrl.startsWith( 'https://' ) || req.body?.vmImageUrl.startsWith( 'http://' ) ) ) {
903
+ // req.body.vmImageUrl = req.body?.vmImageUrl.split( '?' )?.[0]?.split( '/' );
904
+ // req.body.vmImageUrl.splice( 0, 3 );
905
+ // req.body.vmImageUrl = decodeURIComponent( req.body.vmImageUrl.join( '/' ) );
906
+ // }
907
+ if ( !req.body?._id ) {
908
+ req.body.vmLibCode = await getMaxVMLibCode();
909
+ }
910
+ let vmResponse =await vmService.updateOne( { clientId: req.body.clientId, ...( req.body._id ) ? { _id: req.body._id } : { vmName: req.body.vmName } }, req.body );
911
+ let message = req.body?._id ? 'updated' : 'added';
912
+ return res.sendSuccess( { message: `Vm data ${message} successfully`, ...( vmResponse?.upsertedId ) ? { id: vmResponse.upsertedId } : {} } );
913
+ } catch ( e ) {
914
+ logger.error( { functionName: 'addVm', error: e } );
915
+ return res.sendError( e, 500 );
916
+ }
917
+ }
918
+
919
+ export async function getVmLibList( req, res ) {
920
+ try {
921
+ let limit = req.body?.limit || 10;
922
+ let page = req.body?.offset || 1;
923
+ let skip = limit * ( page - 1 );
924
+
925
+ const matchStage = {
926
+ clientId: req.body.clientId,
927
+ ...( req.body?.searchValue ?
928
+ { vmName: { $regex: req.body.searchValue, $options: 'i' } } :
929
+ {} ),
930
+ ...( req.body?.filter?.type?.length ?
931
+ { vmType: { $in: req.body.filter.type } } :
932
+ {} ),
933
+ ...( req.body?.filter?.brand?.length ?
934
+ { vmBrand: { $in: req.body.filter.brand } } :
935
+ {} ),
936
+ ...( req.body?.filter?.category?.length ?
937
+ { vmCategory: { $in: req.body.filter.category } } :
938
+ {} ),
939
+ };
940
+
941
+ const query = [
942
+ { $match: matchStage },
943
+ {
944
+ $lookup: {
945
+ from: 'fixtureconfigs',
946
+ let: { libraryId: '$_id' },
947
+ pipeline: [
948
+ {
949
+ $match: {
950
+ $expr: {
951
+ $in: [ '$$libraryId', { $ifNull: [ '$vmConfig.vmId', [] ] } ],
952
+ },
953
+ },
954
+ },
955
+ {
956
+ $group: {
957
+ _id: null,
958
+ templateIds: { $addToSet: '$_id' },
959
+ },
960
+ },
961
+ ],
962
+ as: 'fixtureTemplate',
963
+ },
964
+ },
965
+ {
966
+ $set: {
967
+ templateId: { $ifNull: [ { $arrayElemAt: [ '$fixtureTemplate.templateIds', 0 ] }, [] ] },
968
+ },
969
+ },
970
+ {
971
+ $lookup: {
972
+ from: 'storefixtures',
973
+ localField: 'templateId',
974
+ foreignField: 'fixtureConfigId',
975
+ as: 'storeFixtureDetails',
976
+ },
977
+ },
978
+ {
979
+ $set: {
980
+ planoId: {
981
+ $setUnion: [
982
+ { $map: { input: '$storeFixtureDetails', as: 'sf', in: '$$sf.planoId' } },
983
+ [],
984
+ ],
985
+ },
986
+ templateCount: { $size: '$templateId' },
987
+ },
988
+ },
989
+ {
990
+ $lookup: {
991
+ from: 'planograms',
992
+ let: { planoIds: '$planoId' },
993
+ pipeline: [
994
+ {
995
+ $match: {
996
+ $expr: {
997
+ $in: [ '$_id', { $ifNull: [ '$$planoIds', [] ] } ],
998
+ },
999
+ },
1000
+ },
1001
+ {
1002
+ $group: {
1003
+ _id: null,
1004
+ statusList: { $push: '$status' },
1005
+ },
1006
+ },
1007
+ ],
1008
+ as: 'planoStatus',
1009
+ },
1010
+ },
1011
+ {
1012
+ $project: {
1013
+ vmName: 1,
1014
+ vmType: 1,
1015
+ vmBrand: 1,
1016
+ vmCategory: 1,
1017
+ clientId: 1,
1018
+ vmHeight: 1,
1019
+ status: 1,
1020
+ vmWidth: 1,
1021
+ vmImageUrl: 1,
1022
+ isDoubleSided: 1,
1023
+ templateId: 1,
1024
+ planoId: 1,
1025
+ vmLibCode: 1,
1026
+ vmSubCategory: 1,
1027
+ planoStatus: { $ifNull: [ { $arrayElemAt: [ '$planoStatus.statusList', 0 ] }, [] ] },
1028
+ templateCount: 1,
1029
+ status: {
1030
+ $cond: {
1031
+ if: { $and: [ { $in: [ 'completed', { $ifNull: [ { $arrayElemAt: [ '$planoStatus.statusList', 0 ] }, [] ] } ] }, { $gt: [ { $size: '$templateId' }, 0 ] } ] },
1032
+ then: 'active',
1033
+ else: {
1034
+ $cond: {
1035
+ if: {
1036
+ $eq: [
1037
+ '$status', 'draft',
1038
+ ],
1039
+ },
1040
+ then: 'draft',
1041
+ else: 'inactive',
1042
+ },
1043
+ },
1044
+ },
1045
+ },
1046
+ },
1047
+ },
1048
+ ];
1049
+
1050
+ if ( req.body.filter.status.length ) {
1051
+ query.push( {
1052
+ $match: {
1053
+ status: { $in: req.body.filter.status },
1054
+ },
1055
+ } );
1056
+ }
1057
+
1058
+ if ( req.body?.sortColumnName && req.body?.sortBy ) {
1059
+ query.push( {
1060
+ $sort: {
1061
+ [req.body.sortColumnName]: req.body.sortBy,
1062
+ },
1063
+ },
1064
+ );
1065
+ } else {
1066
+ query.push( {
1067
+ $sort: { _id: -1 },
1068
+ } );
1069
+ }
1070
+ query.push(
1071
+ {
1072
+ $facet: {
1073
+ ...( !req.body?.export ) ? {
1074
+ fixtureData: [ { $skip: skip }, { $limit: limit } ],
1075
+ } : { fixtureData: [ { $skip: skip } ] },
1076
+ count: [ { $count: 'total' } ],
1077
+ },
1078
+ },
1079
+ );
1080
+ let fixtureDetails = await vmService.aggregate( query );
1081
+ if ( !req.body.export && !fixtureDetails[0]?.fixtureData.length ) {
1082
+ return res.sendError( 'No data found', 204 );
1083
+ }
1084
+ let result = {
1085
+ count: fixtureDetails[0].count[0].total,
1086
+ data: fixtureDetails[0].fixtureData,
1087
+ };
1088
+ if ( !req.body.export ) {
1089
+ return res.sendSuccess( result );
1090
+ } else {
1091
+ const workbook = new ExcelJS.Workbook();
1092
+ const sheet = workbook.addWorksheet( 'Fixture Library' );
1093
+ const hiddenSheet = workbook.addWorksheet( 'Hidden' );
1094
+ hiddenSheet.state = 'veryHidden';
1095
+
1096
+ // 'VM Lib Code',
1097
+
1098
+ sheet.getRow( 1 ).values = [ 'VM Name', 'VM Type', 'VM Brand', 'VM Category', 'VM SubCategory', 'Unit', 'VM Height', 'VM Width', 'VM ImageUrl' ];
1099
+
1100
+ let rowStart = 2;
1101
+ let lockedRowNumber = [];
1102
+ if ( req.body.emptyDownload ) {
1103
+ result.data = [];
1104
+ }
1105
+ for ( let i=0; i<result.data.length; i++ ) {
1106
+ let height = 0;
1107
+ let width = 0;
1108
+ let unit = 'mm';
1109
+ if ( result.data[i]?.vmHeight ) {
1110
+ height = result.data[i].vmHeight.value;
1111
+ unit = result.data[i].vmHeight.unit;
1112
+ }
1113
+ if ( result.data[i]?.vmWidth ) {
1114
+ width = result.data[i].vmWidth.value;
1115
+ unit = result.data[i].vmWidth.unit;
1116
+ }
1117
+ if ( result.data[i]?.vmImageUrl ) {
1118
+ result.data[i].vmImageUrl = process.env.PLANOCDNURL +'/'+result.data[i].vmImageUrl;
1119
+ }
1120
+ // result.data[i]?.vmLibCode || '',
1121
+ sheet.getRow( rowStart ).values = [ result.data[i]?.vmName || '', result.data[i]?.vmType || '', result.data[i]?.vmBrand || '', result.data[i]?.vmCategory || '', result.data[i]?.vmSubCategory ||'', unit, height, width, result.data[i]?.vmImageUrl || '' ];
1122
+ if ( result.data[i].templateId.length || result.data[i].status == 'active' ) {
1123
+ lockedRowNumber.push( rowStart );
1124
+ }
1125
+ rowStart = rowStart + 1;
1126
+ }
1127
+
1128
+ let productBrandDetails = await planoProductService.find( { clientId: req.body.clientId } );
1129
+ let vmTypeList = await vmTypeService.find( { clientId: req.body.clientId } );
1130
+ vmTypeList = vmTypeList.map( ( ele ) => ele.vmType );
1131
+ let brandList = productBrandDetails.map( ( ele ) => ele.brandName );
1132
+ brandList.forEach( ( brand, index ) => {
1133
+ hiddenSheet.getCell( `A${index + 1}` ).value = brand;
1134
+ } );
1135
+ let brandCategories = productBrandDetails.flatMap( ( ele ) => [ ...ele.category ] );
1136
+ let brandSubCategories = productBrandDetails.flatMap( ( ele ) => [ ...ele.subCategory ] );
1137
+ brandCategories = [ ...new Set( brandCategories.map( ( ele ) => ele ) ) ];
1138
+ brandCategories = brandCategories.length ? brandCategories : [ '' ];
1139
+ brandSubCategories = brandSubCategories.length ? brandSubCategories : [ '' ];
1140
+ brandCategories.forEach( ( brand, index ) => {
1141
+ hiddenSheet.getCell( `B${index + 1}` ).value = brand;
1142
+ } );
1143
+ brandSubCategories = [ ...new Set( brandSubCategories.map( ( ele ) => ele ) ) ];
1144
+ brandSubCategories.forEach( ( brand, index ) => {
1145
+ hiddenSheet.getCell( `C${index + 1}` ).value = brand;
1146
+ } );
1147
+
1148
+ const maxRows = 1048576;
1149
+
1150
+ let dropDownRange = [ { key: `B2:B${maxRows}`, optionList: [ `"${vmTypeList.toString()}"` ] }, { key: `C2:C${maxRows}`, optionList: [ `=Hidden!$A$1:$A$${brandList.length}` ] }, { key: `D2:D${maxRows}`, optionList: [ `=Hidden!$B$1:$B$${brandCategories.length}` ] }, { key: `E2:E${maxRows}`, optionList: [ `=Hidden!$C$1:$C$${brandSubCategories.length}` ] }, { key: `F2:F${maxRows}`, optionList: [ '"mm,cm,inches,feet"' ] } ];
1151
+
1152
+
1153
+ dropDownRange.forEach( ( ele ) => {
1154
+ sheet.dataValidations.add( ele.key, {
1155
+ type: 'list',
1156
+ allowBlank: true,
1157
+ formulae: ele.optionList,
1158
+ showErrorMessage: true,
1159
+ errorTitle: 'Invalid Choice',
1160
+ error: 'Please select from the dropdown list.',
1161
+ } );
1162
+ } );
1163
+
1164
+ // let unlockCellValues = 20000;
1165
+ // let splitLoop = [];
1166
+
1167
+ // for ( let i=1; i<=unlockCellValues; i+=2000 ) {
1168
+ // splitLoop.push( { start: i, end: i + 1999 } );
1169
+ // }
1170
+
1171
+ // await Promise.all( splitLoop.map( ( item ) => {
1172
+ // for ( let i=item.start; i<=item.end; i++ ) {
1173
+ // const row = sheet.getRow( i );
1174
+ // if ( i > rowStart - 1 ) {
1175
+ // row.values = [ '', '', '', '', '', '', '', '', '', '' ];
1176
+ // }
1177
+ // if ( !lockedRowNumber.includes( i ) && i != 1 ) {
1178
+ // row.eachCell( ( cell ) => {
1179
+ // const columnLetter = cell.address.replace( /[0-9]/g, '' );
1180
+ // if ( columnLetter != 'A' ) {
1181
+ // cell.protection = { locked: false };
1182
+ // }
1183
+ // } );
1184
+ // }
1185
+ // }
1186
+ // } ) );
1187
+
1188
+ // await sheet.protect( 'password123', {
1189
+ // selectLockedCells: false,
1190
+ // selectUnlockedCells: true,
1191
+ // } );
1192
+
1193
+ sheet.columns.forEach( ( column ) => {
1194
+ let maxLength = 10;
1195
+ column.eachCell( { includeEmpty: true }, ( cell ) => {
1196
+ const cellValue = cell.value ? cell.value.toString() : '';
1197
+ if ( cellValue.length > maxLength ) {
1198
+ maxLength = cellValue.length;
1199
+ }
1200
+ } );
1201
+ column.width = maxLength + 2;
1202
+ } );
1203
+ const buffer = await workbook.xlsx.writeBuffer();
1204
+ res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' );
1205
+ res.setHeader( 'Content-Disposition', 'attachment; filename="VM Library.xlsx"' );
1206
+ return res.send( buffer );
1207
+ }
1208
+ } catch ( e ) {
1209
+ console.log( e );
1210
+ logger.error( { functionName: 'getVmList', error: e } );
1211
+ return res.sendError( e, 500 );
1212
+ }
1213
+ }
1214
+
1215
+ export async function duplicateVmLib( req, res ) {
1216
+ try {
1217
+ if ( !req.body?.vmId ) {
1218
+ return res.sendError( 'VmId is required', 400 );
1219
+ }
1220
+ let vmDetails = await vmService.findOne( { _id: req.body?.vmId }, { createdAt: 0, updatedAt: 0 } );
1221
+ if ( !vmDetails ) {
1222
+ return res.sendError( 'No data found', 204 );
1223
+ }
1224
+ vmDetails = vmDetails.toObject();
1225
+ delete vmDetails._id;
1226
+ let getAllLibraryList = await vmService.findAndSort( { vmName: { $regex: vmDetails.vmName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ), $options: 'i' } }, { vmName: 1 }, { vmName: -1 } );
1227
+ let counter = 1;
1228
+ let newVMName = vmDetails.vmName + ` (${counter})`;
1229
+ if ( getAllLibraryList?.length ) {
1230
+ let vmNameList = getAllLibraryList.map( ( ele ) => ele.vmName );
1231
+ while ( vmNameList.includes( newVMName ) ) {
1232
+ newVMName = vmDetails.vmName + ` (${counter})`;
1233
+ counter++;
1234
+ }
1235
+ }
1236
+ vmDetails.vmName = newVMName;
1237
+ vmDetails.status = 'draft';
1238
+ vmDetails.vmLibCode = await getMaxVMLibCode();
1239
+ let duplicateData = await vmService.create( vmDetails );
1240
+ return res.sendSuccess( { message: 'VM duplicated successfully', id: duplicateData._id } );
1241
+ } catch ( e ) {
1242
+ logger.error( { functionName: 'duplicateVMLib', error: e } );
1243
+ return res.sendError( e, 500 );
1244
+ }
1245
+ }
1246
+
1247
+ export async function deleteVmLibrary( req, res ) {
1248
+ try {
1249
+ if ( !req.body?.vmId ) {
1250
+ return res.sendError( 'vmId is required', 400 );
1251
+ }
1252
+ let vmDetails = await vmService.findOne( { _id: req.body?.vmId } );
1253
+ if ( !vmDetails ) {
1254
+ return res.sendError( 'No data found', 204 );
1255
+ }
1256
+ let vmTemplate = await fixtureTemplateService.count( { 'vmConfig.vmId': { $in: req.body.vmId } } );
1257
+ if ( vmTemplate ) {
1258
+ return res.sendError( `VM is mapped with ${vmTemplate} templates`, 400 );
1259
+ }
1260
+ await vmService.deleteOne( { _id: req.body?.vmId } );
1261
+ return res.sendSuccess( 'VM deleted successfully' );
1262
+ } catch ( e ) {
1263
+ logger.error( { functionName: 'deleteVmLibrary', error: e } );
1264
+ return res.sendError( e, 500 );
1265
+ }
1266
+ }
1267
+
1268
+ export async function getVmDetails( req, res ) {
1269
+ try {
1270
+ if ( !req.query?.vmId ) {
1271
+ return res.sendError( 'Vm id is required', 400 );
1272
+ }
1273
+ let getVmDetails = await vmService.findOne( { _id: req.query.vmId } );
1274
+ if ( !getVmDetails ) {
1275
+ return res.sendError( 'No data found', 204 );
1276
+ }
1277
+ getVmDetails = getVmDetails.toObject();
1278
+ getVmDetails.templateCount = 0;
1279
+ let templateDetails = await fixtureTemplateService.find( { 'vmConfig.vmId': { $in: req.query.vmId } } );
1280
+ if ( getVmDetails.status != 'draft' ) {
1281
+ getVmDetails.status = 'inactive';
1282
+ if ( templateDetails.length ) {
1283
+ getVmDetails.templateCount = templateDetails.length;
1284
+ let fixtureDetails = await storeFixtureService.find( { 'vmConfig.vmId': { $in: req.query.vmId } }, { planoId: 1 } );
1285
+ if ( fixtureDetails.length ) {
1286
+ let planoList = fixtureDetails.map( ( ele ) => ele.planoId );
1287
+ let planoDetails = await planoService.find( { _id: { $in: planoList } }, { status: 1 } );
1288
+ planoDetails = planoDetails.map( ( ele ) => ele.status );
1289
+ if ( planoDetails.includes( 'completed' ) ) {
1290
+ getVmDetails.status = 'active';
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1295
+ return res.sendSuccess( getVmDetails );
1296
+ } catch ( e ) {
1297
+ logger.error( { functionName: 'getVmDetails', error: e } );
1298
+ return res.sendError( e, 500 );
1299
+ }
1300
+ }
1301
+
1302
+ export async function vmBulkUpload( req, res ) {
1303
+ try {
1304
+ let inputData = req.body;
1305
+
1306
+ for ( let ele of inputData.vmData ) {
1307
+ ele = { ...ele, clientId: req.body.clientId, vmWidth: { value: ele.vmWidth, unit: ele.unit }, vmHeight: { value: ele.vmHeight, unit: ele.unit } };
1308
+ ele.vmLibCode = await getMaxVMLibCode();
1309
+ ele.status = inputData.newVmStatus;
1310
+ if ( vmLibData && !ele?.vmImageUrl.includes( '/vmType/' ) && ele?.vmImageUrl ) {
1311
+ let response = await fetch( ele?.vmImageUrl );
1312
+ let arrayBuffer = await response.arrayBuffer();
1313
+ let params = {
1314
+ Bucket: JSON.parse( process.env.BUCKET ).storeBuilder,
1315
+ Key: `vmType/${vmLibData._id}/${Date.now()}/${path.basename( ele?.vmImageUrl )}`,
1316
+ fileName: path.basename( ele?.vmImageUrl ),
1317
+ ContentType: response.headers.get( 'content-type' ),
1318
+ body: Buffer.from( arrayBuffer ),
1319
+ };
1320
+ let fileRes = await fileUpload( params );
1321
+ ele.vmImageUrl = fileRes?.Key;
1322
+ }
1323
+ vmLibData = await vmService.updateOne( { vmName: ele?.vmName, clientId: inputData.clientId }, ele );
1324
+ }
1325
+ // await Promise.all( inputData.vmData.map( async ( ele ) => {
1326
+ // ele = { ...ele, clientId: req.body.clientId, vmWidth: { value: ele.vmWidth, unit: ele.unit }, vmHeight: { value: ele.vmHeight, unit: ele.unit } };
1327
+ // let vmLibData;
1328
+ // if ( !ele?.vmLibCode ) {
1329
+ // ele.vmLibCode = await getMaxVMLibCode();
1330
+ // ele.status = inputData.newVmStatus;
1331
+ // vmLibData = await vmService.create( ele );
1332
+ // } else {
1333
+ // if ( typeof ele?.isEdit == undefined ) {
1334
+ // ele.status = inputData.updateVmStatus;
1335
+ // await vmService.updateOne( { vmLibCode: ele.vmLibCode }, ele );
1336
+ // vmLibData = await vmService.findOne( { vmLibCode: ele.vmLibCode } );
1337
+ // }
1338
+ // }
1339
+ // if ( vmLibData && !ele?.vmImageUrl.includes( '/vmType/' ) && ele?.vmImageUrl ) {
1340
+ // let response = await fetch( ele?.vmImageUrl );
1341
+ // let arrayBuffer = await response.arrayBuffer();
1342
+ // let params = {
1343
+ // Bucket: JSON.parse( process.env.BUCKET ).storeBuilder,
1344
+ // Key: `vmType/${vmLibData._id}/${Date.now()}/${path.basename( ele?.vmImageUrl )}`,
1345
+ // fileName: path.basename( ele?.vmImageUrl ),
1346
+ // ContentType: response.headers.get( 'content-type' ),
1347
+ // body: Buffer.from( arrayBuffer ),
1348
+ // };
1349
+ // let fileRes = await fileUpload( params );
1350
+ // ele.vmImageUrl = fileRes?.Key;
1351
+ // await vmService.updateOne( { _id: vmLibData._id }, { vmImageUrl: ele.vmImageUrl } );
1352
+ // }
1353
+ // } ) );
1354
+ // let deleteList = inputData.deleteVmList.map( ( ele ) => new ObjectId( ele ) );
1355
+ // if ( deleteList.length ) {
1356
+ // await vmService.deleteMany( { _id: { $in: deleteList } } );
1357
+ // }
1358
+ return res.sendSuccess( 'Vmlibrary details uploaded successfully' );
1359
+ } catch ( e ) {
1360
+ console.log( e );
1361
+ logger.error( { functionName: 'vmBulkUpload', error: e } );
1362
+ return res.sendError( e, 500 );
1363
+ }
1364
+ }
1365
+
1366
+ async function getMaxVMLibCode() {
1367
+ try {
1368
+ let getVMLibDetails = await vmService.find( { vmLibCode: { $exists: true } }, { vmLibCode: 1 } );
1369
+ if ( !getVMLibDetails.length ) {
1370
+ return 'VM01';
1371
+ } else {
1372
+ let numList = getVMLibDetails.map( ( ele ) => ele.vmLibCode.substring( 2 ) );
1373
+ let missingNum = [];
1374
+ for ( let i=1; i<=getVMLibDetails.length; i++ ) {
1375
+ let numPad = String( i ).padStart( 2, '0' );
1376
+ if ( !numList.includes( numPad ) ) {
1377
+ missingNum.push( numPad );
1378
+ }
1379
+ }
1380
+ if ( missingNum.length ) {
1381
+ return 'VM'+ missingNum[0];
1382
+ } else {
1383
+ return 'VM'+ String( parseInt( getVMLibDetails.length+1 ) ).padStart( 2, '0' );
1384
+ }
1385
+ }
1386
+ } catch ( e ) {
1387
+ console.log( e );
1388
+ logger.error( { functionName: 'getMaxVMLibCode', error: e } );
1389
+ return false;
1390
+ }
1391
+ }
1392
+
1393
+ export async function fixtureNameList( req, res ) {
1394
+ try {
1395
+ // planoLibraryService
1396
+ let getFixtureDetails = await fixtureTemplateService.aggregate( [
1397
+ {
1398
+ $match: {
1399
+ clientId: req.query.clientId,
1400
+ templateType: 'master',
1401
+ },
1402
+ },
1403
+ {
1404
+ $group: {
1405
+ _id: '',
1406
+ fixtureDetails: {
1407
+ $push: { fixtureName: {
1408
+ $concat: [
1409
+ '$fixtureCategory',
1410
+ ' - ',
1411
+ { $toString: '$fixtureWidth.value' },
1412
+ '$fixtureWidth.unit',
1413
+ ] }, id: '$_id',
1414
+ },
1415
+ },
1416
+ },
1417
+ },
1418
+ {
1419
+ $project: {
1420
+ _id: 1,
1421
+ fixtureDetails: 1,
1422
+ },
1423
+ },
1424
+ ] );
1425
+
1426
+ let result = getFixtureDetails?.[0]?.fixtureDetails.reduce( ( acc, ele ) => {
1427
+ if ( !acc[ele.fixtureName] ) {
1428
+ acc[ele.fixtureName] = ele;
1429
+ }
1430
+ return acc;
1431
+ }, {} );
1432
+
1433
+ return res.sendSuccess( result ? Object.values( result ) : [] );
1434
+ } catch ( e ) {
1435
+ logger.error( { functionName: 'fixtureNameList', error: e } );
1436
+ return res.sendError( e, 500 );
1437
+ }
1438
+ }
1439
+
1440
+ export async function fixtureLibNameList( req, res ) {
1441
+ try {
1442
+ let getFixtureDetails = await planoLibraryService.aggregate( [
1443
+ {
1444
+ $match: {
1445
+ clientId: req.query.clientId,
1446
+ status: 'complete',
1447
+ },
1448
+ },
1449
+ {
1450
+ $group: {
1451
+ _id: '',
1452
+ fixtureDetails: {
1453
+ $push: { fixtureName: {
1454
+ $concat: [
1455
+ '$fixtureCategory',
1456
+ ' - ',
1457
+ { $toString: '$fixtureWidth.value' },
1458
+ '$fixtureWidth.unit',
1459
+ ] }, id: '$_id',
1460
+ },
1461
+ },
1462
+ },
1463
+ },
1464
+ {
1465
+ $project: {
1466
+ _id: 1,
1467
+ fixtureDetails: 1,
1468
+ },
1469
+ },
1470
+ ] );
1471
+
1472
+ return res.sendSuccess( getFixtureDetails?.[0]?.fixtureDetails || [] );
1473
+ } catch ( e ) {
1474
+ logger.error( { functionName: 'fixtureNameList', error: e } );
1475
+ return res.sendError( e, 500 );
1476
+ }
1477
+ }
1478
+
1479
+ export async function vmNameList( req, res ) {
1480
+ try {
1481
+ let getVmDetails = await vmService.find( { clientId: req.query.clientId, status: 'complete' }, { vmName: 1, vmWidth: 1, vmHeight: 1, vmImageUrl: 1, vmType: 1 } );
1482
+ return res.sendSuccess( getVmDetails );
1483
+ } catch ( e ) {
1484
+ logger.error( { functionName: 'vmNameList', error: e } );
1485
+ return res.sendError( e, 500 );
1486
+ }
1487
+ }