tango-app-api-trax 3.7.87 → 3.7.89
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.
|
@@ -38,8 +38,8 @@ function toPlainObject( doc ) {
|
|
|
38
38
|
* @param {string} [checklistName] Full checklist name from API or DB
|
|
39
39
|
* @return {{ titleLine1: string, titleLine2: string }} titleLine1 and titleLine2 for the PDF cover
|
|
40
40
|
*/
|
|
41
|
-
function splitCoverTitle( checklistName ) {
|
|
42
|
-
const n = ( checklistName || '' ).trim();
|
|
41
|
+
function splitCoverTitle( checklistName ) {
|
|
42
|
+
const n = ( checklistName || '' ).trim();
|
|
43
43
|
|
|
44
44
|
if ( !n ) {
|
|
45
45
|
return { titleLine1: 'AOM', titleLine2: 'Visit Checklist' };
|
|
@@ -50,122 +50,122 @@ function splitCoverTitle( checklistName ) {
|
|
|
50
50
|
if ( m ) {
|
|
51
51
|
return { titleLine1: m[1].trim() || 'AOM', titleLine2: 'Visit Checklist' };
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
return { titleLine1: n, titleLine2: 'Visit Checklist' };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getMatchedAnswerForUserAnswer( question, userAnswer ) {
|
|
58
|
-
if ( !userAnswer || !Array.isArray( question?.answers ) ) return null;
|
|
59
|
-
|
|
60
|
-
return question.answers.find( ( answer ) => {
|
|
61
|
-
if ( userAnswer.no !== undefined && answer.index === userAnswer.no ) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if ( userAnswer.index !== undefined && answer.index === userAnswer.index ) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if ( userAnswer.answeroptionNumber !== undefined && answer.answeroptionNumber === userAnswer.answeroptionNumber ) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return answer.answer === userAnswer.answer;
|
|
74
|
-
} ) || null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isMeaningfulUserAnswer( userAnswer = {} ) {
|
|
78
|
-
return Boolean(
|
|
79
|
-
userAnswer &&
|
|
80
|
-
(
|
|
81
|
-
( typeof userAnswer.answer === 'string' && userAnswer.answer.trim() ) ||
|
|
82
|
-
( typeof userAnswer.validationAnswer === 'string' && userAnswer.validationAnswer.trim() ) ||
|
|
83
|
-
( typeof userAnswer.referenceImage === 'string' && userAnswer.referenceImage.trim() )
|
|
84
|
-
),
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function getSourceUserAnswers( question ) {
|
|
89
|
-
const fromUserAnswer = Array.isArray( question?.userAnswer ) ? question.userAnswer : [];
|
|
90
|
-
const fromMultiAnswer = Array.isArray( question?.Multianswer ) ? question.Multianswer : [];
|
|
91
|
-
const isMultiAnswerQuestion = question?.answerType === 'multiplechoicemultiple' ||
|
|
92
|
-
question?.answerType === 'multipleImage' ||
|
|
93
|
-
question?.answerType === 'image/video' ||
|
|
94
|
-
( question?.answerType === 'dropdown' && question?.allowMultiple );
|
|
95
|
-
|
|
96
|
-
if ( isMultiAnswerQuestion ) {
|
|
97
|
-
return [ ...fromUserAnswer, ...fromMultiAnswer ];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return fromUserAnswer;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getUserAnswerKey( userAnswer = {}, index = 0 ) {
|
|
104
|
-
return [
|
|
105
|
-
userAnswer.no ?? '',
|
|
106
|
-
userAnswer.index ?? '',
|
|
107
|
-
userAnswer.answeroptionNumber ?? '',
|
|
108
|
-
userAnswer.answer ?? '',
|
|
109
|
-
userAnswer.validationAnswer ?? '',
|
|
110
|
-
userAnswer.referenceImage ?? '',
|
|
111
|
-
index,
|
|
112
|
-
].join( '|' );
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function getMediaDisplayType( questionAnswerType, userAnswer = {} ) {
|
|
116
|
-
if ( questionAnswerType === 'video' ) {
|
|
117
|
-
return userAnswer.answerType === 'image' ? 'image' : 'video';
|
|
118
|
-
}
|
|
119
|
-
if ( [ 'image', 'descriptiveImage', 'multipleImage' ].includes( questionAnswerType ) ) {
|
|
120
|
-
return userAnswer.answerType === 'video' ? 'video' : 'image';
|
|
121
|
-
}
|
|
122
|
-
if ( questionAnswerType === 'image/video' ) {
|
|
123
|
-
return userAnswer.answerType === 'video' ? 'video' : 'image';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return 'text';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function getValidationDisplayType( validationType ) {
|
|
130
|
-
if ( validationType === 'Capture Image' ) return 'image';
|
|
131
|
-
if ( validationType === 'Capture Video' ) return 'video';
|
|
132
|
-
return 'text';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function buildQuestionAnswerEntries( question ) {
|
|
136
|
-
const rawUserAnswers = getSourceUserAnswers( question );
|
|
137
|
-
const uniqueUserAnswers = [];
|
|
138
|
-
const seenUserAnswers = new Set();
|
|
139
|
-
|
|
140
|
-
rawUserAnswers.forEach( ( userAnswer, index ) => {
|
|
141
|
-
if ( !isMeaningfulUserAnswer( userAnswer ) ) return;
|
|
142
|
-
|
|
143
|
-
const key = getUserAnswerKey( userAnswer, index );
|
|
144
|
-
if ( seenUserAnswers.has( key ) ) return;
|
|
145
|
-
|
|
146
|
-
seenUserAnswers.add( key );
|
|
147
|
-
uniqueUserAnswers.push( userAnswer );
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
return uniqueUserAnswers.map( ( userAnswer ) => {
|
|
151
|
-
const matchedAnswer = getMatchedAnswerForUserAnswer( question, userAnswer );
|
|
152
|
-
const validation = matchedAnswer?.validation ?? userAnswer?.validation ?? false;
|
|
153
|
-
const validationType = matchedAnswer?.validationType || userAnswer?.validationType || '';
|
|
154
|
-
const validationAnswer = matchedAnswer?.validationAnswer || userAnswer?.validationAnswer || '';
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
answer: userAnswer?.answer || '',
|
|
158
|
-
answerType: getMediaDisplayType( question?.answerType, userAnswer ),
|
|
159
|
-
referenceImage: userAnswer?.referenceImage || matchedAnswer?.referenceImage || '',
|
|
160
|
-
remarks: userAnswer?.remarks || '',
|
|
161
|
-
sopFlag: userAnswer?.sopFlag ?? matchedAnswer?.sopFlag ?? false,
|
|
162
|
-
validation,
|
|
163
|
-
validationType,
|
|
164
|
-
validationAnswer,
|
|
165
|
-
validationDisplayType: getValidationDisplayType( validationType ),
|
|
166
|
-
};
|
|
167
|
-
} );
|
|
168
|
-
}
|
|
53
|
+
|
|
54
|
+
return { titleLine1: n, titleLine2: 'Visit Checklist' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getMatchedAnswerForUserAnswer( question, userAnswer ) {
|
|
58
|
+
if ( !userAnswer || !Array.isArray( question?.answers ) ) return null;
|
|
59
|
+
|
|
60
|
+
return question.answers.find( ( answer ) => {
|
|
61
|
+
if ( userAnswer.no !== undefined && answer.index === userAnswer.no ) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ( userAnswer.index !== undefined && answer.index === userAnswer.index ) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if ( userAnswer.answeroptionNumber !== undefined && answer.answeroptionNumber === userAnswer.answeroptionNumber ) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return answer.answer === userAnswer.answer;
|
|
74
|
+
} ) || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isMeaningfulUserAnswer( userAnswer = {} ) {
|
|
78
|
+
return Boolean(
|
|
79
|
+
userAnswer &&
|
|
80
|
+
(
|
|
81
|
+
( typeof userAnswer.answer === 'string' && userAnswer.answer.trim() ) ||
|
|
82
|
+
( typeof userAnswer.validationAnswer === 'string' && userAnswer.validationAnswer.trim() ) ||
|
|
83
|
+
( typeof userAnswer.referenceImage === 'string' && userAnswer.referenceImage.trim() )
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSourceUserAnswers( question ) {
|
|
89
|
+
const fromUserAnswer = Array.isArray( question?.userAnswer ) ? question.userAnswer : [];
|
|
90
|
+
const fromMultiAnswer = Array.isArray( question?.Multianswer ) ? question.Multianswer : [];
|
|
91
|
+
const isMultiAnswerQuestion = question?.answerType === 'multiplechoicemultiple' ||
|
|
92
|
+
question?.answerType === 'multipleImage' ||
|
|
93
|
+
question?.answerType === 'image/video' ||
|
|
94
|
+
( question?.answerType === 'dropdown' && question?.allowMultiple );
|
|
95
|
+
|
|
96
|
+
if ( isMultiAnswerQuestion ) {
|
|
97
|
+
return [ ...fromUserAnswer, ...fromMultiAnswer ];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return fromUserAnswer;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getUserAnswerKey( userAnswer = {}, index = 0 ) {
|
|
104
|
+
return [
|
|
105
|
+
userAnswer.no ?? '',
|
|
106
|
+
userAnswer.index ?? '',
|
|
107
|
+
userAnswer.answeroptionNumber ?? '',
|
|
108
|
+
userAnswer.answer ?? '',
|
|
109
|
+
userAnswer.validationAnswer ?? '',
|
|
110
|
+
userAnswer.referenceImage ?? '',
|
|
111
|
+
index,
|
|
112
|
+
].join( '|' );
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getMediaDisplayType( questionAnswerType, userAnswer = {} ) {
|
|
116
|
+
if ( questionAnswerType === 'video' ) {
|
|
117
|
+
return userAnswer.answerType === 'image' ? 'image' : 'video';
|
|
118
|
+
}
|
|
119
|
+
if ( [ 'image', 'descriptiveImage', 'multipleImage' ].includes( questionAnswerType ) ) {
|
|
120
|
+
return userAnswer.answerType === 'video' ? 'video' : 'image';
|
|
121
|
+
}
|
|
122
|
+
if ( questionAnswerType === 'image/video' ) {
|
|
123
|
+
return userAnswer.answerType === 'video' ? 'video' : 'image';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return 'text';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getValidationDisplayType( validationType ) {
|
|
130
|
+
if ( validationType === 'Capture Image' ) return 'image';
|
|
131
|
+
if ( validationType === 'Capture Video' ) return 'video';
|
|
132
|
+
return 'text';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildQuestionAnswerEntries( question ) {
|
|
136
|
+
const rawUserAnswers = getSourceUserAnswers( question );
|
|
137
|
+
const uniqueUserAnswers = [];
|
|
138
|
+
const seenUserAnswers = new Set();
|
|
139
|
+
|
|
140
|
+
rawUserAnswers.forEach( ( userAnswer, index ) => {
|
|
141
|
+
if ( !isMeaningfulUserAnswer( userAnswer ) ) return;
|
|
142
|
+
|
|
143
|
+
const key = getUserAnswerKey( userAnswer, index );
|
|
144
|
+
if ( seenUserAnswers.has( key ) ) return;
|
|
145
|
+
|
|
146
|
+
seenUserAnswers.add( key );
|
|
147
|
+
uniqueUserAnswers.push( userAnswer );
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
return uniqueUserAnswers.map( ( userAnswer ) => {
|
|
151
|
+
const matchedAnswer = getMatchedAnswerForUserAnswer( question, userAnswer );
|
|
152
|
+
const validation = matchedAnswer?.validation ?? userAnswer?.validation ?? false;
|
|
153
|
+
const validationType = matchedAnswer?.validationType || userAnswer?.validationType || '';
|
|
154
|
+
const validationAnswer = matchedAnswer?.validationAnswer || userAnswer?.validationAnswer || '';
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
answer: userAnswer?.answer || '',
|
|
158
|
+
answerType: getMediaDisplayType( question?.answerType, userAnswer ),
|
|
159
|
+
referenceImage: userAnswer?.referenceImage || matchedAnswer?.referenceImage || '',
|
|
160
|
+
remarks: userAnswer?.remarks || '',
|
|
161
|
+
sopFlag: userAnswer?.sopFlag ?? matchedAnswer?.sopFlag ?? false,
|
|
162
|
+
validation,
|
|
163
|
+
validationType,
|
|
164
|
+
validationAnswer,
|
|
165
|
+
validationDisplayType: getValidationDisplayType( validationType ),
|
|
166
|
+
};
|
|
167
|
+
} );
|
|
168
|
+
}
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
/**
|
|
@@ -193,28 +193,29 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
193
193
|
( questionAnswer || [] ).forEach( ( section, sectionIdx ) => {
|
|
194
194
|
let sectionScore = 0;
|
|
195
195
|
|
|
196
|
-
let sectionMax = 0;
|
|
197
|
-
|
|
198
|
-
let passedCount = 0;
|
|
199
|
-
|
|
200
|
-
const questions = [];
|
|
201
|
-
const sourceQuestionsCount = section.questions?.length || 0;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
( section.questions || [] ).forEach( ( q ) => {
|
|
205
|
-
const userAnswersWithRef = buildQuestionAnswerEntries( q );
|
|
206
|
-
|
|
207
|
-
if ( !userAnswersWithRef.length ) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const ua = userAnswersWithRef[0];
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
196
|
+
let sectionMax = 0;
|
|
197
|
+
|
|
198
|
+
let passedCount = 0;
|
|
199
|
+
|
|
200
|
+
const questions = [];
|
|
201
|
+
const sourceQuestionsCount = section.questions?.length || 0;
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
( section.questions || [] ).forEach( ( q ) => {
|
|
205
|
+
const userAnswersWithRef = buildQuestionAnswerEntries( q );
|
|
206
|
+
|
|
207
|
+
if ( !userAnswersWithRef.length ) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const ua = userAnswersWithRef[0];
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
const max = q.compliance ? Math.max( ...q?.answers.map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) ) ) : 0;
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
const score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
|
|
218
|
+
|
|
218
219
|
|
|
219
220
|
sectionScore += score;
|
|
220
221
|
|
|
@@ -227,7 +228,7 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
227
228
|
if ( score >= 10 ) passedCount++;
|
|
228
229
|
|
|
229
230
|
|
|
230
|
-
const answerText = ( ua?.answer || '' ).toString().trim();
|
|
231
|
+
const answerText = ( ua?.answer || '' ).toString().trim();
|
|
231
232
|
|
|
232
233
|
const isYes = answerText.toLowerCase() === 'yes';
|
|
233
234
|
|
|
@@ -249,43 +250,43 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
249
250
|
} );
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
questions.push( {
|
|
253
|
+
questions.push( {
|
|
253
254
|
|
|
254
255
|
qno: q.qno || q.uniqueqno,
|
|
255
256
|
|
|
256
|
-
qname: q.qname || q.oldQname,
|
|
257
|
-
|
|
258
|
-
score: score,
|
|
259
|
-
|
|
260
|
-
remarks: ( q.userAnswer?.[0]?.remarks ) || q.remarks || '',
|
|
261
|
-
|
|
262
|
-
answerType: q.answerType || '',
|
|
263
|
-
|
|
264
|
-
compliance: Boolean( q.compliance ),
|
|
265
|
-
|
|
266
|
-
isYes: isYes || ( !isNo && score >= 10 ),
|
|
257
|
+
qname: q.qname || q.oldQname,
|
|
258
|
+
|
|
259
|
+
score: score,
|
|
260
|
+
|
|
261
|
+
remarks: ( q.userAnswer?.[0]?.remarks ) || q.remarks || '',
|
|
262
|
+
|
|
263
|
+
answerType: q.answerType || '',
|
|
264
|
+
|
|
265
|
+
compliance: Boolean( q.compliance ),
|
|
266
|
+
|
|
267
|
+
isYes: isYes || ( !isNo && score >= 10 ),
|
|
267
268
|
|
|
268
269
|
isNo,
|
|
269
270
|
|
|
270
271
|
answerDisplay: isYes ? 'Yes' : ( isNo ? 'No' : ( answerText && answerText.startsWith( 'http' ) ? 'Image' : ( answerText || '—' ) ) ),
|
|
271
272
|
|
|
272
|
-
userAnswer: userAnswersWithRef.length ? userAnswersWithRef : [ {
|
|
273
|
-
answer: '',
|
|
274
|
-
answerType: 'text',
|
|
275
|
-
remarks: '',
|
|
276
|
-
referenceImage: '',
|
|
277
|
-
imageMatchStatus: 'Matched',
|
|
278
|
-
validation: false,
|
|
279
|
-
validationType: '',
|
|
280
|
-
validationAnswer: '',
|
|
281
|
-
validationDisplayType: 'text',
|
|
282
|
-
} ],
|
|
283
|
-
|
|
284
|
-
} );
|
|
285
|
-
} );
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const questionsCount = questions.length || sourceQuestionsCount;
|
|
273
|
+
userAnswer: userAnswersWithRef.length ? userAnswersWithRef : [ {
|
|
274
|
+
answer: '',
|
|
275
|
+
answerType: 'text',
|
|
276
|
+
remarks: '',
|
|
277
|
+
referenceImage: '',
|
|
278
|
+
imageMatchStatus: 'Matched',
|
|
279
|
+
validation: false,
|
|
280
|
+
validationType: '',
|
|
281
|
+
validationAnswer: '',
|
|
282
|
+
validationDisplayType: 'text',
|
|
283
|
+
} ],
|
|
284
|
+
|
|
285
|
+
} );
|
|
286
|
+
} );
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
const questionsCount = questions.length || sourceQuestionsCount;
|
|
289
290
|
|
|
290
291
|
|
|
291
292
|
sectionInsights.push( {
|
|
@@ -340,7 +341,7 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
340
341
|
|
|
341
342
|
*/
|
|
342
343
|
|
|
343
|
-
export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, brandInfo = {} ) {
|
|
344
|
+
export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, brandInfo = {} ) {
|
|
344
345
|
const doc = toPlainObject( processedDoc );
|
|
345
346
|
|
|
346
347
|
const questionAnswer = doc.questionAnswers || [];
|
|
@@ -371,28 +372,28 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
371
372
|
( doc.submitTime_string?.split( ',' )?.[0]?.trim() || '' );
|
|
372
373
|
|
|
373
374
|
|
|
374
|
-
const hasCompliancePage = questionAnswer.some( ( section ) =>
|
|
375
|
-
( section.questions || [] ).some( ( question ) => Boolean( question.compliance ) ),
|
|
376
|
-
);
|
|
377
|
-
const detailPageStart = hasCompliancePage ? 3 : 2;
|
|
378
|
-
const detailPageGroups = [];
|
|
379
|
-
|
|
380
|
-
for ( let i = 0; i < questionAnswers.length; i += 2 ) {
|
|
381
|
-
const groupSections = questionAnswers.slice( i, i + 2 );
|
|
382
|
-
const pageNumber = detailPageStart + detailPageGroups.length;
|
|
383
|
-
|
|
384
|
-
groupSections.forEach( ( section ) => {
|
|
385
|
-
section.pageNumber = pageNumber;
|
|
386
|
-
} );
|
|
387
|
-
|
|
388
|
-
detailPageGroups.push( {
|
|
389
|
-
pageNumber,
|
|
390
|
-
sections: groupSections,
|
|
391
|
-
isLastGroup: i + 2 >= questionAnswers.length,
|
|
392
|
-
} );
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const totalPages = 1 + ( hasCompliancePage ? 1 : 0 ) + detailPageGroups.length;
|
|
375
|
+
const hasCompliancePage = questionAnswer.some( ( section ) =>
|
|
376
|
+
( section.questions || [] ).some( ( question ) => Boolean( question.compliance ) ),
|
|
377
|
+
);
|
|
378
|
+
const detailPageStart = hasCompliancePage ? 3 : 2;
|
|
379
|
+
const detailPageGroups = [];
|
|
380
|
+
|
|
381
|
+
for ( let i = 0; i < questionAnswers.length; i += 2 ) {
|
|
382
|
+
const groupSections = questionAnswers.slice( i, i + 2 );
|
|
383
|
+
const pageNumber = detailPageStart + detailPageGroups.length;
|
|
384
|
+
|
|
385
|
+
groupSections.forEach( ( section ) => {
|
|
386
|
+
section.pageNumber = pageNumber;
|
|
387
|
+
} );
|
|
388
|
+
|
|
389
|
+
detailPageGroups.push( {
|
|
390
|
+
pageNumber,
|
|
391
|
+
sections: groupSections,
|
|
392
|
+
isLastGroup: i + 2 >= questionAnswers.length,
|
|
393
|
+
} );
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const totalPages = 1 + ( hasCompliancePage ? 1 : 0 ) + detailPageGroups.length;
|
|
396
397
|
|
|
397
398
|
|
|
398
399
|
const checklistName = doc.checkListName || 'Visit Checklist';
|
|
@@ -401,8 +402,41 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
401
402
|
|
|
402
403
|
const { titleLine1, titleLine2 } = splitCoverTitle( checklistName );
|
|
403
404
|
|
|
405
|
+
|
|
406
|
+
let historyData = [];
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
const rawHistory = doc.historyData;
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if ( Array.isArray( rawHistory ) && rawHistory.length > 0 ) {
|
|
413
|
+
historyData = rawHistory.map( ( h ) => {
|
|
414
|
+
const pct = Math.min( 100, Math.max( h.complianceScore ?? 0 ) );
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
date: h.date,
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
value: pct,
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
percentage: pct,
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
barHeight: Math.round( ( pct / 100 ) * 140 ),
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
};
|
|
433
|
+
} );
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
404
437
|
return {
|
|
405
438
|
|
|
439
|
+
|
|
406
440
|
brandLogo: brandInfo.brandLogo || '',
|
|
407
441
|
|
|
408
442
|
brandName: brandInfo.clientName || brandInfo.brandName || 'lenskart',
|
|
@@ -427,15 +461,15 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
427
461
|
|
|
428
462
|
aiBreached: typeof doc.runAIFlag === 'number' ? doc.runAIFlag : 0,
|
|
429
463
|
|
|
430
|
-
submittedBy: doc.userName || '--',
|
|
431
|
-
|
|
432
|
-
country: doc.country || '--',
|
|
433
|
-
|
|
434
|
-
hasCompliancePage,
|
|
435
|
-
|
|
436
|
-
detailPageStart,
|
|
437
|
-
|
|
438
|
-
totalPages,
|
|
464
|
+
submittedBy: doc.userName || '--',
|
|
465
|
+
|
|
466
|
+
country: doc.country || '--',
|
|
467
|
+
|
|
468
|
+
hasCompliancePage,
|
|
469
|
+
|
|
470
|
+
detailPageStart,
|
|
471
|
+
|
|
472
|
+
totalPages,
|
|
439
473
|
|
|
440
474
|
totalPercentage,
|
|
441
475
|
|
|
@@ -449,7 +483,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
449
483
|
|
|
450
484
|
'',
|
|
451
485
|
|
|
452
|
-
historyData
|
|
486
|
+
historyData,
|
|
453
487
|
|
|
454
488
|
sectionInsights,
|
|
455
489
|
|
|
@@ -492,26 +526,26 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
492
526
|
const totalPercentage = maxScore > 0 ? Math.round( ( totalScore / maxScore ) * 100 ) : 0;
|
|
493
527
|
|
|
494
528
|
|
|
495
|
-
const hasCompliancePage = Boolean( checklistInfo.complianceCount );
|
|
496
|
-
const detailPageStart = hasCompliancePage ? 3 : 2;
|
|
497
|
-
const detailPageGroups = [];
|
|
498
|
-
|
|
499
|
-
for ( let i = 0; i < questionAnswers.length; i += 2 ) {
|
|
500
|
-
const groupSections = questionAnswers.slice( i, i + 2 );
|
|
501
|
-
const pageNumber = detailPageStart + detailPageGroups.length;
|
|
502
|
-
|
|
503
|
-
groupSections.forEach( ( section ) => {
|
|
504
|
-
section.pageNumber = pageNumber;
|
|
505
|
-
} );
|
|
506
|
-
|
|
507
|
-
detailPageGroups.push( {
|
|
508
|
-
pageNumber,
|
|
509
|
-
sections: groupSections,
|
|
510
|
-
isLastGroup: i + 2 >= questionAnswers.length,
|
|
511
|
-
} );
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const totalPages = 1 + ( hasCompliancePage ? 1 : 0 ) + detailPageGroups.length;
|
|
529
|
+
const hasCompliancePage = Boolean( checklistInfo.complianceCount );
|
|
530
|
+
const detailPageStart = hasCompliancePage ? 3 : 2;
|
|
531
|
+
const detailPageGroups = [];
|
|
532
|
+
|
|
533
|
+
for ( let i = 0; i < questionAnswers.length; i += 2 ) {
|
|
534
|
+
const groupSections = questionAnswers.slice( i, i + 2 );
|
|
535
|
+
const pageNumber = detailPageStart + detailPageGroups.length;
|
|
536
|
+
|
|
537
|
+
groupSections.forEach( ( section ) => {
|
|
538
|
+
section.pageNumber = pageNumber;
|
|
539
|
+
} );
|
|
540
|
+
|
|
541
|
+
detailPageGroups.push( {
|
|
542
|
+
pageNumber,
|
|
543
|
+
sections: groupSections,
|
|
544
|
+
isLastGroup: i + 2 >= questionAnswers.length,
|
|
545
|
+
} );
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const totalPages = 1 + ( hasCompliancePage ? 1 : 0 ) + detailPageGroups.length;
|
|
515
549
|
|
|
516
550
|
const rawChecklistTitle = checklistInfo?.checklistName || getchecklistData?.data?.checklistName || 'AOM Visit Checklist';
|
|
517
551
|
|
|
@@ -538,7 +572,6 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
538
572
|
};
|
|
539
573
|
} );
|
|
540
574
|
}
|
|
541
|
-
console.log( brandInfo.brandLogo, 'jhgfghj' );
|
|
542
575
|
|
|
543
576
|
return {
|
|
544
577
|
|
|
@@ -570,15 +603,15 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
570
603
|
|
|
571
604
|
aiBreached: checklistAnswer?.aiBreachedCount ?? 0,
|
|
572
605
|
|
|
573
|
-
submittedBy: checklistInfo?.submittedBy || storeProfile?.userName || '--',
|
|
574
|
-
|
|
575
|
-
country: storeProfile?.Country || '--',
|
|
576
|
-
|
|
577
|
-
hasCompliancePage,
|
|
578
|
-
|
|
579
|
-
detailPageStart,
|
|
580
|
-
|
|
581
|
-
totalPages,
|
|
606
|
+
submittedBy: checklistInfo?.submittedBy || storeProfile?.userName || '--',
|
|
607
|
+
|
|
608
|
+
country: storeProfile?.Country || '--',
|
|
609
|
+
|
|
610
|
+
hasCompliancePage,
|
|
611
|
+
|
|
612
|
+
detailPageStart,
|
|
613
|
+
|
|
614
|
+
totalPages,
|
|
582
615
|
|
|
583
616
|
totalPercentage,
|
|
584
617
|
|
|
@@ -598,7 +631,7 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
598
631
|
|
|
599
632
|
flags,
|
|
600
633
|
|
|
601
|
-
complianceCount: hasCompliancePage,
|
|
634
|
+
complianceCount: hasCompliancePage,
|
|
602
635
|
|
|
603
636
|
};
|
|
604
637
|
}
|
|
@@ -625,16 +658,283 @@ export function buildVisitChecklistTemplateData( arg1, arg2, arg3 ) {
|
|
|
625
658
|
|
|
626
659
|
/**
|
|
627
660
|
|
|
628
|
-
* Generates PDF buffer from template data
|
|
629
661
|
|
|
630
|
-
*
|
|
662
|
+
* In-memory image cache: URL → base64 data URI.
|
|
663
|
+
|
|
631
664
|
|
|
632
|
-
*
|
|
665
|
+
* Shared across all PDFs in a single request lifecycle.
|
|
633
666
|
|
|
634
|
-
* @return {Promise<Buffer>} PDF buffer
|
|
635
667
|
|
|
636
668
|
*/
|
|
637
669
|
|
|
670
|
+
|
|
671
|
+
export function createImageCache() {
|
|
672
|
+
const cache = new Map();
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
async function fetchAsDataUri( url ) {
|
|
676
|
+
if ( !url || typeof url !== 'string' || !url.startsWith( 'http' ) ) return url;
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
if ( cache.has( url ) ) return cache.get( url );
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
const controller = new AbortController();
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
const timeout = setTimeout( () => controller.abort(), 10000 );
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
const res = await fetch( url, { signal: controller.signal } );
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
clearTimeout( timeout );
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
if ( !res.ok ) {
|
|
696
|
+
cache.set( url, url );
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
return url;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
const contentType = res.headers.get( 'content-type' ) || 'image/png';
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
const buffer = Buffer.from( await res.arrayBuffer() );
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
const dataUri = `data:${contentType};base64,${buffer.toString( 'base64' )}`;
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
cache.set( url, dataUri );
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
return dataUri;
|
|
716
|
+
} catch {
|
|
717
|
+
cache.set( url, url );
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
return url;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
async function resolveAllImages( resolvedData ) {
|
|
726
|
+
const urls = new Set();
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
if ( resolvedData.brandLogo && resolvedData.brandLogo.startsWith( 'http' ) ) {
|
|
730
|
+
urls.add( resolvedData.brandLogo );
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
const collectFromSection = ( section ) => {
|
|
735
|
+
section.questions?.forEach( ( q ) => {
|
|
736
|
+
q.userAnswer?.forEach( ( ua ) => {
|
|
737
|
+
if ( ua.referenceImage && ua.referenceImage.startsWith( 'http' ) ) urls.add( ua.referenceImage );
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
if ( ua.answer && ua.answerType === 'image' && ua.answer.startsWith( 'http' ) ) urls.add( ua.answer );
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
if ( ua.validationAnswer && ua.validationDisplayType === 'image' && ua.validationAnswer.startsWith( 'http' ) ) urls.add( ua.validationAnswer );
|
|
744
|
+
} );
|
|
745
|
+
} );
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
resolvedData.questionAnswers?.forEach( collectFromSection );
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
resolvedData.detailPageGroups?.forEach( ( group ) => {
|
|
753
|
+
group.sections?.forEach( collectFromSection );
|
|
754
|
+
} );
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
// Fetch all unique URLs in parallel (max 20 concurrent)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
const urlArray = [ ...urls ];
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
const BATCH_SIZE = 20;
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
for ( let i = 0; i < urlArray.length; i += BATCH_SIZE ) {
|
|
767
|
+
await Promise.all( urlArray.slice( i, i + BATCH_SIZE ).map( ( u ) => fetchAsDataUri( u ) ) );
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
// Replace URLs with cached data URIs
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
if ( resolvedData.brandLogo ) {
|
|
775
|
+
resolvedData.brandLogo = cache.get( resolvedData.brandLogo ) || resolvedData.brandLogo;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
const replaceInSection = ( section ) => {
|
|
780
|
+
section.questions?.forEach( ( q ) => {
|
|
781
|
+
q.userAnswer?.forEach( ( ua ) => {
|
|
782
|
+
if ( ua.referenceImage && cache.has( ua.referenceImage ) ) ua.referenceImage = cache.get( ua.referenceImage );
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
if ( ua.answer && ua.answerType === 'image' && cache.has( ua.answer ) ) ua.answer = cache.get( ua.answer );
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
if ( ua.validationAnswer && ua.validationDisplayType === 'image' && cache.has( ua.validationAnswer ) ) ua.validationAnswer = cache.get( ua.validationAnswer );
|
|
789
|
+
} );
|
|
790
|
+
} );
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
resolvedData.questionAnswers?.forEach( replaceInSection );
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
resolvedData.detailPageGroups?.forEach( ( group ) => {
|
|
798
|
+
group.sections?.forEach( replaceInSection );
|
|
799
|
+
} );
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
return resolvedData;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
return { fetchAsDataUri, resolveAllImages, cache };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
export function getCompiledVisitChecklistTemplate() {
|
|
811
|
+
const templatePath = path.join( __dirname, '../hbs/visit-checklist.hbs' );
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
const templateHtml = fs.readFileSync( templatePath, 'utf8' );
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
return handlebars.compile( templateHtml );
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2sskgmri.cloudfront.net/' ) {
|
|
822
|
+
if ( baseUrl && !baseUrl.endsWith( '/' ) ) baseUrl += '/';
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
const resolveUrl = ( url ) => {
|
|
826
|
+
if ( !url || typeof url !== 'string' ) return url;
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
if ( url.startsWith( 'http' ) ) return url;
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
return baseUrl + url.replace( /^\//, '' );
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
const resolvedData = JSON.parse( JSON.stringify( templateData ) );
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
const resolveQuestionMedia = ( section ) => {
|
|
840
|
+
section.questions?.forEach( ( q ) => {
|
|
841
|
+
q.userAnswer?.forEach( ( ua ) => {
|
|
842
|
+
if ( ua.referenceImage ) ua.referenceImage = resolveUrl( ua.referenceImage );
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
if ( ua.validationAnswer && ua.validationDisplayType !== 'text' ) {
|
|
846
|
+
ua.validationAnswer = resolveUrl( ua.validationAnswer );
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
851
|
+
ua.answer = resolveUrl( ua.answer );
|
|
852
|
+
}
|
|
853
|
+
} );
|
|
854
|
+
} );
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
resolvedData.questionAnswers?.forEach( resolveQuestionMedia );
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
resolvedData.detailPageGroups?.forEach( ( group ) => {
|
|
862
|
+
group.sections?.forEach( resolveQuestionMedia );
|
|
863
|
+
} );
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
|
|
867
|
+
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
return resolvedData;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
export async function generatePDFFromPage( page, template, templateData, baseUrl, imageCache ) {
|
|
876
|
+
const resolvedData = resolveTemplateUrls( templateData, baseUrl );
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
if ( imageCache ) {
|
|
880
|
+
await imageCache.resolveAllImages( resolvedData );
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
const html = template( resolvedData );
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
await page.setContent( html, { waitUntil: 'domcontentloaded' } );
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
if ( !imageCache ) {
|
|
891
|
+
await Promise.race( [
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
page.evaluate( async () => {
|
|
895
|
+
const images = Array.from( document.images );
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
await Promise.all(
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
images.map( ( img ) => {
|
|
902
|
+
if ( img.complete ) return;
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
return new Promise( ( resolve ) => {
|
|
906
|
+
img.onload = img.onerror = resolve;
|
|
907
|
+
} );
|
|
908
|
+
} ),
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
);
|
|
912
|
+
} ),
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
new Promise( ( resolve ) => setTimeout( resolve, 30000 ) ),
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
] );
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
return page.pdf( {
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
format: 'A4',
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
printBackground: true,
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
} );
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
|
|
638
938
|
export async function generateVisitChecklistPDF( templateData, baseUrl = 'https://d1r0hc2sskgmri.cloudfront.net/' ) {
|
|
639
939
|
const templatePath = path.join( __dirname, '../hbs/visit-checklist.hbs' );
|
|
640
940
|
|
|
@@ -657,26 +957,26 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
657
957
|
|
|
658
958
|
const resolvedData = JSON.parse( JSON.stringify( templateData ) );
|
|
659
959
|
|
|
660
|
-
const resolveQuestionMedia = ( section ) => {
|
|
661
|
-
section.questions?.forEach( ( q ) => {
|
|
662
|
-
q.userAnswer?.forEach( ( ua ) => {
|
|
663
|
-
if ( ua.referenceImage ) ua.referenceImage = resolveUrl( ua.referenceImage );
|
|
664
|
-
|
|
665
|
-
if ( ua.validationAnswer && ua.validationDisplayType !== 'text' ) {
|
|
666
|
-
ua.validationAnswer = resolveUrl( ua.validationAnswer );
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
670
|
-
ua.answer = resolveUrl( ua.answer );
|
|
671
|
-
}
|
|
672
|
-
} );
|
|
673
|
-
} );
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
resolvedData.questionAnswers?.forEach( resolveQuestionMedia );
|
|
677
|
-
resolvedData.detailPageGroups?.forEach( ( group ) => {
|
|
678
|
-
group.sections?.forEach( resolveQuestionMedia );
|
|
679
|
-
} );
|
|
960
|
+
const resolveQuestionMedia = ( section ) => {
|
|
961
|
+
section.questions?.forEach( ( q ) => {
|
|
962
|
+
q.userAnswer?.forEach( ( ua ) => {
|
|
963
|
+
if ( ua.referenceImage ) ua.referenceImage = resolveUrl( ua.referenceImage );
|
|
964
|
+
|
|
965
|
+
if ( ua.validationAnswer && ua.validationDisplayType !== 'text' ) {
|
|
966
|
+
ua.validationAnswer = resolveUrl( ua.validationAnswer );
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
970
|
+
ua.answer = resolveUrl( ua.answer );
|
|
971
|
+
}
|
|
972
|
+
} );
|
|
973
|
+
} );
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
resolvedData.questionAnswers?.forEach( resolveQuestionMedia );
|
|
977
|
+
resolvedData.detailPageGroups?.forEach( ( group ) => {
|
|
978
|
+
group.sections?.forEach( resolveQuestionMedia );
|
|
979
|
+
} );
|
|
680
980
|
|
|
681
981
|
if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
|
|
682
982
|
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|