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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-audit",
3
- "version": "3.6.26",
3
+ "version": "3.6.28",
4
4
  "description": "audit & audit metrics apis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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 ( trustScore > value ) {
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 ( trustScore < value ) {
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 ( trustScore >= value ) {
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 ( trustScore <= value ) {
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 ( trustScore == value ) {
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 ( trustScore === value ) {
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, // sender email added here
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', // Change to your AWS region
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: 24px; background: #f9fafb; color: #121a26; }
111
- .container { max-width: 700px; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
112
- .title { font-size: 24px; font-weight: 700; color: #121a26; }
113
- .subtitle { font-size: 16px; color: #384860; line-height: 1.5; margin-top: 8px; }
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: #667085; }
116
- .footer { margin-top: 32px; font-size: 14px; color: #384860; }
117
- .compliance { font-size: 18px; font-weight: 700; color: #f04438; margin-top: 16px; }
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
- <p class="title">Non Compliance on Eye Test Procedures</p>
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 and avoid any discrepancies.<br>
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
- <p class="compliance">Compliance Percentage: ${compliancePercentage}%</p>
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, // sender email (must be verified in SES)
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 response = await sesClient.send( new SendEmailCommand( params ) );
164
- // console.log( 'Email sent successfully:', response.MessageId );
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
- // console.error( 'Error sending email:', error );
239
+ logger.error( 'Error sending compliance email', error );
168
240
  throw error;
169
241
  }
170
242
  }