tango-app-api-audit 3.6.27 → 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 +3 -1
- package/src/controllers/audit.controllers.js +8 -8
- package/src/controllers/eyeTestAudit.controllers.js +258 -34
- 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.28",
|
|
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,45 @@ 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 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
|
-
case '>': if (
|
|
878
|
+
case '>': if ( humanScore > 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,29 @@ 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
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
902
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
903
|
+
compliancePercentage: humanScore,
|
|
904
|
+
steps: steps,
|
|
905
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
906
|
+
} );
|
|
869
907
|
}
|
|
870
908
|
break;
|
|
871
|
-
case '<': if (
|
|
909
|
+
case '<': if ( humanScore < value ) {
|
|
872
910
|
const record2 = {
|
|
873
911
|
RMEmail: inputData.RMEmail,
|
|
912
|
+
RMName: inputData.RMEmail,
|
|
874
913
|
cluster: inputData.cluster,
|
|
875
914
|
optumId: inputUpdatedData?._source?.optumId,
|
|
915
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
876
916
|
storeName: inputUpdatedData?._source?.storeName,
|
|
877
917
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
918
|
+
humanScore: humanScore,
|
|
919
|
+
auditedBy: userName?.userName || '',
|
|
878
920
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
879
921
|
auditedId: inputUpdatedData?._id,
|
|
880
922
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -883,15 +925,29 @@ export async function save( req, res ) {
|
|
|
883
925
|
|
|
884
926
|
};
|
|
885
927
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record2 );
|
|
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
|
+
} );
|
|
886
938
|
};
|
|
887
939
|
break;
|
|
888
|
-
case '>=': if (
|
|
940
|
+
case '>=': if ( humanScore >= value ) {
|
|
889
941
|
const record3 = {
|
|
890
942
|
RMEmail: inputData.RMEmail,
|
|
943
|
+
RMName: inputData.RMEmail,
|
|
891
944
|
cluster: inputData.cluster,
|
|
892
945
|
optumId: inputUpdatedData?._source?.optumId,
|
|
946
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
893
947
|
storeName: inputUpdatedData?._source?.storeName,
|
|
894
948
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
949
|
+
humanScore: humanScore,
|
|
950
|
+
auditedBy: userName?.userName || '',
|
|
895
951
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
896
952
|
auditedId: inputUpdatedData?._id,
|
|
897
953
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -900,15 +956,29 @@ export async function save( req, res ) {
|
|
|
900
956
|
|
|
901
957
|
};
|
|
902
958
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record3 );
|
|
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
|
+
} );
|
|
903
969
|
}
|
|
904
970
|
break;
|
|
905
|
-
case '<=': if (
|
|
971
|
+
case '<=': if ( humanScore <= value ) {
|
|
906
972
|
const record4 = {
|
|
907
973
|
RMEmail: inputData.RMEmail,
|
|
974
|
+
RMName: inputData.RMEmail,
|
|
908
975
|
cluster: inputData.cluster,
|
|
909
976
|
optumId: inputUpdatedData?._source?.optumId,
|
|
977
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
910
978
|
storeName: inputUpdatedData?._source?.storeName,
|
|
911
979
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
980
|
+
humanScore: humanScore,
|
|
981
|
+
auditedBy: userName?.userName || '',
|
|
912
982
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
913
983
|
auditedId: inputUpdatedData?._id,
|
|
914
984
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -917,16 +987,30 @@ export async function save( req, res ) {
|
|
|
917
987
|
|
|
918
988
|
};
|
|
919
989
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record4 );
|
|
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
|
+
} );
|
|
920
1000
|
}
|
|
921
1001
|
break;
|
|
922
1002
|
case '=':
|
|
923
|
-
case '==': if (
|
|
1003
|
+
case '==': if ( humanScore == value ) {
|
|
924
1004
|
const record5 = {
|
|
925
1005
|
RMEmail: inputData.RMEmail,
|
|
1006
|
+
RMName: inputData.RMEmail,
|
|
926
1007
|
cluster: inputData.cluster,
|
|
927
1008
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1009
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
928
1010
|
storeName: inputUpdatedData?._source?.storeName,
|
|
929
1011
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1012
|
+
humanScore: humanScore,
|
|
1013
|
+
auditedBy: userName?.userName || '',
|
|
930
1014
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
931
1015
|
auditedId: inputUpdatedData?._id,
|
|
932
1016
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -935,15 +1019,29 @@ export async function save( req, res ) {
|
|
|
935
1019
|
|
|
936
1020
|
};
|
|
937
1021
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record5 );
|
|
1022
|
+
sendComplianceEmail( {
|
|
1023
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1024
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1025
|
+
userName: 'User',
|
|
1026
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1027
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1028
|
+
compliancePercentage: humanScore,
|
|
1029
|
+
steps: steps,
|
|
1030
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1031
|
+
} );
|
|
938
1032
|
}
|
|
939
1033
|
break;
|
|
940
|
-
case '===': if (
|
|
1034
|
+
case '===': if ( humanScore === value ) {
|
|
941
1035
|
const record6 = {
|
|
942
1036
|
RMEmail: inputData.RMEmail,
|
|
1037
|
+
RMName: inputData.RMEmail,
|
|
943
1038
|
cluster: inputData.cluster,
|
|
944
1039
|
optumId: inputUpdatedData?._source?.optumId,
|
|
1040
|
+
optomEmailId: inputUpdatedData?._source?.salesmanEmailId,
|
|
945
1041
|
storeName: inputUpdatedData?._source?.storeName,
|
|
946
1042
|
complianceScore: inputUpdatedData?._source?.ComplianceScore,
|
|
1043
|
+
humanScore: humanScore,
|
|
1044
|
+
auditedBy: userName?.userName || '',
|
|
947
1045
|
trustScore: inputUpdatedData?._source?.trustScore,
|
|
948
1046
|
auditedId: inputUpdatedData?._id,
|
|
949
1047
|
queueId: inputUpdatedData?._source?.queueId,
|
|
@@ -951,12 +1049,22 @@ export async function save( req, res ) {
|
|
|
951
1049
|
updatedAt: new Date(),
|
|
952
1050
|
};
|
|
953
1051
|
await insertWithId( openSearch.eyeTestEmailAlert, id, record6 );
|
|
1052
|
+
sendComplianceEmail( {
|
|
1053
|
+
toEmail: inputUpdatedData?._source?.salesmanEmailId,
|
|
1054
|
+
fromEmail: ses.eyeTestAlertEmail,
|
|
1055
|
+
userName: 'User',
|
|
1056
|
+
storeName: inputUpdatedData?._source?.storeName,
|
|
1057
|
+
startTime: `${inputUpdatedData?._source?.fileDate}T${inputUpdatedData?._source.testEndTime}`,
|
|
1058
|
+
compliancePercentage: humanScore,
|
|
1059
|
+
steps: steps,
|
|
1060
|
+
videoUrl: `${url.physical}${inputUpdatedData?._source?.filePath}`,
|
|
1061
|
+
} );
|
|
954
1062
|
}
|
|
955
1063
|
break;
|
|
956
1064
|
default: throw new Error( `Unknown condition: ${condition}` );
|
|
957
1065
|
}
|
|
958
1066
|
}
|
|
959
|
-
return res.sendSuccess( 'The file has been
|
|
1067
|
+
return res.sendSuccess( 'The file has been submitted successfully' );
|
|
960
1068
|
} catch ( error ) {
|
|
961
1069
|
const err = error.message || 'Internal Server Error';
|
|
962
1070
|
logger.error( { message: error, data: req.params, function: 'eyetestAudit-controller-save' } );
|
|
@@ -1198,18 +1306,25 @@ export async function userAuditedData( req, res ) {
|
|
|
1198
1306
|
|
|
1199
1307
|
export async function summaryList( req, res ) {
|
|
1200
1308
|
try {
|
|
1309
|
+
// declare inputData and get request body
|
|
1201
1310
|
const inputData = req.body;
|
|
1311
|
+
|
|
1312
|
+
// throw 204 if client id is not 11
|
|
1202
1313
|
if ( inputData.clientId !== '11' ) {
|
|
1203
1314
|
return res.sendError( 'No data found', 204 );
|
|
1204
1315
|
}
|
|
1205
1316
|
|
|
1317
|
+
// declare openSearch and get data form the .env/secret manager
|
|
1206
1318
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1319
|
+
|
|
1320
|
+
// set limit, offset, sortBy, sortOrder value if not set default value
|
|
1207
1321
|
const limit = inputData.limit || 10;
|
|
1208
1322
|
const offset = inputData.offset ? ( inputData.offset - 1 ) * limit : 0;
|
|
1209
1323
|
|
|
1210
1324
|
const sortBy = inputData?.sortBy ? [ 'coveredStepsAI', 'trustScore', 'date', 'ComplianceScore', 'auditedSteps' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'storeName.keyword';
|
|
1211
1325
|
const order = inputData?.sortOrder || -1;
|
|
1212
1326
|
|
|
1327
|
+
// declare filter and assign range values from inputData
|
|
1213
1328
|
let filter= [
|
|
1214
1329
|
{
|
|
1215
1330
|
'range': {
|
|
@@ -1222,6 +1337,7 @@ export async function summaryList( req, res ) {
|
|
|
1222
1337
|
|
|
1223
1338
|
];
|
|
1224
1339
|
|
|
1340
|
+
// filter by Audit Status if key has minimum one element
|
|
1225
1341
|
if ( inputData?.auditStatus && inputData?.auditStatus?.length > 0 ) {
|
|
1226
1342
|
filter.push( {
|
|
1227
1343
|
'terms': {
|
|
@@ -1229,6 +1345,8 @@ export async function summaryList( req, res ) {
|
|
|
1229
1345
|
},
|
|
1230
1346
|
} );
|
|
1231
1347
|
}
|
|
1348
|
+
|
|
1349
|
+
// filter by Visitors Type if key has minimum one element
|
|
1232
1350
|
if ( inputData?.demographics && inputData?.demographics?.length > 0 ) {
|
|
1233
1351
|
filter.push( {
|
|
1234
1352
|
'terms': {
|
|
@@ -1236,6 +1354,8 @@ export async function summaryList( req, res ) {
|
|
|
1236
1354
|
},
|
|
1237
1355
|
} );
|
|
1238
1356
|
}
|
|
1357
|
+
|
|
1358
|
+
// filter by Eye test Type if key has string value one element
|
|
1239
1359
|
if ( inputData.type && inputData.type!='' ) {
|
|
1240
1360
|
filter.push( {
|
|
1241
1361
|
'term': {
|
|
@@ -1245,6 +1365,17 @@ export async function summaryList( req, res ) {
|
|
|
1245
1365
|
} );
|
|
1246
1366
|
}
|
|
1247
1367
|
|
|
1368
|
+
// filter by near addition if value != "" and eye test must be a physical
|
|
1369
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
1370
|
+
filter.push( {
|
|
1371
|
+
'term': {
|
|
1372
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
} );
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// filter by stores if key has minimum one element else throw an error
|
|
1248
1379
|
if ( inputData.storeId && inputData.storeId.length > 0 ) {
|
|
1249
1380
|
filter.push( {
|
|
1250
1381
|
|
|
@@ -1257,11 +1388,14 @@ export async function summaryList( req, res ) {
|
|
|
1257
1388
|
return res.sendError( 'No data found', 204 );
|
|
1258
1389
|
}
|
|
1259
1390
|
|
|
1391
|
+
// Declare mustQuery as an array and include all elements from the filter array
|
|
1260
1392
|
let mustQuery = [
|
|
1261
1393
|
...filter,
|
|
1262
1394
|
];
|
|
1263
1395
|
|
|
1264
|
-
|
|
1396
|
+
// filter by complianceScore/tango score if key has minimum one element
|
|
1397
|
+
if ( inputData.complianceScore && inputData.complianceScore?.length > 0 ) {
|
|
1398
|
+
// If the key type is not 'store', split the compliance score ranges
|
|
1265
1399
|
if ( inputData.keyType !== 'store' ) {
|
|
1266
1400
|
const rangeShould = inputData?.complianceScore?.map( ( r ) => {
|
|
1267
1401
|
const [ gte, lte ] = r.split( '-' ).map( Number );
|
|
@@ -1271,7 +1405,7 @@ export async function summaryList( req, res ) {
|
|
|
1271
1405
|
},
|
|
1272
1406
|
};
|
|
1273
1407
|
} );
|
|
1274
|
-
|
|
1408
|
+
// Add the range conditions to mustQuery (any one of the ranges should match)
|
|
1275
1409
|
mustQuery.push(
|
|
1276
1410
|
{
|
|
1277
1411
|
bool: {
|
|
@@ -1280,7 +1414,7 @@ export async function summaryList( req, res ) {
|
|
|
1280
1414
|
},
|
|
1281
1415
|
},
|
|
1282
1416
|
);
|
|
1283
|
-
} else {
|
|
1417
|
+
} else { // If-else condition: use 'terms' filter to match records by Compliance Score / Tango Score
|
|
1284
1418
|
const values = Array.isArray( inputData.complianceScore ) ?
|
|
1285
1419
|
inputData.complianceScore.map( Number ) :
|
|
1286
1420
|
[ Number( inputData.complianceScore ) ];
|
|
@@ -1293,6 +1427,7 @@ export async function summaryList( req, res ) {
|
|
|
1293
1427
|
}
|
|
1294
1428
|
}
|
|
1295
1429
|
|
|
1430
|
+
// filter by category if key exist allow "fake, genuine" else throw valid category
|
|
1296
1431
|
if ( inputData?.category ) {
|
|
1297
1432
|
switch ( inputData?.category ) {
|
|
1298
1433
|
case 'fake':
|
|
@@ -1898,7 +2033,9 @@ export async function summaryList( req, res ) {
|
|
|
1898
2033
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
1899
2034
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
1900
2035
|
'Optom ID': element?._source?.optumId,
|
|
2036
|
+
'Optom Email ID': element?._source?.salesmanEmailId,
|
|
1901
2037
|
'Queue ID': element?._source?.queueId,
|
|
2038
|
+
'Power Types': element?._source?.nearAddition === 'true' ? 'With Near Addition' : element?._source?.nearAddition === 'false' ? 'Without Near Addition':'',
|
|
1902
2039
|
'Tango Score': `${element?._source?.totalSteps>0 ? Math.round( ( element?._source?.coveredStepsAI/ element?._source?.totalSteps )*100 ): 0} %`,
|
|
1903
2040
|
'Steps Covered By AI': element?._source?.coveredStepsAI || 0,
|
|
1904
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',
|
|
@@ -1923,12 +2060,16 @@ export async function summaryList( req, res ) {
|
|
|
1923
2060
|
'Store ID': element?._source?.storeId || 'Un Assigned',
|
|
1924
2061
|
'Store Name': element?._source?.storeName ||'Un Assigned',
|
|
1925
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,
|
|
1926
2066
|
'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
2067
|
'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
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',
|
|
1931
2071
|
'Test Duration': duration || '',
|
|
2072
|
+
'Comments ': element._source?.subComment || '',
|
|
1932
2073
|
'Audit Status': element?._source?.auditStatus || '',
|
|
1933
2074
|
'Audited By': userName?.userName || '',
|
|
1934
2075
|
|
|
@@ -2118,6 +2259,15 @@ export async function summaryCard( req, res ) {
|
|
|
2118
2259
|
},
|
|
2119
2260
|
} );
|
|
2120
2261
|
}
|
|
2262
|
+
|
|
2263
|
+
if ( inputData.nearAddition && inputData.nearAddition!='' && inputData.type ==='physical' ) {
|
|
2264
|
+
filter.push( {
|
|
2265
|
+
'term': {
|
|
2266
|
+
'nearAddition.keyword': inputData.nearAddition,
|
|
2267
|
+
},
|
|
2268
|
+
|
|
2269
|
+
} );
|
|
2270
|
+
}
|
|
2121
2271
|
let mustQuery = [
|
|
2122
2272
|
...filter,
|
|
2123
2273
|
];
|
|
@@ -2271,7 +2421,7 @@ export async function summaryCard( req, res ) {
|
|
|
2271
2421
|
'humanScore': {
|
|
2272
2422
|
filter: {
|
|
2273
2423
|
terms: {
|
|
2274
|
-
'auditStatus.keyword': [ 'Audited', '
|
|
2424
|
+
'auditStatus.keyword': [ 'Audited', 'Re-Audited' ],
|
|
2275
2425
|
},
|
|
2276
2426
|
},
|
|
2277
2427
|
aggs: {
|
|
@@ -2386,7 +2536,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2386
2536
|
inputData.clientId = req.user.userType == 'tango'? inputData?.clientId : req?.user?.clientId;
|
|
2387
2537
|
// get openserach name form the env file
|
|
2388
2538
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2389
|
-
|
|
2539
|
+
const sortBy = inputData?.sortBy ? [ 'complianceScore', 'humanScore', 'trustScore', 'updatedAt', 'createdAt' ].includes( inputData.sortBy ) ? inputData.sortBy :`${inputData.sortBy}.keyword` : 'RMName.keyword';
|
|
2390
2540
|
|
|
2391
2541
|
let filter =[
|
|
2392
2542
|
{
|
|
@@ -2399,7 +2549,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2399
2549
|
},
|
|
2400
2550
|
];
|
|
2401
2551
|
let sort =[
|
|
2402
|
-
{ [
|
|
2552
|
+
{ [sortBy]: { order: inputData.sortOrder === 1 ? 'asc' : 'desc' } },
|
|
2403
2553
|
];
|
|
2404
2554
|
if ( inputData.RmList && inputData.RmList?.lenght > 0 ) {
|
|
2405
2555
|
filter.push( {
|
|
@@ -2416,19 +2566,86 @@ export async function emailAlertLog( req, res ) {
|
|
|
2416
2566
|
},
|
|
2417
2567
|
} );
|
|
2418
2568
|
}
|
|
2569
|
+
let search = {
|
|
2570
|
+
must: filter,
|
|
2571
|
+
};
|
|
2572
|
+
if ( inputData.searchValue && inputData.searchValue !== '' ) {
|
|
2573
|
+
search ={
|
|
2574
|
+
'must': filter,
|
|
2575
|
+
'should': [
|
|
2576
|
+
{
|
|
2577
|
+
'wildcard': {
|
|
2578
|
+
'RMEmail.keyword': {
|
|
2579
|
+
'value': `*${inputData.searchValue}*`,
|
|
2580
|
+
},
|
|
2581
|
+
},
|
|
2582
|
+
},
|
|
2583
|
+
{
|
|
2584
|
+
'wildcard': {
|
|
2585
|
+
'RMName.keyword': {
|
|
2586
|
+
'value': `*${inputData.searchValue}*`,
|
|
2587
|
+
},
|
|
2588
|
+
},
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
'wildcard': {
|
|
2592
|
+
'cluster.keyword': {
|
|
2593
|
+
'value': `*${inputData.searchValue}*`,
|
|
2594
|
+
},
|
|
2595
|
+
},
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
'wildcard': {
|
|
2599
|
+
'optumId.keyword': {
|
|
2600
|
+
'value': `*${inputData.searchValue}*`,
|
|
2601
|
+
},
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
'wildcard': {
|
|
2606
|
+
'queueId.keyword': {
|
|
2607
|
+
'value': `*${inputData.searchValue}*`,
|
|
2608
|
+
},
|
|
2609
|
+
},
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
'wildcard': {
|
|
2613
|
+
'storeName.keyword': {
|
|
2614
|
+
'value': `*${inputData.searchValue}*`,
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
'wildcard': {
|
|
2620
|
+
'auditedBy.keyword': {
|
|
2621
|
+
'value': `*${inputData.searchValue}*`,
|
|
2622
|
+
},
|
|
2623
|
+
},
|
|
2624
|
+
},
|
|
2625
|
+
{
|
|
2626
|
+
'wildcard': {
|
|
2627
|
+
'optomEmailId.keyword': {
|
|
2628
|
+
'value': `*${inputData.searchValue}*`,
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
},
|
|
2632
|
+
],
|
|
2633
|
+
'minimum_should_match': 1,
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2419
2636
|
const getQuery = {
|
|
2420
2637
|
'size': inputData.isExport == true? 50000 : inputData?.limit || 10,
|
|
2421
2638
|
'from': offset || 0,
|
|
2422
2639
|
'query': {
|
|
2423
|
-
'bool':
|
|
2424
|
-
'must': filter,
|
|
2425
|
-
},
|
|
2640
|
+
'bool': search,
|
|
2426
2641
|
},
|
|
2427
2642
|
'sort': sort,
|
|
2428
2643
|
};
|
|
2429
2644
|
|
|
2645
|
+
|
|
2430
2646
|
// get query result from the open saerch
|
|
2431
2647
|
const getData= await getOpenSearchData( openSearch.eyeTestEmailAlert, getQuery );
|
|
2648
|
+
const count = getData?.body?.hits?.total?.value;
|
|
2432
2649
|
// throw error if got null or empty array
|
|
2433
2650
|
if ( !getData || getData?.body?.hits?.hits?.length == 0 ) {
|
|
2434
2651
|
return res.sendError( 'No records found', 204 );
|
|
@@ -2436,30 +2653,40 @@ export async function emailAlertLog( req, res ) {
|
|
|
2436
2653
|
let response =[];
|
|
2437
2654
|
for ( let item of getData?.body?.hits?.hits ) {
|
|
2438
2655
|
response.push( {
|
|
2439
|
-
RMEmail: item?._source?.RMEmail,
|
|
2656
|
+
RMEmail: item?._source?.RMEmail || '',
|
|
2657
|
+
RMName: item?._source?.RMName || '',
|
|
2440
2658
|
cluster: item?._source?.cluster,
|
|
2441
2659
|
optumId: item?._source?.optumId,
|
|
2660
|
+
optomEmailId: item?._source?.optomEmailId,
|
|
2442
2661
|
storeName: item?._source?.storeName,
|
|
2443
|
-
complianceScore: item?._source?.complianceScore,
|
|
2444
|
-
|
|
2662
|
+
complianceScore: Math.round( item?._source?.complianceScore || 0 ),
|
|
2663
|
+
humanScore: Math.round( item?._source?.humanScore || 0 ),
|
|
2664
|
+
trustScore: item?._source?.trustScore || '--',
|
|
2445
2665
|
auditedId: item?._source?.auditedId,
|
|
2446
2666
|
queueId: item?._source?.queueId,
|
|
2667
|
+
auditedBy: item?._source?.auditedBy || '--',
|
|
2447
2668
|
|
|
2448
2669
|
} );
|
|
2449
2670
|
}
|
|
2450
2671
|
|
|
2451
2672
|
if ( inputData.isExport == true ) {
|
|
2673
|
+
logger.info( { response: response } );
|
|
2452
2674
|
const chunkedMappingData = await chunkArray( response, 10 );
|
|
2453
2675
|
const promises = chunkedMappingData.map( async ( chunk ) => {
|
|
2454
2676
|
const exportData = [];
|
|
2455
2677
|
for ( const element of chunk ) {
|
|
2456
2678
|
exportData.push( {
|
|
2457
|
-
'RM
|
|
2679
|
+
'RM Name': element.RMEmail,
|
|
2458
2680
|
'Cluster Name': element.cluster,
|
|
2459
|
-
'Optom
|
|
2681
|
+
'Optom ID': element.optumId,
|
|
2682
|
+
'Optom Email': element.optomEmailId || '--',
|
|
2460
2683
|
'Store Name': element.storeName,
|
|
2461
|
-
'
|
|
2462
|
-
'
|
|
2684
|
+
'Audited By': element.auditedBy || '--',
|
|
2685
|
+
'Tango Score': Math.round( element.complianceScore || 0 ),
|
|
2686
|
+
|
|
2687
|
+
'Trust Score': element.trustScore || '--',
|
|
2688
|
+
'Human Audited Score': Math.round( element.humanScore || 0 ),
|
|
2689
|
+
|
|
2463
2690
|
|
|
2464
2691
|
} );
|
|
2465
2692
|
}
|
|
@@ -2468,6 +2695,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2468
2695
|
} );
|
|
2469
2696
|
const mappedArrays = await Promise.all( promises );
|
|
2470
2697
|
const result = mappedArrays.flat();
|
|
2698
|
+
logger.info( { result: result } );
|
|
2471
2699
|
await download( result, res );
|
|
2472
2700
|
return;
|
|
2473
2701
|
}
|
|
@@ -2477,7 +2705,7 @@ export async function emailAlertLog( req, res ) {
|
|
|
2477
2705
|
|
|
2478
2706
|
|
|
2479
2707
|
logger.info( { inputData: inputData, getData: response } );
|
|
2480
|
-
return res.sendSuccess( { result: response, count:
|
|
2708
|
+
return res.sendSuccess( { result: response, count: count } );
|
|
2481
2709
|
} catch ( error ) {
|
|
2482
2710
|
const err = error.message || 'Internal Server Error';
|
|
2483
2711
|
logger.error( { message: error, data: req.body, function: 'eyetestAudit-controller-emailAlertLog' } );
|
|
@@ -2637,10 +2865,6 @@ export async function getEmailConfig( req, res ) {
|
|
|
2637
2865
|
configureType: 'email',
|
|
2638
2866
|
};
|
|
2639
2867
|
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
2868
|
if ( !getOne ) {
|
|
2645
2869
|
return res.sendError( ' No data found', 204 );
|
|
2646
2870
|
}
|
|
@@ -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>
|
|
168
|
+
Your prompt attention to this matter will help us maintain the highest standards.<br>
|
|
169
|
+
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
|
+
|