tango-app-api-audit 3.6.26 → 3.6.28
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
|
@@ -752,6 +752,7 @@ export async function save( req, res ) {
|
|
|
752
752
|
const inputData = req.body;
|
|
753
753
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
754
754
|
const ses = JSON.parse( process.env.SES );
|
|
755
|
+
const url = JSON.parse( process.env.URL );
|
|
755
756
|
|
|
756
757
|
let searchFilter =[
|
|
757
758
|
{
|
|
@@ -847,7 +848,6 @@ export async function save( req, res ) {
|
|
|
847
848
|
const getEyetestConfig = await findOneEyeTestConfig( { clientId: req?.user.userType == 'tango'? inputData?.clientId : req.user.clientId, configureType: 'email' }, { complianceThreshold: 1 } );
|
|
848
849
|
|
|
849
850
|
if ( getEyetestConfig &&( inputData.auditStatus == 'Audited' ||inputData.auditStatus == 'Re-Audited' ) && inputUpdatedData && inputData.type === 'physical' && inputData?.isReSend !== false ) {
|
|
850
|
-
const trustScore = Math.round( 100-( ( inputData?.overRides / inputData?.steps?.length ) * 100 ) );
|
|
851
851
|
const value = Number( getEyetestConfig?.complianceThreshold?.value?.split( '%' )[0] );
|
|
852
852
|
const condition = getEyetestConfig?.complianceThreshold?.condition;
|
|
853
853
|
const id= inputData?.isReSend === true ?`${inputData.id}_${inputUpdatedData?._source?.optumId}_${inputData?.fileId}_${Date.now()}` : `${inputData.id}_${inputUpdatedData?._source?.optumId}_${inputData?.fileId}`;
|
|
@@ -875,7 +875,7 @@ export async function save( req, res ) {
|
|
|
875
875
|
( a, b ) => order.indexOf( a.processName ) - order.indexOf( b.processName ),
|
|
876
876
|
);
|
|
877
877
|
switch ( condition ) {
|
|
878
|
-
case '>': if (
|
|
878
|
+
case '>': if ( humanScore > value ) {
|
|
879
879
|
const record1 = {
|
|
880
880
|
RMEmail: inputData.RMEmail,
|
|
881
881
|
RMName: inputData.RMEmail,
|
|
@@ -898,12 +898,15 @@ export async function save( req, res ) {
|
|
|
898
898
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
899
899
|
fromEmail: ses.eyeTestAlertEmail,
|
|
900
900
|
userName: 'User',
|
|
901
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
902
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
901
903
|
compliancePercentage: humanScore,
|
|
902
904
|
steps: steps,
|
|
905
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
903
906
|
} );
|
|
904
907
|
}
|
|
905
908
|
break;
|
|
906
|
-
case '<': if (
|
|
909
|
+
case '<': if ( humanScore < value ) {
|
|
907
910
|
const record2 = {
|
|
908
911
|
RMEmail: inputData.RMEmail,
|
|
909
912
|
RMName: inputData.RMEmail,
|
|
@@ -926,12 +929,15 @@ export async function save( req, res ) {
|
|
|
926
929
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
927
930
|
fromEmail: ses.eyeTestAlertEmail,
|
|
928
931
|
userName: 'User',
|
|
932
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
933
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
929
934
|
compliancePercentage: humanScore,
|
|
930
935
|
steps: steps,
|
|
936
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
931
937
|
} );
|
|
932
938
|
};
|
|
933
939
|
break;
|
|
934
|
-
case '>=': if (
|
|
940
|
+
case '>=': if ( humanScore >= value ) {
|
|
935
941
|
const record3 = {
|
|
936
942
|
RMEmail: inputData.RMEmail,
|
|
937
943
|
RMName: inputData.RMEmail,
|
|
@@ -954,12 +960,15 @@ export async function save( req, res ) {
|
|
|
954
960
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
955
961
|
fromEmail: ses.eyeTestAlertEmail,
|
|
956
962
|
userName: 'User',
|
|
963
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
964
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
957
965
|
compliancePercentage: humanScore,
|
|
958
966
|
steps: steps,
|
|
967
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
959
968
|
} );
|
|
960
969
|
}
|
|
961
970
|
break;
|
|
962
|
-
case '<=': if (
|
|
971
|
+
case '<=': if ( humanScore <= value ) {
|
|
963
972
|
const record4 = {
|
|
964
973
|
RMEmail: inputData.RMEmail,
|
|
965
974
|
RMName: inputData.RMEmail,
|
|
@@ -982,13 +991,16 @@ export async function save( req, res ) {
|
|
|
982
991
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
983
992
|
fromEmail: ses.eyeTestAlertEmail,
|
|
984
993
|
userName: 'User',
|
|
994
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
995
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
985
996
|
compliancePercentage: humanScore,
|
|
986
997
|
steps: steps,
|
|
998
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
987
999
|
} );
|
|
988
1000
|
}
|
|
989
1001
|
break;
|
|
990
1002
|
case '=':
|
|
991
|
-
case '==': if (
|
|
1003
|
+
case '==': if ( humanScore == value ) {
|
|
992
1004
|
const record5 = {
|
|
993
1005
|
RMEmail: inputData.RMEmail,
|
|
994
1006
|
RMName: inputData.RMEmail,
|
|
@@ -1011,12 +1023,15 @@ export async function save( req, res ) {
|
|
|
1011
1023
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1012
1024
|
fromEmail: ses.eyeTestAlertEmail,
|
|
1013
1025
|
userName: 'User',
|
|
1026
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1027
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1014
1028
|
compliancePercentage: humanScore,
|
|
1015
1029
|
steps: steps,
|
|
1030
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1016
1031
|
} );
|
|
1017
1032
|
}
|
|
1018
1033
|
break;
|
|
1019
|
-
case '===': if (
|
|
1034
|
+
case '===': if ( humanScore === value ) {
|
|
1020
1035
|
const record6 = {
|
|
1021
1036
|
RMEmail: inputData.RMEmail,
|
|
1022
1037
|
RMName: inputData.RMEmail,
|
|
@@ -1038,8 +1053,11 @@ export async function save( req, res ) {
|
|
|
1038
1053
|
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1039
1054
|
fromEmail: ses.eyeTestAlertEmail,
|
|
1040
1055
|
userName: 'User',
|
|
1056
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1057
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1041
1058
|
compliancePercentage: humanScore,
|
|
1042
1059
|
steps: steps,
|
|
1060
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1043
1061
|
} );
|
|
1044
1062
|
}
|
|
1045
1063
|
break;
|
|
@@ -2015,7 +2033,9 @@ export async function summaryList( req, res ) {
|
|
|
2015
2033
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
2016
2034
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
2017
2035
|
'Optom ID': element?._source?.optumId,
|
|
2036
|
+
'Optom Email ID': element?._source?.salesmanEmailId,
|
|
2018
2037
|
'Queue ID': element?._source?.queueId,
|
|
2038
|
+
'Power Types': element?._source?.nearAddition === 'true' ? 'With Near Addition' : element?._source?.nearAddition === 'false' ? 'Without Near Addition':'',
|
|
2019
2039
|
'Tango Score': `${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.coveredStepsAI/ element?._source?.totalSteps )*100 ): 0} %`,
|
|
2020
2040
|
'Steps Covered By AI': element?._source?.coveredStepsAI || 0,
|
|
2021
2041
|
'Human Audited Score': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?`${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.auditedSteps/ element?._source?.totalSteps )*100 ): 0} %`:'NA',
|
|
@@ -2040,12 +2060,16 @@ export async function summaryList( req, res ) {
|
|
|
2040
2060
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
2041
2061
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
2042
2062
|
'Engagement ID': element?._source?.engagementId,
|
|
2063
|
+
'Optom ID': element?._source?.optm_userId,
|
|
2064
|
+
'Optom Email ID': element?._source?.userEmail,
|
|
2065
|
+
'Optom Name': element?._source?.displayName,
|
|
2043
2066
|
'Tango Score': `${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.coveredStepsAI/ element?._source?.totalSteps )*100 ): 0} %`,
|
|
2044
|
-
'Human Audited Score': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?`${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.auditedSteps/ element?._source?.totalSteps )*100 ): 0} %`:'NA',
|
|
2045
2067
|
'Steps Covered By AI': element?._source?.coveredStepsAI || 0,
|
|
2046
|
-
'Steps Covered By Human': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?element?._source?.auditedSteps :'NA',
|
|
2047
2068
|
'Trust Score': element?._source?.trustScore == null || element?._source?.trustScore ==undefined ? 'NA' : `${element?._source?.trustScore} %`,
|
|
2069
|
+
'Steps Covered By Human': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?element?._source?.auditedSteps :'NA',
|
|
2070
|
+
'Human Audited Score': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?`${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.auditedSteps/ element?._source?.totalSteps )*100 ): 0} %`:'NA',
|
|
2048
2071
|
'Test Duration': duration || '',
|
|
2072
|
+
'Comments ': element._source?.subComment || '',
|
|
2049
2073
|
'Audit Status': element?._source?.auditStatus || '',
|
|
2050
2074
|
'Audited By': userName?.userName || '',
|
|
2051
2075
|
|
|
@@ -64,36 +64,38 @@ export async function sendAlert( req, res, next ) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Send HTML Email using AWS SES
|
|
69
|
-
* @param {Object} params
|
|
70
|
-
* @param {string} params.toEmail - Recipient email address
|
|
71
|
-
* @param {string} params.userName - Recipient name
|
|
72
|
-
* @param {number} params.compliancePercentage - Compliance score
|
|
73
|
-
*/
|
|
74
67
|
export async function sendComplianceEmail( {
|
|
75
68
|
toEmail,
|
|
76
|
-
fromEmail,
|
|
69
|
+
fromEmail,
|
|
77
70
|
userName,
|
|
71
|
+
storeName,
|
|
72
|
+
startTime,
|
|
78
73
|
compliancePercentage,
|
|
79
74
|
steps,
|
|
75
|
+
videoUrl, // S3 cdn video URL
|
|
80
76
|
} ) {
|
|
81
|
-
// Initialize AWS SES client
|
|
82
|
-
logger.info( { steps: steps } );
|
|
77
|
+
// Initialize AWS SES client
|
|
83
78
|
const sesClient = new SESClient( {
|
|
84
|
-
region: 'ap-south-1',
|
|
79
|
+
region: 'ap-south-1',
|
|
85
80
|
credentials: {
|
|
86
81
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID_SIGNED_URL,
|
|
87
82
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY__SIGNED_URL,
|
|
88
83
|
},
|
|
89
84
|
} );
|
|
85
|
+
|
|
86
|
+
// Fetch video as base64 to attach in SES
|
|
87
|
+
const videoResponse = await fetch( videoUrl );
|
|
88
|
+
const videoBuffer = await videoResponse.arrayBuffer();
|
|
89
|
+
const videoBase64 = Buffer.from( videoBuffer ).toString( 'base64' );
|
|
90
|
+
|
|
91
|
+
// HTML Email Template
|
|
90
92
|
const htmlBody = ( userName, steps, compliancePercentage ) => {
|
|
91
93
|
const tableRows = steps
|
|
92
94
|
.map(
|
|
93
95
|
( item ) => `
|
|
94
96
|
<tr>
|
|
95
97
|
<td>${item.stepName}</td>
|
|
96
|
-
<td>${item.comment?? '--'}</td>
|
|
98
|
+
<td>${item.comment ?? '--'}</td>
|
|
97
99
|
</tr>
|
|
98
100
|
`,
|
|
99
101
|
)
|
|
@@ -107,23 +109,63 @@ export async function sendComplianceEmail( {
|
|
|
107
109
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
108
110
|
<title>Non Compliance Report</title>
|
|
109
111
|
<style>
|
|
110
|
-
body { font-family: 'Inter', sans-serif; margin: 0; padding:
|
|
111
|
-
.container { max-width: 700px; background: #fff; padding: 24px; border-radius:
|
|
112
|
-
.
|
|
113
|
-
.
|
|
112
|
+
body { font-family: 'Inter', sans-serif; margin: 0; padding: 0; background: #f9fafb; color: #121a26; }
|
|
113
|
+
.container { max-width: 700px; margin: 24px auto; background: #fff; padding: 24px; border-radius: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
114
|
+
.header { display: flex; align-items: center; gap: 10px; }
|
|
115
|
+
.logo { width: 36px; height: 36px; border-radius: 50%; }
|
|
116
|
+
.title { font-size: 20px; font-weight: 700; color: #121a26; }
|
|
117
|
+
.subtitle { font-size: 15px; color: #384860; margin-top: 8px; line-height: 1.6; }
|
|
118
|
+
.divider { height: 2px; background: #eaeef0; margin: 16px 0; }
|
|
114
119
|
.table { width: 100%; border-collapse: collapse; margin-top: 16px; }
|
|
115
|
-
.table th, .table td { text-align: left; padding: 12px 16px; border-bottom: 1px solid #eaeef0; color: #
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
.table th, .table td { text-align: left; padding: 12px 16px; border-bottom: 1px solid #eaeef0; color: #384860; font-size: 14px; }
|
|
121
|
+
.compliance-box {
|
|
122
|
+
background: #F1F5F9;
|
|
123
|
+
color: #F04438;
|
|
124
|
+
font-weight: 700;
|
|
125
|
+
font-size: 18px;
|
|
126
|
+
padding: 12px 16px;
|
|
127
|
+
border-radius: 8px;
|
|
128
|
+
text-align: left;
|
|
129
|
+
margin-top: 24px;
|
|
130
|
+
border: 1px solid #e2e8f0; /* subtle border for better definition */
|
|
131
|
+
}
|
|
132
|
+
.video {
|
|
133
|
+
margin-top: 20px;
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
}
|
|
137
|
+
.video video {
|
|
138
|
+
width: 100%;
|
|
139
|
+
border-radius: 8px;
|
|
140
|
+
}
|
|
141
|
+
a.button {
|
|
142
|
+
display: inline-block;
|
|
143
|
+
margin-top: 16px;
|
|
144
|
+
padding: 10px 16px;
|
|
145
|
+
background: #2563eb;
|
|
146
|
+
color: #fff;
|
|
147
|
+
text-decoration: none;
|
|
148
|
+
border-radius: 6px;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
}
|
|
151
|
+
.footer { margin-top: 24px; color: #555; font-size: 16px; text-align: left; }
|
|
152
|
+
.attachment { margin-top: 24px; color: #555; font-size: 18px; text-align: left; }
|
|
153
|
+
.copy-rights { margin-top: 24px; color: #555; font-size: 12px; text-align: left; }
|
|
118
154
|
</style>
|
|
119
155
|
</head>
|
|
120
156
|
<body>
|
|
121
157
|
<div class="container">
|
|
122
|
-
<
|
|
158
|
+
<div class="header">
|
|
159
|
+
<img src="https://dashboard-api.tangoeye.ai/logo.png" alt="Team Tango" />
|
|
160
|
+
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="divider"></div>
|
|
164
|
+
|
|
123
165
|
<p class="subtitle">
|
|
124
166
|
Dear ${userName},<br><br>
|
|
125
167
|
Please ensure that all conducted eye tests meet the required compliance standards.<br>
|
|
126
|
-
Your prompt attention to this matter will help us maintain the highest standards
|
|
168
|
+
Your prompt attention to this matter will help us maintain the highest standards.<br>
|
|
127
169
|
Please take a look at the below findings.
|
|
128
170
|
</p>
|
|
129
171
|
|
|
@@ -139,17 +181,39 @@ export async function sendComplianceEmail( {
|
|
|
139
181
|
</tbody>
|
|
140
182
|
</table>
|
|
141
183
|
|
|
142
|
-
<
|
|
184
|
+
<div class="compliance-box">Compliance Percentage: ${compliancePercentage}%</div>
|
|
185
|
+
|
|
186
|
+
<p class="attachment">One Attachment</p>
|
|
187
|
+
<!-- Video Embed (only if provided) -->
|
|
188
|
+
${
|
|
189
|
+
videoUrl ?
|
|
190
|
+
`
|
|
191
|
+
<div class="video">
|
|
192
|
+
<a href="${videoUrl}" target="_blank" style="text-decoration:none; color:#00A3FF; font-weight:600;">
|
|
193
|
+
<img src="https://img.icons8.com/?size=100&id=63671&format=png&color=000000"
|
|
194
|
+
alt="Play Video" width="80" height="80"
|
|
195
|
+
style="vertical-align:middle; margin-right:10px;">
|
|
196
|
+
${storeName}_${startTime}-video.mp4
|
|
197
|
+
</a>
|
|
198
|
+
</div>
|
|
199
|
+
` :
|
|
200
|
+
''
|
|
201
|
+
}
|
|
202
|
+
|
|
143
203
|
<p class="footer">Regards,<br>Team Tango</p>
|
|
204
|
+
</br></br>
|
|
205
|
+
<p class = "copy-rights">2024 © Tango Eye . All rights reserved.</p>
|
|
144
206
|
</div>
|
|
207
|
+
|
|
145
208
|
</body>
|
|
146
209
|
</html>`;
|
|
147
210
|
};
|
|
148
211
|
|
|
149
212
|
const emailHTML = htmlBody( userName, steps, compliancePercentage );
|
|
150
213
|
|
|
214
|
+
// Send email with video attachment
|
|
151
215
|
const params = {
|
|
152
|
-
Source: fromEmail,
|
|
216
|
+
Source: fromEmail,
|
|
153
217
|
Destination: {
|
|
154
218
|
ToAddresses: [ toEmail ],
|
|
155
219
|
},
|
|
@@ -157,14 +221,22 @@ export async function sendComplianceEmail( {
|
|
|
157
221
|
Subject: { Data: 'Non Compliance on Eye Test Procedures' },
|
|
158
222
|
Body: { Html: { Data: emailHTML } },
|
|
159
223
|
},
|
|
224
|
+
Attachments: [
|
|
225
|
+
{
|
|
226
|
+
Name: `${storeName}_${startTime}-video.mp4`,
|
|
227
|
+
Content: videoBase64,
|
|
228
|
+
ContentType: 'video/mp4',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
160
231
|
};
|
|
161
232
|
|
|
162
233
|
try {
|
|
163
|
-
const
|
|
164
|
-
|
|
234
|
+
const command = new SendEmailCommand( params );
|
|
235
|
+
const response = await sesClient.send( command );
|
|
236
|
+
logger.info( { response: response, command: command, params: params, code: response?.$metadata?.httpStatusCode } );
|
|
165
237
|
return response;
|
|
166
238
|
} catch ( error ) {
|
|
167
|
-
|
|
239
|
+
logger.error( 'Error sending compliance email', error );
|
|
168
240
|
throw error;
|
|
169
241
|
}
|
|
170
242
|
}
|