tango-app-api-trax 3.9.37 → 3.9.39
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/index.js +1 -2
- package/package.json +1 -1
- package/src/controllers/internalTrax.controller.js +95 -217
- package/src/controllers/mobileTrax.controller.js +29 -69
- package/src/controllers/trax.controller.js +2 -3
- package/src/controllers/traxDashboard.controllers.js +55 -69
- package/src/hbs/flag.hbs +1 -1
- package/src/hbs/template.hbs +0 -7
- package/src/hbs/visit-checklist.hbs +91 -71
- package/src/routes/internalTraxApi.router.js +0 -1
- package/src/services/app.service.js +9 -15
- package/src/services/approver.service.js +15 -23
- package/src/services/authentication.service.js +3 -9
- package/src/services/camera.service.js +13 -19
- package/src/services/checklist.service.js +27 -35
- package/src/services/checklistAssign.service.js +38 -43
- package/src/services/checklistQuestion.service.js +34 -39
- package/src/services/checklistlog.service.js +34 -39
- package/src/services/clientRequest.service.js +2 -9
- package/src/services/clients.services.js +18 -23
- package/src/services/cluster.service.js +23 -31
- package/src/services/domain.service.js +18 -23
- package/src/services/download.services.js +25 -35
- package/src/services/group.service.js +17 -23
- package/src/services/lenskartEmployeeMapping.service.js +10 -15
- package/src/services/locus.service.js +28 -35
- package/src/services/notification.service.js +26 -35
- package/src/services/otp.service.js +13 -20
- package/src/services/planogram.service.js +2 -9
- package/src/services/processedTaskConfig.service.js +27 -35
- package/src/services/processedTaskList.service.js +26 -32
- package/src/services/processedchecklist.services.js +47 -55
- package/src/services/processedchecklistconfig.services.js +34 -39
- package/src/services/recurringFlagTracker.service.js +32 -39
- package/src/services/runAIFeatures.services.js +27 -32
- package/src/services/runAIRequest.services.js +38 -43
- package/src/services/store.service.js +27 -32
- package/src/services/tagging.service.js +2 -9
- package/src/services/taskConfig.service.js +27 -35
- package/src/services/teams.service.js +24 -35
- package/src/services/ticket.service.js +10 -15
- package/src/services/user.service.js +20 -27
- package/src/services/userAssignedstores.service.js +5 -12
- package/src/utils/visitChecklistPdf.utils.js +21 -449
- package/src/logging/activityLogFlusher.js +0 -59
- package/src/logging/activityLogMiddleware.js +0 -45
- package/src/logging/activityLogStore.js +0 -91
- package/src/logging/compressBatches.js +0 -83
- package/src/logging/config.js +0 -24
- package/src/logging/createLoggableService.js +0 -46
- package/src/logging/logExternalCall.js +0 -37
|
@@ -146,54 +146,6 @@ function flattenImageRefs( arr ) {
|
|
|
146
146
|
.filter( Boolean );
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/**
|
|
150
|
-
* Builds the ordered media list (reference → uploaded → validation) for a SINGLE
|
|
151
|
-
* answer entry, deduped within that entry. Used to render each answer's images
|
|
152
|
-
* inline next to its own text/label, instead of pooling every answer's images
|
|
153
|
-
* into one question-level grid (which decoupled labels from their images).
|
|
154
|
-
* @param {Object} ua a single entry produced by buildQuestionAnswerEntries
|
|
155
|
-
* @return {Array} list of { type, label, url, detectionStatus } for this entry, in render order
|
|
156
|
-
*/
|
|
157
|
-
function buildEntryMediaItems( ua = {} ) {
|
|
158
|
-
const items = [];
|
|
159
|
-
const seen = new Set();
|
|
160
|
-
|
|
161
|
-
const push = ( type, label, url, detectionStatus = '' ) => {
|
|
162
|
-
if ( !url || typeof url !== 'string' ) return;
|
|
163
|
-
if ( seen.has( url ) ) return;
|
|
164
|
-
seen.add( url );
|
|
165
|
-
items.push( { type, label, url, detectionStatus } );
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Each uploaded/validation image shows its OWN runAI verdict (per-image, never
|
|
169
|
-
// aggregated). References are not AI-checked, so they carry no badge.
|
|
170
|
-
const statusByUrl = buildAnswerImageStatus( ua );
|
|
171
|
-
const statusFor = ( url ) => statusByUrl.get( url ) || '';
|
|
172
|
-
|
|
173
|
-
if ( ua.hasReferenceImage ) {
|
|
174
|
-
if ( Array.isArray( ua.multiReferenceImage ) && ua.multiReferenceImage.length ) {
|
|
175
|
-
ua.multiReferenceImage.forEach( ( url ) => push( 'reference', 'Reference Image', url ) );
|
|
176
|
-
} else if ( ua.referenceImage ) {
|
|
177
|
-
push( 'reference', 'Reference Image', ua.referenceImage );
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if ( ua.answerType === 'image' && ua.answer ) {
|
|
182
|
-
push( 'uploaded', 'Uploaded Image', ua.answer, statusFor( ua.answer ) );
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if ( ua.validation ) {
|
|
186
|
-
if ( ua.validationDisplayType === 'image' && ua.validationAnswer ) {
|
|
187
|
-
push( 'validation', 'Validation Image', ua.validationAnswer, statusFor( ua.validationAnswer ) );
|
|
188
|
-
}
|
|
189
|
-
if ( ua.validationDisplayType === 'multiImage' && Array.isArray( ua.validationImage ) ) {
|
|
190
|
-
ua.validationImage.forEach( ( url ) => push( 'validation', 'Validation Image', url, statusFor( url ) ) );
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return items;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
149
|
function buildQuestionAnswerEntries( question ) {
|
|
198
150
|
const rawUserAnswers = getSourceUserAnswers( question );
|
|
199
151
|
const uniqueUserAnswers = [];
|
|
@@ -228,278 +180,21 @@ function buildQuestionAnswerEntries( question ) {
|
|
|
228
180
|
}
|
|
229
181
|
}
|
|
230
182
|
const validationImage = flattenImageRefs( userAnswer?.validationImage );
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const referenceImage = userAnswer?.referenceImage || matchedAnswer?.referenceImage || '';
|
|
235
|
-
// Left column shows a reference image only for non-image/video, non-multipleImage answer types.
|
|
236
|
-
// When there is no reference image, uploaded/validation media is rendered on the left instead of the right.
|
|
237
|
-
const hasReferenceImage = question?.answerType !== 'image/video' &&
|
|
238
|
-
question?.answerType !== 'multipleImage' &&
|
|
239
|
-
Boolean( referenceImage || multiReferenceImage.length );
|
|
240
|
-
|
|
241
|
-
// runAI verdicts for this answer, in source order. Used to tag each uploaded /
|
|
242
|
-
// validation image with its OWN verdict (by answerImage filename, else by
|
|
243
|
-
// position) so multi-image answers don't all collapse to one aggregated status.
|
|
244
|
-
const isPerAnswerType = PER_ANSWER_DETECTION_TYPES.includes( question?.answerType );
|
|
245
|
-
const runAIValues = getRunAIMatchValues( userAnswer?.runAIData );
|
|
246
|
-
const imageStatus = buildImageStatusMap( userAnswer?.runAIData );
|
|
247
|
-
|
|
248
|
-
// Choice answers (yes/no, multiple choice, dropdown) take this answer's own
|
|
249
|
-
// verdict directly — never aggregated. Other types keep the aggregate only as
|
|
250
|
-
// a last-resort fallback.
|
|
251
|
-
const detectionStatus = isPerAnswerType ?
|
|
252
|
-
verdictFromValue( runAIValues[0] ) :
|
|
253
|
-
getDetectionStatus( userAnswer?.runAIData );
|
|
254
|
-
|
|
255
|
-
const entry = {
|
|
183
|
+
|
|
184
|
+
return {
|
|
256
185
|
answer: userAnswer?.answer || '',
|
|
257
186
|
answerType: getMediaDisplayType( question?.answerType, userAnswer ),
|
|
258
|
-
referenceImage,
|
|
259
|
-
hasReferenceImage,
|
|
187
|
+
referenceImage: userAnswer?.referenceImage || matchedAnswer?.referenceImage || '',
|
|
260
188
|
multiReferenceImage,
|
|
261
189
|
remarks: userAnswer?.remarks || '',
|
|
262
190
|
sopFlag: userAnswer?.sopFlag ?? matchedAnswer?.sopFlag ?? false,
|
|
263
|
-
detectionStatus,
|
|
264
|
-
imageStatus,
|
|
265
|
-
runAIValues,
|
|
266
191
|
validation,
|
|
267
192
|
validationType,
|
|
268
193
|
validationAnswer,
|
|
269
194
|
validationImage,
|
|
270
|
-
validationVideo,
|
|
271
195
|
validationDisplayType: getValidationDisplayType( validationType ),
|
|
272
196
|
};
|
|
273
|
-
|
|
274
|
-
// Text-label answers (e.g. "Attach the image of Cat") carry their own
|
|
275
|
-
// reference/validation images. Group those images with this entry so the
|
|
276
|
-
// label and its images render together, instead of pooling every answer's
|
|
277
|
-
// images into one question-level grid. Image/video answers keep flowing
|
|
278
|
-
// into the pooled grid (handled by buildQuestionMediaItems).
|
|
279
|
-
entry.answerMedia = entry.answerType === 'text' ? buildEntryMediaItems( entry ) : [];
|
|
280
|
-
|
|
281
|
-
// Choice answers with no media show their verdict next to the answer text;
|
|
282
|
-
// when they carry validation media the badge sits on that media instead.
|
|
283
|
-
entry.showDetectionStatus = isPerAnswerType && Boolean( detectionStatus ) && !entry.answerMedia.length;
|
|
284
|
-
|
|
285
|
-
return entry;
|
|
286
|
-
} );
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Flattens a question's user answers into one ordered, deduped image list for the
|
|
292
|
-
* 2-per-row answer media grid. Order: reference images first, then uploaded images,
|
|
293
|
-
* then validation images. Videos are shown as links elsewhere, so only images are
|
|
294
|
-
* collected here. When a question has no reference image, the uploaded images simply
|
|
295
|
-
* fill the grid from the start (taking the reference slot).
|
|
296
|
-
* @param {Array} userAnswers entries produced by buildQuestionAnswerEntries
|
|
297
|
-
* @param {string} [forcedStatus] when set (image / image-video), every uploaded &
|
|
298
|
-
* validation image shows this aggregated verdict instead of its own per-image one
|
|
299
|
-
* @return {Array} list of { type, label, url, detectionStatus } media items in render order
|
|
300
|
-
*/
|
|
301
|
-
function buildQuestionMediaItems( userAnswers = [], forcedStatus = '' ) {
|
|
302
|
-
const items = [];
|
|
303
|
-
const seen = new Set();
|
|
304
|
-
|
|
305
|
-
const push = ( type, label, url, detectionStatus = '' ) => {
|
|
306
|
-
if ( !url || typeof url !== 'string' ) return;
|
|
307
|
-
if ( seen.has( url ) ) return;
|
|
308
|
-
seen.add( url );
|
|
309
|
-
items.push( { type, label, url, detectionStatus } );
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
// Entries that render their media inline with the answer (answerMedia) are
|
|
313
|
-
// excluded here so their images are not duplicated in the pooled grid.
|
|
314
|
-
const pool = userAnswers.filter( ( ua ) => !( ua.answerMedia && ua.answerMedia.length ) );
|
|
315
|
-
|
|
316
|
-
// Resolve each image's status per answer (per-image; forcedStatus aggregates
|
|
317
|
-
// image / image-video). Built per answer so positional matching stays aligned.
|
|
318
|
-
const statusByUrl = new Map();
|
|
319
|
-
pool.forEach( ( ua ) => {
|
|
320
|
-
buildAnswerImageStatus( ua, forcedStatus ).forEach( ( status, url ) => statusByUrl.set( url, status ) );
|
|
321
|
-
} );
|
|
322
|
-
const statusFor = ( url ) => statusByUrl.get( url ) || '';
|
|
323
|
-
|
|
324
|
-
// 1. Reference images first (deduped, so a single shared reference shows once).
|
|
325
|
-
// Skipped for image/video & multipleImage upload questions (hasReferenceImage
|
|
326
|
-
// is false), which have no comparison reference — only uploaded answers.
|
|
327
|
-
pool.forEach( ( ua ) => {
|
|
328
|
-
if ( !ua.hasReferenceImage ) return;
|
|
329
|
-
if ( Array.isArray( ua.multiReferenceImage ) && ua.multiReferenceImage.length ) {
|
|
330
|
-
ua.multiReferenceImage.forEach( ( url ) => push( 'reference', 'Reference Image', url ) );
|
|
331
|
-
} else if ( ua.referenceImage ) {
|
|
332
|
-
push( 'reference', 'Reference Image', ua.referenceImage );
|
|
333
|
-
}
|
|
334
|
-
} );
|
|
335
|
-
|
|
336
|
-
// 2. Uploaded images — tagged with each image's own detection status (from runAIData).
|
|
337
|
-
pool.forEach( ( ua ) => {
|
|
338
|
-
if ( ua.answerType === 'image' && ua.answer ) {
|
|
339
|
-
push( 'uploaded', 'Uploaded Image', ua.answer, statusFor( ua.answer ) );
|
|
340
|
-
}
|
|
341
|
-
} );
|
|
342
|
-
|
|
343
|
-
// 3. Validation images (single 'Capture Image' and multi 'Capture Multiple Image').
|
|
344
|
-
pool.forEach( ( ua ) => {
|
|
345
|
-
if ( !ua.validation ) return;
|
|
346
|
-
|
|
347
|
-
if ( ua.validationDisplayType === 'image' && ua.validationAnswer ) {
|
|
348
|
-
push( 'validation', 'Validation Image', ua.validationAnswer, statusFor( ua.validationAnswer ) );
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if ( ua.validationDisplayType === 'multiImage' && Array.isArray( ua.validationImage ) ) {
|
|
352
|
-
ua.validationImage.forEach( ( url ) => push( 'validation', 'Validation Image', url, statusFor( url ) ) );
|
|
353
|
-
}
|
|
354
|
-
} );
|
|
355
|
-
|
|
356
|
-
return items;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Extracts every 'Matched/Not Matched' value from a userAnswer's runAIData.
|
|
362
|
-
* Handles both shapes:
|
|
363
|
-
* - [ { answerImage, results: [ { featureName, value } ] } ] (per-image nested)
|
|
364
|
-
* - [ { featureName, value } ] (flat)
|
|
365
|
-
* @param {Array} runAIData
|
|
366
|
-
* @return {string[]} list of 'True' / 'False' values (in source order)
|
|
367
|
-
*/
|
|
368
|
-
function getRunAIMatchValues( runAIData ) {
|
|
369
|
-
if ( !Array.isArray( runAIData ) ) return [];
|
|
370
|
-
|
|
371
|
-
const values = [];
|
|
372
|
-
|
|
373
|
-
runAIData.forEach( ( item ) => {
|
|
374
|
-
if ( Array.isArray( item?.results ) ) {
|
|
375
|
-
item.results.forEach( ( res ) => {
|
|
376
|
-
if ( res?.featureName === 'Matched/Not Matched' ) values.push( res.value );
|
|
377
|
-
} );
|
|
378
|
-
} else if ( item?.featureName === 'Matched/Not Matched' ) {
|
|
379
|
-
values.push( item.value );
|
|
380
|
-
}
|
|
381
|
-
} );
|
|
382
|
-
|
|
383
|
-
return values;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// Answer types whose runAI verdict is aggregated across all uploaded images:
|
|
388
|
-
// if any image is not matched, the whole question reads 'Not Matched'. Every
|
|
389
|
-
// other type shows each image's own verdict.
|
|
390
|
-
const AGGREGATE_DETECTION_TYPES = [ 'image', 'image/video' ];
|
|
391
|
-
|
|
392
|
-
// Choice answers: each selected answer carries its OWN runAI verdict and shows it
|
|
393
|
-
// directly — never aggregated ('.every') across the question's other answers.
|
|
394
|
-
const PER_ANSWER_DETECTION_TYPES = [ 'yes/no', 'multiplechoicesingle', 'multiplechoicemultiple', 'dropdown' ];
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Maps a single runAI value to a display status.
|
|
399
|
-
* @param {string|boolean} value
|
|
400
|
-
* @return {string} 'Matched' | 'Not Matched' | ''
|
|
401
|
-
*/
|
|
402
|
-
function verdictFromValue( value ) {
|
|
403
|
-
if ( value === 'True' || value === true ) return 'Matched';
|
|
404
|
-
if ( value === 'False' || value === false ) return 'Not Matched';
|
|
405
|
-
return '';
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* The filename of an image URL/path, ignoring any query string. Used to match an
|
|
411
|
-
* uploaded/validation image against its runAIData entry (whose answerImage may
|
|
412
|
-
* carry a different prefix / signed-URL query), regardless of CDN resolution.
|
|
413
|
-
* @param {string} url
|
|
414
|
-
* @return {string}
|
|
415
|
-
*/
|
|
416
|
-
function imageBasename( url ) {
|
|
417
|
-
return String( url || '' ).split( '?' )[0].split( '/' ).pop();
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Builds a per-image detection status map from the nested runAIData shape
|
|
423
|
-
* ([ { answerImage, results: [ { featureName, value } ] } ]), keyed by the
|
|
424
|
-
* answerImage's filename. The flat shape has no per-image key, so it yields an
|
|
425
|
-
* empty map and callers fall back to the answer-level status.
|
|
426
|
-
* @param {Array} runAIData
|
|
427
|
-
* @return {Object} { [filename]: 'Matched' | 'Not Matched' }
|
|
428
|
-
*/
|
|
429
|
-
function buildImageStatusMap( runAIData ) {
|
|
430
|
-
const map = {};
|
|
431
|
-
if ( !Array.isArray( runAIData ) ) return map;
|
|
432
|
-
|
|
433
|
-
runAIData.forEach( ( item ) => {
|
|
434
|
-
if ( !item || !item.answerImage ) return;
|
|
435
|
-
const results = Array.isArray( item.results ) ? item.results : [ item ];
|
|
436
|
-
const values = results
|
|
437
|
-
.filter( ( r ) => r?.featureName === 'Matched/Not Matched' )
|
|
438
|
-
.map( ( r ) => r.value );
|
|
439
|
-
if ( !values.length ) return;
|
|
440
|
-
const matched = values.every( ( v ) => v === 'True' || v === true );
|
|
441
|
-
map[imageBasename( item.answerImage )] = matched ? 'Matched' : 'Not Matched';
|
|
442
197
|
} );
|
|
443
|
-
|
|
444
|
-
return map;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Resolves the detection status for each checked image (uploaded + validation) of a
|
|
450
|
-
* single answer, returned as a Map of url → 'Matched' | 'Not Matched' | ''.
|
|
451
|
-
*
|
|
452
|
-
* Each image gets its OWN verdict — never aggregated across the answer's other images:
|
|
453
|
-
* 1. matched explicitly by its answerImage filename (nested runAIData), else
|
|
454
|
-
* 2. matched positionally — the Nth checked image takes the Nth runAI verdict
|
|
455
|
-
* (works for the flat runAIData shape and when filenames don't line up).
|
|
456
|
-
* For image / image-video, forcedStatus (the question-level aggregate) overrides all.
|
|
457
|
-
* @param {Object} ua a single entry produced by buildQuestionAnswerEntries
|
|
458
|
-
* @param {string} [forcedStatus] aggregated status applied to every image (image/image-video)
|
|
459
|
-
* @return {Map<string,string>} url → status
|
|
460
|
-
*/
|
|
461
|
-
function buildAnswerImageStatus( ua = {}, forcedStatus = '' ) {
|
|
462
|
-
const statusByUrl = new Map();
|
|
463
|
-
const byName = ua.imageStatus || {};
|
|
464
|
-
const values = Array.isArray( ua.runAIValues ) ? ua.runAIValues : [];
|
|
465
|
-
let idx = 0;
|
|
466
|
-
|
|
467
|
-
const assign = ( url ) => {
|
|
468
|
-
if ( !url || typeof url !== 'string' || statusByUrl.has( url ) ) return;
|
|
469
|
-
let status = forcedStatus;
|
|
470
|
-
if ( !status ) {
|
|
471
|
-
const key = imageBasename( url );
|
|
472
|
-
status = ( key in byName ) ? byName[key] : verdictFromValue( values[idx] );
|
|
473
|
-
idx++;
|
|
474
|
-
}
|
|
475
|
-
statusByUrl.set( url, status );
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
if ( ua.answerType === 'image' && ua.answer ) assign( ua.answer );
|
|
479
|
-
|
|
480
|
-
if ( ua.validation ) {
|
|
481
|
-
if ( ua.validationDisplayType === 'image' && ua.validationAnswer ) assign( ua.validationAnswer );
|
|
482
|
-
if ( ua.validationDisplayType === 'multiImage' && Array.isArray( ua.validationImage ) ) {
|
|
483
|
-
ua.validationImage.forEach( assign );
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return statusByUrl;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Resolves a 'Matched' / 'Not Matched' detection status from runAIData. Handles both
|
|
493
|
-
* runAIData shapes (per-image nested results, and flat). Any non-match verdict makes
|
|
494
|
-
* the whole status 'Not Matched'. Returns '' when there is no runAI verdict to report.
|
|
495
|
-
* @param {Array} runAIData
|
|
496
|
-
* @return {string} 'Matched' | 'Not Matched' | ''
|
|
497
|
-
*/
|
|
498
|
-
function getDetectionStatus( runAIData ) {
|
|
499
|
-
const values = getRunAIMatchValues( runAIData );
|
|
500
|
-
if ( !values.length ) return '';
|
|
501
|
-
const allMatched = values.every( ( v ) => v === 'True' || v === true );
|
|
502
|
-
return allMatched ? 'Matched' : 'Not Matched';
|
|
503
198
|
}
|
|
504
199
|
|
|
505
200
|
|
|
@@ -524,8 +219,6 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
524
219
|
|
|
525
220
|
const flags = [];
|
|
526
221
|
|
|
527
|
-
let hasSopFlag = false;
|
|
528
|
-
|
|
529
222
|
|
|
530
223
|
( questionAnswer || [] ).forEach( ( section, sectionIdx ) => {
|
|
531
224
|
let sectionScore = 0;
|
|
@@ -547,10 +240,6 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
547
240
|
|
|
548
241
|
const ua = userAnswersWithRef[0];
|
|
549
242
|
|
|
550
|
-
if ( !hasSopFlag && userAnswersWithRef.some( ( entry ) => entry.sopFlag === true ) ) {
|
|
551
|
-
hasSopFlag = true;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
243
|
|
|
555
244
|
const max = q.compliance ? Math.max( ...q?.answers.map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) ) ) : 0;
|
|
556
245
|
|
|
@@ -558,34 +247,14 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
558
247
|
let score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
|
|
559
248
|
|
|
560
249
|
|
|
561
|
-
if ( q.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if ( matchValues.length ) {
|
|
570
|
-
// All images must be matched ('True'); if any one is not, treat as not matched.
|
|
571
|
-
const allMatched = matchValues.every( ( value ) => value === 'True' );
|
|
572
|
-
score = allMatched ? ( q?.answers?.[0]?.matchedCount ?? 0 ) : ( q?.answers?.[0]?.notMatchedCount ?? 0 );
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// image / image-video aggregate their runAI verdict across all uploaded images
|
|
578
|
-
// (any 'Not Matched' → whole question 'Not Matched'). Every other type keeps
|
|
579
|
-
// each image's own verdict, so this stays '' for them.
|
|
580
|
-
let aggregateDetectionStatus = '';
|
|
581
|
-
if ( AGGREGATE_DETECTION_TYPES.includes( q.answerType ) ) {
|
|
582
|
-
const aiAnswers = q.answerType === 'image/video' ?
|
|
583
|
-
( q.userAnswer || [] ).filter( ( a ) => a?.answerType === 'image' ) :
|
|
584
|
-
( q.userAnswer || [] );
|
|
585
|
-
const matchValues = aiAnswers.flatMap( ( a ) => getRunAIMatchValues( a?.runAIData ) );
|
|
586
|
-
if ( matchValues.length ) {
|
|
587
|
-
aggregateDetectionStatus = matchValues.every( ( v ) => v === 'True' || v === true ) ?
|
|
588
|
-
'Matched' : 'Not Matched';
|
|
250
|
+
if ( q.answerType == 'image' && q.compliance && q.userAnswer?.[0]?.runAIData ) {
|
|
251
|
+
let find = q.userAnswer?.[0]?.runAIData?.find( ( run ) => run?.featureName == 'Matched/Not Matched' );
|
|
252
|
+
if ( find ) {
|
|
253
|
+
if ( find?.value == 'True' ) {
|
|
254
|
+
score = q?.answers?.[0]?.matchedCount;
|
|
255
|
+
} else {
|
|
256
|
+
score = q?.answers?.[0]?.notMatchedCount;
|
|
257
|
+
}
|
|
589
258
|
}
|
|
590
259
|
}
|
|
591
260
|
|
|
@@ -647,8 +316,6 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
647
316
|
|
|
648
317
|
answerDisplay: isYes ? 'Yes' : ( isNo ? 'No' : ( answerText && answerText.startsWith( 'http' ) ? 'Image' : ( answerText || '—' ) ) ),
|
|
649
318
|
|
|
650
|
-
mediaItems: buildQuestionMediaItems( userAnswersWithRef, aggregateDetectionStatus ),
|
|
651
|
-
|
|
652
319
|
userAnswer: userAnswersWithRef.length ? userAnswersWithRef : [ {
|
|
653
320
|
answer: '',
|
|
654
321
|
answerType: 'text',
|
|
@@ -672,15 +339,15 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
672
339
|
|
|
673
340
|
sectionName: section.sectionName || `Section ${sectionIdx + 1}`,
|
|
674
341
|
|
|
675
|
-
targetScore: sectionMax
|
|
342
|
+
targetScore: sectionMax,
|
|
676
343
|
|
|
677
|
-
actualScore:
|
|
344
|
+
actualScore: sectionScore,
|
|
678
345
|
|
|
679
346
|
questionsCount,
|
|
680
347
|
|
|
681
348
|
passedCount,
|
|
682
349
|
|
|
683
|
-
percentage: sectionMax > 0 ? Math.round( ( sectionScore / sectionMax ) * 100 ) :
|
|
350
|
+
percentage: sectionMax > 0 ? Math.round( ( sectionScore / sectionMax ) * 100 ) : 0,
|
|
684
351
|
|
|
685
352
|
} );
|
|
686
353
|
|
|
@@ -706,7 +373,7 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
706
373
|
const numQuestions = questionAnswer.reduce( ( sum, s ) => sum + ( s.questions?.length || 0 ), 0 );
|
|
707
374
|
|
|
708
375
|
|
|
709
|
-
return { totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions
|
|
376
|
+
return { totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions };
|
|
710
377
|
}
|
|
711
378
|
|
|
712
379
|
|
|
@@ -728,7 +395,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
728
395
|
|
|
729
396
|
const {
|
|
730
397
|
|
|
731
|
-
totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions,
|
|
398
|
+
totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions,
|
|
732
399
|
|
|
733
400
|
} = mapSectionsFromQuestionAnswer( questionAnswer );
|
|
734
401
|
|
|
@@ -814,9 +481,6 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
814
481
|
|
|
815
482
|
let referenceId = doc.coverage == 'store' ? doc?.storeName : doc?.userName;
|
|
816
483
|
|
|
817
|
-
const userImage = doc.userImage || '';
|
|
818
|
-
const userSignature = doc.userSignature || '';
|
|
819
|
-
|
|
820
484
|
return {
|
|
821
485
|
|
|
822
486
|
|
|
@@ -842,22 +506,12 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
842
506
|
|
|
843
507
|
numFlags: typeof doc.questionFlag === 'number' ? doc.questionFlag : flags.length,
|
|
844
508
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
runAIFlag: typeof doc.runAIFlag === 'number' ? doc.runAIFlag : 0,
|
|
848
|
-
|
|
849
|
-
showRunAIFlag: ( typeof doc.runAIQuestionCount === 'number' ? doc.runAIQuestionCount : 0 ) > 0,
|
|
509
|
+
aiBreached: typeof doc.runAIFlag === 'number' ? doc.runAIFlag : 0,
|
|
850
510
|
|
|
851
511
|
submittedBy: doc.userName || '--',
|
|
852
512
|
|
|
853
513
|
country: doc.country || '--',
|
|
854
514
|
|
|
855
|
-
userImage,
|
|
856
|
-
|
|
857
|
-
userSignature,
|
|
858
|
-
|
|
859
|
-
showUserVerification: Boolean( userImage || userSignature ),
|
|
860
|
-
|
|
861
515
|
hasCompliancePage,
|
|
862
516
|
|
|
863
517
|
detailPageStart,
|
|
@@ -913,7 +567,7 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
913
567
|
|
|
914
568
|
const {
|
|
915
569
|
|
|
916
|
-
totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions,
|
|
570
|
+
totalScore, maxScore, sectionInsights, questionAnswers, flags, numQuestions,
|
|
917
571
|
|
|
918
572
|
} = mapSectionsFromQuestionAnswer( questionAnswer );
|
|
919
573
|
|
|
@@ -968,9 +622,6 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
968
622
|
} );
|
|
969
623
|
}
|
|
970
624
|
|
|
971
|
-
const userImage = checklistAnswer?.userImage || checklistInfo?.userImage || '';
|
|
972
|
-
const userSignature = checklistAnswer?.userSignature || checklistInfo?.userSignature || '';
|
|
973
|
-
|
|
974
625
|
return {
|
|
975
626
|
|
|
976
627
|
brandLogo: brandInfo.brandLogo || '',
|
|
@@ -999,22 +650,12 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
999
650
|
|
|
1000
651
|
numFlags: checklistAnswer?.flagCount ?? flags.length,
|
|
1001
652
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
runAIFlag: checklistAnswer?.runAIFlag ?? checklistInfo?.runAIFlag ?? checklistAnswer?.aiBreachedCount ?? 0,
|
|
1005
|
-
|
|
1006
|
-
showRunAIFlag: ( checklistInfo?.runAIQuestionCount ?? checklistAnswer?.runAIQuestionCount ?? 0 ) > 0,
|
|
653
|
+
aiBreached: checklistAnswer?.aiBreachedCount ?? 0,
|
|
1007
654
|
|
|
1008
655
|
submittedBy: checklistInfo?.submittedBy || storeProfile?.userName || '--',
|
|
1009
656
|
|
|
1010
657
|
country: storeProfile?.Country || '--',
|
|
1011
658
|
|
|
1012
|
-
userImage,
|
|
1013
|
-
|
|
1014
|
-
userSignature,
|
|
1015
|
-
|
|
1016
|
-
showUserVerification: Boolean( userImage || userSignature ),
|
|
1017
|
-
|
|
1018
659
|
hasCompliancePage,
|
|
1019
660
|
|
|
1020
661
|
detailPageStart,
|
|
@@ -1141,11 +782,6 @@ export function createImageCache() {
|
|
|
1141
782
|
}
|
|
1142
783
|
|
|
1143
784
|
|
|
1144
|
-
if ( resolvedData.userImage && resolvedData.userImage.startsWith( 'http' ) ) {
|
|
1145
|
-
urls.add( resolvedData.userImage );
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
785
|
const collectFromSection = ( section ) => {
|
|
1150
786
|
section.questions?.forEach( ( q ) => {
|
|
1151
787
|
if ( q.questionReferenceImage && q.questionReferenceImage.startsWith( 'http' ) ) urls.add( q.questionReferenceImage );
|
|
@@ -1169,14 +805,6 @@ export function createImageCache() {
|
|
|
1169
805
|
if ( typeof u === 'string' && u.startsWith( 'http' ) ) urls.add( u );
|
|
1170
806
|
} );
|
|
1171
807
|
}
|
|
1172
|
-
|
|
1173
|
-
ua.answerMedia?.forEach( ( m ) => {
|
|
1174
|
-
if ( typeof m.url === 'string' && m.url.startsWith( 'http' ) ) urls.add( m.url );
|
|
1175
|
-
} );
|
|
1176
|
-
} );
|
|
1177
|
-
|
|
1178
|
-
q.mediaItems?.forEach( ( m ) => {
|
|
1179
|
-
if ( typeof m.url === 'string' && m.url.startsWith( 'http' ) ) urls.add( m.url );
|
|
1180
808
|
} );
|
|
1181
809
|
} );
|
|
1182
810
|
};
|
|
@@ -1214,11 +842,6 @@ export function createImageCache() {
|
|
|
1214
842
|
}
|
|
1215
843
|
|
|
1216
844
|
|
|
1217
|
-
if ( resolvedData.userImage ) {
|
|
1218
|
-
resolvedData.userImage = cache.get( resolvedData.userImage ) || resolvedData.userImage;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
845
|
const replaceInSection = ( section ) => {
|
|
1223
846
|
section.questions?.forEach( ( q ) => {
|
|
1224
847
|
if ( q.questionReferenceImage && cache.has( q.questionReferenceImage ) ) q.questionReferenceImage = cache.get( q.questionReferenceImage );
|
|
@@ -1240,19 +863,7 @@ export function createImageCache() {
|
|
|
1240
863
|
if ( ua.validationDisplayType === 'multiImage' && ua.validationImage?.length ) {
|
|
1241
864
|
ua.validationImage = ua.validationImage.map( ( u ) => ( cache.has( u ) ? cache.get( u ) : u ) );
|
|
1242
865
|
}
|
|
1243
|
-
|
|
1244
|
-
if ( ua.answerMedia?.length ) {
|
|
1245
|
-
ua.answerMedia.forEach( ( m ) => {
|
|
1246
|
-
if ( cache.has( m.url ) ) m.url = cache.get( m.url );
|
|
1247
|
-
} );
|
|
1248
|
-
}
|
|
1249
866
|
} );
|
|
1250
|
-
|
|
1251
|
-
if ( q.mediaItems?.length ) {
|
|
1252
|
-
q.mediaItems.forEach( ( m ) => {
|
|
1253
|
-
if ( cache.has( m.url ) ) m.url = cache.get( m.url );
|
|
1254
|
-
} );
|
|
1255
|
-
}
|
|
1256
867
|
} );
|
|
1257
868
|
};
|
|
1258
869
|
|
|
@@ -1325,27 +936,11 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
|
|
|
1325
936
|
ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
|
|
1326
937
|
}
|
|
1327
938
|
|
|
1328
|
-
if ( ua?.validationVideo?.length ) {
|
|
1329
|
-
ua.validationVideo = ua.validationVideo.map( ( ele ) => resolveUrl( ele ) );
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
939
|
|
|
1333
940
|
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
1334
941
|
ua.answer = resolveUrl( ua.answer );
|
|
1335
942
|
}
|
|
1336
|
-
|
|
1337
|
-
if ( ua.answerMedia?.length ) {
|
|
1338
|
-
ua.answerMedia.forEach( ( m ) => {
|
|
1339
|
-
m.url = resolveUrl( m.url );
|
|
1340
|
-
} );
|
|
1341
|
-
}
|
|
1342
943
|
} );
|
|
1343
|
-
|
|
1344
|
-
if ( q.mediaItems?.length ) {
|
|
1345
|
-
q.mediaItems.forEach( ( m ) => {
|
|
1346
|
-
m.url = resolveUrl( m.url );
|
|
1347
|
-
} );
|
|
1348
|
-
}
|
|
1349
944
|
} );
|
|
1350
945
|
};
|
|
1351
946
|
|
|
@@ -1359,12 +954,9 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
|
|
|
1359
954
|
|
|
1360
955
|
resolvedData.sections?.forEach( resolveQuestionMedia );
|
|
1361
956
|
|
|
1362
|
-
if ( resolvedData?.brandLogo && !resolvedData?.brandLogo?.startsWith( 'http' ) ) {
|
|
1363
|
-
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1364
|
-
}
|
|
1365
957
|
|
|
1366
|
-
if ( resolvedData
|
|
1367
|
-
resolvedData.
|
|
958
|
+
if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
|
|
959
|
+
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1368
960
|
}
|
|
1369
961
|
|
|
1370
962
|
|
|
@@ -1478,26 +1070,10 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
1478
1070
|
ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
|
|
1479
1071
|
}
|
|
1480
1072
|
|
|
1481
|
-
if ( ua.validationVideo?.length ) {
|
|
1482
|
-
ua.validationVideo = ua.validationVideo.map( ( ele ) => resolveUrl( ele ) );
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
1073
|
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
1486
1074
|
ua.answer = resolveUrl( ua.answer );
|
|
1487
1075
|
}
|
|
1488
|
-
|
|
1489
|
-
if ( ua.answerMedia?.length ) {
|
|
1490
|
-
ua.answerMedia.forEach( ( m ) => {
|
|
1491
|
-
m.url = resolveUrl( m.url );
|
|
1492
|
-
} );
|
|
1493
|
-
}
|
|
1494
1076
|
} );
|
|
1495
|
-
|
|
1496
|
-
if ( q.mediaItems?.length ) {
|
|
1497
|
-
q.mediaItems.forEach( ( m ) => {
|
|
1498
|
-
m.url = resolveUrl( m.url );
|
|
1499
|
-
} );
|
|
1500
|
-
}
|
|
1501
1077
|
} );
|
|
1502
1078
|
};
|
|
1503
1079
|
|
|
@@ -1511,10 +1087,6 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
1511
1087
|
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1512
1088
|
}
|
|
1513
1089
|
|
|
1514
|
-
if ( resolvedData.userImage && !resolvedData.userImage.startsWith( 'http' ) && !resolvedData.userImage.startsWith( 'data:' ) ) {
|
|
1515
|
-
resolvedData.userImage = resolveUrl( resolvedData.userImage );
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
1090
|
|
|
1519
1091
|
const html = template( resolvedData );
|
|
1520
1092
|
|