From 826c32d006e3f61db1be0212e1ba67f992436a07 Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:50:48 +0530 Subject: [PATCH 01/15] fix: facility id null (#433) --- .../controller/abdmfacility/AbdmFacilityController.java | 7 +++---- .../service/abdmfacility/AbdmFacilityServiceImpl.java | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/abdmfacility/AbdmFacilityController.java b/src/main/java/com/iemr/common/controller/abdmfacility/AbdmFacilityController.java index 1aa28712..dc0b8e13 100644 --- a/src/main/java/com/iemr/common/controller/abdmfacility/AbdmFacilityController.java +++ b/src/main/java/com/iemr/common/controller/abdmfacility/AbdmFacilityController.java @@ -31,13 +31,12 @@ public String getAbdmFacilityDetails(@PathVariable int workLocationId, @RequestH OutputResponse response = new OutputResponse(); try { - String resp = abdmFacilityService.getMappedAbdmFacility(workLocationId); - + if (resp != null) response.setResponse(resp); - + else + response.setResponse("No ABDM facility mapped for this worklocation"); } catch (Exception e) { - response.setError(5000, e.getMessage()); logger.error(e.getMessage()); } diff --git a/src/main/java/com/iemr/common/service/abdmfacility/AbdmFacilityServiceImpl.java b/src/main/java/com/iemr/common/service/abdmfacility/AbdmFacilityServiceImpl.java index e6da2522..f6d5ff17 100644 --- a/src/main/java/com/iemr/common/service/abdmfacility/AbdmFacilityServiceImpl.java +++ b/src/main/java/com/iemr/common/service/abdmfacility/AbdmFacilityServiceImpl.java @@ -15,8 +15,9 @@ public class AbdmFacilityServiceImpl implements AbdmFacilityService{ @Override public String getMappedAbdmFacility(int psmId) { ProviderServiceAddressMapping res = abdmFacilityRepo.getAbdmFacility(psmId); + if (res == null) + return null; return res.toString(); - } } From a552831b76d1d3d600b2d7114492ec3f79f3fc65 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 10 Aug 2026 16:12:05 +0530 Subject: [PATCH 02/15] fix order issue in dynamic from --- .../service/dynamicForm/FormMasterServiceImpl.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java index 38154f4a..2861819b 100644 --- a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java +++ b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java @@ -243,8 +243,16 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str return dto; }) - .sorted(Comparator.comparing(FieldResponseDTO::getId)) - .collect(Collectors.toList()); + .sorted( + Comparator.comparing( + FieldResponseDTO::getSequence, + Comparator.nullsLast(Comparator.naturalOrder()) + ).thenComparing( + FieldResponseDTO::getId, + Comparator.nullsLast(Comparator.naturalOrder()) + ) + ) .collect(Collectors.toList()); + GroupedFieldResponseDTO singleSection = new GroupedFieldResponseDTO(); From 99b17d8e943e36710a763c13257c7248c3aba532 Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:45:09 +0530 Subject: [PATCH 03/15] fix: call report issue for 104 (#445) * fix: call report issue for 104 * fix: update cti logger properties * fix: update the properties * fix: update the pom version * fix: add the range to sync * fix: update the configproperties to fetch the value from .env --- pom.xml | 2 +- .../environment/common_example.properties | 12 +- .../common/config/quartz/QuartzConfig.java | 169 ++++++++------- .../NHMDetailCallReportScheduler.java | 19 +- .../data/callhandling/BeneficiaryCall.java | 7 +- .../nhm_dashboard/DetailedCallReportRepo.java | 12 ++ .../BeneficiaryCallServiceImpl.java | 6 +- .../ctiCall/CallCentreDataSyncImpl.java | 128 +++++++----- .../NHM_DashboardServiceImpl.java | 196 +++++++++++++++--- 9 files changed, 370 insertions(+), 181 deletions(-) diff --git a/pom.xml b/pom.xml index b4e8c0d9..acdb4fce 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iemr.common-API common-api - 3.8.1 + 3.9.0 war Common-API diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index ac440991..8e3d969d 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -71,9 +71,9 @@ cron-scheduler-ctidatasync=0 30 01 * * ? * ##-------------------------------###cti data check with call detail report Scheduler------------------------------------------------------ -#Runs at everyday 12:10AM +#Runs at everyday 3:00AM - after the NHM data pull and CTI data sync complete start-ctidatacheck-scheduler=false -cron-scheduler-ctidatacheck=0 00 02 * * * +cron-scheduler-ctidatacheck=0 00 03 * * * ##---------------------------------#### Registration schedular for Avni------------------------------------------------------------------------------ @@ -93,7 +93,13 @@ cron-scheduler-everwelldatasync=0 0/5 * * * ? * ##-----------------------------------------------#NHM data dashboard schedular---------------------------------------------------------------- # run at everyday 12:01AM start-nhmdashboard-scheduler=true -cron-scheduler-nhmdashboard=0 1 * * * ? * +cron-scheduler-nhmdashboard=0 1 0 * * ? * +nhm-detailedcallreport-backfill-days=7 +# one-off recovery of older / partly imported days (yyyy-MM-dd, both inclusive, +# max 60 days per run). Leave empty during normal operation - while these are set +# the job pulls this range instead of only the missing days. +nhm-detailedcallreport-backfill-start-date= +nhm-detailedcallreport-backfill-end-date= ##----------------------------------------------------#grievance data sync----------------------------------------------------------- start-grievancedatasync-scheduler=false diff --git a/src/main/java/com/iemr/common/config/quartz/QuartzConfig.java b/src/main/java/com/iemr/common/config/quartz/QuartzConfig.java index 61e70fd0..cb6734b9 100644 --- a/src/main/java/com/iemr/common/config/quartz/QuartzConfig.java +++ b/src/main/java/com/iemr/common/config/quartz/QuartzConfig.java @@ -24,10 +24,12 @@ import java.io.IOException; import java.util.Properties; +import org.quartz.JobDetail; import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -38,8 +40,6 @@ import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; -import com.iemr.common.utils.config.ConfigProperties; - import jakarta.annotation.PostConstruct; @Configuration @@ -55,11 +55,80 @@ public class QuartzConfig { @Autowired private ApplicationContext applicationContext; + /* + * These are read through @Value and not through ConfigProperties on purpose. + * ConfigProperties keeps the Environment in a static field that is populated by + * an @Autowired setter on its own bean, so it can still be null while the @Bean + * methods below run - in that case it falls back to reading application.properties + * straight off the classpath, and any ${ENV_VAR} placeholder in there comes back + * as the literal text. getBoolean() then quietly turns that into false and the job + * is scheduled with quartzJobDefaultSchedule instead, with nothing in the log. + * Injected fields are set before any @Bean method is called and go through the + * normal placeholder resolution, so environment overrides are honoured. + */ + @Value("${start-unblock-scheduler:false}") + private boolean startUnblockJob; + @Value("${cron-scheduler-unblock:" + quartzJobDefaultSchedule + "}") + private String unblockSchedule; + + @Value("${start-sms-scheduler:false}") + private boolean startSmsJob; + @Value("${cron-scheduler-sms:" + quartzJobDefaultSchedule + "}") + private String smsSchedule; + + @Value("${start-email-scheduler:false}") + private boolean startEmailJob; + @Value("${cron-scheduler-email:" + quartzJobDefaultSchedule + "}") + private String emailSchedule; + + @Value("${start-registration-scheduler:false}") + private boolean startRegistrationJob; + @Value("${cron-scheduler-registration:" + quartzJobDefaultSchedule + "}") + private String registrationSchedule; + + @Value("${start-everwelldatasync-scheduler:false}") + private boolean startEverwellDataSyncJob; + @Value("${cron-scheduler-everwelldatasync:" + quartzJobDefaultSchedule + "}") + private String everwellDataSyncSchedule; + + @Value("${start-ctidatasync-scheduler:false}") + private boolean startCtiDataSyncJob; + @Value("${cron-scheduler-ctidatasync:" + quartzJobDefaultSchedule + "}") + private String ctiDataSyncSchedule; + + @Value("${start-avni-scheduler:false}") + private boolean startAvniRegistrationJob; + @Value("${cron-avni-registration:" + quartzJobDefaultSchedule + "}") + private String avniRegistrationSchedule; + + @Value("${start-nhmdashboard-scheduler:false}") + private boolean startNhmDashboardJob; + @Value("${cron-scheduler-nhmdashboard:" + quartzJobDefaultSchedule + "}") + private String nhmDashboardSchedule; + @PostConstruct public void init() { log.debug("QuartzConfig initialized."); } + /** + * Builds the trigger for a job, logging what it resolved to. A job that is + * switched off gets quartzJobDefaultSchedule, which only comes round on the 31st + * of December - so the log line is the only way to tell "off" apart from + * "misconfigured" without waiting until the end of the year. + */ + private CronTriggerFactoryBean cronTrigger(String jobName, boolean startJob, String schedule, + JobDetail jobDetail) { + String scheduleConfig = startJob ? schedule : quartzJobDefaultSchedule; + log.info("Quartz job {} - enabled: {}, cron: {}", jobName, startJob, scheduleConfig); + + CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); + cronTriggerFactoryBean.setJobDetail(jobDetail); + cronTriggerFactoryBean.setCronExpression(scheduleConfig); + cronTriggerFactoryBean.setGroup(quartzJobGroup); + return cronTriggerFactoryBean; + } + @Bean public Properties quartzProperties() { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); @@ -114,17 +183,7 @@ public JobDetailFactoryBean processMQJobForUnblock() { @Bean public CronTriggerFactoryBean processMQTriggerForUnblock() { - Boolean startJob = ConfigProperties.getBoolean("start-unblock-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - ; - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-unblock"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForUnblock().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("unblock", startUnblockJob, unblockSchedule, processMQJobForUnblock().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -139,17 +198,7 @@ public JobDetailFactoryBean processMQJobForSMS() { @Bean public CronTriggerFactoryBean processMQTriggerForSMS() { - Boolean startJob = ConfigProperties.getBoolean("start-sms-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - ; - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-sms"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForSMS().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("sms", startSmsJob, smsSchedule, processMQJobForSMS().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -164,16 +213,7 @@ public JobDetailFactoryBean processMQJobForEmail() { @Bean public CronTriggerFactoryBean processMQTriggerForEmail() { - Boolean startJob = ConfigProperties.getBoolean("start-email-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (Boolean.TRUE.equals(startJob)) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-email"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForEmail().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("email", startEmailJob, emailSchedule, processMQJobForEmail().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -188,16 +228,8 @@ public JobDetailFactoryBean processMQJobForRegistration() { @Bean public CronTriggerFactoryBean processMQTriggerForRegistration() { - Boolean startJob = ConfigProperties.getBoolean("start-registration-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-registration"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForRegistration().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("everwell-registration", startRegistrationJob, registrationSchedule, + processMQJobForRegistration().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -212,16 +244,8 @@ public JobDetailFactoryBean processMQJobForEverwellDataSync() { @Bean public CronTriggerFactoryBean processMQTriggerForEverwellDataSync() { - Boolean startJob = ConfigProperties.getBoolean("start-everwelldatasync-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-everwelldatasync"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForEverwellDataSync().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("everwell-datasync", startEverwellDataSyncJob, everwellDataSyncSchedule, + processMQJobForEverwellDataSync().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -236,16 +260,8 @@ public JobDetailFactoryBean processMQJobForCtiDataSync() { @Bean public CronTriggerFactoryBean processMQTriggerForCtiDataSync() { - Boolean startJob = ConfigProperties.getBoolean("start-ctidatasync-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-ctidatasync"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForCtiDataSync().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("cti-datasync", startCtiDataSyncJob, ctiDataSyncSchedule, + processMQJobForCtiDataSync().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -260,16 +276,8 @@ public JobDetailFactoryBean processMQJobForAvniRegistration() { @Bean public CronTriggerFactoryBean processMQTriggerForAvniRegistration() { - Boolean startJob = ConfigProperties.getBoolean("start-avni-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-avni-registration"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForAvniRegistration().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - return cronTriggerFactoryBean; + return cronTrigger("avni-registration", startAvniRegistrationJob, avniRegistrationSchedule, + processMQJobForAvniRegistration().getObject()); } // -------------------------------------------------------------------------------------------------------------- @@ -284,17 +292,8 @@ public JobDetailFactoryBean processMQJobForNHMDashboardData() { @Bean public CronTriggerFactoryBean processMQTriggerForNHMDashboardData() { - Boolean startJob = ConfigProperties.getBoolean("start-nhmdashboard-scheduler"); - CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); - String scheduleConfig = quartzJobDefaultSchedule; - if (startJob) { - scheduleConfig = ConfigProperties.getPropertyByName("cron-scheduler-nhmdashboard"); - } - cronTriggerFactoryBean.setJobDetail(processMQJobForNHMDashboardData().getObject()); - cronTriggerFactoryBean.setCronExpression(scheduleConfig); - cronTriggerFactoryBean.setGroup(quartzJobGroup); - - return cronTriggerFactoryBean; + return cronTrigger("nhm-dashboard", startNhmDashboardJob, nhmDashboardSchedule, + processMQJobForNHMDashboardData().getObject()); } } diff --git a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java index d02eb3f2..31e72019 100644 --- a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java +++ b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java @@ -41,18 +41,23 @@ public class NHMDetailCallReportScheduler { @Value("${start-ctidatacheck-scheduler}") private boolean startCtiDataCheckFlag; + /** + * Number of days (ending yesterday) checked against t_bencall. Kept in sync with + * the detailed call report backfill window, so that days pulled late from CTI are + * also reconciled. The reconciliation itself is idempotent. + */ + @Value("${nhm-detailedcallreport-backfill-days:7}") + private int lookBackDays; @Scheduled(cron = "${cron-scheduler-ctidatacheck}") public void detailedCallReport() { if (startCtiDataCheckFlag) { try { - String endDate = null; - String fromDate = null; - LocalDateTime date = null; - date = LocalDateTime.now().minusDays(1); - String[] dateArr = date.toString().split("T"); - endDate = dateArr[0].concat(" 23:59:59"); - fromDate = dateArr[0].concat(" 00:00:01"); + int days = lookBackDays > 0 ? lookBackDays : 1; + LocalDateTime endDay = LocalDateTime.now().minusDays(1); + LocalDateTime startDay = endDay.minusDays(days - 1L); + String endDate = endDay.toString().split("T")[0].concat(" 23:59:59"); + String fromDate = startDay.toString().split("T")[0].concat(" 00:00:00"); Timestamp fromTime = Timestamp.valueOf(fromDate); Timestamp endTime = Timestamp.valueOf(endDate); diff --git a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java index c2c1ed10..1eca8fec 100644 --- a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java +++ b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java @@ -23,6 +23,8 @@ import java.sql.Timestamp; +import org.springframework.beans.factory.annotation.Value; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.annotations.Expose; import com.iemr.common.data.beneficiary.Beneficiary; @@ -234,6 +236,9 @@ public class BeneficiaryCall { @Column(name = "InsName") private String instName; + @Value("${cti-logger_base_url}") + private String loggerBaseURL; + @Transient @Expose private String[] instNames; @@ -280,7 +285,7 @@ public BeneficiaryCall(Long beneficiaryRegID, Boolean is1097, String createdBy) public BeneficiaryCall(Long benCallID, Timestamp createdDate, String agentID, String callID, String recordingPath, String archivePath) { - String loggerBaseURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + // String loggerBaseURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); this.benCallID = benCallID; this.createdDate = createdDate; this.agentID = agentID; diff --git a/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java b/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java index bb89881b..f4c5b125 100644 --- a/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java +++ b/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java @@ -21,10 +21,13 @@ */ package com.iemr.common.repository.nhm_dashboard; +import java.sql.Date; import java.sql.Timestamp; import java.util.List; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.iemr.common.data.nhm_dashboard.DetailedCallReport; @@ -32,4 +35,13 @@ @Repository public interface DetailedCallReportRepo extends CrudRepository { List findByCallStartTimeBetween(Timestamp startDate, Timestamp endDate); + + /** + * Call dates for which data has already been pulled from CTI. Used to detect + * the days that were missed by earlier scheduler runs, so that they can be + * pulled again instead of staying permanently empty. + */ + @Query(value = "select distinct date(Call_Start_Time) from t_DetailedCallReport " + + "where Call_Start_Time between :startDate and :endDate", nativeQuery = true) + List findExistingCallDates(@Param("startDate") Timestamp startDate, @Param("endDate") Timestamp endDate); } diff --git a/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java b/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java index c28d41cb..637d7ae4 100644 --- a/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java +++ b/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java @@ -145,7 +145,11 @@ public class BeneficiaryCallServiceImpl implements BeneficiaryCallService { private Logger logger = LoggerFactory.getLogger(BeneficiaryCallServiceImpl.class); - private static String ctiLoggerURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + // private static String ctiLoggerURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + + @Value("${cti-logger_base_url}") + private String ctiLoggerURL; + @Autowired private IdentityBeneficiaryService identityBeneficiaryService; diff --git a/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java b/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java index a729eaf8..f568fe9e 100644 --- a/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java +++ b/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java @@ -63,7 +63,9 @@ public class CallCentreDataSyncImpl implements CallCentreDataSync { private static HttpUtils httpUtils; @Autowired private CTIService ctiService; - private static String ctiLoggerURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + // private static String ctiLoggerURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + @Value("${cti-logger_base_url}") + private String ctiLoggerURL; public CallCentreDataSyncImpl() { if (httpUtils == null) { @@ -81,10 +83,10 @@ public String callUrl(String urlRequest) { @Override public void ctiDataSync() { LocalDate currentDate = LocalDate.now(); - // Calculate three days before the current date - LocalDate startDate = currentDate.minusDays(3); - // Calculate two days before the current date - LocalDate endDate = currentDate.minusDays(2); + // Look back 7 days to retry records that failed in previous runs + LocalDate startDate = currentDate.minusDays(7); + // Up to yesterday + LocalDate endDate = currentDate.minusDays(1); // Convert LocalDate to LocalDateTime to set time as 00:00:00 LocalDateTime startDateTime = startDate.atTime(0, 0, 0); LocalDateTime endDateTime = endDate.atTime(23, 59, 59); @@ -96,64 +98,78 @@ public void ctiDataSync() { List list = callReportRepo.getAllBenCallIDetails(startTimeStamp, endTimeStamp); if (!list.isEmpty()) { - - // List benList = new ArrayList<>(); - String callDuartion = null; - String filePath = null; - String URL = null; - String callinfoapiURL = null; - String ctiResponse = null; - String callEndTime = null; - String callStartTime = null; - String recordingPath = ""; + logger.info("Total records to process for CTI data sync: " + list.size()); for (BeneficiaryCall call : list) { - if (call.getCallID() != null) { - recordingPath = null; - try { - JSONObject requestFile = new JSONObject(); - requestFile.put("agent_id", call.getAgentID()); - requestFile.put("session_id", call.getCallID()); - - OutputResponse response1 = ctiService.getVoiceFileNew(requestFile.toString(), "extra parameter"); - if(response1 != null && response1.getStatusCode() == 200) { - - CTIResponse ctiResponsePath = InputMapper.gson().fromJson(response1.getData(), - CTIResponse.class); - String recordingFilePath = ctiResponsePath.getResponse().toString(); - if(recordingFilePath.length() > 20) - recordingPath = recordingFilePath.substring(20); - logger.info("recordingPath: " + recordingPath); - } - - callDuartion = null; - callinfoapiURL = this.callinfoapiURL; - URL = callinfoapiURL.replace("CTI_SERVER", ctiServerIP).replace("AGENT_ID", call.getAgentID()) - .replace("SESSION_ID", call.getCallID()).replace("PHONE_NO", call.getPhoneNo()); - - logger.info("calling CTI API url: " + URL); - ctiResponse = this.callUrl(URL); - logger.info("calling CTI_CDR_CALL_INFO API returned " + ctiResponse); - - CTIData data = InputMapper.gson().fromJson(ctiResponse, CTIData.class); - CTIResponse model = data.getResponse(); - - if (model.getResponse_code().equals("1")) { - callDuartion = model.getCall_duration(); - callEndTime = model.getCall_end_date_time(); - callStartTime = model.getCall_start_date_time(); - } - if (callDuartion != null) - call.setCZcallDuration(Integer.parseInt(callDuartion)); - call.setRecordingPath(recordingPath); + if (call.getCallID() == null) { + logger.warn("Skipping record with null callID, benCallID: " + call.getBenCallID()); + continue; + } + String recordingPath = null; + String callDuartion = null; + String callEndTime = null; + String callStartTime = null; + try { + JSONObject requestFile = new JSONObject(); + requestFile.put("agent_id", call.getAgentID()); + requestFile.put("session_id", call.getCallID()); + + OutputResponse response1 = ctiService.getVoiceFileNew(requestFile.toString(), "extra parameter"); + if(response1 != null && response1.getStatusCode() == 200) { + + CTIResponse ctiResponsePath = InputMapper.gson().fromJson(response1.getData(), + CTIResponse.class); + String recordingFilePath = ctiResponsePath.getResponse().toString(); + if(recordingFilePath.length() > 20) + recordingPath = recordingFilePath.substring(20); + else if (!recordingFilePath.isEmpty()) + recordingPath = recordingFilePath; + logger.info("recordingPath: " + recordingPath); + } + + String callInfoURL = this.callinfoapiURL; + String URL = callInfoURL.replace("CTI_SERVER", ctiServerIP).replace("AGENT_ID", call.getAgentID()) + .replace("SESSION_ID", call.getCallID()).replace("PHONE_NO", call.getPhoneNo()); + + logger.info("calling CTI API url: " + URL); + String ctiResponse = this.callUrl(URL); + logger.info("calling CTI_CDR_CALL_INFO API returned " + ctiResponse); + + CTIData data = InputMapper.gson().fromJson(ctiResponse, CTIData.class); + CTIResponse model = data.getResponse(); + + if (model != null && "1".equals(model.getResponse_code())) { + callDuartion = model.getCall_duration(); + callEndTime = model.getCall_end_date_time(); + callStartTime = model.getCall_start_date_time(); + } else { + logger.warn("CTI API returned non-success for sessionID: " + call.getCallID() + + ", response_code: " + (model != null ? model.getResponse_code() : "null")); + } + + // Only save if we got at least the call duration from CTI + if (callDuartion != null) { + call.setCZcallDuration(Integer.parseInt(callDuartion)); call.setCZcallEndTime(callEndTime); call.setCZcallStartTime(callStartTime); + call.setRecordingPath(recordingPath); + callReportRepo.save(call); + logger.info("CTI data sync saved for benCallID: " + call.getBenCallID()); + } else if (recordingPath != null) { + // Duration not available yet, but recording path is — save path only + call.setRecordingPath(recordingPath); callReportRepo.save(call); - logger.info("calling CTI_CDR_CALL_INFO after API call save response " + call); - } catch (Exception e) { - logger.error("VoiceFile failed with error " + e.getMessage(), e); + logger.info("Only recordingPath saved (duration pending) for benCallID: " + call.getBenCallID()); + } else { + logger.warn("No CTI data available yet for sessionID: " + call.getCallID() + + ", benCallID: " + call.getBenCallID() + " - will retry next run"); } + } catch (Exception e) { + logger.error("CTI data sync failed for benCallID: " + call.getBenCallID() + + ", sessionID: " + call.getCallID() + " - " + e.getMessage(), e); } } + } else { + logger.info("No pending records found for CTI data sync"); } } } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java b/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java index d7afd579..d86c1ad9 100644 --- a/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java +++ b/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java @@ -24,10 +24,13 @@ import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +68,21 @@ public class NHM_DashboardServiceImpl implements NHM_DashboardService { @Value("${cti-server-ip}") private String serverURL; + /** + * Number of days (ending yesterday) the detailed call report pull looks back to + * re-pull days that were missed. 1 = previous day only, i.e. old behaviour. + */ + @Value("${nhm-detailedcallreport-backfill-days:7}") + private int detailedCallReportBackfillDays; + + @Value("${nhm-detailedcallreport-backfill-start-date:}") + private String backfillStartDate; + + @Value("${nhm-detailedcallreport-backfill-end-date:}") + private String backfillEndDate; + + private static final int MAX_EXPLICIT_BACKFILL_DAYS = 60; + public String pushAbandonCalls(AbandonCallSummary abandonCallSummary) throws Exception { logger.info("NHM_abandon call push API request : " + abandonCallSummary.toString()); @@ -119,7 +137,6 @@ public String getDetailedCallReport() throws Exception { return new Gson().toJson(resultSet); } - // JOB calling C-Zentrix 2 APIs => AgentSummaryReport & DetailedCallReport public String pull_NHM_Data_CTI() throws IEMRException { String response = ""; String result1 = ""; @@ -134,17 +151,130 @@ public String pull_NHM_Data_CTI() throws IEMRException { logger.error(e.getLocalizedMessage()); } - try { - List detailedCallReportList = callDetailedCallReportCTI_API(); - if (detailedCallReportList.size() > 0) { - result2 = saveDetailedCallReport(detailedCallReportList); + StringBuilder detailedCallReportResult = new StringBuilder(); + for (LocalDate callDate : getPendingDetailedCallReportDates()) { + try { + List detailedCallReportList = callDetailedCallReportCTI_API(callDate); + if (detailedCallReportList.size() > 0) { + detailedCallReportResult.append(callDate).append(" : ") + .append(saveNewDetailedCallReport(detailedCallReportList, callDate)).append("; "); + } + } catch (Exception e) { + logger.error("DetailedCallReport pull failed for " + callDate + " - " + e.getLocalizedMessage()); + } + } + result2 = detailedCallReportResult.toString(); + return response.concat(result1).concat(" ").concat(result2); + } + + + List getPendingDetailedCallReportDates() { + LocalDate yesterday = LocalDate.now().minusDays(1); + + LocalDate explicitStart = parseBackfillDate(backfillStartDate, "start"); + LocalDate explicitEnd = parseBackfillDate(backfillEndDate, "end"); + if (explicitStart != null) { + LocalDate lastDate = explicitEnd != null ? explicitEnd : yesterday; + // today is still in progress, never pull it + if (lastDate.isAfter(yesterday)) + lastDate = yesterday; + if (lastDate.isBefore(explicitStart)) { + logger.error("Configured detailed call report backfill range is empty - start " + explicitStart + + " is after end " + lastDate + ", falling back to the missing day check"); + } else { + List explicitDates = new ArrayList<>(); + for (LocalDate date = explicitStart; !date.isAfter(lastDate); date = date.plusDays(1)) { + if (explicitDates.size() >= MAX_EXPLICIT_BACKFILL_DAYS) { + logger.warn("Configured detailed call report backfill range exceeds " + + MAX_EXPLICIT_BACKFILL_DAYS + " days - stopping at " + date.minusDays(1) + + ", move the start date forward and run again to continue"); + break; + } + explicitDates.add(date); + } + logger.info("DetailedCallReport configured backfill range " + explicitStart + " to " + lastDate + + " - pulling " + explicitDates.size() + " day(s)"); + return explicitDates; + } + } + + int lookBackDays = detailedCallReportBackfillDays > 0 ? detailedCallReportBackfillDays : 1; + LocalDate firstDate = yesterday.minusDays(lookBackDays - 1L); + + Set existingDates = new HashSet<>(); + try { + List dates = detailedCallReportRepo.findExistingCallDates( + Timestamp.valueOf(firstDate.atStartOfDay()), + Timestamp.valueOf(yesterday.atTime(LocalTime.MAX).withNano(0))); + for (java.sql.Date date : dates) { + if (date != null) + existingDates.add(date.toLocalDate()); } } catch (Exception e) { - logger.error(e.getLocalizedMessage()); + // on any problem in gap detection, fall back to the previous behaviour + logger.error("Error while detecting missing detailed call report dates - " + e.getLocalizedMessage()); + return Arrays.asList(yesterday); } - return response.concat(result1).concat(" ").concat(result2); + List pendingDates = new ArrayList<>(); + for (LocalDate date = firstDate; !date.isAfter(yesterday); date = date.plusDays(1)) { + if (!existingDates.contains(date)) + pendingDates.add(date); + } + logger.info("DetailedCallReport pending dates between " + firstDate + " and " + yesterday + " : " + pendingDates); + return pendingDates; + } + + private LocalDate parseBackfillDate(String value, String label) { + if (value == null || value.trim().isEmpty()) + return null; + try { + return LocalDate.parse(value.trim()); + } catch (Exception e) { + logger.error("Ignoring detailed call report backfill " + label + " date '" + value + + "' - expected format yyyy-MM-dd"); + return null; + } + } + + String saveNewDetailedCallReport(List detailedCallReportList, LocalDate callDate) + throws IEMRException { + parseDetailedCallReportTimestamps(detailedCallReportList); + + Set existingKeys = new HashSet<>(); + for (DetailedCallReport existing : detailedCallReportRepo.findByCallStartTimeBetween( + Timestamp.valueOf(callDate.atStartOfDay()), + Timestamp.valueOf(callDate.atTime(LocalTime.MAX).withNano(0)))) { + existingKeys.add(getDetailedCallReportKey(existing)); + } + + List newRecords = new ArrayList<>(); + for (DetailedCallReport detailedCallReport : detailedCallReportList) { + if (existingKeys.add(getDetailedCallReportKey(detailedCallReport))) + newRecords.add(detailedCallReport); + } + + int duplicates = detailedCallReportList.size() - newRecords.size(); + if (newRecords.isEmpty()) { + logger.info("DetailedCallReport " + callDate + " - all " + detailedCallReportList.size() + + " record(s) already present, nothing to save"); + return "0 records saved, " + duplicates + " already present"; + } + + List resultSet = (List) detailedCallReportRepo.saveAll(newRecords); + logger.info("DetailedCallReport " + callDate + " - pulled " + detailedCallReportList.size() + ", saved " + + resultSet.size() + ", already present " + duplicates); + return resultSet.size() + " records saved, " + duplicates + " already present"; + } + + /** + * Natural key of a call record. A session can hold more than one leg (transfer, + * redial), so the phone number and start time are part of the key as well. + */ + private String getDetailedCallReportKey(DetailedCallReport detailedCallReport) { + return String.valueOf(detailedCallReport.getSession_ID()) + '|' + detailedCallReport.getPHONE() + '|' + + detailedCallReport.getCallStartTime() + '|' + detailedCallReport.getAgent_ID(); } public String saveAgentSummaryReport(List agentSummaryReportList) throws IEMRException { @@ -158,7 +288,26 @@ public String saveAgentSummaryReport(List agentSummaryReport public String saveDetailedCallReport(List detailedCallReportList) throws IEMRException { if (detailedCallReportList != null && detailedCallReportList.size() > 0) { - for (DetailedCallReport detailedCallReport : detailedCallReportList) { + parseDetailedCallReportTimestamps(detailedCallReportList); + + List resultSet = (List) detailedCallReportRepo + .saveAll(detailedCallReportList); + + return resultSet.size() + " detailedCallReport records saved successfully"; + } else + throw new IEMRException("please pass valid DetailedCallReport data in list"); + } + + /** + * CTI sends the times as strings; they are moved into the timestamp columns + * here. Has to run before the records are compared against what is already + * stored, because the comparison uses the parsed start time. + */ + private void parseDetailedCallReportTimestamps(List detailedCallReportList) { + if (detailedCallReportList == null) + return; + + for (DetailedCallReport detailedCallReport : detailedCallReportList) { try { if (detailedCallReport.getCall_Start_Time() != null && !detailedCallReport.getCall_Start_Time().equalsIgnoreCase("0000-00-00 00:00:00")) @@ -192,14 +341,7 @@ public String saveDetailedCallReport(List detailedCallReport } catch (Exception e) { logger.error("Call_Start_Time" + e.getLocalizedMessage()); } - } - - List resultSet = (List) detailedCallReportRepo - .saveAll(detailedCallReportList); - - return resultSet.size() + " detailedCallReport records saved successfully"; - } else - throw new IEMRException("please pass valid DetailedCallReport data in list"); + } } public List callAgentSummaryReportCTI_API() throws IEMRException { @@ -213,8 +355,8 @@ public List callAgentSummaryReportCTI_API() throws IEMRExcep date = LocalDateTime.now().minusDays(1); String[] dateArr = date.toString().split("T"); endDate = dateArr[0].concat(" 23:59:59"); - fromDate = dateArr[0].concat(" 00:00:01"); - + fromDate = dateArr[0].concat(" 00:00:00"); + // if (job != null && job.toLowerCase().contains("hour")) { // String jobVal = job.split(" ")[0]; // LocalDateTime nowTime = LocalDateTime.now(); @@ -248,18 +390,18 @@ else if (response.toLowerCase().contains("no data")) } public List callDetailedCallReportCTI_API() throws IEMRException { + return callDetailedCallReportCTI_API(LocalDate.now().minusDays(1)); + } + + public List callDetailedCallReportCTI_API(LocalDate callDate) throws IEMRException { List detailedCallReportList = new ArrayList(); // String job = ConfigProperties.getPropertyByName("get-details-call-report-job"); - String endDate = null; - String fromDate = null; - - LocalDateTime date = null; - date = LocalDateTime.now().minusDays(1); - String[] dateArr = date.toString().split("T"); - endDate = dateArr[0].concat(" 23:59:59"); - fromDate = dateArr[0].concat(" 00:00:01"); - + // full day window - 00:00:00 and not 00:00:01, else calls placed in the very + // first second of the day are dropped + String fromDate = callDate.toString().concat(" 00:00:00"); + String endDate = callDate.toString().concat(" 23:59:59"); + // if (job != null && job.toLowerCase().contains("hour")) { // String jobVal = job.split(" ")[0]; // LocalDateTime nowTime = LocalDateTime.now(); From e88fe3b81ecac3760503691272e23e87f971b31f Mon Sep 17 00:00:00 2001 From: SnehaRH <77656297+snehar-nd@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:19:11 +0530 Subject: [PATCH 04/15] fix: use valid HTTP status 500 instead of invalid 5000 in report error responses (#448) CustomerRelationshipSecondaryReports controller returned ResponseEntity.status(5000) on generic exceptions across all report endpoints (including getAllByGender and getAllBySexualOrientation used by the 1097 report downloads). 5000 is not a valid three-digit HTTP status, so Spring throws IllegalArgumentException while building the response, which surfaces to clients as a confusing "Status code '5000' should be a three-digit positive integer" error instead of the actual failure reason. Co-authored-by: Sneha Co-authored-by: Claude Sonnet 5 --- .../CustomerRelationshipSecondaryReports.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/secondaryReport/CustomerRelationshipSecondaryReports.java b/src/main/java/com/iemr/common/controller/secondaryReport/CustomerRelationshipSecondaryReports.java index 88a8c863..41afc888 100644 --- a/src/main/java/com/iemr/common/controller/secondaryReport/CustomerRelationshipSecondaryReports.java +++ b/src/main/java/com/iemr/common/controller/secondaryReport/CustomerRelationshipSecondaryReports.java @@ -76,7 +76,7 @@ public ResponseEntity getQualityReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -102,7 +102,7 @@ public ResponseEntity getComplaintDetailReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -130,7 +130,7 @@ public ResponseEntity getCallSummaryReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -155,7 +155,7 @@ public ResponseEntity getAllBySexualOrientation( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -181,7 +181,7 @@ public ResponseEntity getDistrictWiseCallReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -205,7 +205,7 @@ public ResponseEntity getUnblockedUserReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -233,7 +233,7 @@ public ResponseEntity getCallQualityReport( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -260,7 +260,7 @@ public ResponseEntity getCountsByPreferredLanguage( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -285,7 +285,7 @@ public ResponseEntity getAllByAgeGroup( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -312,7 +312,7 @@ public ResponseEntity getAllReportsByDate( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } @@ -340,7 +340,7 @@ public ResponseEntity getAllByGender( if (e.getMessage().equalsIgnoreCase("No data found")) return ResponseEntity.status(500).body(e.getMessage()); else - return ResponseEntity.status(5000).body(e.getMessage()); + return ResponseEntity.status(500).body(e.getMessage()); } } From d32456a0b53e8c4618470fe6e5174e4e0b17d2bc Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:35:15 +0530 Subject: [PATCH 05/15] Revert the unwanted scheduler changes (#449) * Revert "fix: add the range to sync" This reverts commit 92b3f200bd6ba2539ca5efa179952d14acd553bf. * Revert "fix: call report issue for 104" This reverts commit a47b709730b9134a39fc2d99b425b832473f2b20. --- .../environment/common_example.properties | 12 +- .../NHMDetailCallReportScheduler.java | 19 +- .../nhm_dashboard/DetailedCallReportRepo.java | 12 -- .../ctiCall/CallCentreDataSyncImpl.java | 124 +++++------ .../NHM_DashboardServiceImpl.java | 196 +++--------------- 5 files changed, 92 insertions(+), 271 deletions(-) diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 8e3d969d..ac440991 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -71,9 +71,9 @@ cron-scheduler-ctidatasync=0 30 01 * * ? * ##-------------------------------###cti data check with call detail report Scheduler------------------------------------------------------ -#Runs at everyday 3:00AM - after the NHM data pull and CTI data sync complete +#Runs at everyday 12:10AM start-ctidatacheck-scheduler=false -cron-scheduler-ctidatacheck=0 00 03 * * * +cron-scheduler-ctidatacheck=0 00 02 * * * ##---------------------------------#### Registration schedular for Avni------------------------------------------------------------------------------ @@ -93,13 +93,7 @@ cron-scheduler-everwelldatasync=0 0/5 * * * ? * ##-----------------------------------------------#NHM data dashboard schedular---------------------------------------------------------------- # run at everyday 12:01AM start-nhmdashboard-scheduler=true -cron-scheduler-nhmdashboard=0 1 0 * * ? * -nhm-detailedcallreport-backfill-days=7 -# one-off recovery of older / partly imported days (yyyy-MM-dd, both inclusive, -# max 60 days per run). Leave empty during normal operation - while these are set -# the job pulls this range instead of only the missing days. -nhm-detailedcallreport-backfill-start-date= -nhm-detailedcallreport-backfill-end-date= +cron-scheduler-nhmdashboard=0 1 * * * ? * ##----------------------------------------------------#grievance data sync----------------------------------------------------------- start-grievancedatasync-scheduler=false diff --git a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java index 31e72019..d02eb3f2 100644 --- a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java +++ b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java @@ -41,23 +41,18 @@ public class NHMDetailCallReportScheduler { @Value("${start-ctidatacheck-scheduler}") private boolean startCtiDataCheckFlag; - /** - * Number of days (ending yesterday) checked against t_bencall. Kept in sync with - * the detailed call report backfill window, so that days pulled late from CTI are - * also reconciled. The reconciliation itself is idempotent. - */ - @Value("${nhm-detailedcallreport-backfill-days:7}") - private int lookBackDays; @Scheduled(cron = "${cron-scheduler-ctidatacheck}") public void detailedCallReport() { if (startCtiDataCheckFlag) { try { - int days = lookBackDays > 0 ? lookBackDays : 1; - LocalDateTime endDay = LocalDateTime.now().minusDays(1); - LocalDateTime startDay = endDay.minusDays(days - 1L); - String endDate = endDay.toString().split("T")[0].concat(" 23:59:59"); - String fromDate = startDay.toString().split("T")[0].concat(" 00:00:00"); + String endDate = null; + String fromDate = null; + LocalDateTime date = null; + date = LocalDateTime.now().minusDays(1); + String[] dateArr = date.toString().split("T"); + endDate = dateArr[0].concat(" 23:59:59"); + fromDate = dateArr[0].concat(" 00:00:01"); Timestamp fromTime = Timestamp.valueOf(fromDate); Timestamp endTime = Timestamp.valueOf(endDate); diff --git a/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java b/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java index f4c5b125..bb89881b 100644 --- a/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java +++ b/src/main/java/com/iemr/common/repository/nhm_dashboard/DetailedCallReportRepo.java @@ -21,13 +21,10 @@ */ package com.iemr.common.repository.nhm_dashboard; -import java.sql.Date; import java.sql.Timestamp; import java.util.List; -import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.iemr.common.data.nhm_dashboard.DetailedCallReport; @@ -35,13 +32,4 @@ @Repository public interface DetailedCallReportRepo extends CrudRepository { List findByCallStartTimeBetween(Timestamp startDate, Timestamp endDate); - - /** - * Call dates for which data has already been pulled from CTI. Used to detect - * the days that were missed by earlier scheduler runs, so that they can be - * pulled again instead of staying permanently empty. - */ - @Query(value = "select distinct date(Call_Start_Time) from t_DetailedCallReport " - + "where Call_Start_Time between :startDate and :endDate", nativeQuery = true) - List findExistingCallDates(@Param("startDate") Timestamp startDate, @Param("endDate") Timestamp endDate); } diff --git a/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java b/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java index f568fe9e..b3cfaf9c 100644 --- a/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java +++ b/src/main/java/com/iemr/common/service/ctiCall/CallCentreDataSyncImpl.java @@ -83,10 +83,10 @@ public String callUrl(String urlRequest) { @Override public void ctiDataSync() { LocalDate currentDate = LocalDate.now(); - // Look back 7 days to retry records that failed in previous runs - LocalDate startDate = currentDate.minusDays(7); - // Up to yesterday - LocalDate endDate = currentDate.minusDays(1); + // Calculate three days before the current date + LocalDate startDate = currentDate.minusDays(3); + // Calculate two days before the current date + LocalDate endDate = currentDate.minusDays(2); // Convert LocalDate to LocalDateTime to set time as 00:00:00 LocalDateTime startDateTime = startDate.atTime(0, 0, 0); LocalDateTime endDateTime = endDate.atTime(23, 59, 59); @@ -98,78 +98,64 @@ public void ctiDataSync() { List list = callReportRepo.getAllBenCallIDetails(startTimeStamp, endTimeStamp); if (!list.isEmpty()) { - logger.info("Total records to process for CTI data sync: " + list.size()); + + // List benList = new ArrayList<>(); + String callDuartion = null; + String filePath = null; + String URL = null; + String callinfoapiURL = null; + String ctiResponse = null; + String callEndTime = null; + String callStartTime = null; + String recordingPath = ""; for (BeneficiaryCall call : list) { - if (call.getCallID() == null) { - logger.warn("Skipping record with null callID, benCallID: " + call.getBenCallID()); - continue; - } - String recordingPath = null; - String callDuartion = null; - String callEndTime = null; - String callStartTime = null; - try { - JSONObject requestFile = new JSONObject(); - requestFile.put("agent_id", call.getAgentID()); - requestFile.put("session_id", call.getCallID()); - - OutputResponse response1 = ctiService.getVoiceFileNew(requestFile.toString(), "extra parameter"); - if(response1 != null && response1.getStatusCode() == 200) { - - CTIResponse ctiResponsePath = InputMapper.gson().fromJson(response1.getData(), - CTIResponse.class); - String recordingFilePath = ctiResponsePath.getResponse().toString(); - if(recordingFilePath.length() > 20) - recordingPath = recordingFilePath.substring(20); - else if (!recordingFilePath.isEmpty()) - recordingPath = recordingFilePath; - logger.info("recordingPath: " + recordingPath); - } - - String callInfoURL = this.callinfoapiURL; - String URL = callInfoURL.replace("CTI_SERVER", ctiServerIP).replace("AGENT_ID", call.getAgentID()) - .replace("SESSION_ID", call.getCallID()).replace("PHONE_NO", call.getPhoneNo()); - - logger.info("calling CTI API url: " + URL); - String ctiResponse = this.callUrl(URL); - logger.info("calling CTI_CDR_CALL_INFO API returned " + ctiResponse); - - CTIData data = InputMapper.gson().fromJson(ctiResponse, CTIData.class); - CTIResponse model = data.getResponse(); - - if (model != null && "1".equals(model.getResponse_code())) { - callDuartion = model.getCall_duration(); - callEndTime = model.getCall_end_date_time(); - callStartTime = model.getCall_start_date_time(); - } else { - logger.warn("CTI API returned non-success for sessionID: " + call.getCallID() - + ", response_code: " + (model != null ? model.getResponse_code() : "null")); - } - - // Only save if we got at least the call duration from CTI - if (callDuartion != null) { - call.setCZcallDuration(Integer.parseInt(callDuartion)); + if (call.getCallID() != null) { + recordingPath = null; + try { + JSONObject requestFile = new JSONObject(); + requestFile.put("agent_id", call.getAgentID()); + requestFile.put("session_id", call.getCallID()); + + OutputResponse response1 = ctiService.getVoiceFileNew(requestFile.toString(), "extra parameter"); + if(response1 != null && response1.getStatusCode() == 200) { + + CTIResponse ctiResponsePath = InputMapper.gson().fromJson(response1.getData(), + CTIResponse.class); + String recordingFilePath = ctiResponsePath.getResponse().toString(); + if(recordingFilePath.length() > 20) + recordingPath = recordingFilePath.substring(20); + logger.info("recordingPath: " + recordingPath); + } + + callDuartion = null; + callinfoapiURL = this.callinfoapiURL; + URL = callinfoapiURL.replace("CTI_SERVER", ctiServerIP).replace("AGENT_ID", call.getAgentID()) + .replace("SESSION_ID", call.getCallID()).replace("PHONE_NO", call.getPhoneNo()); + + logger.info("calling CTI API url: " + URL); + ctiResponse = this.callUrl(URL); + logger.info("calling CTI_CDR_CALL_INFO API returned " + ctiResponse); + + CTIData data = InputMapper.gson().fromJson(ctiResponse, CTIData.class); + CTIResponse model = data.getResponse(); + + if (model.getResponse_code().equals("1")) { + callDuartion = model.getCall_duration(); + callEndTime = model.getCall_end_date_time(); + callStartTime = model.getCall_start_date_time(); + } + if (callDuartion != null) + call.setCZcallDuration(Integer.parseInt(callDuartion)); + call.setRecordingPath(recordingPath); call.setCZcallEndTime(callEndTime); call.setCZcallStartTime(callStartTime); - call.setRecordingPath(recordingPath); - callReportRepo.save(call); - logger.info("CTI data sync saved for benCallID: " + call.getBenCallID()); - } else if (recordingPath != null) { - // Duration not available yet, but recording path is — save path only - call.setRecordingPath(recordingPath); callReportRepo.save(call); - logger.info("Only recordingPath saved (duration pending) for benCallID: " + call.getBenCallID()); - } else { - logger.warn("No CTI data available yet for sessionID: " + call.getCallID() - + ", benCallID: " + call.getBenCallID() + " - will retry next run"); + logger.info("calling CTI_CDR_CALL_INFO after API call save response " + call); + } catch (Exception e) { + logger.error("VoiceFile failed with error " + e.getMessage(), e); } - } catch (Exception e) { - logger.error("CTI data sync failed for benCallID: " + call.getBenCallID() - + ", sessionID: " + call.getCallID() + " - " + e.getMessage(), e); } } - } else { - logger.info("No pending records found for CTI data sync"); } } } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java b/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java index d86c1ad9..d7afd579 100644 --- a/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java +++ b/src/main/java/com/iemr/common/service/nhm_dashboard/NHM_DashboardServiceImpl.java @@ -24,13 +24,10 @@ import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,21 +65,6 @@ public class NHM_DashboardServiceImpl implements NHM_DashboardService { @Value("${cti-server-ip}") private String serverURL; - /** - * Number of days (ending yesterday) the detailed call report pull looks back to - * re-pull days that were missed. 1 = previous day only, i.e. old behaviour. - */ - @Value("${nhm-detailedcallreport-backfill-days:7}") - private int detailedCallReportBackfillDays; - - @Value("${nhm-detailedcallreport-backfill-start-date:}") - private String backfillStartDate; - - @Value("${nhm-detailedcallreport-backfill-end-date:}") - private String backfillEndDate; - - private static final int MAX_EXPLICIT_BACKFILL_DAYS = 60; - public String pushAbandonCalls(AbandonCallSummary abandonCallSummary) throws Exception { logger.info("NHM_abandon call push API request : " + abandonCallSummary.toString()); @@ -137,6 +119,7 @@ public String getDetailedCallReport() throws Exception { return new Gson().toJson(resultSet); } + // JOB calling C-Zentrix 2 APIs => AgentSummaryReport & DetailedCallReport public String pull_NHM_Data_CTI() throws IEMRException { String response = ""; String result1 = ""; @@ -151,130 +134,17 @@ public String pull_NHM_Data_CTI() throws IEMRException { logger.error(e.getLocalizedMessage()); } - StringBuilder detailedCallReportResult = new StringBuilder(); - for (LocalDate callDate : getPendingDetailedCallReportDates()) { - try { - List detailedCallReportList = callDetailedCallReportCTI_API(callDate); - if (detailedCallReportList.size() > 0) { - detailedCallReportResult.append(callDate).append(" : ") - .append(saveNewDetailedCallReport(detailedCallReportList, callDate)).append("; "); - } - } catch (Exception e) { - logger.error("DetailedCallReport pull failed for " + callDate + " - " + e.getLocalizedMessage()); - } - } - result2 = detailedCallReportResult.toString(); - - return response.concat(result1).concat(" ").concat(result2); - } - - - List getPendingDetailedCallReportDates() { - LocalDate yesterday = LocalDate.now().minusDays(1); - - LocalDate explicitStart = parseBackfillDate(backfillStartDate, "start"); - LocalDate explicitEnd = parseBackfillDate(backfillEndDate, "end"); - if (explicitStart != null) { - LocalDate lastDate = explicitEnd != null ? explicitEnd : yesterday; - // today is still in progress, never pull it - if (lastDate.isAfter(yesterday)) - lastDate = yesterday; - if (lastDate.isBefore(explicitStart)) { - logger.error("Configured detailed call report backfill range is empty - start " + explicitStart - + " is after end " + lastDate + ", falling back to the missing day check"); - } else { - List explicitDates = new ArrayList<>(); - for (LocalDate date = explicitStart; !date.isAfter(lastDate); date = date.plusDays(1)) { - if (explicitDates.size() >= MAX_EXPLICIT_BACKFILL_DAYS) { - logger.warn("Configured detailed call report backfill range exceeds " - + MAX_EXPLICIT_BACKFILL_DAYS + " days - stopping at " + date.minusDays(1) - + ", move the start date forward and run again to continue"); - break; - } - explicitDates.add(date); - } - logger.info("DetailedCallReport configured backfill range " + explicitStart + " to " + lastDate - + " - pulling " + explicitDates.size() + " day(s)"); - return explicitDates; - } - } - - int lookBackDays = detailedCallReportBackfillDays > 0 ? detailedCallReportBackfillDays : 1; - LocalDate firstDate = yesterday.minusDays(lookBackDays - 1L); - - Set existingDates = new HashSet<>(); try { - List dates = detailedCallReportRepo.findExistingCallDates( - Timestamp.valueOf(firstDate.atStartOfDay()), - Timestamp.valueOf(yesterday.atTime(LocalTime.MAX).withNano(0))); - for (java.sql.Date date : dates) { - if (date != null) - existingDates.add(date.toLocalDate()); - } - } catch (Exception e) { - // on any problem in gap detection, fall back to the previous behaviour - logger.error("Error while detecting missing detailed call report dates - " + e.getLocalizedMessage()); - return Arrays.asList(yesterday); - } - - List pendingDates = new ArrayList<>(); - for (LocalDate date = firstDate; !date.isAfter(yesterday); date = date.plusDays(1)) { - if (!existingDates.contains(date)) - pendingDates.add(date); - } - logger.info("DetailedCallReport pending dates between " + firstDate + " and " + yesterday + " : " + pendingDates); - return pendingDates; - } + List detailedCallReportList = callDetailedCallReportCTI_API(); + if (detailedCallReportList.size() > 0) { + result2 = saveDetailedCallReport(detailedCallReportList); - private LocalDate parseBackfillDate(String value, String label) { - if (value == null || value.trim().isEmpty()) - return null; - try { - return LocalDate.parse(value.trim()); + } } catch (Exception e) { - logger.error("Ignoring detailed call report backfill " + label + " date '" + value - + "' - expected format yyyy-MM-dd"); - return null; - } - } - - String saveNewDetailedCallReport(List detailedCallReportList, LocalDate callDate) - throws IEMRException { - parseDetailedCallReportTimestamps(detailedCallReportList); - - Set existingKeys = new HashSet<>(); - for (DetailedCallReport existing : detailedCallReportRepo.findByCallStartTimeBetween( - Timestamp.valueOf(callDate.atStartOfDay()), - Timestamp.valueOf(callDate.atTime(LocalTime.MAX).withNano(0)))) { - existingKeys.add(getDetailedCallReportKey(existing)); - } - - List newRecords = new ArrayList<>(); - for (DetailedCallReport detailedCallReport : detailedCallReportList) { - if (existingKeys.add(getDetailedCallReportKey(detailedCallReport))) - newRecords.add(detailedCallReport); - } - - int duplicates = detailedCallReportList.size() - newRecords.size(); - if (newRecords.isEmpty()) { - logger.info("DetailedCallReport " + callDate + " - all " + detailedCallReportList.size() - + " record(s) already present, nothing to save"); - return "0 records saved, " + duplicates + " already present"; + logger.error(e.getLocalizedMessage()); } - List resultSet = (List) detailedCallReportRepo.saveAll(newRecords); - logger.info("DetailedCallReport " + callDate + " - pulled " + detailedCallReportList.size() + ", saved " - + resultSet.size() + ", already present " + duplicates); - return resultSet.size() + " records saved, " + duplicates + " already present"; - } - - /** - * Natural key of a call record. A session can hold more than one leg (transfer, - * redial), so the phone number and start time are part of the key as well. - */ - private String getDetailedCallReportKey(DetailedCallReport detailedCallReport) { - return String.valueOf(detailedCallReport.getSession_ID()) + '|' + detailedCallReport.getPHONE() + '|' - + detailedCallReport.getCallStartTime() + '|' + detailedCallReport.getAgent_ID(); + return response.concat(result1).concat(" ").concat(result2); } public String saveAgentSummaryReport(List agentSummaryReportList) throws IEMRException { @@ -288,26 +158,7 @@ public String saveAgentSummaryReport(List agentSummaryReport public String saveDetailedCallReport(List detailedCallReportList) throws IEMRException { if (detailedCallReportList != null && detailedCallReportList.size() > 0) { - parseDetailedCallReportTimestamps(detailedCallReportList); - - List resultSet = (List) detailedCallReportRepo - .saveAll(detailedCallReportList); - - return resultSet.size() + " detailedCallReport records saved successfully"; - } else - throw new IEMRException("please pass valid DetailedCallReport data in list"); - } - - /** - * CTI sends the times as strings; they are moved into the timestamp columns - * here. Has to run before the records are compared against what is already - * stored, because the comparison uses the parsed start time. - */ - private void parseDetailedCallReportTimestamps(List detailedCallReportList) { - if (detailedCallReportList == null) - return; - - for (DetailedCallReport detailedCallReport : detailedCallReportList) { + for (DetailedCallReport detailedCallReport : detailedCallReportList) { try { if (detailedCallReport.getCall_Start_Time() != null && !detailedCallReport.getCall_Start_Time().equalsIgnoreCase("0000-00-00 00:00:00")) @@ -341,7 +192,14 @@ private void parseDetailedCallReportTimestamps(List detailed } catch (Exception e) { logger.error("Call_Start_Time" + e.getLocalizedMessage()); } - } + } + + List resultSet = (List) detailedCallReportRepo + .saveAll(detailedCallReportList); + + return resultSet.size() + " detailedCallReport records saved successfully"; + } else + throw new IEMRException("please pass valid DetailedCallReport data in list"); } public List callAgentSummaryReportCTI_API() throws IEMRException { @@ -355,8 +213,8 @@ public List callAgentSummaryReportCTI_API() throws IEMRExcep date = LocalDateTime.now().minusDays(1); String[] dateArr = date.toString().split("T"); endDate = dateArr[0].concat(" 23:59:59"); - fromDate = dateArr[0].concat(" 00:00:00"); - + fromDate = dateArr[0].concat(" 00:00:01"); + // if (job != null && job.toLowerCase().contains("hour")) { // String jobVal = job.split(" ")[0]; // LocalDateTime nowTime = LocalDateTime.now(); @@ -390,18 +248,18 @@ else if (response.toLowerCase().contains("no data")) } public List callDetailedCallReportCTI_API() throws IEMRException { - return callDetailedCallReportCTI_API(LocalDate.now().minusDays(1)); - } - - public List callDetailedCallReportCTI_API(LocalDate callDate) throws IEMRException { List detailedCallReportList = new ArrayList(); // String job = ConfigProperties.getPropertyByName("get-details-call-report-job"); - // full day window - 00:00:00 and not 00:00:01, else calls placed in the very - // first second of the day are dropped - String fromDate = callDate.toString().concat(" 00:00:00"); - String endDate = callDate.toString().concat(" 23:59:59"); - + String endDate = null; + String fromDate = null; + + LocalDateTime date = null; + date = LocalDateTime.now().minusDays(1); + String[] dateArr = date.toString().split("T"); + endDate = dateArr[0].concat(" 23:59:59"); + fromDate = dateArr[0].concat(" 00:00:01"); + // if (job != null && job.toLowerCase().contains("hour")) { // String jobVal = job.split(" ")[0]; // LocalDateTime nowTime = LocalDateTime.now(); From 71fcbb2972c6aac39886bd8809cb847afa4e1dfa Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Thu, 10 Sep 2026 00:46:24 +0530 Subject: [PATCH 06/15] fix: remove the wrong column (#454) --- .../com/iemr/common/data/callhandling/BeneficiaryCall.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java index 1eca8fec..a024acdf 100644 --- a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java +++ b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java @@ -235,9 +235,6 @@ public class BeneficiaryCall { @Expose @Column(name = "InsName") private String instName; - - @Value("${cti-logger_base_url}") - private String loggerBaseURL; @Transient @Expose From be20463657727b3cf66c0d66e7e723ee9329133d Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Thu, 10 Sep 2026 00:56:28 +0530 Subject: [PATCH 07/15] fix: build issue (#455) --- .../common/data/callhandling/BeneficiaryCall.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java index a024acdf..59c9518a 100644 --- a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java +++ b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java @@ -23,12 +23,9 @@ import java.sql.Timestamp; -import org.springframework.beans.factory.annotation.Value; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.annotations.Expose; import com.iemr.common.data.beneficiary.Beneficiary; -import com.iemr.common.utils.config.ConfigProperties; import com.iemr.common.utils.mapper.OutputMapper; import jakarta.persistence.Column; @@ -280,17 +277,6 @@ public BeneficiaryCall(Long beneficiaryRegID, Boolean is1097, String createdBy) this.createdBy = createdBy; } - public BeneficiaryCall(Long benCallID, Timestamp createdDate, String agentID, String callID, String recordingPath, - String archivePath) { - // String loggerBaseURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); - this.benCallID = benCallID; - this.createdDate = createdDate; - this.agentID = agentID; - this.callID = callID; - this.recordingPath = (recordingPath != null) ? (loggerBaseURL + "/" + recordingPath) : recordingPath; - this.archivePath = (archivePath != null) ? (loggerBaseURL + "/" + archivePath) : archivePath; - } - @Transient private OutputMapper outputMapper = new OutputMapper(); From 7cd6509597580d2d0e3caf1a8da42f052820488e Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:37:56 +0530 Subject: [PATCH 08/15] fix order issue in dynamic from (#447) (#451) --- .../data/callhandling/BeneficiaryCall.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java index 59c9518a..fd55de84 100644 --- a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java +++ b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java @@ -23,6 +23,8 @@ import java.sql.Timestamp; +import org.springframework.beans.factory.annotation.Value; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.annotations.Expose; import com.iemr.common.data.beneficiary.Beneficiary; @@ -232,6 +234,9 @@ public class BeneficiaryCall { @Expose @Column(name = "InsName") private String instName; + + @Value("${cti-logger_base_url}") + private String loggerBaseURL; @Transient @Expose @@ -277,6 +282,17 @@ public BeneficiaryCall(Long beneficiaryRegID, Boolean is1097, String createdBy) this.createdBy = createdBy; } + public BeneficiaryCall(Long benCallID, Timestamp createdDate, String agentID, String callID, String recordingPath, + String archivePath) { + // String loggerBaseURL = ConfigProperties.getPropertyByName("cti-logger_base_url"); + this.benCallID = benCallID; + this.createdDate = createdDate; + this.agentID = agentID; + this.callID = callID; + this.recordingPath = (recordingPath != null) ? (loggerBaseURL + "/" + recordingPath) : recordingPath; + this.archivePath = (archivePath != null) ? (loggerBaseURL + "/" + archivePath) : archivePath; + } + @Transient private OutputMapper outputMapper = new OutputMapper(); From 16901867b2711b28f38336e4ebb0b2cc88655b41 Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 9 Oct 2026 11:34:43 +0530 Subject: [PATCH 09/15] fix issue get userName from token (#459) --- .../common/controller/users/IEMRAdminController.java | 6 +++--- src/main/java/com/iemr/common/utils/JwtUtil.java | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 582ab0ff..63c8ca92 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -218,7 +218,7 @@ public String userAuthenticate( if (mUser.size() == 1) { String userIdStr = mUser.get(0).getUserID().toString(); jwtToken = isMobile - ? jwtUtil.generateSecureToken(userIdStr) + ? jwtUtil.generateSecureToken(m_User.getUserName(),userIdStr) : jwtUtil.generateToken(m_User.getUserName(), userIdStr); User user = new User(); // Assuming the Users class exists @@ -235,7 +235,7 @@ public String userAuthenticate( ); if (isMobile) { - refreshToken = jwtUtil.generateSecureRefreshToken(user.getUserID().toString()); + refreshToken = jwtUtil.generateSecureRefreshToken(m_User.getUserName(),user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); String jti = jwtUtil.getJtiFromToken(refreshToken); redisTemplate.opsForValue().set( @@ -604,7 +604,7 @@ public String superUserAuthenticate( ); if (isMobile) { - refreshToken = jwtUtil.generateSecureRefreshToken(user.getUserID().toString()); + refreshToken = jwtUtil.generateSecureRefreshToken(m_User.getUserName(),user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); String jti = jwtUtil.getJtiFromToken(refreshToken); redisTemplate.opsForValue().set( diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index 98ff7b7b..9e85b761 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -47,17 +47,17 @@ public String generateToken(String username, String userId) { } // Mobile login: token without PII in sub - public String generateSecureToken(String userId) { - return buildSecureToken(userId, "access", ACCESS_EXPIRATION_TIME); + public String generateSecureToken(String username,String userId) { + return buildSecureToken(username, userId, "access", ACCESS_EXPIRATION_TIME); } - public String generateSecureRefreshToken(String userId) { - return buildSecureToken(userId, "refresh", REFRESH_EXPIRATION_TIME); + public String generateSecureRefreshToken(String userName,String userId) { + return buildSecureToken(userName,userId, "refresh", REFRESH_EXPIRATION_TIME); } - private String buildSecureToken(String userId, String tokenType, long expiration) { + private String buildSecureToken(String userName ,String userId, String tokenType, long expiration) { return Jwts.builder() - .subject(userId) + .subject(userName) .claim("userId", userId) .claim("token_type", tokenType) .id(UUID.randomUUID().toString()) From c6fa993a99982d56aeee8c16d2f559ae7c5af14b Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:45:14 +0530 Subject: [PATCH 10/15] fix issue for in transient (#461) --- .../java/com/iemr/common/data/callhandling/BeneficiaryCall.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java index fd55de84..43e07a3d 100644 --- a/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java +++ b/src/main/java/com/iemr/common/data/callhandling/BeneficiaryCall.java @@ -234,7 +234,7 @@ public class BeneficiaryCall { @Expose @Column(name = "InsName") private String instName; - + @Transient @Value("${cti-logger_base_url}") private String loggerBaseURL; From 481890c717f28401431c1de639637c1fa6b250e2 Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:00:29 +0530 Subject: [PATCH 11/15] add owner module column --- .../com/iemr/common/data/dynamic_from/FormFieldOption.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java b/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java index 05f06df2..c545be57 100644 --- a/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java +++ b/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java @@ -33,5 +33,8 @@ public class FormFieldOption { @Column(name = "sort_order") private Integer sortOrder; + @Column(name = "owner_module") + private String ownerModule; + // getters/setters } \ No newline at end of file From c8a3f920b91bdf57922b8d50178b5a5ac101dc92 Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:04:19 +0530 Subject: [PATCH 12/15] add owner module column --- .../FirebaseNotificationServiceTest.java | 364 +++++++++--------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java b/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java index bf8781ea..0c8cc71a 100644 --- a/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java +++ b/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java @@ -1,182 +1,182 @@ -package com.iemr.common.service.firebaseNotification; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -import java.sql.Timestamp; -import java.util.Map; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.iemr.common.data.userToken.UserTokenData; -import com.iemr.common.model.notification.NotificationMessage; -import com.iemr.common.model.notification.UserToken; -import com.iemr.common.repo.userToken.UserTokenRepo; -import com.iemr.common.utils.CookieUtil; -import com.iemr.common.utils.JwtUtil; - -/** Covers the Firebase push-notification service and its device-token bookkeeping. */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class FirebaseNotificationServiceTest { - - @Mock - private UserTokenRepo userTokenRepo; - @Mock - private CookieUtil cookieUtil; - @Mock - private JwtUtil jwtUtil; - - private FirebaseNotificationService service; - - @BeforeEach - void setUp() { - service = new FirebaseNotificationService(); - ReflectionTestUtils.setField(service, "userTokenRepo", userTokenRepo); - ReflectionTestUtils.setField(service, "cookieUtil", cookieUtil); - ReflectionTestUtils.setField(service, "jwtUtil", jwtUtil); - } - - @AfterEach - void clearRequestContext() { - RequestContextHolder.resetRequestAttributes(); - } - - private NotificationMessage notification() { - NotificationMessage message = new NotificationMessage(); - message.setTitle("Call assigned"); - message.setBody("Beneficiary BEN-42 is waiting"); - message.setToken("topic-agents"); - message.setData(Map.of("callId", "42")); - return message; - } - - @Test - @DisplayName("no configured Firebase messaging means the notification is skipped, not attempted") - void notificationIsSkippedWhenFirebaseIsNotConfigured() { - assertThat(service.sendNotification(notification())).isNull(); - } - - @Test - @DisplayName("a configured Firebase publishes the notification to the requested topic") - void notificationIsPublishedToTopic() throws Exception { - FirebaseMessaging messaging = mock(FirebaseMessaging.class); - ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); - when(messaging.send(any(Message.class))).thenReturn("projects/amrit/messages/1"); - - try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { - statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); - - assertThat(service.sendNotification(notification())).isEqualTo("projects/amrit/messages/1"); - verify(messaging).send(any(Message.class)); - } - } - - @Test - @DisplayName("a Firebase delivery failure is reported rather than propagated") - void deliveryFailureIsReported() throws Exception { - FirebaseMessaging messaging = mock(FirebaseMessaging.class); - ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); - when(messaging.send(any(Message.class))).thenThrow(mock(FirebaseMessagingException.class)); - - try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { - statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); - - assertThat(service.sendNotification(notification())).isEqualTo("Error sending notification"); - } - } - - @Test - @DisplayName("an existing device token is updated in place") - void existingTokenIsUpdated() { - UserTokenData existing = new UserTokenData(); - existing.setUserId(7); - existing.setToken("old-token"); - when(userTokenRepo.findById(7)).thenReturn(Optional.of(existing)); - - UserToken userToken = new UserToken(); - userToken.setUserId(7); - userToken.setToken("new-token"); - - assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); - verify(userTokenRepo).save(captor.capture()); - assertThat(captor.getValue()).isSameAs(existing); - assertThat(captor.getValue().getToken()).isEqualTo("new-token"); - assertThat(captor.getValue().getUpdatedAt()).isNotNull(); - } - - @Test - @DisplayName("a user with no stored token gets a new row") - void newTokenIsCreated() { - when(userTokenRepo.findById(8)).thenReturn(Optional.empty()); - - UserToken userToken = new UserToken(); - userToken.setUserId(8); - userToken.setToken("fresh-token"); - - assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); - verify(userTokenRepo).save(captor.capture()); - assertThat(captor.getValue().getUserId()).isEqualTo(8); - assertThat(captor.getValue().getToken()).isEqualTo("fresh-token"); - } - - @Test - @DisplayName("the caller's stored token is resolved from the JWT in their cookie") - void storedTokenIsResolvedFromTheJwtCookie() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); - when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); - UserTokenData stored = new UserTokenData(); - stored.setToken("device-token"); - when(userTokenRepo.findById(11)).thenReturn(Optional.of(stored)); - - try (MockedStatic cookies = mockStatic(CookieUtil.class)) { - cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); - - assertThat(service.getUserToken()).isEqualTo("device-token"); - } - } - - @Test - @DisplayName("a caller with no stored token gets nothing back") - void noStoredTokenYieldsNull() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); - when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); - when(userTokenRepo.findById(11)).thenReturn(Optional.empty()); - - try (MockedStatic cookies = mockStatic(CookieUtil.class)) { - cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); - - assertThat(service.getUserToken()).isNull(); - } - } -} +//package com.iemr.common.service.firebaseNotification; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.assertj.core.api.Assertions.assertThatThrownBy; +//import static org.mockito.ArgumentMatchers.any; +//import static org.mockito.Mockito.mock; +//import static org.mockito.Mockito.mockStatic; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.verifyNoInteractions; +//import static org.mockito.Mockito.when; +// +//import java.sql.Timestamp; +//import java.util.Map; +//import java.util.Optional; +// +//import com.iemr.common.repo.userToken.UserFcmTokenRepo; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.ArgumentCaptor; +//import org.mockito.Mock; +//import org.mockito.MockedStatic; +//import org.mockito.junit.jupiter.MockitoExtension; +//import org.mockito.junit.jupiter.MockitoSettings; +//import org.mockito.quality.Strictness; +//import org.springframework.mock.web.MockHttpServletRequest; +//import org.springframework.test.util.ReflectionTestUtils; +//import org.springframework.web.context.request.RequestContextHolder; +//import org.springframework.web.context.request.ServletRequestAttributes; +// +//import com.google.firebase.messaging.FirebaseMessaging; +//import com.google.firebase.messaging.FirebaseMessagingException; +//import com.google.firebase.messaging.Message; +//import com.iemr.common.data.userToken.UserTokenData; +//import com.iemr.common.model.notification.NotificationMessage; +//import com.iemr.common.model.notification.UserToken; +//import com.iemr.common.repo.userToken.UserTokenRepo; +//import com.iemr.common.utils.CookieUtil; +//import com.iemr.common.utils.JwtUtil; +// +///** Covers the Firebase push-notification service and its device-token bookkeeping. */ +//@ExtendWith(MockitoExtension.class) +//@MockitoSettings(strictness = Strictness.LENIENT) +//class FirebaseNotificationServiceTest { +// +// @Mock +// private UserFcmTokenRepo userTokenRepo; +// @Mock +// private CookieUtil cookieUtil; +// @Mock +// private JwtUtil jwtUtil; +// +// private FirebaseNotificationService service; +// +// @BeforeEach +// void setUp() { +// service = new FirebaseNotificationService(); +// ReflectionTestUtils.setField(service, "cookieUtil", cookieUtil); +// ReflectionTestUtils.setField(service, "jwtUtil", jwtUtil); +// } +// +// @AfterEach +// void clearRequestContext() { +// RequestContextHolder.resetRequestAttributes(); +// } +// +// private NotificationMessage notification() { +// NotificationMessage message = new NotificationMessage(); +// message.setTitle("Call assigned"); +// message.setBody("Beneficiary BEN-42 is waiting"); +// message.setToken("topic-agents"); +// message.setData(Map.of("callId", "42")); +// return message; +// } +// +// @Test +// @DisplayName("no configured Firebase messaging means the notification is skipped, not attempted") +// void notificationIsSkippedWhenFirebaseIsNotConfigured() { +// assertThat(service.sendNotification(notification())).isNull(); +// } +// +// @Test +// @DisplayName("a configured Firebase publishes the notification to the requested topic") +// void notificationIsPublishedToTopic() throws Exception { +// FirebaseMessaging messaging = mock(FirebaseMessaging.class); +// ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); +// when(messaging.send(any(Message.class))).thenReturn("projects/amrit/messages/1"); +// +// try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { +// statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); +// +// assertThat(service.sendNotification(notification())).isEqualTo("projects/amrit/messages/1"); +// verify(messaging).send(any(Message.class)); +// } +// } +// +// @Test +// @DisplayName("a Firebase delivery failure is reported rather than propagated") +// void deliveryFailureIsReported() throws Exception { +// FirebaseMessaging messaging = mock(FirebaseMessaging.class); +// ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); +// when(messaging.send(any(Message.class))).thenThrow(mock(FirebaseMessagingException.class)); +// +// try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { +// statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); +// +// assertThat(service.sendNotification(notification())).isEqualTo("Error sending notification"); +// } +// } +// +// @Test +// @DisplayName("an existing device token is updated in place") +// void existingTokenIsUpdated() { +// UserTokenData existing = new UserTokenData(); +// existing.setUserId(7); +// existing.setToken("old-token"); +// when(userTokenRepo.findById(7)).thenReturn(Optional.of(existing)); +// +// UserToken userToken = new UserToken(); +// userToken.setUserId(7); +// userToken.setToken("new-token"); +// +// assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); +// +// ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); +// verify(userTokenRepo).save(captor.capture()); +// assertThat(captor.getValue()).isSameAs(existing); +// assertThat(captor.getValue().getToken()).isEqualTo("new-token"); +// assertThat(captor.getValue().getUpdatedAt()).isNotNull(); +// } +// +// @Test +// @DisplayName("a user with no stored token gets a new row") +// void newTokenIsCreated() { +// when(userTokenRepo.findById(8)).thenReturn(Optional.empty()); +// +// UserToken userToken = new UserToken(); +// userToken.setUserId(8); +// userToken.setToken("fresh-token"); +// +// assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); +// +// ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); +// verify(userTokenRepo).save(captor.capture()); +// assertThat(captor.getValue().getUserId()).isEqualTo(8); +// assertThat(captor.getValue().getToken()).isEqualTo("fresh-token"); +// } +// +// @Test +// @DisplayName("the caller's stored token is resolved from the JWT in their cookie") +// void storedTokenIsResolvedFromTheJwtCookie() throws Exception { +// MockHttpServletRequest request = new MockHttpServletRequest(); +// RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); +// when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); +// UserTokenData stored = new UserTokenData(); +// stored.setToken("device-token"); +// when(userTokenRepo.findById(11)).thenReturn(Optional.of(stored)); +// +// try (MockedStatic cookies = mockStatic(CookieUtil.class)) { +// cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); +// +// assertThat(service.getUserToken()).isEqualTo("device-token"); +// } +// } +// +// @Test +// @DisplayName("a caller with no stored token gets nothing back") +// void noStoredTokenYieldsNull() throws Exception { +// MockHttpServletRequest request = new MockHttpServletRequest(); +// RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); +// when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); +// when(userTokenRepo.findById(11)).thenReturn(Optional.empty()); +// +// try (MockedStatic cookies = mockStatic(CookieUtil.class)) { +// cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); +// +// assertThat(service.getUserToken()).isNull(); +// } +// } +//} From 4a818bacaa06d901aaa31a31ac9025e55df0fe14 Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:08:33 +0530 Subject: [PATCH 13/15] add owner module column --- src/test/java/com/iemr/common/utils/JwtUtilTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/iemr/common/utils/JwtUtilTest.java b/src/test/java/com/iemr/common/utils/JwtUtilTest.java index 6bb2ea50..3d04ded8 100644 --- a/src/test/java/com/iemr/common/utils/JwtUtilTest.java +++ b/src/test/java/com/iemr/common/utils/JwtUtilTest.java @@ -95,7 +95,7 @@ void refreshTokenIsMarked() { @Test @DisplayName("a secure token uses the user id as its subject") void secureTokenSubjectIsTheUserId() { - Claims claims = jwtUtil.getAllClaimsFromToken(jwtUtil.generateSecureToken("42")); + Claims claims = jwtUtil.getAllClaimsFromToken(jwtUtil.generateSecureToken("","42")); assertThat(claims.getSubject()).isEqualTo("42"); assertThat(claims.get("token_type", String.class)).isEqualTo("access"); @@ -104,7 +104,7 @@ void secureTokenSubjectIsTheUserId() { @Test @DisplayName("a secure refresh token uses the user id as its subject") void secureRefreshTokenSubjectIsTheUserId() { - Claims claims = jwtUtil.getAllClaimsFromToken(jwtUtil.generateSecureRefreshToken("42")); + Claims claims = jwtUtil.getAllClaimsFromToken(jwtUtil.generateSecureToken("","42")); assertThat(claims.getSubject()).isEqualTo("42"); assertThat(claims.get("token_type", String.class)).isEqualTo("refresh"); From 8eee5833a8f2a30895da372f6a7b6b5665979ada Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:16:50 +0530 Subject: [PATCH 14/15] add owner module column --- .../common/service/dynamicForm/FormMasterServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java index 2861819b..c7804593 100644 --- a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java +++ b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java @@ -207,6 +207,10 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str Map map = new LinkedHashMap<>(); map.put("id", opt.getId()); map.put("value", opt.getValue()); + if(opt.getOwnerModule()!=null && !opt.getOwnerModule().isEmpty()){ + map.put("ownerModule",opt.getOwnerModule()); + + } if ("hi".equalsIgnoreCase(lang)) map.put("label", opt.getLabelHi()); else if ("as".equalsIgnoreCase(lang)) map.put("label", opt.getLabelAs()); else if("en".equals(lang)) map.put("label", opt.getLabelEn()); From 7015e6ad5c7bc03fdeb6a124135e0cf19d05d9fb Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:20:27 +0530 Subject: [PATCH 15/15] add owner module column --- .../FirebaseNotificationServiceTest.java | 363 +++++++++--------- 1 file changed, 181 insertions(+), 182 deletions(-) diff --git a/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java b/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java index 0c8cc71a..38fa22a7 100644 --- a/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java +++ b/src/test/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationServiceTest.java @@ -1,182 +1,181 @@ -//package com.iemr.common.service.firebaseNotification; -// -//import static org.assertj.core.api.Assertions.assertThat; -//import static org.assertj.core.api.Assertions.assertThatThrownBy; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.Mockito.mock; -//import static org.mockito.Mockito.mockStatic; -//import static org.mockito.Mockito.verify; -//import static org.mockito.Mockito.verifyNoInteractions; -//import static org.mockito.Mockito.when; -// -//import java.sql.Timestamp; -//import java.util.Map; -//import java.util.Optional; -// -//import com.iemr.common.repo.userToken.UserFcmTokenRepo; -//import org.junit.jupiter.api.AfterEach; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.ArgumentCaptor; -//import org.mockito.Mock; -//import org.mockito.MockedStatic; -//import org.mockito.junit.jupiter.MockitoExtension; -//import org.mockito.junit.jupiter.MockitoSettings; -//import org.mockito.quality.Strictness; -//import org.springframework.mock.web.MockHttpServletRequest; -//import org.springframework.test.util.ReflectionTestUtils; -//import org.springframework.web.context.request.RequestContextHolder; -//import org.springframework.web.context.request.ServletRequestAttributes; -// -//import com.google.firebase.messaging.FirebaseMessaging; -//import com.google.firebase.messaging.FirebaseMessagingException; -//import com.google.firebase.messaging.Message; -//import com.iemr.common.data.userToken.UserTokenData; -//import com.iemr.common.model.notification.NotificationMessage; -//import com.iemr.common.model.notification.UserToken; -//import com.iemr.common.repo.userToken.UserTokenRepo; -//import com.iemr.common.utils.CookieUtil; -//import com.iemr.common.utils.JwtUtil; -// -///** Covers the Firebase push-notification service and its device-token bookkeeping. */ -//@ExtendWith(MockitoExtension.class) -//@MockitoSettings(strictness = Strictness.LENIENT) -//class FirebaseNotificationServiceTest { -// -// @Mock -// private UserFcmTokenRepo userTokenRepo; -// @Mock -// private CookieUtil cookieUtil; -// @Mock -// private JwtUtil jwtUtil; -// -// private FirebaseNotificationService service; -// -// @BeforeEach -// void setUp() { -// service = new FirebaseNotificationService(); -// ReflectionTestUtils.setField(service, "cookieUtil", cookieUtil); -// ReflectionTestUtils.setField(service, "jwtUtil", jwtUtil); -// } -// -// @AfterEach -// void clearRequestContext() { -// RequestContextHolder.resetRequestAttributes(); -// } -// -// private NotificationMessage notification() { -// NotificationMessage message = new NotificationMessage(); -// message.setTitle("Call assigned"); -// message.setBody("Beneficiary BEN-42 is waiting"); -// message.setToken("topic-agents"); -// message.setData(Map.of("callId", "42")); -// return message; -// } -// -// @Test -// @DisplayName("no configured Firebase messaging means the notification is skipped, not attempted") -// void notificationIsSkippedWhenFirebaseIsNotConfigured() { -// assertThat(service.sendNotification(notification())).isNull(); -// } -// -// @Test -// @DisplayName("a configured Firebase publishes the notification to the requested topic") -// void notificationIsPublishedToTopic() throws Exception { -// FirebaseMessaging messaging = mock(FirebaseMessaging.class); -// ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); -// when(messaging.send(any(Message.class))).thenReturn("projects/amrit/messages/1"); -// -// try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { -// statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); -// -// assertThat(service.sendNotification(notification())).isEqualTo("projects/amrit/messages/1"); -// verify(messaging).send(any(Message.class)); -// } -// } -// -// @Test -// @DisplayName("a Firebase delivery failure is reported rather than propagated") -// void deliveryFailureIsReported() throws Exception { -// FirebaseMessaging messaging = mock(FirebaseMessaging.class); -// ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); -// when(messaging.send(any(Message.class))).thenThrow(mock(FirebaseMessagingException.class)); -// -// try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { -// statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); -// -// assertThat(service.sendNotification(notification())).isEqualTo("Error sending notification"); -// } -// } -// -// @Test -// @DisplayName("an existing device token is updated in place") -// void existingTokenIsUpdated() { -// UserTokenData existing = new UserTokenData(); -// existing.setUserId(7); -// existing.setToken("old-token"); -// when(userTokenRepo.findById(7)).thenReturn(Optional.of(existing)); -// -// UserToken userToken = new UserToken(); -// userToken.setUserId(7); -// userToken.setToken("new-token"); -// -// assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); -// -// ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); -// verify(userTokenRepo).save(captor.capture()); -// assertThat(captor.getValue()).isSameAs(existing); -// assertThat(captor.getValue().getToken()).isEqualTo("new-token"); -// assertThat(captor.getValue().getUpdatedAt()).isNotNull(); -// } -// -// @Test -// @DisplayName("a user with no stored token gets a new row") -// void newTokenIsCreated() { -// when(userTokenRepo.findById(8)).thenReturn(Optional.empty()); -// -// UserToken userToken = new UserToken(); -// userToken.setUserId(8); -// userToken.setToken("fresh-token"); -// -// assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); -// -// ArgumentCaptor captor = ArgumentCaptor.forClass(UserTokenData.class); -// verify(userTokenRepo).save(captor.capture()); -// assertThat(captor.getValue().getUserId()).isEqualTo(8); -// assertThat(captor.getValue().getToken()).isEqualTo("fresh-token"); -// } -// -// @Test -// @DisplayName("the caller's stored token is resolved from the JWT in their cookie") -// void storedTokenIsResolvedFromTheJwtCookie() throws Exception { -// MockHttpServletRequest request = new MockHttpServletRequest(); -// RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); -// when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); -// UserTokenData stored = new UserTokenData(); -// stored.setToken("device-token"); -// when(userTokenRepo.findById(11)).thenReturn(Optional.of(stored)); -// -// try (MockedStatic cookies = mockStatic(CookieUtil.class)) { -// cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); -// -// assertThat(service.getUserToken()).isEqualTo("device-token"); -// } -// } -// -// @Test -// @DisplayName("a caller with no stored token gets nothing back") -// void noStoredTokenYieldsNull() throws Exception { -// MockHttpServletRequest request = new MockHttpServletRequest(); -// RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); -// when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); -// when(userTokenRepo.findById(11)).thenReturn(Optional.empty()); -// -// try (MockedStatic cookies = mockStatic(CookieUtil.class)) { -// cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); -// -// assertThat(service.getUserToken()).isNull(); -// } -// } -//} +package com.iemr.common.service.firebaseNotification; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.Map; +import java.util.Optional; + +import com.iemr.common.data.userToken.UserFcmTokenData; +import com.iemr.common.repo.userToken.UserFcmTokenRepo; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.iemr.common.model.notification.NotificationMessage; +import com.iemr.common.model.notification.UserToken; +import com.iemr.common.utils.CookieUtil; +import com.iemr.common.utils.JwtUtil; + +/** Covers the Firebase push-notification service and its device-token bookkeeping. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class FirebaseNotificationServiceTest { + + @Mock + private UserFcmTokenRepo userTokenRepo; + @Mock + private CookieUtil cookieUtil; + @Mock + private JwtUtil jwtUtil; + + private FirebaseNotificationService service; + + @BeforeEach + void setUp() { + service = new FirebaseNotificationService(); + ReflectionTestUtils.setField(service, "cookieUtil", cookieUtil); + ReflectionTestUtils.setField(service, "jwtUtil", jwtUtil); + } + + @AfterEach + void clearRequestContext() { + RequestContextHolder.resetRequestAttributes(); + } + + private NotificationMessage notification() { + NotificationMessage message = new NotificationMessage(); + message.setTitle("Call assigned"); + message.setBody("Beneficiary BEN-42 is waiting"); + message.setToken("topic-agents"); + message.setData(Map.of("callId", "42")); + return message; + } + + @Test + @DisplayName("no configured Firebase messaging means the notification is skipped, not attempted") + void notificationIsSkippedWhenFirebaseIsNotConfigured() { + assertThat(service.sendNotification(notification())).isNull(); + } + + @Test + @DisplayName("a configured Firebase publishes the notification to the requested topic") + void notificationIsPublishedToTopic() throws Exception { + FirebaseMessaging messaging = mock(FirebaseMessaging.class); + ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); + when(messaging.send(any(Message.class))).thenReturn("projects/amrit/messages/1"); + + try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { + statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); + + assertThat(service.sendNotification(notification())).isEqualTo("projects/amrit/messages/1"); + verify(messaging).send(any(Message.class)); + } + } + + @Test + @DisplayName("a Firebase delivery failure is reported rather than propagated") + void deliveryFailureIsReported() throws Exception { + FirebaseMessaging messaging = mock(FirebaseMessaging.class); + ReflectionTestUtils.setField(service, "firebaseMessaging", messaging); + when(messaging.send(any(Message.class))).thenThrow(mock(FirebaseMessagingException.class)); + + try (MockedStatic statics = mockStatic(FirebaseMessaging.class)) { + statics.when(FirebaseMessaging::getInstance).thenReturn(messaging); + + assertThat(service.sendNotification(notification())).isEqualTo("Error sending notification"); + } + } + + @Test + @DisplayName("an existing device token is updated in place") + void existingTokenIsUpdated() { + UserFcmTokenData existing = new UserFcmTokenData(); + existing.setUserId(7); + existing.setToken("old-token"); + when(userTokenRepo.findById(7)).thenReturn(Optional.of(existing)); + + UserToken userToken = new UserToken(); + userToken.setUserId(7); + userToken.setToken("new-token"); + + assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserFcmTokenData.class); + verify(userTokenRepo).save(captor.capture()); + assertThat(captor.getValue()).isSameAs(existing); + assertThat(captor.getValue().getToken()).isEqualTo("new-token"); + assertThat(captor.getValue().getUpdatedAt()).isNotNull(); + } + + @Test + @DisplayName("a user with no stored token gets a new row") + void newTokenIsCreated() { + when(userTokenRepo.findById(8)).thenReturn(Optional.empty()); + + UserToken userToken = new UserToken(); + userToken.setUserId(8); + userToken.setToken("fresh-token"); + + assertThat(service.updateToken(userToken)).isEqualTo("Save Successfully"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserFcmTokenData.class); + verify(userTokenRepo).save(captor.capture()); + assertThat(captor.getValue().getUserId()).isEqualTo(8); + assertThat(captor.getValue().getToken()).isEqualTo("fresh-token"); + } + + @Test + @DisplayName("the caller's stored token is resolved from the JWT in their cookie") + void storedTokenIsResolvedFromTheJwtCookie() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); + UserFcmTokenData stored = new UserFcmTokenData(); + stored.setToken("device-token"); + when(userTokenRepo.findById(11)).thenReturn(Optional.of(stored)); + + try (MockedStatic cookies = mockStatic(CookieUtil.class)) { + cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); + + assertThat(service.getUserToken()).isEqualTo("device-token"); + } + } + + @Test + @DisplayName("a caller with no stored token gets nothing back") + void noStoredTokenYieldsNull() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + when(jwtUtil.getUserIdFromToken("jwt-value")).thenReturn("11"); + when(userTokenRepo.findById(11)).thenReturn(Optional.empty()); + + try (MockedStatic cookies = mockStatic(CookieUtil.class)) { + cookies.when(() -> CookieUtil.getJwtTokenFromCookie(request)).thenReturn("jwt-value"); + + assertThat(service.getUserToken()).isNull(); + } + } +}