tango-app-api-audit 3.6.27 → 3.6.30
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 +3 -1
- package/src/controllers/audit.controllers.js +8 -8
- package/src/controllers/eyeTestAudit.controllers.js +306 -46
- package/src/controllers/traxAudit.controllers.js +2 -3
- package/src/dtos/eyeTestAudit.dtos.js +6 -8
- package/src/routes/eyeTestAudit.routes.js +2 -2
- package/src/validation/eyeTest.validation.js +204 -1
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.30",
|
|
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,8 @@ 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 );
|
|
755
|
+
const url = JSON.parse( process.env.URL );
|
|
753
756
|
|
|
754
757
|
let searchFilter =[
|
|
755
758
|
{
|
|
@@ -811,7 +814,6 @@ export async function save( req, res ) {
|
|
|
811
814
|
},
|
|
812
815
|
};
|
|
813
816
|
const getOutput = await getOpenSearchData( openSearch.EyeTestOutput, searchQuery );
|
|
814
|
-
logger.info( { getOutput: getOutput } );
|
|
815
817
|
const output = getOutput?.body?.hits?.hits?.length > 0 ? getOutput?.body?.hits?.hits[0] : null;
|
|
816
818
|
if ( !output ) {
|
|
817
819
|
return res.sendError( 'This file is currently being audited by another user. Any actions you perform will not be saved.', 400 );
|
|
@@ -845,19 +847,76 @@ export async function save( req, res ) {
|
|
|
845
847
|
|
|
846
848
|
const getEyetestConfig = await findOneEyeTestConfig( { clientId: req?.user.userType == 'tango'? inputData?.clientId : req.user.clientId, configureType: 'email' }, { complianceThreshold: 1 } );
|
|
847
849
|
|
|
848
|
-
if ( getEyetestConfig && inputData.auditStatus == 'Audited' && inputUpdatedData ) {
|
|
849
|
-
const trustScore = Math.round( 100-( ( inputData?.overRides / inputData?.steps?.length ) * 100 ) );
|
|
850
|
+
if ( getEyetestConfig &&( inputData.auditStatus == 'Audited' ||inputData.auditStatus == 'Re-Audited' ) && inputUpdatedData && inputData.type === 'physical' && inputData?.isReSend !== false ) {
|
|
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 emailList = inputData?.RMEmail?.split( ',' );
|
|
857
|
+
let rmName;
|
|
858
|
+
|
|
859
|
+
if ( emailList?.length > 0 ) {
|
|
860
|
+
const userQuery = [
|
|
861
|
+
{
|
|
862
|
+
$match: {
|
|
863
|
+
$and: [
|
|
864
|
+
{
|
|
865
|
+
email: { $in: emailList },
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
$group: {
|
|
872
|
+
_id: null,
|
|
873
|
+
userList: { $addToSet: '$userName' },
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
$project: {
|
|
878
|
+
userList: 1,
|
|
879
|
+
_id: 0,
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
];
|
|
883
|
+
const getRMName = await aggregateUser( userQuery );
|
|
884
|
+
rmName = getRMName?.[0]?.userList || '';
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const order = [
|
|
888
|
+
'Distance-VA-Check',
|
|
889
|
+
'Torchlight Check',
|
|
890
|
+
'JCC Check',
|
|
891
|
+
'Duochrome Check',
|
|
892
|
+
'Near Addition Check',
|
|
893
|
+
'Prescription Verification Check',
|
|
894
|
+
];
|
|
895
|
+
const steps = inputData?.steps
|
|
896
|
+
.filter( ( step ) =>
|
|
897
|
+
step.hasOwnProperty( 'auditedStatus' ) ?
|
|
898
|
+
step.auditedStatus === false :
|
|
899
|
+
step.AIStatus === false,
|
|
900
|
+
)
|
|
901
|
+
.map( ( step ) => ( {
|
|
902
|
+
stepName: step.processName,
|
|
903
|
+
comment: step?.reason === 'others'? step.otherReason:step?.reason,
|
|
904
|
+
} ) );
|
|
905
|
+
steps.sort(
|
|
906
|
+
( a, b ) => order.indexOf( a.processName ) - order.indexOf( b.processName ),
|
|
907
|
+
);
|
|
853
908
|
switch ( condition ) {
|
|
854
|
-
case '>': if (
|
|
909
|
+
case '>': if ( humanScore > value ) {
|
|
855
910
|
const record1 = {
|
|
856
911
|
RMEmail: inputData.RMEmail,
|
|
912
|
+
RMName: rmName.toString(),
|
|
857
913
|
cluster: inputData.cluster,
|
|
858
914
|
optumId: inputUpdatedData?._source?.optumId,
|
|
915
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
859
916
|
storeName: inputUpdatedData?._source?.storeName,
|
|
860
917
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
918
|
+
humanScore: humanScore,
|
|
919
|
+
auditedBy: userName?.userName || '',
|
|
861
920
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
862
921
|
auditedId: inputUpdatedData?._id,
|
|
863
922
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -866,15 +925,29 @@ export async function save( req, res ) {
|
|
|
866
925
|
|
|
867
926
|
};
|
|
868
927
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record1 );
|
|
928
|
+
sendComplianceEmail( {
|
|
929
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
930
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
931
|
+
userName: 'User',
|
|
932
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
933
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
934
|
+
compliancePercentage: humanScore,
|
|
935
|
+
steps: steps,
|
|
936
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
937
|
+
} );
|
|
869
938
|
}
|
|
870
939
|
break;
|
|
871
|
-
case '<': if (
|
|
940
|
+
case '<': if ( humanScore < value ) {
|
|
872
941
|
const record2 = {
|
|
873
942
|
RMEmail: inputData.RMEmail,
|
|
943
|
+
RMName: rmName.toString(),
|
|
874
944
|
cluster: inputData.cluster,
|
|
875
945
|
optumId: inputUpdatedData?._source?.optumId,
|
|
946
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
876
947
|
storeName: inputUpdatedData?._source?.storeName,
|
|
877
948
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
949
|
+
humanScore: humanScore,
|
|
950
|
+
auditedBy: userName?.userName || '',
|
|
878
951
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
879
952
|
auditedId: inputUpdatedData?._id,
|
|
880
953
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -883,15 +956,29 @@ export async function save( req, res ) {
|
|
|
883
956
|
|
|
884
957
|
};
|
|
885
958
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record2 );
|
|
959
|
+
sendComplianceEmail( {
|
|
960
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
961
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
962
|
+
userName: 'User',
|
|
963
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
964
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
965
|
+
compliancePercentage: humanScore,
|
|
966
|
+
steps: steps,
|
|
967
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
968
|
+
} );
|
|
886
969
|
};
|
|
887
970
|
break;
|
|
888
|
-
case '>=': if (
|
|
971
|
+
case '>=': if ( humanScore >= value ) {
|
|
889
972
|
const record3 = {
|
|
890
973
|
RMEmail: inputData.RMEmail,
|
|
974
|
+
RMName: rmName.toString(),
|
|
891
975
|
cluster: inputData.cluster,
|
|
892
976
|
optumId: inputUpdatedData?._source?.optumId,
|
|
977
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
893
978
|
storeName: inputUpdatedData?._source?.storeName,
|
|
894
979
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
980
|
+
humanScore: humanScore,
|
|
981
|
+
auditedBy: userName?.userName || '',
|
|
895
982
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
896
983
|
auditedId: inputUpdatedData?._id,
|
|
897
984
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -900,15 +987,29 @@ export async function save( req, res ) {
|
|
|
900
987
|
|
|
901
988
|
};
|
|
902
989
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record3 );
|
|
990
|
+
sendComplianceEmail( {
|
|
991
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
992
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
993
|
+
userName: 'User',
|
|
994
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
995
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
996
|
+
compliancePercentage: humanScore,
|
|
997
|
+
steps: steps,
|
|
998
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
999
|
+
} );
|
|
903
1000
|
}
|
|
904
1001
|
break;
|
|
905
|
-
case '<=': if (
|
|
1002
|
+
case '<=': if ( humanScore <= value ) {
|
|
906
1003
|
const record4 = {
|
|
907
1004
|
RMEmail: inputData.RMEmail,
|
|
1005
|
+
RMName: rmName.toString(),
|
|
908
1006
|
cluster: inputData.cluster,
|
|
909
1007
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1008
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
910
1009
|
storeName: inputUpdatedData?._source?.storeName,
|
|
911
1010
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1011
|
+
humanScore: humanScore,
|
|
1012
|
+
auditedBy: userName?.userName || '',
|
|
912
1013
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
913
1014
|
auditedId: inputUpdatedData?._id,
|
|
914
1015
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -917,16 +1018,30 @@ export async function save( req, res ) {
|
|
|
917
1018
|
|
|
918
1019
|
};
|
|
919
1020
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record4 );
|
|
1021
|
+
sendComplianceEmail( {
|
|
1022
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1023
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1024
|
+
userName: 'User',
|
|
1025
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1026
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1027
|
+
compliancePercentage: humanScore,
|
|
1028
|
+
steps: steps,
|
|
1029
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1030
|
+
} );
|
|
920
1031
|
}
|
|
921
1032
|
break;
|
|
922
1033
|
case '=':
|
|
923
|
-
case '==': if (
|
|
1034
|
+
case '==': if ( humanScore == value ) {
|
|
924
1035
|
const record5 = {
|
|
925
1036
|
RMEmail: inputData.RMEmail,
|
|
1037
|
+
RMName: rmName.toString(),
|
|
926
1038
|
cluster: inputData.cluster,
|
|
927
1039
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1040
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
928
1041
|
storeName: inputUpdatedData?._source?.storeName,
|
|
929
1042
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1043
|
+
humanScore: humanScore,
|
|
1044
|
+
auditedBy: userName?.userName || '',
|
|
930
1045
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
931
1046
|
auditedId: inputUpdatedData?._id,
|
|
932
1047
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -935,15 +1050,29 @@ export async function save( req, res ) {
|
|
|
935
1050
|
|
|
936
1051
|
};
|
|
937
1052
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record5 );
|
|
1053
|
+
sendComplianceEmail( {
|
|
1054
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1055
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1056
|
+
userName: 'User',
|
|
1057
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1058
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1059
|
+
compliancePercentage: humanScore,
|
|
1060
|
+
steps: steps,
|
|
1061
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1062
|
+
} );
|
|
938
1063
|
}
|
|
939
1064
|
break;
|
|
940
|
-
case '===': if (
|
|
1065
|
+
case '===': if ( humanScore === value ) {
|
|
941
1066
|
const record6 = {
|
|
942
1067
|
RMEmail: inputData.RMEmail,
|
|
1068
|
+
RMName: rmName.toString(),
|
|
943
1069
|
cluster: inputData.cluster,
|
|
944
1070
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1071
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
945
1072
|
storeName: inputUpdatedData?._source?.storeName,
|
|
946
1073
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1074
|
+
humanScore: humanScore,
|
|
1075
|
+
auditedBy: userName?.userName || '',
|
|
947
1076
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
948
1077
|
auditedId: inputUpdatedData?._id,
|
|
949
1078
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -951,12 +1080,22 @@ export async function save( req, res ) {
|
|
|
951
1080
|
updatedAt: new Date(),
|
|
952
1081
|
};
|
|
953
1082
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record6 );
|
|
1083
|
+
sendComplianceEmail( {
|
|
1084
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1085
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1086
|
+
userName: 'User',
|
|
1087
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1088
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1089
|
+
compliancePercentage: humanScore,
|
|
1090
|
+
steps: steps,
|
|
1091
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1092
|
+
} );
|
|
954
1093
|
}
|
|
955
1094
|
break;
|
|
956
1095
|
default: throw new Error( `Unknown condition: ${condition}` );
|
|
957
1096
|
}
|
|
958
1097
|
}
|
|
959
|
-
return res.sendSuccess( 'The file has been
|
|
1098
|
+
return res.sendSuccess( 'The file has been submitted successfully' );
|
|
960
1099
|
} catch ( error ) {
|
|
961
1100
|
const err = error.message || 'Internal Server Error';
|
|
962
1101
|
logger.error( { message: error, data: req.params, function: 'eyetestAudit-controller-save' } );
|
|
@@ -1198,18 +1337,25 @@ export async function userAuditedData( req, res ) {
|
|
|
1198
1337
|
|
|
1199
1338
|
export async function summaryList( req, res ) {
|
|
1200
1339
|
try {
|
|
1340
|
+
// declare inputData and get request body
|
|
1201
1341
|
const inputData = req.body;
|
|
1342
|
+
|
|
1343
|
+
// throw 204 if client id is not 11
|
|
1202
1344
|
if ( inputData.clientId !== '11' ) {
|
|
1203
1345
|
return res.sendError( 'No data found', 204 );
|
|
1204
1346
|
}
|
|
1205
1347
|
|
|
1348
|
+
// declare openSearch and get data form the .env/secret manager
|
|
1206
1349
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1350
|
+
|
|
1351
|
+
// set limit, offset, sortBy, sortOrder value if not set default value
|
|
1207
1352
|
const limit = inputData.limit || 10;
|
|
1208
1353
|
const offset = inputData.offset ? ( inputData.offset - 1 ) * limit : 0;
|
|
1209
1354
|
|
|
1210
1355
|
const sortBy = inputData?.sortBy ? [ 'coveredStepsAI', 'trustScore', 'date', 'ComplianceScore', 'auditedSteps' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'storeName.keyword';
|
|
1211
1356
|
const order = inputData?.sortOrder || -1;
|
|
1212
1357
|
|
|
1358
|
+
// declare filter and assign range values from inputData
|
|
1213
1359
|
let filter= [
|
|
1214
1360
|
{
|
|
1215
1361
|
'range': {
|
|
@@ -1222,6 +1368,7 @@ export async function summaryList( req, res ) {
|
|
|
1222
1368
|
|
|
1223
1369
|
];
|
|
1224
1370
|
|
|
1371
|
+
// filter by Audit Status if key has minimum one element
|
|
1225
1372
|
if ( inputData?.auditStatus && inputData?.auditStatus?.length > 0 ) {
|
|
1226
1373
|
filter.push( {
|
|
1227
1374
|
'terms': {
|
|
@@ -1229,6 +1376,8 @@ export async function summaryList( req, res ) {
|
|
|
1229
1376
|
},
|
|
1230
1377
|
} );
|
|
1231
1378
|
}
|
|
1379
|
+
|
|
1380
|
+
// filter by Visitors Type if key has minimum one element
|
|
1232
1381
|
if ( inputData?.demographics && inputData?.demographics?.length > 0 ) {
|
|
1233
1382
|
filter.push( {
|
|
1234
1383
|
'terms': {
|
|
@@ -1236,6 +1385,8 @@ export async function summaryList( req, res ) {
|
|
|
1236
1385
|
},
|
|
1237
1386
|
} );
|
|
1238
1387
|
}
|
|
1388
|
+
|
|
1389
|
+
// filter by Eye test Type if key has string value one element
|
|
1239
1390
|
if ( inputData.type && inputData.type!='' ) {
|
|
1240
1391
|
filter.push( {
|
|
1241
1392
|
'term': {
|
|
@@ -1245,6 +1396,17 @@ export async function summaryList( req, res ) {
|
|
|
1245
1396
|
} );
|
|
1246
1397
|
}
|
|
1247
1398
|
|
|
1399
|
+
// filter by near addition if value != "" and eye test must be a physical
|
|
1400
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
1401
|
+
filter.push( {
|
|
1402
|
+
'term': {
|
|
1403
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
1404
|
+
},
|
|
1405
|
+
|
|
1406
|
+
} );
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// filter by stores if key has minimum one element else throw an error
|
|
1248
1410
|
if ( inputData.storeId && inputData.storeId.length > 0 ) {
|
|
1249
1411
|
filter.push( {
|
|
1250
1412
|
|
|
@@ -1257,11 +1419,14 @@ export async function summaryList( req, res ) {
|
|
|
1257
1419
|
return res.sendError( 'No data found', 204 );
|
|
1258
1420
|
}
|
|
1259
1421
|
|
|
1422
|
+
// Declare mustQuery as an array and include all elements from the filter array
|
|
1260
1423
|
let mustQuery = [
|
|
1261
1424
|
...filter,
|
|
1262
1425
|
];
|
|
1263
1426
|
|
|
1264
|
-
|
|
1427
|
+
// filter by complianceScore/tango score if key has minimum one element
|
|
1428
|
+
if ( inputData.complianceScore && inputData.complianceScore?.length > 0 ) {
|
|
1429
|
+
// If the key type is not 'store', split the compliance score ranges
|
|
1265
1430
|
if ( inputData.keyType !== 'store' ) {
|
|
1266
1431
|
const rangeShould = inputData?.complianceScore?.map( ( r ) => {
|
|
1267
1432
|
const [ gte, lte ] = r.split( '-' ).map( Number );
|
|
@@ -1271,7 +1436,7 @@ export async function summaryList( req, res ) {
|
|
|
1271
1436
|
},
|
|
1272
1437
|
};
|
|
1273
1438
|
} );
|
|
1274
|
-
|
|
1439
|
+
// Add the range conditions to mustQuery (any one of the ranges should match)
|
|
1275
1440
|
mustQuery.push(
|
|
1276
1441
|
{
|
|
1277
1442
|
bool: {
|
|
@@ -1280,7 +1445,7 @@ export async function summaryList( req, res ) {
|
|
|
1280
1445
|
},
|
|
1281
1446
|
},
|
|
1282
1447
|
);
|
|
1283
|
-
} else {
|
|
1448
|
+
} else { // If-else condition: use 'terms' filter to match records by Compliance Score / Tango Score
|
|
1284
1449
|
const values = Array.isArray( inputData.complianceScore ) ?
|
|
1285
1450
|
inputData.complianceScore.map( Number ) :
|
|
1286
1451
|
[ Number( inputData.complianceScore ) ];
|
|
@@ -1293,6 +1458,7 @@ export async function summaryList( req, res ) {
|
|
|
1293
1458
|
}
|
|
1294
1459
|
}
|
|
1295
1460
|
|
|
1461
|
+
// filter by category if key exist allow "fake, genuine" else throw valid category
|
|
1296
1462
|
if ( inputData?.category ) {
|
|
1297
1463
|
switch ( inputData?.category ) {
|
|
1298
1464
|
case 'fake':
|
|
@@ -1898,7 +2064,9 @@ export async function summaryList( req, res ) {
|
|
|
1898
2064
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
1899
2065
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
1900
2066
|
'Optom ID': element?._source?.optumId,
|
|
2067
|
+
'Optom Email ID': element?._source?.salesmanEmailId,
|
|
1901
2068
|
'Queue ID': element?._source?.queueId,
|
|
2069
|
+
'Power Types': element?._source?.nearAddition === 'true' ? 'With Near Addition' : element?._source?.nearAddition === 'false' ? 'Without Near Addition':'',
|
|
1902
2070
|
'Tango Score': `${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.coveredStepsAI/ element?._source?.totalSteps )*100 ): 0} %`,
|
|
1903
2071
|
'Steps Covered By AI': element?._source?.coveredStepsAI || 0,
|
|
1904
2072
|
'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',
|
|
@@ -1923,12 +2091,16 @@ export async function summaryList( req, res ) {
|
|
|
1923
2091
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
1924
2092
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
1925
2093
|
'Engagement ID': element?._source?.engagementId,
|
|
2094
|
+
'Optom ID': element?._source?.optm_userId,
|
|
2095
|
+
'Optom Email ID': element?._source?.userEmail,
|
|
2096
|
+
'Optom Name': element?._source?.displayName,
|
|
1926
2097
|
'Tango Score': `${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.coveredStepsAI/ element?._source?.totalSteps )*100 ): 0} %`,
|
|
1927
|
-
'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',
|
|
1928
2098
|
'Steps Covered By AI': element?._source?.coveredStepsAI || 0,
|
|
1929
|
-
'Steps Covered By Human': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?element?._source?.auditedSteps :'NA',
|
|
1930
2099
|
'Trust Score': element?._source?.trustScore == null || element?._source?.trustScore ==undefined ? 'NA' : `${element?._source?.trustScore} %`,
|
|
2100
|
+
'Steps Covered By Human': [ 'Re-Audited', 'Audited' ].includes( element?._source?.auditStatus )?element?._source?.auditedSteps :'NA',
|
|
2101
|
+
'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',
|
|
1931
2102
|
'Test Duration': duration || '',
|
|
2103
|
+
'Comments ': element._source?.subComment || '',
|
|
1932
2104
|
'Audit Status': element?._source?.auditStatus || '',
|
|
1933
2105
|
'Audited By': userName?.userName || '',
|
|
1934
2106
|
|
|
@@ -2118,6 +2290,15 @@ export async function summaryCard( req, res ) {
|
|
|
2118
2290
|
},
|
|
2119
2291
|
} );
|
|
2120
2292
|
}
|
|
2293
|
+
|
|
2294
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
2295
|
+
filter.push( {
|
|
2296
|
+
'term': {
|
|
2297
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
2298
|
+
},
|
|
2299
|
+
|
|
2300
|
+
} );
|
|
2301
|
+
}
|
|
2121
2302
|
let mustQuery = [
|
|
2122
2303
|
...filter,
|
|
2123
2304
|
];
|
|
@@ -2271,7 +2452,7 @@ export async function summaryCard( req, res ) {
|
|
|
2271
2452
|
'humanScore': {
|
|
2272
2453
|
filter: {
|
|
2273
2454
|
terms: {
|
|
2274
|
-
'auditStatus.keyword': [ 'Audited', '
|
|
2455
|
+
'auditStatus.keyword': [ 'Audited', 'Re-Audited' ],
|
|
2275
2456
|
},
|
|
2276
2457
|
},
|
|
2277
2458
|
aggs: {
|
|
@@ -2386,49 +2567,123 @@ export async function emailAlertLog( req, res ) {
|
|
|
2386
2567
|
inputData.clientId = req.user.userType == 'tango'? inputData?.clientId : req?.user?.clientId;
|
|
2387
2568
|
// get openserach name form the env file
|
|
2388
2569
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2389
|
-
|
|
2570
|
+
const sortBy = inputData?.sortBy ? [ 'complianceScore', 'humanScore', 'trustScore', 'updatedAt', 'createdAt' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'RMName.keyword';
|
|
2390
2571
|
|
|
2391
2572
|
let filter =[
|
|
2392
2573
|
{
|
|
2393
2574
|
'range': {
|
|
2394
2575
|
'createdAt': {
|
|
2395
2576
|
'gte': dayjs( inputData.fromDate ),
|
|
2396
|
-
'lte': dayjs(
|
|
2577
|
+
'lte': dayjs( inputData.toDate ),
|
|
2397
2578
|
},
|
|
2398
2579
|
},
|
|
2399
2580
|
},
|
|
2400
2581
|
];
|
|
2401
2582
|
let sort =[
|
|
2402
|
-
{ [
|
|
2583
|
+
{ [sortBy]: { order: inputData.sortOrder === 1 ? 'asc' : 'desc' } },
|
|
2403
2584
|
];
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2585
|
+
|
|
2586
|
+
let search = {
|
|
2587
|
+
'must': filter,
|
|
2588
|
+
'should': [],
|
|
2589
|
+
'minimum_should_match': 1,
|
|
2590
|
+
};
|
|
2591
|
+
if ( inputData.searchValue && inputData.searchValue !== '' ) {
|
|
2592
|
+
search ={
|
|
2593
|
+
'must': filter,
|
|
2594
|
+
'should': [
|
|
2595
|
+
{
|
|
2596
|
+
'wildcard': {
|
|
2597
|
+
'RMEmail.keyword': {
|
|
2598
|
+
'value': `*${inputData.searchValue}*`,
|
|
2599
|
+
},
|
|
2600
|
+
},
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
'wildcard': {
|
|
2604
|
+
'RMName.keyword': {
|
|
2605
|
+
'value': `*${inputData.searchValue}*`,
|
|
2606
|
+
},
|
|
2607
|
+
},
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
'wildcard': {
|
|
2611
|
+
'cluster.keyword': {
|
|
2612
|
+
'value': `*${inputData.searchValue}*`,
|
|
2613
|
+
},
|
|
2614
|
+
},
|
|
2615
|
+
},
|
|
2616
|
+
{
|
|
2617
|
+
'wildcard': {
|
|
2618
|
+
'optumId.keyword': {
|
|
2619
|
+
'value': `*${inputData.searchValue}*`,
|
|
2620
|
+
},
|
|
2621
|
+
},
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
'wildcard': {
|
|
2625
|
+
'queueId.keyword': {
|
|
2626
|
+
'value': `*${inputData.searchValue}*`,
|
|
2627
|
+
},
|
|
2628
|
+
},
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
'wildcard': {
|
|
2632
|
+
'storeName.keyword': {
|
|
2633
|
+
'value': `*${inputData.searchValue}*`,
|
|
2634
|
+
},
|
|
2635
|
+
},
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
'wildcard': {
|
|
2639
|
+
'auditedBy.keyword': {
|
|
2640
|
+
'value': `*${inputData.searchValue}*`,
|
|
2641
|
+
},
|
|
2642
|
+
},
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
'wildcard': {
|
|
2646
|
+
'optomEmailId.keyword': {
|
|
2647
|
+
'value': `*${inputData.searchValue}*`,
|
|
2648
|
+
},
|
|
2649
|
+
},
|
|
2650
|
+
},
|
|
2651
|
+
],
|
|
2652
|
+
|
|
2653
|
+
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
if ( inputData.RMList && inputData.RMList?.length > 0 ) {
|
|
2658
|
+
const shouldRMQuery =inputData.RMList.map( ( email ) => ( {
|
|
2659
|
+
wildcard: {
|
|
2660
|
+
'RMEmail.keyword': `*${email}*`,
|
|
2408
2661
|
},
|
|
2409
|
-
} );
|
|
2662
|
+
} ) );
|
|
2663
|
+
search['should'].push( ...shouldRMQuery );
|
|
2410
2664
|
}
|
|
2411
2665
|
|
|
2412
|
-
if ( inputData.cluster && inputData.cluster?.
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
'
|
|
2666
|
+
if ( inputData.cluster && inputData.cluster?.length > 0 ) {
|
|
2667
|
+
const shouldClusterQuery =inputData?.cluster?.map( ( cluster ) => ( {
|
|
2668
|
+
wildcard: {
|
|
2669
|
+
'RMEmail.keyword': `*${cluster}*`,
|
|
2416
2670
|
},
|
|
2417
|
-
} );
|
|
2671
|
+
} ) );
|
|
2672
|
+
search['should'].push( ...shouldClusterQuery );
|
|
2418
2673
|
}
|
|
2419
2674
|
const getQuery = {
|
|
2420
2675
|
'size': inputData.isExport == true? 50000 : inputData?.limit || 10,
|
|
2421
2676
|
'from': offset || 0,
|
|
2422
2677
|
'query': {
|
|
2423
|
-
'bool':
|
|
2424
|
-
'must': filter,
|
|
2425
|
-
},
|
|
2678
|
+
'bool': search,
|
|
2426
2679
|
},
|
|
2427
2680
|
'sort': sort,
|
|
2428
2681
|
};
|
|
2429
2682
|
|
|
2683
|
+
|
|
2430
2684
|
// get query result from the open saerch
|
|
2431
2685
|
const getData= await getOpenSearchData( openSearch.eyeTestEmailAlert, getQuery );
|
|
2686
|
+
const count = getData?.body?.hits?.total?.value;
|
|
2432
2687
|
// throw error if got null or empty array
|
|
2433
2688
|
if ( !getData || getData?.body?.hits?.hits?.length == 0 ) {
|
|
2434
2689
|
return res.sendError( 'No records found', 204 );
|
|
@@ -2436,30 +2691,39 @@ export async function emailAlertLog( req, res ) {
|
|
|
2436
2691
|
let response =[];
|
|
2437
2692
|
for ( let item of getData?.body?.hits?.hits ) {
|
|
2438
2693
|
response.push( {
|
|
2439
|
-
RMEmail: item?._source?.RMEmail,
|
|
2694
|
+
RMEmail: item?._source?.RMEmail || '',
|
|
2695
|
+
RMName: item?._source?.RMName || '',
|
|
2440
2696
|
cluster: item?._source?.cluster,
|
|
2441
2697
|
optumId: item?._source?.optumId,
|
|
2698
|
+
optomEmailId: item?._source?.optomEmailId,
|
|
2442
2699
|
storeName: item?._source?.storeName,
|
|
2443
|
-
complianceScore: item?._source?.complianceScore,
|
|
2444
|
-
|
|
2700
|
+
complianceScore: Math.round( item?._source?.complianceScore || 0 ) + ' %',
|
|
2701
|
+
humanScore: Math.round( item?._source?.humanScore || 0 )+ ' %',
|
|
2702
|
+
trustScore: item?._source?.trustScore+ ' %' || '--',
|
|
2445
2703
|
auditedId: item?._source?.auditedId,
|
|
2446
2704
|
queueId: item?._source?.queueId,
|
|
2705
|
+
auditedBy: item?._source?.auditedBy || '--',
|
|
2447
2706
|
|
|
2448
2707
|
} );
|
|
2449
2708
|
}
|
|
2450
2709
|
|
|
2451
2710
|
if ( inputData.isExport == true ) {
|
|
2711
|
+
logger.info( { response: response } );
|
|
2452
2712
|
const chunkedMappingData = await chunkArray( response, 10 );
|
|
2453
2713
|
const promises = chunkedMappingData.map( async ( chunk ) => {
|
|
2454
2714
|
const exportData = [];
|
|
2455
2715
|
for ( const element of chunk ) {
|
|
2456
2716
|
exportData.push( {
|
|
2457
|
-
'RM
|
|
2717
|
+
'RM Name': element.RMEmail,
|
|
2458
2718
|
'Cluster Name': element.cluster,
|
|
2459
|
-
'Optom
|
|
2719
|
+
'Optom ID': element.optumId,
|
|
2720
|
+
'Optom Email': element.optomEmailId || '--',
|
|
2460
2721
|
'Store Name': element.storeName,
|
|
2461
|
-
'
|
|
2462
|
-
'
|
|
2722
|
+
'Audited By': element.auditedBy || '--',
|
|
2723
|
+
'Tango Score': Math.round( element.complianceScore || 0 )+' %',
|
|
2724
|
+
'Trust Score': element.trustScore+' %' || '--',
|
|
2725
|
+
'Human Audited Score': Math.round( element.humanScore || 0 ) + ' %',
|
|
2726
|
+
|
|
2463
2727
|
|
|
2464
2728
|
} );
|
|
2465
2729
|
}
|
|
@@ -2468,6 +2732,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2468
2732
|
} );
|
|
2469
2733
|
const mappedArrays = await Promise.all( promises );
|
|
2470
2734
|
const result = mappedArrays.flat();
|
|
2735
|
+
|
|
2471
2736
|
await download( result, res );
|
|
2472
2737
|
return;
|
|
2473
2738
|
}
|
|
@@ -2476,8 +2741,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2476
2741
|
// throw error if null or undefined or empty array
|
|
2477
2742
|
|
|
2478
2743
|
|
|
2479
|
-
|
|
2480
|
-
return res.sendSuccess( { result: response, count: response.length } );
|
|
2744
|
+
return res.sendSuccess( { result: response, count: count } );
|
|
2481
2745
|
} catch ( error ) {
|
|
2482
2746
|
const err = error.message || 'Internal Server Error';
|
|
2483
2747
|
logger.error( { message: error, data: req.body, function: 'eyetestAudit-controller-emailAlertLog' } );
|
|
@@ -2637,10 +2901,6 @@ export async function getEmailConfig( req, res ) {
|
|
|
2637
2901
|
configureType: 'email',
|
|
2638
2902
|
};
|
|
2639
2903
|
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
2904
|
if ( !getOne ) {
|
|
2645
2905
|
return res.sendError( ' No data found', 204 );
|
|
2646
2906
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkFileExist, chunkArray, download, getDate, getDateWithCustomizeTime, getJsonFileData, getObject, getOpenSearchData, getUTC, insertOpenSearchData, listFileByPath, logger, signedUrl, sqsReceive, updateOpenSearchData, sendMessageToQueue, listQueue, copyImage
|
|
1
|
+
import { checkFileExist, chunkArray, download, getDate, getDateWithCustomizeTime, getJsonFileData, getObject, getOpenSearchData, getUTC, insertOpenSearchData, listFileByPath, logger, signedUrl, sqsReceive, updateOpenSearchData, sendMessageToQueue, listQueue, copyImage } from 'tango-app-api-middleware';
|
|
2
2
|
import { aggregateBinaryAudit, findOneBinaryAudit, updateOneBinaryAuditModel, updateOneUpsertBinaryAudit } from '../service/binaryAudit.service.js';
|
|
3
3
|
import { countDocumentsStore, findOneStore } from '../service/store.service.js';
|
|
4
4
|
import { findOneUser } from '../service/user.service.js';
|
|
@@ -1149,8 +1149,7 @@ export async function saveBinary( req, res ) {
|
|
|
1149
1149
|
fileDetails: getTraxInsertData?.sqs?.Body?.fileDetails,
|
|
1150
1150
|
createdAt: new Date(),
|
|
1151
1151
|
};
|
|
1152
|
-
|
|
1153
|
-
await upsertOpenSearchData( openSearch.traxDetectionOutput, customerId, { doc: OpenSearchOutput } );
|
|
1152
|
+
await insertOpenSearchData( openSearch.traxDetectionOutput, OpenSearchOutput );
|
|
1154
1153
|
const logData2 = {
|
|
1155
1154
|
userId: req.user._id,
|
|
1156
1155
|
userName: req.user.userName,
|
|
@@ -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,205 @@ 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
|
+
export async function sendComplianceEmail( {
|
|
68
|
+
toEmail,
|
|
69
|
+
fromEmail,
|
|
70
|
+
userName,
|
|
71
|
+
storeName,
|
|
72
|
+
startTime,
|
|
73
|
+
compliancePercentage,
|
|
74
|
+
steps,
|
|
75
|
+
videoUrl, // S3 cdn video URL
|
|
76
|
+
} ) {
|
|
77
|
+
// Initialize AWS SES client
|
|
78
|
+
const sesClient = new SESClient( {
|
|
79
|
+
region: 'ap-south-1',
|
|
80
|
+
credentials: {
|
|
81
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID_SIGNED_URL,
|
|
82
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY__SIGNED_URL,
|
|
83
|
+
},
|
|
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
|
|
92
|
+
const htmlBody = ( userName, steps, compliancePercentage ) => {
|
|
93
|
+
const tableRows = steps
|
|
94
|
+
.map(
|
|
95
|
+
( item ) => `
|
|
96
|
+
<tr>
|
|
97
|
+
<td>${item.stepName}</td>
|
|
98
|
+
<td>${item.comment ?? '--'}</td>
|
|
99
|
+
</tr>
|
|
100
|
+
`,
|
|
101
|
+
)
|
|
102
|
+
.join( '' );
|
|
103
|
+
|
|
104
|
+
return `
|
|
105
|
+
<!DOCTYPE html>
|
|
106
|
+
<html>
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="UTF-8" />
|
|
109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
110
|
+
<title>Non Compliance Report</title>
|
|
111
|
+
<style>
|
|
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; }
|
|
119
|
+
.table { width: 100%; border-collapse: collapse; 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; }
|
|
154
|
+
</style>
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<div class="container">
|
|
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
|
+
|
|
165
|
+
<p class="subtitle">
|
|
166
|
+
Dear ${userName},<br><br>
|
|
167
|
+
Please ensure that all conducted eye tests meet the required compliance standards.<br><br>
|
|
168
|
+
Your prompt attention to this matter will help us maintain the highest standards and avoid any<br>
|
|
169
|
+
discrepancies. Please take a look at the below findings.
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
<table class="table">
|
|
173
|
+
<thead>
|
|
174
|
+
<tr>
|
|
175
|
+
<th>Step Name</th>
|
|
176
|
+
<th>Comments</th>
|
|
177
|
+
</tr>
|
|
178
|
+
</thead>
|
|
179
|
+
<tbody>
|
|
180
|
+
${tableRows}
|
|
181
|
+
</tbody>
|
|
182
|
+
</table>
|
|
183
|
+
|
|
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
|
+
|
|
203
|
+
<p class="footer">Regards,<br>Team Tango</p>
|
|
204
|
+
</br></br>
|
|
205
|
+
<p class = "copy-rights">2024 © Tango Eye . All rights reserved.</p>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
</body>
|
|
209
|
+
</html>`;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const emailHTML = htmlBody( userName, steps, compliancePercentage );
|
|
213
|
+
|
|
214
|
+
// Send email with video attachment
|
|
215
|
+
const params = {
|
|
216
|
+
Source: fromEmail,
|
|
217
|
+
Destination: {
|
|
218
|
+
ToAddresses: [ toEmail ],
|
|
219
|
+
},
|
|
220
|
+
Message: {
|
|
221
|
+
Subject: { Data: 'Non Compliance on Eye Test Procedures' },
|
|
222
|
+
Body: { Html: { Data: emailHTML } },
|
|
223
|
+
},
|
|
224
|
+
Attachments: [
|
|
225
|
+
{
|
|
226
|
+
Name: `${storeName}_${startTime}-video.mp4`,
|
|
227
|
+
Content: videoBase64,
|
|
228
|
+
ContentType: 'video/mp4',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
try {
|
|
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 } );
|
|
237
|
+
return response;
|
|
238
|
+
} catch ( error ) {
|
|
239
|
+
logger.error( 'Error sending compliance email', error );
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|