tango-app-api-store-builder 1.0.31 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-store-builder",
3
- "version": "1.0.31",
3
+ "version": "1.0.32",
4
4
  "description": "storeBuilder",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -32,7 +32,7 @@
32
32
  "path": "^0.12.7",
33
33
  "selenium-webdriver": "^4.31.0",
34
34
  "sharp": "^0.34.1",
35
- "tango-api-schema": "^2.5.17",
35
+ "tango-api-schema": "^2.5.23",
36
36
  "tango-app-api-middleware": "3.1.48",
37
37
  "url": "^0.11.4",
38
38
  "winston": "^3.17.0",
@@ -1,6 +1,6 @@
1
1
  import * as floorService from '../service/storeBuilder.service.js';
2
- import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData, getOpenSearchById } from 'tango-app-api-middleware';
3
- // import * as storeService from '../service/store.service.js';
2
+ import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData, getOpenSearchById, fileUpload } from 'tango-app-api-middleware';
3
+ import * as storeService from '../service/store.service.js';
4
4
  import * as planoService from '../service/planogram.service.js';
5
5
  import * as storeFixtureService from '../service/storeFixture.service.js';
6
6
  import * as fixtureShelfService from '../service/fixtureShelf.service.js';
@@ -1372,7 +1372,7 @@ export async function updateStoreFixture( req, res ) {
1372
1372
  };
1373
1373
  vmDetails = await planoVmService.create( planovmData );
1374
1374
  }
1375
- vmConfig.push( { ...vm, vmId: vmDetails._id, vmName: vmDetails.vmName, vmImage: vmDetails.vmImageUrl } );
1375
+ vmConfig.push({ ...vm, vmId: vmDetails._id, vmName: vmDetails.vmName, vmImage: vmDetails.vmImageUrl });
1376
1376
  }
1377
1377
  data.vmConfig = vmConfig;
1378
1378
  let groupName = {
@@ -2363,3 +2363,261 @@ export async function getPlanoStoreVideoPageParams( req, res ) {
2363
2363
  res.sendError( 'Failed to fetch plano store video page params', 500 );
2364
2364
  }
2365
2365
  }
2366
+
2367
+ export async function createPlanoFromCAD(req, res) {
2368
+ try {
2369
+ const user = req.user;
2370
+ const file = req.files.file;
2371
+ const { storeId, floorNumber } = req.body;
2372
+
2373
+ if (!req?.files?.file) return res.sendError('File is required', 400);
2374
+
2375
+ if (!storeId || !floorNumber) return res.sendError('storeId and floorNumber are required', 400);
2376
+
2377
+ if (!file.name.endsWith('.dxf')) return res.sendError('Only DXF files are allowed', 400);
2378
+
2379
+
2380
+ // if (file.size > 20 * 1024 * 1024) {
2381
+ // return res.sendError('File size exceeds 20MB limit', 400);
2382
+ // }
2383
+
2384
+ // File Upload
2385
+ const fileUploadPayload = {
2386
+ Bucket: JSON.parse(process.env.BUCKET).storeBuilder,
2387
+ Key: `planoCad/`,
2388
+ fileName: Date.now() + '.' + file.name?.split('.')?.slice(-1)?.[0],
2389
+ ContentType: file.mimetype,
2390
+ body: file.data,
2391
+ };
2392
+
2393
+ const fileRes = await fileUpload(fileUploadPayload);
2394
+
2395
+ if (!fileRes?.Key) return res.sendError('Failed to upload file to storage', 502);
2396
+
2397
+ // CAD Extraction
2398
+ const dataExtractPayload = {
2399
+ s3Bucket: fileUploadPayload.Bucket,
2400
+ filePath: fileRes.Key,
2401
+ };
2402
+
2403
+ const cadRes = await fetch(
2404
+ JSON.parse(process.env.LAMBDAURL).extractCADDetails,
2405
+ {
2406
+ method: 'POST',
2407
+ headers: { 'Content-Type': 'application/json' },
2408
+ body: JSON.stringify(dataExtractPayload),
2409
+ }
2410
+ );
2411
+
2412
+ if (!cadRes.ok) {
2413
+ const errorText = await cadRes.text();
2414
+ logger.error({ cadLambdaError: errorText });
2415
+ return res.sendError('CAD extraction service failed', 502);
2416
+ }
2417
+
2418
+ const cadData = await cadRes.json();
2419
+
2420
+ if (
2421
+ cadData?.status !== 'success' ||
2422
+ !cadData?.data?.layoutPolygon
2423
+ ) {
2424
+ return res.sendError('Invalid CAD layout data', 502);
2425
+ }
2426
+
2427
+ const userMeta = {
2428
+ createdBy: user._id,
2429
+ createdByEmail: user.email,
2430
+ createdByName: user.userName,
2431
+ }
2432
+
2433
+ // PLANOGRAM
2434
+ let planoData = await planoService.findOne({ storeId }, { _id: 1, clientId: 1, storeName: 1, });
2435
+
2436
+ if (!planoData) {
2437
+ const storeData = await storeService.findOne({ storeId }, { clientId: 1, storeName: 1 });
2438
+ if (!storeData) return res.sendError('Unable to find the store to create planogram', 400);
2439
+
2440
+ const newPlano = {
2441
+ clientId: storeData.clientId,
2442
+ storeName: storeData.storeName,
2443
+ storeId,
2444
+ layoutName: `${storeData.storeName} - Layout`,
2445
+ attachments: [],
2446
+ status: 'completed',
2447
+ productResolutionLevel: 'L2',
2448
+ scanType: 'qr',
2449
+ planoStrategy: "nonIvm",
2450
+ ...userMeta
2451
+ };
2452
+
2453
+ planoData = await planoService.create(newPlano);
2454
+ if (!planoData) return res.sendError('Failed to create new planogram', 400);
2455
+ }
2456
+
2457
+ const floorPayload = {
2458
+ layoutPolygon: processWallData(cadData.data.layoutPolygon),
2459
+ planoProgress: 25,
2460
+ floorName: `Floor ${floorNumber}`,
2461
+ isEdited: false,
2462
+ cadLayout: true
2463
+ };
2464
+
2465
+ let floorData = await floorService.findOne({ planoId: planoData._id, floorNumber }, { _id: 1, clientId: 1, storeName: 1, storeId: 1, planoId: 1 });
2466
+ if (floorData) {
2467
+ await floorService.updateOne({ _id: floorData._id }, { ...floorPayload, updatedBy: user._id });
2468
+ } else {
2469
+ const newFloor = {
2470
+ clientId: planoData.clientId,
2471
+ planoId: planoData._id,
2472
+ layoutName: `${planoData.storeName} - Layout`,
2473
+ status: 'completed',
2474
+ storeName: planoData.storeName,
2475
+ floorNumber,
2476
+ storeId,
2477
+ ...floorPayload,
2478
+ ...userMeta,
2479
+ };
2480
+
2481
+ floorData = await floorService.create(newFloor);
2482
+ if (!floorData) return res.sendError('Failed to create new floor layout', 400);
2483
+ }
2484
+
2485
+ const meta = {
2486
+ clientId: floorData.clientId,
2487
+ storeName: floorData.storeName,
2488
+ storeId: floorData.storeId,
2489
+ planoId: floorData.planoId,
2490
+ floorId: floorData._id,
2491
+ }
2492
+
2493
+ logger.info({
2494
+ functionName: 'CAD DATA: ',
2495
+ body: cadData.data,
2496
+ });
2497
+
2498
+ // Create Fixtures
2499
+ const processedFixtures = createStoreFixtures(cadData.data, meta);
2500
+
2501
+ try {
2502
+ await storeFixtureService.deleteMany({ floorId: meta.floorId });
2503
+ await fixtureShelfService.deleteMany({ floorId: meta.floorId });
2504
+ for (let i = 0; i < processedFixtures.length; i++) {
2505
+ const fix = processedFixtures[i];
2506
+ await storeFixtureService.create(fix);
2507
+ }
2508
+ } catch (e) {
2509
+ logger.error({
2510
+ functionName: 'createPlanoFromCAD [Create Fixtures]',
2511
+ error: e,
2512
+ });
2513
+ }
2514
+
2515
+ return res.sendSuccess(meta);
2516
+
2517
+ } catch (error) {
2518
+ logger.error({
2519
+ functionName: 'createPlanoFromCAD',
2520
+ error,
2521
+ });
2522
+
2523
+ return res.sendError('Failed to create planogram.', 500);
2524
+ }
2525
+ }
2526
+
2527
+ function processWallData(cadRes) {
2528
+ return cadRes.map((w) => {
2529
+ return {
2530
+ "elementType": w?.elementType ?? 'wall',
2531
+ "distance": w?.distance ?? 16,
2532
+ "elementNumber": w?.elementNumber ?? 0,
2533
+ "unit": w?.unit ?? "ft",
2534
+ "direction": w?.direction ?? "right",
2535
+ "angle": w?.angle ?? 0,
2536
+ "relativePosition": w?.relativePosition,
2537
+ }
2538
+ });
2539
+ }
2540
+
2541
+ function createStoreFixtures(data, meta) {
2542
+
2543
+ let fixtures = [];
2544
+ const walls = data?.layoutPolygon ?? [];
2545
+ for (let i = 1; i <= walls.length; i++) {
2546
+ const w = walls[i - 1];
2547
+ const wFixtures = w?.fixtures ?? [];
2548
+
2549
+ for (let j = 1; j <= wFixtures.length; j++) {
2550
+ const f = wFixtures[j - 1];
2551
+
2552
+ const fixtureInfo = {
2553
+ f: f,
2554
+ meta: meta,
2555
+ wallNumber: w.elementNumber,
2556
+ fixtureNumber: j,
2557
+ }
2558
+
2559
+ fixtures.push(processFixtureData(fixtureInfo));
2560
+ }
2561
+ }
2562
+
2563
+ const centerFixtures = data?.centerFixture ?? [];
2564
+ for (let i = 1; i <= centerFixtures.length; i++) {
2565
+ const f = centerFixtures[i - 1];
2566
+
2567
+ const fixtureInfo = {
2568
+ f: f,
2569
+ meta: meta,
2570
+ wallNumber: undefined,
2571
+ fixtureNumber: i,
2572
+ }
2573
+
2574
+ fixtures.push(processFixtureData(fixtureInfo));
2575
+ }
2576
+
2577
+ return fixtures;
2578
+ }
2579
+
2580
+ function processFixtureData(data) {
2581
+ const f = data.f;
2582
+
2583
+ function normalizeCadText(value) {
2584
+ if (typeof value !== 'string') return value;
2585
+ return value.replace(/^\\[^;]+;/, '').trim();
2586
+ }
2587
+
2588
+ return {
2589
+ ...data.meta,
2590
+ fixtureName: normalizeCadText(f?.header?.label ?? ''),
2591
+ relativePosition: f?.relativePosition,
2592
+ fixtureType: f?.associatedElementType,
2593
+ header: {
2594
+ ...f?.header,
2595
+ label: normalizeCadText(f?.header?.label ?? '')
2596
+ },
2597
+ footer: f?.footer,
2598
+ fixtureWidth: f?.fixtureWidth,
2599
+ fixtureLength: f?.fixtureLength,
2600
+ fixtureNumber: f?.fixtureNumber,
2601
+ associatedElementNumber: data?.wallNumber,
2602
+ associatedElementFixtureNumber: data.fixtureNumber,
2603
+ associatedElementType: f?.associatedElementType === 'wall' ? 'wall' : undefined,
2604
+ productBrandName: Array.isArray(f?.productBrandName) ? f?.productBrandName?.map(normalizeCadText) : normalizeCadText(f?.productBrandName),
2605
+
2606
+ fixtureHeight: {
2607
+ value: 0,
2608
+ unit: 'mm',
2609
+ },
2610
+ fixtureStaticLength: {
2611
+ value: 1524,
2612
+ unit: 'mm',
2613
+ },
2614
+ fixtureStaticWidth: {
2615
+ value: 1220,
2616
+ unit: 'mm',
2617
+ },
2618
+ productResolutionLevel: 'L2',
2619
+ isBodyEnabled: true,
2620
+ isMerchEdited: false,
2621
+ isVmEdited: false,
2622
+ };
2623
+ }
@@ -2792,7 +2792,7 @@ export async function storeFixturesv2( req, res ) {
2792
2792
  planograms.map( async ( planogram ) => {
2793
2793
  const floors = await storeBuilderService.find(
2794
2794
  { planoId: planogram._id, ...( req.body?.floorId && { _id: req.body.floorId } ) },
2795
- { floorName: 1, layoutPolygon: 1, planoId: 1, isEdited: 1, planoProgress: 1, updatedAt: 1, verificationStatus: 1, merchRolloutStatus: 1, vmRolloutStatus: 1 },
2795
+ { floorName: 1, layoutPolygon: 1, planoId: 1, isEdited: 1, planoProgress: 1, updatedAt: 1, verificationStatus: 1, merchRolloutStatus: 1, vmRolloutStatus: 1,cadLayout:1 },
2796
2796
  );
2797
2797
 
2798
2798
  const floorsWithFixtures = await Promise.all(
@@ -8455,7 +8455,7 @@ export async function getPlanogramListOld( req, res ) {
8455
8455
 
8456
8456
  return res.sendSuccess( { data, count: total } );
8457
8457
  } catch ( err ) {
8458
- logger.error( { functionName: 'getPlanogramListOld', error: err } );
8458
+ logger.error( { functionName: 'getLayoutList', error: err } );
8459
8459
  return res.sendError( err, 500 );
8460
8460
  }
8461
8461
  }
@@ -8582,16 +8582,15 @@ export async function getPlanogramList( req, res ) {
8582
8582
  merchCompliance: {
8583
8583
  $floor: [
8584
8584
  {
8585
- $cond: {
8586
- if: { $gt: [ { $ifNull: [ '$productCount', 0 ] }, 0 ] },
8587
- then: {
8585
+ $multiply: [
8586
+ {
8588
8587
  $divide: [
8589
8588
  { $ifNull: [ { $arrayElemAt: [ '$complianceCount.count', 0 ] }, 0 ] },
8590
- '$productCount',
8589
+ { $ifNull: [ '$productCount', 1 ] },
8591
8590
  ],
8592
8591
  },
8593
- else: 0,
8594
- },
8592
+ 100,
8593
+ ],
8595
8594
  },
8596
8595
  ],
8597
8596
  },
@@ -8684,7 +8683,81 @@ export async function getPlanogramList( req, res ) {
8684
8683
 
8685
8684
  return res.sendSuccess( { data, count: total } );
8686
8685
  } catch ( err ) {
8687
- logger.error( { functionName: 'getPlanogramList', error: err } );
8686
+ logger.error( { functionName: 'getLayoutList', error: err } );
8688
8687
  return res.sendError( err, 500 );
8689
8688
  }
8690
8689
  }
8690
+
8691
+ export async function getStoreFloorsList(req, res) {
8692
+ try {
8693
+
8694
+ const { storeId } = req.query;
8695
+
8696
+ if (!storeId) {
8697
+ return res.sendError('storeId is required');
8698
+ }
8699
+
8700
+ const query = [
8701
+ {
8702
+ $match: {
8703
+ storeId: storeId
8704
+ }
8705
+ },
8706
+ {
8707
+ $lookup: {
8708
+ from: "storelayouts",
8709
+ let: { planoId: "$_id" },
8710
+ pipeline: [
8711
+ {
8712
+ $match: {
8713
+ $expr: { $eq: ["$planoId", "$$planoId"] }
8714
+ }
8715
+ },
8716
+ {
8717
+ $project: {
8718
+ _id: 1,
8719
+ floorNumber: 1,
8720
+ floorName: 1
8721
+ }
8722
+ }
8723
+ ],
8724
+ as: "floors"
8725
+ }
8726
+ },
8727
+ {
8728
+ $project: {
8729
+ _id: 0,
8730
+ floors: 1
8731
+ }
8732
+ }
8733
+ ]
8734
+
8735
+
8736
+ const floorsRes = await planoService.aggregate(query);
8737
+
8738
+ return res.sendSuccess(floorsRes?.[0]?.floors ?? []);
8739
+
8740
+ } catch (e) {
8741
+ logger.error({ functionName: 'getStoreFloorsList', error: e });
8742
+ return res.sendError(e, 500);
8743
+ }
8744
+ }
8745
+
8746
+ export async function getAllStores(req, res) {
8747
+ try {
8748
+
8749
+ const { clientId } = req.query;
8750
+
8751
+ if (!clientId) {
8752
+ return res.sendError('clientId is required', 400);
8753
+ }
8754
+
8755
+ const storeRes = await storeService.find({ clientId: clientId, status: 'active' }, { storeId: 1, storeName: 1, _id: 0 })
8756
+
8757
+ return res.sendSuccess(storeRes);
8758
+
8759
+ } catch (e) {
8760
+ logger.error({ functionName: 'getAllStores', error: e });
8761
+ return res.sendError(e, 500);
8762
+ }
8763
+ }
@@ -6,25 +6,26 @@ import { isAllowedSessionHandler } from 'tango-app-api-middleware';
6
6
  export const managePlanoRouter = express.Router();
7
7
 
8
8
  managePlanoRouter
9
- .post( '/updateStorePlano', isAllowedSessionHandler, managePlanoController.updateStorePlano )
10
- .post( '/getplanoFeedback', isAllowedSessionHandler, managePlanoController.getplanoFeedback )
11
- .post( '/getplanoFeedbackv1', isAllowedSessionHandler, managePlanoController.getplanoFeedbackv1 )
12
- .post( '/getStoreFixturesfeedback', isAllowedSessionHandler, managePlanoController.getStoreFixturesfeedback )
13
- .get( '/fixtureList', isAllowedSessionHandler, managePlanoController.fixtureList )
14
- .get( '/templateList', isAllowedSessionHandler, managePlanoController.templateList )
15
- .get( '/fixtureBrandsList', isAllowedSessionHandler, managePlanoController.fixtureBrandsList )
16
- .get( '/fixtureVMList', isAllowedSessionHandler, managePlanoController.fixtureVMList )
17
- .get( '/fixtureVMListv1', isAllowedSessionHandler, managePlanoController.fixtureVMListv1 )
18
- .post( '/updateFixtureStatus', isAllowedSessionHandler, managePlanoController.updateFixtureStatus )
19
- .post( '/updateApprovalStatus', isAllowedSessionHandler, managePlanoController.updateApprovalStatus )
20
- .post( '/updateStoreFixture', isAllowedSessionHandler, managePlanoController.updateStoreFixture )
21
- .post( '/updateredostatus', isAllowedSessionHandler, managePlanoController.updateredostatus )
22
- .post( '/updateGlobalComment', isAllowedSessionHandler, managePlanoController.updateGlobalComment )
23
- .post( '/getGlobalComment', isAllowedSessionHandler, managePlanoController.getGlobalComment )
24
- .post( '/createRevision', isAllowedSessionHandler, managePlanoController.createPlanoRevision )
25
- .post( '/getRevisions', isAllowedSessionHandler, managePlanoController.getAllPlanoRevisions )
26
- .post( '/getRevisionData', isAllowedSessionHandler, managePlanoController.getPlanoRevisionById )
27
- .post( '/getRolloutFeedback', isAllowedSessionHandler, managePlanoController.getRolloutFeedback )
28
- .post( '/getRolloutFeedbackv2', isAllowedSessionHandler, managePlanoController.getRolloutFeedbackv2 )
29
- .post( '/updateRolloutStatus', isAllowedSessionHandler, managePlanoController.updateRolloutStatus )
30
- .post( '/getPlanoStoreVideoParams', isAllowedSessionHandler, managePlanoController.getPlanoStoreVideoPageParams );
9
+ .post('/updateStorePlano', isAllowedSessionHandler, managePlanoController.updateStorePlano)
10
+ .post('/getplanoFeedback', isAllowedSessionHandler, managePlanoController.getplanoFeedback)
11
+ .post('/getplanoFeedbackv1', isAllowedSessionHandler, managePlanoController.getplanoFeedbackv1)
12
+ .post('/getStoreFixturesfeedback', isAllowedSessionHandler, managePlanoController.getStoreFixturesfeedback)
13
+ .get('/fixtureList', isAllowedSessionHandler, managePlanoController.fixtureList)
14
+ .get('/templateList', isAllowedSessionHandler, managePlanoController.templateList)
15
+ .get('/fixtureBrandsList', isAllowedSessionHandler, managePlanoController.fixtureBrandsList)
16
+ .get('/fixtureVMList', isAllowedSessionHandler, managePlanoController.fixtureVMList)
17
+ .get('/fixtureVMListv1', isAllowedSessionHandler, managePlanoController.fixtureVMListv1)
18
+ .post('/updateFixtureStatus', isAllowedSessionHandler, managePlanoController.updateFixtureStatus)
19
+ .post('/updateApprovalStatus', isAllowedSessionHandler, managePlanoController.updateApprovalStatus)
20
+ .post('/updateStoreFixture', isAllowedSessionHandler, managePlanoController.updateStoreFixture)
21
+ .post('/updateredostatus', isAllowedSessionHandler, managePlanoController.updateredostatus)
22
+ .post('/updateGlobalComment', isAllowedSessionHandler, managePlanoController.updateGlobalComment)
23
+ .post('/getGlobalComment', isAllowedSessionHandler, managePlanoController.getGlobalComment)
24
+ .post('/createRevision', isAllowedSessionHandler, managePlanoController.createPlanoRevision)
25
+ .post('/getRevisions', isAllowedSessionHandler, managePlanoController.getAllPlanoRevisions)
26
+ .post('/getRevisionData', isAllowedSessionHandler, managePlanoController.getPlanoRevisionById)
27
+ .post('/getRolloutFeedback', isAllowedSessionHandler, managePlanoController.getRolloutFeedback)
28
+ .post('/getRolloutFeedbackv2', isAllowedSessionHandler, managePlanoController.getRolloutFeedbackv2)
29
+ .post('/updateRolloutStatus', isAllowedSessionHandler, managePlanoController.updateRolloutStatus)
30
+ .post('/getPlanoStoreVideoParams', isAllowedSessionHandler, managePlanoController.getPlanoStoreVideoPageParams)
31
+ .post('/createPlanoFromCAD', isAllowedSessionHandler, managePlanoController.createPlanoFromCAD);
@@ -61,12 +61,14 @@ storeBuilderRouter
61
61
  .post( '/planoList', isAllowedSessionHandler, getAssinedStore, storeBuilderController.planoList )
62
62
  .get( '/taskDetails', isAllowedSessionHandler, storeBuilderController.getTaskDetails )
63
63
  .get( '/getPlanoUser', isAllowedSessionHandler, storeBuilderController.getPlanoUser )
64
- .post( '/planoRolloutList', isAllowedSessionHandler, getAssinedStore, storeBuilderController.getRolloutDetails )
64
+ .post( '/planoRolloutList', isAllowedSessionHandler, storeBuilderController.getRolloutDetails )
65
65
  .get( '/getRolloutTaskDetails', isAllowedSessionHandler, storeBuilderController.getRolloutTaskDetails )
66
66
  .get( '/getScandid', storeBuilderController.getScandid )
67
67
  .get( '/getPlanoStoreList', isAllowedSessionHandler, getAssinedStore, storeBuilderController.getPlanoStoreList )
68
68
  .get( '/getFixtureAIDetails', isAllowedSessionHandler, storeBuilderController.getFixtureAIDetails )
69
69
  .post( '/updateProductMapping', storeBuilderController.updateProductMapping )
70
70
  .post( '/calculateCompliance', isAllowedSessionHandler, storeBuilderController.calculateCompliance )
71
- .post( '/getPlanogramList', isAllowedSessionHandler, getAssinedStore, storeBuilderController.getPlanogramList )
71
+ .post( '/getPlanogramList', isAllowedSessionHandler, storeBuilderController.getPlanogramList )
72
+ .get('/getAllStores', isAllowedSessionHandler, storeBuilderController.getAllStores)
73
+ .get('/getStoreFloorsList', isAllowedSessionHandler, storeBuilderController.getStoreFloorsList)
72
74
  ;