tango-app-api-trax 3.7.13-qid-halfshutter-10 → 3.7.13-qid-halfshutter-12

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-trax",
3
- "version": "3.7.13-qid-halfshutter-10",
3
+ "version": "3.7.13-qid-halfshutter-12",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "mongodb": "^6.8.0",
28
28
  "nodemon": "^3.1.4",
29
29
  "path": "^0.12.7",
30
- "tango-api-schema": "^2.4.11",
30
+ "tango-api-schema": "^2.4.15",
31
31
  "tango-app-api-middleware": "^3.5.2",
32
32
  "url": "^0.11.4",
33
33
  "winston": "^3.13.1",
@@ -2623,3 +2623,59 @@ export async function getRunAIQuestions( req, res ) {
2623
2623
  return res.sendError( e, 500 );
2624
2624
  }
2625
2625
  }
2626
+
2627
+ export async function getRunAIList( req, res ) {
2628
+ try {
2629
+ let getChecklistDetails = await CLconfig.find( { publish: true } );
2630
+ if ( !getChecklistDetails.length ) {
2631
+ return res.sendError( 'No data found', 204 );
2632
+ }
2633
+ let runAIList=new Set();
2634
+ await Promise.all( getChecklistDetails.map( async ( check ) => {
2635
+ let getQuestionList = await CLquestions.find( { checkListId: check._id } );
2636
+ if ( getQuestionList.length ) {
2637
+ getQuestionList.forEach( ( sec ) => {
2638
+ sec.question.forEach( ( qn ) => {
2639
+ if ( qn.answers.some( ( ele ) => ele.runAI ) ) {
2640
+ runAIList.add( check._id );
2641
+ }
2642
+ } );
2643
+ } );
2644
+ }
2645
+ } ) );
2646
+ return res.sendSuccess( Array.from( runAIList ) );
2647
+ } catch ( e ) {
2648
+ logger.error( { functionName: 'getRunAIList', error: e } );
2649
+ return res.sendError( e, 500 );
2650
+ }
2651
+ }
2652
+
2653
+ export async function updateChecklistConfig( req, res ) {
2654
+ try {
2655
+ if ( req.body.type == 'checklist' ) {
2656
+ let checklistDetails = await CLconfig.findOne( { _id: req.body.id } );
2657
+ if ( !checklistDetails ) {
2658
+ return res.sendError( 'No data found', 204 );
2659
+ }
2660
+ await CLconfig.updateOne( { _id: req.body.id }, req.body.data );
2661
+
2662
+ if ( req.body?.updateStore ) {
2663
+ await processedchecklist.updateMany( { date_string: dayjs().format( 'YYYY-MM-DD' ), sourceCheckList_id: req.body.id, ...( req.body.store?.length && { storeName: { $in: req.body.store } } ) }, req.body.data );
2664
+ }
2665
+ } else {
2666
+ let taskDetails = await taskService.findOne( { _id: req.body.id } );
2667
+ if ( !taskDetails ) {
2668
+ return res.sendError( 'No data found', 204 );
2669
+ }
2670
+ await taskService.updateOne( { _id: req.body.id }, req.body.data );
2671
+
2672
+ if ( req.body?.updateStore ) {
2673
+ await processedTaskService.updateMany( { date_string: dayjs().format( 'YYYY-MM-DD' ), sourceCheckList_id: req.body.id, ...( req.body.store?.length && { storeName: { $in: req.body.store } } ) }, req.body.data );
2674
+ }
2675
+ }
2676
+ return res.sendSuccess( 'updated successfully' );
2677
+ } catch ( e ) {
2678
+ logger.error( { functionName: 'updateChecklist', error: e } );
2679
+ return res.sendError( e, 500 );
2680
+ }
2681
+ }
@@ -1,4 +1,4 @@
1
- import { logger, signedUrl, fileUpload, getOtp, sendEmailWithSES, getUuid, insertOpenSearchData } from 'tango-app-api-middleware';
1
+ import { logger, signedUrl, fileUpload, getOtp, sendEmailWithSES, getUuid, insertOpenSearchData, listFileByPath, getObject, deleteFiles } from 'tango-app-api-middleware';
2
2
  import * as processedchecklist from '../services/processedchecklist.services.js';
3
3
  import * as processedtask from '../services/processedTaskList.service.js';
4
4
  import * as PCLconfig from '../services/processedchecklistconfig.services.js';
@@ -19,7 +19,6 @@ import timeZone from 'dayjs/plugin/timezone.js';
19
19
  import { findOTP, updateOneOTP } from '../services/otp.service.js';
20
20
  import * as clientService from '../services/clients.services.js';
21
21
  import { create } from '../services/authentication.service.js';
22
- import { readFileSync } from 'fs';
23
22
  import { join } from 'path';
24
23
  import handlebars from 'handlebars';
25
24
  dayjs.extend( customParseFormat );
@@ -1078,7 +1077,6 @@ export async function sopMobilechecklistMultiSectionFormatterv1( req, res, next
1078
1077
 
1079
1078
  if ( requestData?.editSubmit && requestData?.editSubmit == 'true' ) {
1080
1079
  let sampleData = reqAnswers[0];
1081
- console.log( 'Edit submit sampleData :', sampleData );
1082
1080
  CLQAnswers.forEach( ( section ) => {
1083
1081
  let requestSection = reqAnswers.filter( ( reqSection ) => reqSection.sectionName == section?.sectionOldName || reqSection.sectionName == section?.sectionName );
1084
1082
  if ( requestSection.length ) {
@@ -3572,6 +3570,9 @@ export async function taskQuestionList( req, res ) {
3572
3570
  userEmail: { $ifNull: [ '$userEmail', '' ] },
3573
3571
  storeName: { $ifNull: [ '$storeName', '' ] },
3574
3572
  redoStatus: { $ifNull: [ '$redoStatus', false ] },
3573
+ rawImageUpload: { $ifNull: [ '$rawImageUpload', false ] },
3574
+ rawVideoUpload: { $ifNull: [ '$rawVideoUpload', false ] },
3575
+ videoUploadTimeLimit: { $ifNull: [ '$videoUploadTimeLimit', 0 ] },
3575
3576
  },
3576
3577
  } );
3577
3578
 
@@ -3773,7 +3774,7 @@ export async function uploadAnswerImage( req, res ) {
3773
3774
  let imageUrl;
3774
3775
  let filePath = `${folder}/${req.user.clientId}/${date}/${input.checklistId}/${input.sectionName.replace( ' ', '' )}/${input.questionNo}/`;
3775
3776
  let params = {
3776
- fileName: `${Date.now()}-${Math.floor( 1000 + Math.random() * 9000 )}.${req.files.answerImage.name.split( '.' )[1]}`,
3777
+ fileName: `${Date.now()}-${Math.floor( 1000 + Math.random() * 9000 )}-${req.files.answerImage.name}`,
3777
3778
  Key: filePath,
3778
3779
  Bucket: bucket.sop,
3779
3780
  body: req.files.answerImage.data,
@@ -4390,246 +4391,58 @@ export async function questionListV1( req, res ) {
4390
4391
  }
4391
4392
  }
4392
4393
 
4393
- export async function sopMobilechecklistQuestionValidatorV6( req, res, next ) {
4394
- console.log( 'sopMobilechecklistQuestionValidatorV6 called :', req.body );
4395
- try {
4396
- let requestData = req.body;
4397
- // formatter should have normalized questionAnswers into sectionFormat (sections with questions array)
4398
- requestData.questionAnswers = typeof requestData.questionAnswers == 'string' ? JSON.parse( requestData.questionAnswers ) : requestData.questionAnswers;
4399
-
4400
- const getChecklistQA = await processedchecklist.findOne( { _id: new ObjectId( requestData.processedcheckListId ) }, { questionAnswers: 1, sourceCheckList_id: 1 } );
4401
- if ( !getChecklistQA ) {
4402
- return res.sendError( 'Check List Got Edited Please Fill Again', 422 );
4403
- }
4404
-
4405
- // if publish check required
4406
- if ( getChecklistQA && requestData.submittype == 'submit' ) {
4407
- let checkChecklistStatus = await checklistService.findOne( { _id: getChecklistQA.sourceCheckList_id }, { publish: 1 } );
4408
- if ( !checkChecklistStatus.publish ) {
4409
- return res.sendError( 'Checklist got edited.please contact admin', 400 );
4410
- }
4411
- }
4412
-
4413
- if ( !requestData.questionAnswers || !requestData.questionAnswers.length ) {
4414
- return res.sendError( 'Please Fill all Required Fields', 400 );
4415
- }
4416
-
4417
- // For draft saves we allow partial payloads
4418
- if ( requestData.submittype == 'submit' ) {
4419
- const incomingSections = requestData.questionAnswers; // already normalized by formatter
4420
- const CLQAnswers = getChecklistQA.questionAnswers || [];
4421
-
4422
- let validationCount = 0;
4423
- let errorCount = 0;
4424
-
4425
- // Build a map of incoming sections by section_id for quick lookup
4426
- const incomingMap = new Map();
4427
- incomingSections.forEach( ( s ) => {
4428
- if ( s && s.section_id ) incomingMap.set( s.section_id.toString(), s );
4429
- } );
4430
-
4431
- for ( const section of CLQAnswers ) {
4432
- const incoming = incomingMap.get( section.section_id?.toString() );
4433
- if ( !incoming ) {
4434
- // missing a configured section in final submit
4435
- errorCount++;
4436
- continue;
4437
- }
4438
4394
 
4439
- // incoming.questions is expected to be the formatted array where each question has userAnswer (array) or similar
4440
- for ( const question of incoming.questions || [] ) {
4441
- try {
4442
- if ( question.answerType == 'multiplechoicemultiple' ) {
4443
- if ( !question.userAnswer || !question.userAnswer.length ) validationCount++;
4444
- } else if ( question.answerType == 'multipleImage' ) {
4445
- // multipleImage expects an array of answers in userAnswer
4446
- if ( !question.userAnswer || !question.userAnswer.length ) validationCount++;
4447
- } else {
4448
- // other types: expect at least one userAnswer entry
4449
- if ( !question.userAnswer || !question.userAnswer.length ) validationCount++;
4450
- }
4451
- } catch ( e ) {
4452
- validationCount++;
4453
- }
4454
- }
4455
- }
4456
-
4457
- if ( validationCount ) {
4458
- return res.sendError( 'Please Fill all Required Fields', 400 );
4459
- } else if ( errorCount ) {
4460
- return res.sendError( 'Checklist got edited.please contact admin', 400 );
4461
- } else {
4462
- return next();
4463
- }
4464
- } else {
4465
- return next();
4466
- }
4467
- } catch ( e ) {
4468
- logger.error( { function: 'sopMobilechecklistQuestionValidatorV6', error: e, body: req.body } );
4469
- return res.sendError( e, 500 );
4470
- }
4471
- }
4472
-
4473
- /**
4474
- * @param {Object} req - Request object
4475
- * @param {Object} res - Response object
4476
- * submitChecklistV6
4477
- * New behaviour: updating a single section instead of whole checklist
4478
- * Expected request payload (partial):
4479
- * {
4480
- * processedcheckListId,
4481
- * date,
4482
- * sectionIndex, // 0-based index of the section being saved (preferred)
4483
- * sectionId, // optional: _id or identifier of the section (fallback)
4484
- * questionAnswers, // array with a single section object OR the section object itself
4485
- * submittype: 'draft' | 'submit' // draft = save current section; submit = if this is last section, finalize checklist
4486
- * }
4487
- */
4488
- export async function submitChecklistV6( req, res ) {
4395
+ export async function chunkUpload( req, res ) {
4489
4396
  try {
4490
- const requestData = req.body;
4491
- console.log( 'submitChecklistV6 requestData =>', requestData );
4492
-
4493
- const findQuery = [
4494
- {
4495
- $match: {
4496
- $and: [
4497
- { _id: new ObjectId( requestData.processedcheckListId ) },
4498
- { userId: req.user._id },
4499
- { date_string: requestData.date },
4500
- ],
4501
- },
4502
- },
4503
- ];
4504
-
4505
- const getchecklist = await processedchecklist.aggregate( findQuery );
4506
- if ( !getchecklist.length ) {
4507
- return res.sendError( 'Checklist Got Edited Please Fill Again', 422 );
4397
+ const { chunkIndex, totalChunks } = req.body;
4398
+ if ( chunkIndex === undefined || !totalChunks || !req.files || !req.files.answerImage ) {
4399
+ return res.status( 400 ).json( { error: 'Missing required params or file.' } );
4508
4400
  }
4509
- console.log( 'getchecklist[0] =>', getchecklist );
4510
- const doc = getchecklist[0];
4511
- if ( doc.checklistStatus == 'open' ) {
4512
- return res.sendError( 'checklist in open status need to start', 400 );
4513
- } else if ( doc.checklistStatus == 'submit' ) {
4514
- return res.sendError( 'checklist Already Submitted', 400 );
4401
+ const chunkNum = Number( chunkIndex );
4402
+ const chunkTotal = Number( totalChunks );
4403
+ const chunkFile = req.files.answerImage;
4404
+ const chunkKey = `traxVideoChunks/${req.user._id}/${chunkNum}`;
4405
+ let bucket;
4406
+ try {
4407
+ bucket = JSON.parse( process.env.BUCKET )?.sop;
4408
+ } catch {
4409
+ return res.sendError( 'Bucket config error', 500 );
4515
4410
  }
4516
-
4517
- // Normalize incoming section data
4518
- let incomingSection = null;
4519
- if ( Array.isArray( requestData.questionAnswers ) && requestData.questionAnswers.length ) {
4520
- // if client sends an array with a single section
4521
- incomingSection = requestData.questionAnswers[0];
4522
- } else if ( requestData.questionAnswers && typeof requestData.questionAnswers === 'object' ) {
4523
- incomingSection = requestData.questionAnswers;
4411
+ await fileUpload( {
4412
+ fileName: '',
4413
+ Key: chunkKey,
4414
+ Bucket: bucket,
4415
+ ContentType: chunkFile.mimetype,
4416
+ body: chunkFile.data,
4417
+ } );
4418
+ if ( chunkNum < chunkTotal ) {
4419
+ return res.sendSuccess( { message: 'Chunk uploaded to S3', chunkIndex: chunkNum } );
4524
4420
  }
4525
-
4526
- if ( !incomingSection && typeof requestData.sectionIndex === 'undefined' ) {
4527
- return res.sendError( 'sectionIndex or questionAnswers (section) is required', 400 );
4421
+ const prefix = `traxVideoChunks/${req.user._id}/`;
4422
+ const s3FilesResp = await listFileByPath( { Bucket: bucket, file_path: prefix, MaxKeys: chunkTotal } );
4423
+ const chunkObjs = s3FilesResp?.data || [];
4424
+ chunkObjs.sort( ( a, b ) => {
4425
+ const ai = Number( a.Key.split( '/' ).pop() );
4426
+ const bi = Number( b.Key.split( '/' ).pop() );
4427
+ return ai - bi;
4428
+ } );
4429
+ const buffers = [];
4430
+ for ( const obj of chunkObjs ) {
4431
+ const s3Obj = await getObject( { Bucket: bucket, Key: obj.Key } );
4432
+ buffers.push( Buffer.from( s3Obj.Body ) );
4528
4433
  }
4529
-
4530
- const updateQuery = {
4531
- _id: new ObjectId( requestData.processedcheckListId ),
4532
- userId: req.user._id,
4533
- date_string: requestData.date,
4434
+ const finalBuffer = Buffer.concat( buffers );
4435
+ try {
4436
+ await deleteFiles( bucket, prefix );
4437
+ } catch ( e ) {}
4438
+ req.files.answerImage = {
4439
+ data: finalBuffer,
4440
+ name: chunkFile.name,
4441
+ mimetype: chunkFile.mimetype,
4534
4442
  };
4535
-
4536
- // Merge the existing questionAnswers with incoming section
4537
- const existingQA = Array.isArray( doc.questionAnswers ) ? doc.questionAnswers : [];
4538
- const mergedQA = [ ...existingQA ];
4539
-
4540
- let targetIndex = -1;
4541
- if ( typeof requestData.sectionIndex !== 'undefined' && Number.isInteger( Number( requestData.sectionIndex ) ) ) {
4542
- targetIndex = Number( requestData.sectionIndex );
4543
- } else if ( incomingSection && incomingSection.section_id ) {
4544
- targetIndex = mergedQA.findIndex( ( s ) => s._id && s._id.toString() == incomingSection.section_id.toString() || s.section_id == incomingSection.section_id );
4545
- }
4546
-
4547
- if ( targetIndex === -1 ) {
4548
- // If sectionIndex not provided or not found, try to find by section name
4549
- if ( incomingSection && incomingSection.sectionName ) {
4550
- targetIndex = mergedQA.findIndex( ( s ) => s.sectionName == incomingSection.sectionName );
4551
- }
4552
- }
4553
-
4554
- if ( targetIndex === -1 && incomingSection ) {
4555
- // If still not found, append as new section
4556
- mergedQA.push( incomingSection );
4557
- } else if ( targetIndex >= 0 && incomingSection ) {
4558
- // Replace the target section with incoming data
4559
- mergedQA[targetIndex] = Object.assign( {}, mergedQA[targetIndex], incomingSection );
4560
- }
4561
- console.log( 'mergedQA =>', mergedQA );
4562
- // Compute flags based on merged questionAnswers. Reuse QuestionFlag helper by temporarily setting req.body.questionAnswers
4563
- const prevBodyQA = req.body.questionAnswers;
4564
- req.body.questionAnswers = mergedQA;
4565
- let flagCount = await QuestionFlag( req, res );
4566
- // QuestionFlag may have returned a response (res.sendError) in case of error; ensure numeric
4567
- if ( typeof flagCount !== 'number' ) flagCount = 0;
4568
- req.body.questionAnswers = prevBodyQA; // restore
4569
-
4570
- const currentDateTime = dayjs();
4571
-
4572
- const updateData = {};
4573
- updateData.questionAnswers = mergedQA;
4574
- updateData.questionFlag = flagCount;
4575
- updateData.updatedAt = dayjs.utc( currentDateTime.format( 'hh:mm:ss A, DD MMM YYYY' ), 'hh:mm:ss A, DD MMM YYYY' ).format();
4576
-
4577
- // If this is a final submission (submittype === 'submit') and optionally client indicates this is last section
4578
- if ( requestData.submittype === 'submit' ) {
4579
- updateData.submitTime = dayjs.utc( currentDateTime.format( 'hh:mm:ss A, DD MMM YYYY' ), 'hh:mm:ss A, DD MMM YYYY' ).format();
4580
- updateData.checklistStatus = 'submit';
4581
- updateData.submitMobileTime = requestData?.currentTime;
4582
- updateData.submitTime_string = currentDateTime.format( 'hh:mm A, DD MMM YYYY' );
4583
- } else {
4584
- // draft/partial save
4585
- updateData.submitTime_string = currentDateTime.format( 'hh:mm A, DD MMM YYYY' );
4586
- }
4587
-
4588
- if ( requestData.deviceDetails && requestData.deviceDetails != '' ) {
4589
- try {
4590
- updateData.deviceDetails = JSON.parse( requestData.deviceDetails );
4591
- } catch ( e ) {
4592
- // ignore parse error and keep raw
4593
- updateData.deviceDetails = requestData.deviceDetails;
4594
- }
4595
- }
4596
-
4597
- console.log( 'updateData =>', updateData );
4598
- console.log( 'updateQuery =>', updateQuery );
4599
- const updateResult = await processedchecklist.updateOne( updateQuery, updateData );
4600
-
4601
- if ( updateResult?.modifiedCount > 0 ) {
4602
- // update open search only on final submit
4603
- if ( requestData.submittype === 'submit' ) {
4604
- updateOpenSearch( req.user, requestData );
4605
- const openSearch = JSON.parse( process.env.OPENSEARCH );
4606
- const inserttraxlogs = {
4607
- client_id: req.user.clientId,
4608
- createAt: new Date(),
4609
- sourceCheckList_id: doc.sourceCheckList_id,
4610
- checkListName: doc.checkListName,
4611
- type: 'checkList',
4612
- action: 'submit',
4613
- storeName: doc.storeName,
4614
- store_id: doc.store_id,
4615
- userName: doc.userName,
4616
- userEmail: doc.userEmail,
4617
- createdByEmail: doc.userEmail,
4618
- createdBy: doc.userName,
4619
- coverage: doc.coverage,
4620
- checkListType: doc.checkListType,
4621
- logDetails: {},
4622
- userType: req.user.userType,
4623
- };
4624
- insertOpenSearchData( openSearch.traxActivityLog, inserttraxlogs );
4625
- }
4626
-
4627
- return res.sendSuccess( 'Checklist Updated Successfully' );
4628
- } else {
4629
- return res.sendError( 'something went wrong please try again', 500 );
4630
- }
4443
+ return uploadAnswerImage( req, res );
4631
4444
  } catch ( e ) {
4632
- logger.error( { function: 'submitChecklistV6', error: e, body: req.body } );
4445
+ logger.error( { functionName: 'chunkUpload', error: e } );
4633
4446
  return res.sendError( e, 500 );
4634
4447
  }
4635
4448
  }