tango-app-api-audit 3.6.25 → 3.6.26
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.
|
|
3
|
+
"version": "3.6.26",
|
|
4
4
|
"description": "audit & audit metrics apis",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"author": "praveenraj",
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@aws-sdk/client-ses": "^3.918.0",
|
|
16
17
|
"adm-zip": "^0.5.15",
|
|
17
18
|
"aws-sdk": "^2.1643.0",
|
|
18
19
|
"axios": "^1.7.9",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"joi-to-swagger": "^6.2.0",
|
|
25
26
|
"lodash": "^4.17.21",
|
|
26
27
|
"mongodb": "^6.7.0",
|
|
28
|
+
"nodemailer": "^7.0.10",
|
|
27
29
|
"nodemon": "^3.1.3",
|
|
28
30
|
"swagger-ui-express": "^5.0.1",
|
|
29
31
|
"tango-api-schema": "^2.3.28",
|
|
@@ -609,7 +609,7 @@ export async function getAuditFile( req, res ) {
|
|
|
609
609
|
const getQuery = msg.audit_type =='ReAudit'?
|
|
610
610
|
{
|
|
611
611
|
size: inputData?.limit || 500,
|
|
612
|
-
_source: [ 'module', 'status', 'date', 'store_id', 'outputCluster', 'personPath', 'REIDCluster', 'isEmployee', 'isJunk', 'EmployeeStatusFinal'
|
|
612
|
+
_source: [ 'module', 'status', 'date', 'store_id', 'outputCluster', 'personPath', 'REIDCluster', 'isEmployee', 'isJunk', 'EmployeeStatusFinal' ], // Only fetch necessary fields
|
|
613
613
|
|
|
614
614
|
query: {
|
|
615
615
|
bool: {
|
|
@@ -638,13 +638,13 @@ export async function getAuditFile( req, res ) {
|
|
|
638
638
|
},
|
|
639
639
|
},
|
|
640
640
|
sort: [
|
|
641
|
-
{
|
|
641
|
+
{ timestamp: { order: 'desc' } },
|
|
642
642
|
],
|
|
643
643
|
|
|
644
644
|
}:
|
|
645
645
|
{
|
|
646
646
|
size: inputData?.limit || 500,
|
|
647
|
-
_source: [ 'module', 'status', 'date', 'store_id', 'personPath', 'REIDCluster', 'EmployeeStatusProposed', 'isJunk', 'EmployeeStatusFinal'
|
|
647
|
+
_source: [ 'module', 'status', 'date', 'store_id', 'personPath', 'REIDCluster', 'EmployeeStatusProposed', 'isJunk', 'EmployeeStatusFinal' ], // Only fetch necessary fields
|
|
648
648
|
|
|
649
649
|
query: {
|
|
650
650
|
bool: {
|
|
@@ -668,7 +668,7 @@ export async function getAuditFile( req, res ) {
|
|
|
668
668
|
},
|
|
669
669
|
},
|
|
670
670
|
sort: [
|
|
671
|
-
{
|
|
671
|
+
{ timestamp: { order: 'desc' } },
|
|
672
672
|
],
|
|
673
673
|
|
|
674
674
|
};
|
|
@@ -4490,7 +4490,7 @@ export async function auditImages( req, res ) {
|
|
|
4490
4490
|
case 'BC':
|
|
4491
4491
|
const getQuery = {
|
|
4492
4492
|
size: 10000,
|
|
4493
|
-
_source: [ 'module', 'status', 'date', 'store_id', 'personPath', 'REIDCluster', 'EmployeeStatusProposed', 'isJunk', 'EmployeeStatusFinal'
|
|
4493
|
+
_source: [ 'module', 'status', 'date', 'store_id', 'personPath', 'REIDCluster', 'EmployeeStatusProposed', 'isJunk', 'EmployeeStatusFinal' ], // Only fetch necessary fields
|
|
4494
4494
|
|
|
4495
4495
|
query: {
|
|
4496
4496
|
bool: {
|
|
@@ -4515,7 +4515,7 @@ export async function auditImages( req, res ) {
|
|
|
4515
4515
|
},
|
|
4516
4516
|
|
|
4517
4517
|
sort: [
|
|
4518
|
-
{
|
|
4518
|
+
{ timestamp: { order: 'desc' } },
|
|
4519
4519
|
],
|
|
4520
4520
|
};
|
|
4521
4521
|
const files = [];
|
|
@@ -4565,7 +4565,7 @@ export async function auditImages( req, res ) {
|
|
|
4565
4565
|
|
|
4566
4566
|
const getACQuery = {
|
|
4567
4567
|
size: 10000,
|
|
4568
|
-
_source: [ 'module', 'status', 'date', 'store_id', 'outputCluster', 'personPath', 'REIDCluster', 'isEmployee', 'isJunk', 'EmployeeStatusFinal'
|
|
4568
|
+
_source: [ 'module', 'status', 'date', 'store_id', 'outputCluster', 'personPath', 'REIDCluster', 'isEmployee', 'isJunk', 'EmployeeStatusFinal' ], // Only fetch necessary fields
|
|
4569
4569
|
|
|
4570
4570
|
query: {
|
|
4571
4571
|
bool: {
|
|
@@ -4594,7 +4594,7 @@ export async function auditImages( req, res ) {
|
|
|
4594
4594
|
},
|
|
4595
4595
|
},
|
|
4596
4596
|
sort: [
|
|
4597
|
-
{
|
|
4597
|
+
{ timestamp: { order: 'desc' } },
|
|
4598
4598
|
],
|
|
4599
4599
|
|
|
4600
4600
|
};
|
|
@@ -8,6 +8,7 @@ import 'dayjs/locale/en.js';
|
|
|
8
8
|
import { aggregateEyeTestConfig, findOneEyeTestConfig, updateOneEyeTestConfig } from '../service/eyeTestConfiguration.service.js';
|
|
9
9
|
import { ObjectId } from 'mongodb';
|
|
10
10
|
import { aggregateClusters } from '../service/clusters.service.js';
|
|
11
|
+
import { sendComplianceEmail } from '../validation/eyeTest.validation.js';
|
|
11
12
|
|
|
12
13
|
dayjs.extend( utc );
|
|
13
14
|
dayjs.extend( timezone );
|
|
@@ -750,6 +751,7 @@ export async function save( req, res ) {
|
|
|
750
751
|
try {
|
|
751
752
|
const inputData = req.body;
|
|
752
753
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
754
|
+
const ses = JSON.parse( process.env.SES );
|
|
753
755
|
|
|
754
756
|
let searchFilter =[
|
|
755
757
|
{
|
|
@@ -811,7 +813,6 @@ export async function save( req, res ) {
|
|
|
811
813
|
},
|
|
812
814
|
};
|
|
813
815
|
const getOutput = await getOpenSearchData( openSearch.EyeTestOutput, searchQuery );
|
|
814
|
-
logger.info( { getOutput: getOutput } );
|
|
815
816
|
const output = getOutput?.body?.hits?.hits?.length > 0 ? getOutput?.body?.hits?.hits[0] : null;
|
|
816
817
|
if ( !output ) {
|
|
817
818
|
return res.sendError( 'This file is currently being audited by another user. Any actions you perform will not be saved.', 400 );
|
|
@@ -845,19 +846,46 @@ export async function save( req, res ) {
|
|
|
845
846
|
|
|
846
847
|
const getEyetestConfig = await findOneEyeTestConfig( { clientId: req?.user.userType == 'tango'? inputData?.clientId : req.user.clientId, configureType: 'email' }, { complianceThreshold: 1 } );
|
|
847
848
|
|
|
848
|
-
if ( getEyetestConfig && inputData.auditStatus == 'Audited' && inputUpdatedData ) {
|
|
849
|
+
if ( getEyetestConfig &&( inputData.auditStatus == 'Audited' ||inputData.auditStatus == 'Re-Audited' ) && inputUpdatedData && inputData.type === 'physical' && inputData?.isReSend !== false ) {
|
|
849
850
|
const trustScore = Math.round( 100-( ( inputData?.overRides / inputData?.steps?.length ) * 100 ) );
|
|
850
851
|
const value = Number( getEyetestConfig?.complianceThreshold?.value?.split( '%' )[0] );
|
|
851
852
|
const condition = getEyetestConfig?.complianceThreshold?.condition;
|
|
852
|
-
const id=
|
|
853
|
+
const id= inputData?.isReSend === true ?`${inputData.id}_${inputUpdatedData?._source?.optumId}_${inputData?.fileId}_${Date.now()}` : `${inputData.id}_${inputUpdatedData?._source?.optumId}_${inputData?.fileId}`;
|
|
854
|
+
const humanScore = Math.round( ( inputUpdatedData?._source?.auditedSteps /inputUpdatedData?._source?.totalSteps ) * 100 );
|
|
855
|
+
const userName = await findOneUser( { _id: new mongoose.Types.ObjectId( inputUpdatedData?._source?.userId ) }, { userName: 1 } );
|
|
856
|
+
const order = [
|
|
857
|
+
'Distance-VA-Check',
|
|
858
|
+
'Torchlight Check',
|
|
859
|
+
'JCC Check',
|
|
860
|
+
'Duochrome Check',
|
|
861
|
+
'Near Addition Check',
|
|
862
|
+
'Prescription Verification Check',
|
|
863
|
+
];
|
|
864
|
+
const steps = inputData?.steps
|
|
865
|
+
.filter( ( step ) =>
|
|
866
|
+
step.hasOwnProperty( 'auditedStatus' ) ?
|
|
867
|
+
step.auditedStatus === false :
|
|
868
|
+
step.AIStatus === false,
|
|
869
|
+
)
|
|
870
|
+
.map( ( step ) => ( {
|
|
871
|
+
stepName: step.processName,
|
|
872
|
+
comment: step?.reason === 'others'? step.otherReason:step?.reason,
|
|
873
|
+
} ) );
|
|
874
|
+
steps.sort(
|
|
875
|
+
( a, b ) => order.indexOf( a.processName ) - order.indexOf( b.processName ),
|
|
876
|
+
);
|
|
853
877
|
switch ( condition ) {
|
|
854
878
|
case '>': if ( trustScore > value ) {
|
|
855
879
|
const record1 = {
|
|
856
880
|
RMEmail: inputData.RMEmail,
|
|
881
|
+
RMName: inputData.RMEmail,
|
|
857
882
|
cluster: inputData.cluster,
|
|
858
883
|
optumId: inputUpdatedData?._source?.optumId,
|
|
884
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
859
885
|
storeName: inputUpdatedData?._source?.storeName,
|
|
860
886
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
887
|
+
humanScore: humanScore,
|
|
888
|
+
auditedBy: userName?.userName || '',
|
|
861
889
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
862
890
|
auditedId: inputUpdatedData?._id,
|
|
863
891
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -866,15 +894,26 @@ export async function save( req, res ) {
|
|
|
866
894
|
|
|
867
895
|
};
|
|
868
896
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record1 );
|
|
897
|
+
sendComplianceEmail( {
|
|
898
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
899
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
900
|
+
userName: 'User',
|
|
901
|
+
compliancePercentage: humanScore,
|
|
902
|
+
steps: steps,
|
|
903
|
+
} );
|
|
869
904
|
}
|
|
870
905
|
break;
|
|
871
906
|
case '<': if ( trustScore < value ) {
|
|
872
907
|
const record2 = {
|
|
873
908
|
RMEmail: inputData.RMEmail,
|
|
909
|
+
RMName: inputData.RMEmail,
|
|
874
910
|
cluster: inputData.cluster,
|
|
875
911
|
optumId: inputUpdatedData?._source?.optumId,
|
|
912
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
876
913
|
storeName: inputUpdatedData?._source?.storeName,
|
|
877
914
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
915
|
+
humanScore: humanScore,
|
|
916
|
+
auditedBy: userName?.userName || '',
|
|
878
917
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
879
918
|
auditedId: inputUpdatedData?._id,
|
|
880
919
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -883,15 +922,26 @@ export async function save( req, res ) {
|
|
|
883
922
|
|
|
884
923
|
};
|
|
885
924
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record2 );
|
|
925
|
+
sendComplianceEmail( {
|
|
926
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
927
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
928
|
+
userName: 'User',
|
|
929
|
+
compliancePercentage: humanScore,
|
|
930
|
+
steps: steps,
|
|
931
|
+
} );
|
|
886
932
|
};
|
|
887
933
|
break;
|
|
888
934
|
case '>=': if ( trustScore >= value ) {
|
|
889
935
|
const record3 = {
|
|
890
936
|
RMEmail: inputData.RMEmail,
|
|
937
|
+
RMName: inputData.RMEmail,
|
|
891
938
|
cluster: inputData.cluster,
|
|
892
939
|
optumId: inputUpdatedData?._source?.optumId,
|
|
940
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
893
941
|
storeName: inputUpdatedData?._source?.storeName,
|
|
894
942
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
943
|
+
humanScore: humanScore,
|
|
944
|
+
auditedBy: userName?.userName || '',
|
|
895
945
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
896
946
|
auditedId: inputUpdatedData?._id,
|
|
897
947
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -900,15 +950,26 @@ export async function save( req, res ) {
|
|
|
900
950
|
|
|
901
951
|
};
|
|
902
952
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record3 );
|
|
953
|
+
sendComplianceEmail( {
|
|
954
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
955
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
956
|
+
userName: 'User',
|
|
957
|
+
compliancePercentage: humanScore,
|
|
958
|
+
steps: steps,
|
|
959
|
+
} );
|
|
903
960
|
}
|
|
904
961
|
break;
|
|
905
962
|
case '<=': if ( trustScore <= value ) {
|
|
906
963
|
const record4 = {
|
|
907
964
|
RMEmail: inputData.RMEmail,
|
|
965
|
+
RMName: inputData.RMEmail,
|
|
908
966
|
cluster: inputData.cluster,
|
|
909
967
|
optumId: inputUpdatedData?._source?.optumId,
|
|
968
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
910
969
|
storeName: inputUpdatedData?._source?.storeName,
|
|
911
970
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
971
|
+
humanScore: humanScore,
|
|
972
|
+
auditedBy: userName?.userName || '',
|
|
912
973
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
913
974
|
auditedId: inputUpdatedData?._id,
|
|
914
975
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -917,16 +978,27 @@ export async function save( req, res ) {
|
|
|
917
978
|
|
|
918
979
|
};
|
|
919
980
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record4 );
|
|
981
|
+
sendComplianceEmail( {
|
|
982
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
983
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
984
|
+
userName: 'User',
|
|
985
|
+
compliancePercentage: humanScore,
|
|
986
|
+
steps: steps,
|
|
987
|
+
} );
|
|
920
988
|
}
|
|
921
989
|
break;
|
|
922
990
|
case '=':
|
|
923
991
|
case '==': if ( trustScore == value ) {
|
|
924
992
|
const record5 = {
|
|
925
993
|
RMEmail: inputData.RMEmail,
|
|
994
|
+
RMName: inputData.RMEmail,
|
|
926
995
|
cluster: inputData.cluster,
|
|
927
996
|
optumId: inputUpdatedData?._source?.optumId,
|
|
997
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
928
998
|
storeName: inputUpdatedData?._source?.storeName,
|
|
929
999
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1000
|
+
humanScore: humanScore,
|
|
1001
|
+
auditedBy: userName?.userName || '',
|
|
930
1002
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
931
1003
|
auditedId: inputUpdatedData?._id,
|
|
932
1004
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -935,15 +1007,26 @@ export async function save( req, res ) {
|
|
|
935
1007
|
|
|
936
1008
|
};
|
|
937
1009
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record5 );
|
|
1010
|
+
sendComplianceEmail( {
|
|
1011
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1012
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1013
|
+
userName: 'User',
|
|
1014
|
+
compliancePercentage: humanScore,
|
|
1015
|
+
steps: steps,
|
|
1016
|
+
} );
|
|
938
1017
|
}
|
|
939
1018
|
break;
|
|
940
1019
|
case '===': if ( trustScore === value ) {
|
|
941
1020
|
const record6 = {
|
|
942
1021
|
RMEmail: inputData.RMEmail,
|
|
1022
|
+
RMName: inputData.RMEmail,
|
|
943
1023
|
cluster: inputData.cluster,
|
|
944
1024
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1025
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
945
1026
|
storeName: inputUpdatedData?._source?.storeName,
|
|
946
1027
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1028
|
+
humanScore: humanScore,
|
|
1029
|
+
auditedBy: userName?.userName || '',
|
|
947
1030
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
948
1031
|
auditedId: inputUpdatedData?._id,
|
|
949
1032
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -951,12 +1034,19 @@ export async function save( req, res ) {
|
|
|
951
1034
|
updatedAt: new Date(),
|
|
952
1035
|
};
|
|
953
1036
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record6 );
|
|
1037
|
+
sendComplianceEmail( {
|
|
1038
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1039
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1040
|
+
userName: 'User',
|
|
1041
|
+
compliancePercentage: humanScore,
|
|
1042
|
+
steps: steps,
|
|
1043
|
+
} );
|
|
954
1044
|
}
|
|
955
1045
|
break;
|
|
956
1046
|
default: throw new Error( `Unknown condition: ${condition}` );
|
|
957
1047
|
}
|
|
958
1048
|
}
|
|
959
|
-
return res.sendSuccess( 'The file has been
|
|
1049
|
+
return res.sendSuccess( 'The file has been submitted successfully' );
|
|
960
1050
|
} catch ( error ) {
|
|
961
1051
|
const err = error.message || 'Internal Server Error';
|
|
962
1052
|
logger.error( { message: error, data: req.params, function: 'eyetestAudit-controller-save' } );
|
|
@@ -1198,18 +1288,25 @@ export async function userAuditedData( req, res ) {
|
|
|
1198
1288
|
|
|
1199
1289
|
export async function summaryList( req, res ) {
|
|
1200
1290
|
try {
|
|
1291
|
+
// declare inputData and get request body
|
|
1201
1292
|
const inputData = req.body;
|
|
1293
|
+
|
|
1294
|
+
// throw 204 if client id is not 11
|
|
1202
1295
|
if ( inputData.clientId !== '11' ) {
|
|
1203
1296
|
return res.sendError( 'No data found', 204 );
|
|
1204
1297
|
}
|
|
1205
1298
|
|
|
1299
|
+
// declare openSearch and get data form the .env/secret manager
|
|
1206
1300
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1301
|
+
|
|
1302
|
+
// set limit, offset, sortBy, sortOrder value if not set default value
|
|
1207
1303
|
const limit = inputData.limit || 10;
|
|
1208
1304
|
const offset = inputData.offset ? ( inputData.offset - 1 ) * limit : 0;
|
|
1209
1305
|
|
|
1210
1306
|
const sortBy = inputData?.sortBy ? [ 'coveredStepsAI', 'trustScore', 'date', 'ComplianceScore', 'auditedSteps' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'storeName.keyword';
|
|
1211
1307
|
const order = inputData?.sortOrder || -1;
|
|
1212
1308
|
|
|
1309
|
+
// declare filter and assign range values from inputData
|
|
1213
1310
|
let filter= [
|
|
1214
1311
|
{
|
|
1215
1312
|
'range': {
|
|
@@ -1222,6 +1319,7 @@ export async function summaryList( req, res ) {
|
|
|
1222
1319
|
|
|
1223
1320
|
];
|
|
1224
1321
|
|
|
1322
|
+
// filter by Audit Status if key has minimum one element
|
|
1225
1323
|
if ( inputData?.auditStatus && inputData?.auditStatus?.length > 0 ) {
|
|
1226
1324
|
filter.push( {
|
|
1227
1325
|
'terms': {
|
|
@@ -1229,6 +1327,8 @@ export async function summaryList( req, res ) {
|
|
|
1229
1327
|
},
|
|
1230
1328
|
} );
|
|
1231
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
// filter by Visitors Type if key has minimum one element
|
|
1232
1332
|
if ( inputData?.demographics && inputData?.demographics?.length > 0 ) {
|
|
1233
1333
|
filter.push( {
|
|
1234
1334
|
'terms': {
|
|
@@ -1236,6 +1336,8 @@ export async function summaryList( req, res ) {
|
|
|
1236
1336
|
},
|
|
1237
1337
|
} );
|
|
1238
1338
|
}
|
|
1339
|
+
|
|
1340
|
+
// filter by Eye test Type if key has string value one element
|
|
1239
1341
|
if ( inputData.type && inputData.type!='' ) {
|
|
1240
1342
|
filter.push( {
|
|
1241
1343
|
'term': {
|
|
@@ -1245,6 +1347,17 @@ export async function summaryList( req, res ) {
|
|
|
1245
1347
|
} );
|
|
1246
1348
|
}
|
|
1247
1349
|
|
|
1350
|
+
// filter by near addition if value != "" and eye test must be a physical
|
|
1351
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
1352
|
+
filter.push( {
|
|
1353
|
+
'term': {
|
|
1354
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
1355
|
+
},
|
|
1356
|
+
|
|
1357
|
+
} );
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// filter by stores if key has minimum one element else throw an error
|
|
1248
1361
|
if ( inputData.storeId && inputData.storeId.length > 0 ) {
|
|
1249
1362
|
filter.push( {
|
|
1250
1363
|
|
|
@@ -1257,11 +1370,14 @@ export async function summaryList( req, res ) {
|
|
|
1257
1370
|
return res.sendError( 'No data found', 204 );
|
|
1258
1371
|
}
|
|
1259
1372
|
|
|
1373
|
+
// Declare mustQuery as an array and include all elements from the filter array
|
|
1260
1374
|
let mustQuery = [
|
|
1261
1375
|
...filter,
|
|
1262
1376
|
];
|
|
1263
1377
|
|
|
1264
|
-
|
|
1378
|
+
// filter by complianceScore/tango score if key has minimum one element
|
|
1379
|
+
if ( inputData.complianceScore && inputData.complianceScore?.length > 0 ) {
|
|
1380
|
+
// If the key type is not 'store', split the compliance score ranges
|
|
1265
1381
|
if ( inputData.keyType !== 'store' ) {
|
|
1266
1382
|
const rangeShould = inputData?.complianceScore?.map( ( r ) => {
|
|
1267
1383
|
const [ gte, lte ] = r.split( '-' ).map( Number );
|
|
@@ -1271,7 +1387,7 @@ export async function summaryList( req, res ) {
|
|
|
1271
1387
|
},
|
|
1272
1388
|
};
|
|
1273
1389
|
} );
|
|
1274
|
-
|
|
1390
|
+
// Add the range conditions to mustQuery (any one of the ranges should match)
|
|
1275
1391
|
mustQuery.push(
|
|
1276
1392
|
{
|
|
1277
1393
|
bool: {
|
|
@@ -1280,7 +1396,7 @@ export async function summaryList( req, res ) {
|
|
|
1280
1396
|
},
|
|
1281
1397
|
},
|
|
1282
1398
|
);
|
|
1283
|
-
} else {
|
|
1399
|
+
} else { // If-else condition: use 'terms' filter to match records by Compliance Score / Tango Score
|
|
1284
1400
|
const values = Array.isArray( inputData.complianceScore ) ?
|
|
1285
1401
|
inputData.complianceScore.map( Number ) :
|
|
1286
1402
|
[ Number( inputData.complianceScore ) ];
|
|
@@ -1293,6 +1409,7 @@ export async function summaryList( req, res ) {
|
|
|
1293
1409
|
}
|
|
1294
1410
|
}
|
|
1295
1411
|
|
|
1412
|
+
// filter by category if key exist allow "fake, genuine" else throw valid category
|
|
1296
1413
|
if ( inputData?.category ) {
|
|
1297
1414
|
switch ( inputData?.category ) {
|
|
1298
1415
|
case 'fake':
|
|
@@ -2118,6 +2235,15 @@ export async function summaryCard( req, res ) {
|
|
|
2118
2235
|
},
|
|
2119
2236
|
} );
|
|
2120
2237
|
}
|
|
2238
|
+
|
|
2239
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
2240
|
+
filter.push( {
|
|
2241
|
+
'term': {
|
|
2242
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
2243
|
+
},
|
|
2244
|
+
|
|
2245
|
+
} );
|
|
2246
|
+
}
|
|
2121
2247
|
let mustQuery = [
|
|
2122
2248
|
...filter,
|
|
2123
2249
|
];
|
|
@@ -2271,7 +2397,7 @@ export async function summaryCard( req, res ) {
|
|
|
2271
2397
|
'humanScore': {
|
|
2272
2398
|
filter: {
|
|
2273
2399
|
terms: {
|
|
2274
|
-
'auditStatus.keyword': [ 'Audited', '
|
|
2400
|
+
'auditStatus.keyword': [ 'Audited', 'Re-Audited' ],
|
|
2275
2401
|
},
|
|
2276
2402
|
},
|
|
2277
2403
|
aggs: {
|
|
@@ -2386,7 +2512,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2386
2512
|
inputData.clientId = req.user.userType == 'tango'? inputData?.clientId : req?.user?.clientId;
|
|
2387
2513
|
// get openserach name form the env file
|
|
2388
2514
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2389
|
-
|
|
2515
|
+
const sortBy = inputData?.sortBy ? [ 'complianceScore', 'humanScore', 'trustScore', 'updatedAt', 'createdAt' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'RMName.keyword';
|
|
2390
2516
|
|
|
2391
2517
|
let filter =[
|
|
2392
2518
|
{
|
|
@@ -2399,7 +2525,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2399
2525
|
},
|
|
2400
2526
|
];
|
|
2401
2527
|
let sort =[
|
|
2402
|
-
{ [
|
|
2528
|
+
{ [sortBy]: { order: inputData.sortOrder === 1 ? 'asc' : 'desc' } },
|
|
2403
2529
|
];
|
|
2404
2530
|
if ( inputData.RmList && inputData.RmList?.lenght > 0 ) {
|
|
2405
2531
|
filter.push( {
|
|
@@ -2416,19 +2542,86 @@ export async function emailAlertLog( req, res ) {
|
|
|
2416
2542
|
},
|
|
2417
2543
|
} );
|
|
2418
2544
|
}
|
|
2545
|
+
let search = {
|
|
2546
|
+
must: filter,
|
|
2547
|
+
};
|
|
2548
|
+
if ( inputData.searchValue && inputData.searchValue !== '' ) {
|
|
2549
|
+
search ={
|
|
2550
|
+
'must': filter,
|
|
2551
|
+
'should': [
|
|
2552
|
+
{
|
|
2553
|
+
'wildcard': {
|
|
2554
|
+
'RMEmail.keyword': {
|
|
2555
|
+
'value': `*${inputData.searchValue}*`,
|
|
2556
|
+
},
|
|
2557
|
+
},
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
'wildcard': {
|
|
2561
|
+
'RMName.keyword': {
|
|
2562
|
+
'value': `*${inputData.searchValue}*`,
|
|
2563
|
+
},
|
|
2564
|
+
},
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
'wildcard': {
|
|
2568
|
+
'cluster.keyword': {
|
|
2569
|
+
'value': `*${inputData.searchValue}*`,
|
|
2570
|
+
},
|
|
2571
|
+
},
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
'wildcard': {
|
|
2575
|
+
'optumId.keyword': {
|
|
2576
|
+
'value': `*${inputData.searchValue}*`,
|
|
2577
|
+
},
|
|
2578
|
+
},
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
'wildcard': {
|
|
2582
|
+
'queueId.keyword': {
|
|
2583
|
+
'value': `*${inputData.searchValue}*`,
|
|
2584
|
+
},
|
|
2585
|
+
},
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
'wildcard': {
|
|
2589
|
+
'storeName.keyword': {
|
|
2590
|
+
'value': `*${inputData.searchValue}*`,
|
|
2591
|
+
},
|
|
2592
|
+
},
|
|
2593
|
+
},
|
|
2594
|
+
{
|
|
2595
|
+
'wildcard': {
|
|
2596
|
+
'auditedBy.keyword': {
|
|
2597
|
+
'value': `*${inputData.searchValue}*`,
|
|
2598
|
+
},
|
|
2599
|
+
},
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
'wildcard': {
|
|
2603
|
+
'optomEmailId.keyword': {
|
|
2604
|
+
'value': `*${inputData.searchValue}*`,
|
|
2605
|
+
},
|
|
2606
|
+
},
|
|
2607
|
+
},
|
|
2608
|
+
],
|
|
2609
|
+
'minimum_should_match': 1,
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2419
2612
|
const getQuery = {
|
|
2420
2613
|
'size': inputData.isExport == true? 50000 : inputData?.limit || 10,
|
|
2421
2614
|
'from': offset || 0,
|
|
2422
2615
|
'query': {
|
|
2423
|
-
'bool':
|
|
2424
|
-
'must': filter,
|
|
2425
|
-
},
|
|
2616
|
+
'bool': search,
|
|
2426
2617
|
},
|
|
2427
2618
|
'sort': sort,
|
|
2428
2619
|
};
|
|
2429
2620
|
|
|
2621
|
+
|
|
2430
2622
|
// get query result from the open saerch
|
|
2431
2623
|
const getData= await getOpenSearchData( openSearch.eyeTestEmailAlert, getQuery );
|
|
2624
|
+
const count = getData?.body?.hits?.total?.value;
|
|
2432
2625
|
// throw error if got null or empty array
|
|
2433
2626
|
if ( !getData || getData?.body?.hits?.hits?.length == 0 ) {
|
|
2434
2627
|
return res.sendError( 'No records found', 204 );
|
|
@@ -2436,30 +2629,40 @@ export async function emailAlertLog( req, res ) {
|
|
|
2436
2629
|
let response =[];
|
|
2437
2630
|
for ( let item of getData?.body?.hits?.hits ) {
|
|
2438
2631
|
response.push( {
|
|
2439
|
-
RMEmail: item?._source?.RMEmail,
|
|
2632
|
+
RMEmail: item?._source?.RMEmail || '',
|
|
2633
|
+
RMName: item?._source?.RMName || '',
|
|
2440
2634
|
cluster: item?._source?.cluster,
|
|
2441
2635
|
optumId: item?._source?.optumId,
|
|
2636
|
+
optomEmailId: item?._source?.optomEmailId,
|
|
2442
2637
|
storeName: item?._source?.storeName,
|
|
2443
|
-
complianceScore: item?._source?.complianceScore,
|
|
2444
|
-
|
|
2638
|
+
complianceScore: Math.round( item?._source?.complianceScore || 0 ),
|
|
2639
|
+
humanScore: Math.round( item?._source?.humanScore || 0 ),
|
|
2640
|
+
trustScore: item?._source?.trustScore || '--',
|
|
2445
2641
|
auditedId: item?._source?.auditedId,
|
|
2446
2642
|
queueId: item?._source?.queueId,
|
|
2643
|
+
auditedBy: item?._source?.auditedBy || '--',
|
|
2447
2644
|
|
|
2448
2645
|
} );
|
|
2449
2646
|
}
|
|
2450
2647
|
|
|
2451
2648
|
if ( inputData.isExport == true ) {
|
|
2649
|
+
logger.info( { response: response } );
|
|
2452
2650
|
const chunkedMappingData = await chunkArray( response, 10 );
|
|
2453
2651
|
const promises = chunkedMappingData.map( async ( chunk ) => {
|
|
2454
2652
|
const exportData = [];
|
|
2455
2653
|
for ( const element of chunk ) {
|
|
2456
2654
|
exportData.push( {
|
|
2457
|
-
'RM
|
|
2655
|
+
'RM Name': element.RMEmail,
|
|
2458
2656
|
'Cluster Name': element.cluster,
|
|
2459
|
-
'Optom
|
|
2657
|
+
'Optom ID': element.optumId,
|
|
2658
|
+
'Optom Email': element.optomEmailId || '--',
|
|
2460
2659
|
'Store Name': element.storeName,
|
|
2461
|
-
'
|
|
2462
|
-
'
|
|
2660
|
+
'Audited By': element.auditedBy || '--',
|
|
2661
|
+
'Tango Score': Math.round( element.complianceScore || 0 ),
|
|
2662
|
+
|
|
2663
|
+
'Trust Score': element.trustScore || '--',
|
|
2664
|
+
'Human Audited Score': Math.round( element.humanScore || 0 ),
|
|
2665
|
+
|
|
2463
2666
|
|
|
2464
2667
|
} );
|
|
2465
2668
|
}
|
|
@@ -2468,6 +2671,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2468
2671
|
} );
|
|
2469
2672
|
const mappedArrays = await Promise.all( promises );
|
|
2470
2673
|
const result = mappedArrays.flat();
|
|
2674
|
+
logger.info( { result: result } );
|
|
2471
2675
|
await download( result, res );
|
|
2472
2676
|
return;
|
|
2473
2677
|
}
|
|
@@ -2477,7 +2681,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2477
2681
|
|
|
2478
2682
|
|
|
2479
2683
|
logger.info( { inputData: inputData, getData: response } );
|
|
2480
|
-
return res.sendSuccess( { result: response, count:
|
|
2684
|
+
return res.sendSuccess( { result: response, count: count } );
|
|
2481
2685
|
} catch ( error ) {
|
|
2482
2686
|
const err = error.message || 'Internal Server Error';
|
|
2483
2687
|
logger.error( { message: error, data: req.body, function: 'eyetestAudit-controller-emailAlertLog' } );
|
|
@@ -2637,10 +2841,6 @@ export async function getEmailConfig( req, res ) {
|
|
|
2637
2841
|
configureType: 'email',
|
|
2638
2842
|
};
|
|
2639
2843
|
const getOne = await findOneEyeTestConfig( query, { complianceThreshold: 1, _id: 1 } );
|
|
2640
|
-
logger.info( { getOne: getOne, message: 'getEmailConfig' } );
|
|
2641
|
-
// if ( !getOne?.complianceThreshold ) {
|
|
2642
|
-
// return res.sendError( 'No data found', 204 );
|
|
2643
|
-
// }
|
|
2644
2844
|
if ( !getOne ) {
|
|
2645
2845
|
return res.sendError( ' No data found', 204 );
|
|
2646
2846
|
}
|
|
@@ -64,6 +64,7 @@ export const saveSchema = joi.object( {
|
|
|
64
64
|
spokenLanguage: joi.string().optional(),
|
|
65
65
|
cluster: joi.string().required().allow( '' ),
|
|
66
66
|
RMEmail: joi.string().required().allow( '' ),
|
|
67
|
+
isReSend: joi.boolean().optional(),
|
|
67
68
|
} );
|
|
68
69
|
|
|
69
70
|
export const saveValid = {
|
|
@@ -156,7 +157,7 @@ export const summaryListSchema = joi.object( {
|
|
|
156
157
|
).optional(),
|
|
157
158
|
storeId: joi.array().items( joi.string().required() ).required(),
|
|
158
159
|
complianceScore: joi.array().items( joi.string().required() ).optional(),
|
|
159
|
-
nearAddition: joi.string().optional().allow( '' ),
|
|
160
|
+
nearAddition: joi.string().optional().allow( '', 'true', 'false' ),
|
|
160
161
|
isExport: joi.boolean().optional(),
|
|
161
162
|
searchValue: joi.string().optional().allow( '' ),
|
|
162
163
|
sortBy: joi.string().optional(),
|
|
@@ -234,7 +235,7 @@ export const summaryCardSchema = joi.object( {
|
|
|
234
235
|
).optional(),
|
|
235
236
|
storeId: joi.array().items( joi.string().required() ).required(),
|
|
236
237
|
complianceScore: joi.array().items( joi.string().required() ).optional(),
|
|
237
|
-
nearAddition: joi.string().optional().allow( '' ),
|
|
238
|
+
nearAddition: joi.string().optional().allow( '', 'true', 'false' ),
|
|
238
239
|
category: joi.string().optional() .valid( 'fake', 'genuine' ),
|
|
239
240
|
} );
|
|
240
241
|
|
|
@@ -279,12 +280,8 @@ export const emailAlertLogSchema = joi.object( {
|
|
|
279
280
|
|
|
280
281
|
return value;
|
|
281
282
|
} ),
|
|
282
|
-
type: joi.string().required().messages( {
|
|
283
|
-
'any.required': 'Type is required',
|
|
284
|
-
'string.empty': 'Type cannot be empty',
|
|
285
|
-
} ),
|
|
286
283
|
RMList: joi.array().items(
|
|
287
|
-
joi.string().
|
|
284
|
+
joi.string().required(),
|
|
288
285
|
).optional(),
|
|
289
286
|
clientId: joi.string().when( '$userType', {
|
|
290
287
|
is: 'tango',
|
|
@@ -294,7 +291,7 @@ export const emailAlertLogSchema = joi.object( {
|
|
|
294
291
|
} ),
|
|
295
292
|
otherwise: joi.optional(),
|
|
296
293
|
} ),
|
|
297
|
-
cluster: joi.array().items( joi.string().required() ).
|
|
294
|
+
cluster: joi.array().items( joi.string().required() ).optional(),
|
|
298
295
|
isExport: joi.boolean().optional(),
|
|
299
296
|
searchValue: joi.string().optional().allow( '' ),
|
|
300
297
|
sortBy: joi.string().optional(),
|
|
@@ -413,6 +410,7 @@ export const getEmailConfigSchema = joi.object( {
|
|
|
413
410
|
} ),
|
|
414
411
|
otherwise: joi.optional(),
|
|
415
412
|
} ),
|
|
413
|
+
|
|
416
414
|
} );
|
|
417
415
|
|
|
418
416
|
export const getEmailConfigValid = {
|
|
@@ -3,14 +3,14 @@ import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
|
3
3
|
import { accessVerification } from 'tango-app-api-middleware';
|
|
4
4
|
import { cancelValid, clusterListValid, emailAlertLogValid, getEmailConfigValid, getFileHistoryValid, getFileValid, getListValid, getUserConfigValid, rmListValid, saveValid, summaryCardValid, summaryListValid, updateEmailConfigValid, updateUserConfigValid, userAuditedDataValid, viewFileValid } from '../dtos/eyeTestAudit.dtos.js';
|
|
5
5
|
import { addUserConfig, cancel, clusterList, emailAlertLog, getEmailConfig, getFile, getFileHistory, getList, getUserConfig, rmList, save, summaryCard, summaryList, updateEmailConfig, updateEmailConfig1, updateUserConfig, userAuditedData, viewFile } from '../controllers/eyeTestAudit.controllers.js';
|
|
6
|
-
import { isSuperAdmin, roleValidation } from '../validation/eyeTest.validation.js';
|
|
6
|
+
import { isSuperAdmin, roleValidation, sendAlert } from '../validation/eyeTest.validation.js';
|
|
7
7
|
|
|
8
8
|
export const eyeTestAuditRouter = Router();
|
|
9
9
|
|
|
10
10
|
eyeTestAuditRouter.post( '/get-list', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( getListValid ), getList );
|
|
11
11
|
eyeTestAuditRouter.get( '/view-file/:id', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( viewFileValid ), viewFile );
|
|
12
12
|
eyeTestAuditRouter.get( '/get-file/:id', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( getFileValid ), getFile );
|
|
13
|
-
eyeTestAuditRouter.post( '/save', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( saveValid ), save );
|
|
13
|
+
eyeTestAuditRouter.post( '/save', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( saveValid ), sendAlert, save );
|
|
14
14
|
eyeTestAuditRouter.post( '/cancel', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( cancelValid ), cancel );
|
|
15
15
|
eyeTestAuditRouter.get( '/get-file-history/:id/:type', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( getFileHistoryValid ), getFileHistory );
|
|
16
16
|
eyeTestAuditRouter.get( '/user-audited-data/:id', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( userAuditedDataValid ), userAuditedData );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { logger } from 'tango-app-api-middleware';
|
|
1
|
+
import { getOpenSearchById, logger } from 'tango-app-api-middleware';
|
|
2
|
+
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
2
3
|
|
|
3
4
|
export async function roleValidation( req, res, next ) {
|
|
4
5
|
try {
|
|
@@ -39,3 +40,133 @@ export async function isSuperAdmin( req, res, next ) {
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
export async function sendAlert( req, res, next ) {
|
|
44
|
+
try {
|
|
45
|
+
const inputData = req.body;
|
|
46
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
47
|
+
if ( inputData.auditStatus == 'Re-Audited' && inputData.type == 'physical' && inputData?.isReSend == undefined ) {
|
|
48
|
+
const getOne = await getOpenSearchById( openSearch.EyeTestInput, inputData.id );
|
|
49
|
+
const id = `${inputData.id}_${getOne?.body?._source?.optumId}_${inputData.fileId}`;
|
|
50
|
+
const getId = await getOpenSearchById( openSearch.eyeTestEmailAlert, id );
|
|
51
|
+
logger.info( { getId: getId, id: id } );
|
|
52
|
+
const inputUpdatedData = getId?.body;
|
|
53
|
+
if ( inputUpdatedData ) {
|
|
54
|
+
return res.sendSuccess( 'Do you want resend mail?', 202 );
|
|
55
|
+
} else {
|
|
56
|
+
return next();
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
return next();
|
|
60
|
+
}
|
|
61
|
+
} catch ( error ) {
|
|
62
|
+
const err = error.message;
|
|
63
|
+
return res.sendError( err, 500 );
|
|
64
|
+
}
|
|
65
|
+
}
|
|
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
|
+
export async function sendComplianceEmail( {
|
|
75
|
+
toEmail,
|
|
76
|
+
fromEmail, // sender email added here
|
|
77
|
+
userName,
|
|
78
|
+
compliancePercentage,
|
|
79
|
+
steps,
|
|
80
|
+
} ) {
|
|
81
|
+
// Initialize AWS SES client
|
|
82
|
+
logger.info( { steps: steps } );
|
|
83
|
+
const sesClient = new SESClient( {
|
|
84
|
+
region: 'ap-south-1', // Change to your AWS region
|
|
85
|
+
credentials: {
|
|
86
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID_SIGNED_URL,
|
|
87
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY__SIGNED_URL,
|
|
88
|
+
},
|
|
89
|
+
} );
|
|
90
|
+
const htmlBody = ( userName, steps, compliancePercentage ) => {
|
|
91
|
+
const tableRows = steps
|
|
92
|
+
.map(
|
|
93
|
+
( item ) => `
|
|
94
|
+
<tr>
|
|
95
|
+
<td>${item.stepName}</td>
|
|
96
|
+
<td>${item.comment?? '--'}</td>
|
|
97
|
+
</tr>
|
|
98
|
+
`,
|
|
99
|
+
)
|
|
100
|
+
.join( '' );
|
|
101
|
+
|
|
102
|
+
return `
|
|
103
|
+
<!DOCTYPE html>
|
|
104
|
+
<html>
|
|
105
|
+
<head>
|
|
106
|
+
<meta charset="UTF-8" />
|
|
107
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
108
|
+
<title>Non Compliance Report</title>
|
|
109
|
+
<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; }
|
|
114
|
+
.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; }
|
|
118
|
+
</style>
|
|
119
|
+
</head>
|
|
120
|
+
<body>
|
|
121
|
+
<div class="container">
|
|
122
|
+
<p class="title">Non Compliance on Eye Test Procedures</p>
|
|
123
|
+
<p class="subtitle">
|
|
124
|
+
Dear ${userName},<br><br>
|
|
125
|
+
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>
|
|
127
|
+
Please take a look at the below findings.
|
|
128
|
+
</p>
|
|
129
|
+
|
|
130
|
+
<table class="table">
|
|
131
|
+
<thead>
|
|
132
|
+
<tr>
|
|
133
|
+
<th>Step Name</th>
|
|
134
|
+
<th>Comments</th>
|
|
135
|
+
</tr>
|
|
136
|
+
</thead>
|
|
137
|
+
<tbody>
|
|
138
|
+
${tableRows}
|
|
139
|
+
</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
|
|
142
|
+
<p class="compliance">Compliance Percentage: ${compliancePercentage}%</p>
|
|
143
|
+
<p class="footer">Regards,<br>Team Tango</p>
|
|
144
|
+
</div>
|
|
145
|
+
</body>
|
|
146
|
+
</html>`;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const emailHTML = htmlBody( userName, steps, compliancePercentage );
|
|
150
|
+
|
|
151
|
+
const params = {
|
|
152
|
+
Source: fromEmail, // sender email (must be verified in SES)
|
|
153
|
+
Destination: {
|
|
154
|
+
ToAddresses: [ toEmail ],
|
|
155
|
+
},
|
|
156
|
+
Message: {
|
|
157
|
+
Subject: { Data: 'Non Compliance on Eye Test Procedures' },
|
|
158
|
+
Body: { Html: { Data: emailHTML } },
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const response = await sesClient.send( new SendEmailCommand( params ) );
|
|
164
|
+
// console.log( 'Email sent successfully:', response.MessageId );
|
|
165
|
+
return response;
|
|
166
|
+
} catch ( error ) {
|
|
167
|
+
// console.error( 'Error sending email:', error );
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|