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

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-9",
3
+ "version": "3.7.13-qid-halfshutter-10",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -2623,47 +2623,3 @@ 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
- let checklistDetails = await CLconfig.findOne( { _id: req.body.id } );
2656
- if ( !checklistDetails ) {
2657
- return res.sendError( 'No data found', 204 );
2658
- }
2659
- await CLconfig.updateOne( { _id: req.body.id }, req.body.data );
2660
-
2661
- if ( req.body?.updateStore ) {
2662
- 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 );
2663
- }
2664
- return res.sendSuccess( 'updated successfully' );
2665
- } catch ( e ) {
2666
- logger.error( { functionName: 'updateChecklist', error: e } );
2667
- return res.sendError( e, 500 );
2668
- }
2669
- }
@@ -1,4 +1,4 @@
1
- import { logger, signedUrl, fileUpload, getOtp, sendEmailWithSES, getUuid, insertOpenSearchData, listFileByPath, getObject, deleteFiles } from 'tango-app-api-middleware';
1
+ import { logger, signedUrl, fileUpload, getOtp, sendEmailWithSES, getUuid, insertOpenSearchData } 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,6 +19,7 @@ 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';
22
23
  import { join } from 'path';
23
24
  import handlebars from 'handlebars';
24
25
  dayjs.extend( customParseFormat );
@@ -1077,6 +1078,7 @@ export async function sopMobilechecklistMultiSectionFormatterv1( req, res, next
1077
1078
 
1078
1079
  if ( requestData?.editSubmit && requestData?.editSubmit == 'true' ) {
1079
1080
  let sampleData = reqAnswers[0];
1081
+ console.log( 'Edit submit sampleData :', sampleData );
1080
1082
  CLQAnswers.forEach( ( section ) => {
1081
1083
  let requestSection = reqAnswers.filter( ( reqSection ) => reqSection.sectionName == section?.sectionOldName || reqSection.sectionName == section?.sectionName );
1082
1084
  if ( requestSection.length ) {
@@ -3771,7 +3773,7 @@ export async function uploadAnswerImage( req, res ) {
3771
3773
  let imageUrl;
3772
3774
  let filePath = `${folder}/${req.user.clientId}/${date}/${input.checklistId}/${input.sectionName.replace( ' ', '' )}/${input.questionNo}/`;
3773
3775
  let params = {
3774
- fileName: `${Date.now()}-${Math.floor( 1000 + Math.random() * 9000 )}-${req.files.answerImage.name}`,
3776
+ fileName: `${Date.now()}-${Math.floor( 1000 + Math.random() * 9000 )}.${req.files.answerImage.name.split( '.' )[1]}`,
3775
3777
  Key: filePath,
3776
3778
  Bucket: bucket.sop,
3777
3779
  body: req.files.answerImage.data,
@@ -4388,58 +4390,246 @@ export async function questionListV1( req, res ) {
4388
4390
  }
4389
4391
  }
4390
4392
 
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
+ }
4391
4404
 
4392
- export async function chunkUpload( req, res ) {
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
+
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 ) {
4393
4489
  try {
4394
- const { chunkIndex, totalChunks } = req.body;
4395
- if ( chunkIndex === undefined || !totalChunks || !req.files || !req.files.answerImage ) {
4396
- return res.status( 400 ).json( { error: 'Missing required params or file.' } );
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
4508
  }
4398
- const chunkNum = Number( chunkIndex );
4399
- const chunkTotal = Number( totalChunks );
4400
- const chunkFile = req.files.answerImage;
4401
- const chunkKey = `traxVideoChunks/${req.user._id}/${chunkNum}`;
4402
- let bucket;
4403
- try {
4404
- bucket = JSON.parse( process.env.BUCKET )?.sop;
4405
- } catch {
4406
- return res.sendError( 'Bucket config error', 500 );
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 );
4407
4515
  }
4408
- await fileUpload( {
4409
- fileName: '',
4410
- Key: chunkKey,
4411
- Bucket: bucket,
4412
- ContentType: chunkFile.mimetype,
4413
- body: chunkFile.data,
4414
- } );
4415
- if ( chunkNum < chunkTotal ) {
4416
- return res.sendSuccess( { message: 'Chunk uploaded to S3', chunkIndex: chunkNum } );
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;
4417
4524
  }
4418
- const prefix = `traxVideoChunks/${req.user._id}/`;
4419
- const s3FilesResp = await listFileByPath( { Bucket: bucket, file_path: prefix, MaxKeys: chunkTotal } );
4420
- const chunkObjs = s3FilesResp?.data || [];
4421
- chunkObjs.sort( ( a, b ) => {
4422
- const ai = Number( a.Key.split( '/' ).pop() );
4423
- const bi = Number( b.Key.split( '/' ).pop() );
4424
- return ai - bi;
4425
- } );
4426
- const buffers = [];
4427
- for ( const obj of chunkObjs ) {
4428
- const s3Obj = await getObject( { Bucket: bucket, Key: obj.Key } );
4429
- buffers.push( Buffer.from( s3Obj.Body ) );
4525
+
4526
+ if ( !incomingSection && typeof requestData.sectionIndex === 'undefined' ) {
4527
+ return res.sendError( 'sectionIndex or questionAnswers (section) is required', 400 );
4430
4528
  }
4431
- const finalBuffer = Buffer.concat( buffers );
4432
- try {
4433
- await deleteFiles( bucket, prefix );
4434
- } catch ( e ) {}
4435
- req.files.answerImage = {
4436
- data: finalBuffer,
4437
- name: chunkFile.name,
4438
- mimetype: chunkFile.mimetype,
4529
+
4530
+ const updateQuery = {
4531
+ _id: new ObjectId( requestData.processedcheckListId ),
4532
+ userId: req.user._id,
4533
+ date_string: requestData.date,
4439
4534
  };
4440
- return uploadAnswerImage( req, res );
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
+ }
4441
4631
  } catch ( e ) {
4442
- logger.error( { functionName: 'chunkUpload', error: e } );
4632
+ logger.error( { function: 'submitChecklistV6', error: e, body: req.body } );
4443
4633
  return res.sendError( e, 500 );
4444
4634
  }
4445
4635
  }