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();