/*
* Electronic Logistics Management Information System (eLMIS) is a supply chain management system for health commodities in a developing country setting.
*
* Copyright (C) 2015 John Snow, Inc (JSI). This program was produced for the U.S. Agency for International Development (USAID). It was prepared under the USAID | DELIVER PROJECT, Task Order 4.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openlmis.ivdform.service;
import lombok.NoArgsConstructor;
import org.joda.time.DateTime;
import org.openlmis.core.domain.*;
import org.openlmis.core.exception.DataException;
import org.openlmis.core.repository.ProcessingPeriodRepository;
import org.openlmis.core.repository.helper.CommaSeparator;
import org.openlmis.core.service.*;
import org.openlmis.demographics.service.AnnualFacilityDemographicEstimateService;
import org.openlmis.ivdform.domain.VaccineDisease;
import org.openlmis.ivdform.domain.VaccineProductDose;
import org.openlmis.ivdform.domain.Vitamin;
import org.openlmis.ivdform.domain.VitaminSupplementationAgeGroup;
import org.openlmis.ivdform.domain.reports.*;
import org.openlmis.ivdform.dto.FacilityIvdSummary;
import org.openlmis.ivdform.dto.ReportStatusDTO;
import org.openlmis.ivdform.dto.RoutineReportDTO;
import org.openlmis.ivdform.dto.StockStatusSummary;
import org.openlmis.ivdform.exceptions.FormAlreadySubmittedException;
import org.openlmis.ivdform.exceptions.OutOfOrderFormSubmissionException;
import org.openlmis.ivdform.exceptions.ProgramNotSupportedException;
import org.openlmis.ivdform.repository.VitaminRepository;
import org.openlmis.ivdform.repository.VitaminSupplementationAgeGroupRepository;
import org.openlmis.ivdform.repository.reports.ColdChainLineItemRepository;
import org.openlmis.ivdform.repository.reports.IvdFormRepository;
import org.openlmis.ivdform.repository.reports.LogisticsLineItemRepository;
import org.openlmis.ivdform.repository.reports.StatusChangeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.apache.commons.collections4.CollectionUtils.emptyIfNull;
@Service
@NoArgsConstructor
public class IvdFormService {
private static final String STOCK_STATUS_FOUND = "STOCK_STATUS_FOUND";
private static final String STOCK_STATUS_NOT_FOUND = "STOCK_STATUS_NOT_FOUND";
@Autowired
IvdFormRepository repository;
@Autowired
ProgramProductService programProductService;
@Autowired
DiseaseService diseaseService;
@Autowired
ProcessingPeriodRepository periodService;
@Autowired
ProductDoseService productDoseService;
@Autowired
ColdChainLineItemRepository coldChainRepository;
@Autowired
VitaminRepository vitaminRepository;
@Autowired
VitaminSupplementationAgeGroupRepository ageGroupRepository;
@Autowired
ProgramService programService;
@Autowired
TabVisibilityService tabVisibilityService;
@Autowired
StatusChangeRepository reportStatusChangeRepository;
@Autowired
AnnualFacilityDemographicEstimateService demographicsService;
@Autowired
LogisticsLineItemRepository logisticsLineItemRepository;
@Autowired
MessageService messageService;
@Autowired
FacilityService facilityService;
@Autowired
CommaSeparator commaSeparator;
@Autowired
ConfigurationSettingService configurationSettingService;
@Autowired
GeographicZoneService geographicZoneService;
@Autowired
IVDNotificationService notificationService;
@Autowired
ConfigurationSettingService configService;
private static final String DATE_FORMAT = "yyyy-MM-dd";
@Transactional
public VaccineReport initialize(Long facilityId, Long programId, Long periodId, Long userId) {
VaccineReport report = repository.getByProgramPeriod(facilityId, programId, periodId);
if (report != null) {
return report;
}
validateInitiate(facilityId, programId, periodId);
report = createNewVaccineReport(facilityId, programId, periodId);
repository.insert(report, userId);
ReportStatusChange change = new ReportStatusChange(report, ReportStatus.DRAFT, userId);
reportStatusChangeRepository.insert(change);
return report;
}
private void validateInitiate(Long facilityId, Long programId, Long periodId) {
VaccineReport draftReport = repository.getDraftReport(facilityId, programId);
if (draftReport != null) {
throw new DataException("error.facility.has.pending.draft");
}
List<ReportStatusDTO> openPeriods = this.getPeriodsFor(facilityId, programId, new Date());
if (openPeriods == null || openPeriods.size() == 0 || !periodId.equals(openPeriods.get(0).getPeriodId())) {
ProcessingPeriod period = periodService.getById(periodId);
throw new OutOfOrderFormSubmissionException("error.ivd.form.out.of.order.ivd.not.supported"
, (openPeriods.size() == 0) ? "No past period " : openPeriods.get(0).getPeriodName()
, period.getName());
}
}
@Transactional
public void save(VaccineReport report, Long userId) {
VaccineReport reportFromDb = getVaccineReportFromDbForUpdate(report);
repository.update(reportFromDb, report, userId);
}
@Transactional
public void submit(VaccineReport report, Long userId) {
report.setStatus(ReportStatus.SUBMITTED);
VaccineReport reportFromDb = getVaccineReportFromDbForUpdate(report);
repository.update(reportFromDb, report, userId);
repository.changeStatus(reportFromDb, ReportStatus.SUBMITTED, userId);
notificationService.sendIVDStatusChangeNotification(reportFromDb, userId);
}
private VaccineReport getVaccineReportFromDbForUpdate(VaccineReport report) {
Long id = this.getReportIdForFacilityAndPeriod(report.getFacilityId(), report.getPeriodId());
VaccineReport reportFromDb = repository.getByIdWithFullDetails(id);
if (ReportStatus.APPROVED.equals(reportFromDb.getStatus()) || ReportStatus.SUBMITTED.equals(reportFromDb.getStatus())) {
throw new FormAlreadySubmittedException("ivd.form.exception.form.already.submitted");
}
return reportFromDb;
}
public VaccineReport createNewVaccineReport(Long facilityId, Long programId, Long periodId) {
VaccineReport report;
List<ProgramProduct> programProducts = programProductService.getActiveByProgram(programId);
List<VaccineDisease> diseases = diseaseService.getAll();
List<VaccineProductDose> dosesToCover = productDoseService.getForProgram(programId);
List<ColdChainLineItem> coldChainLineItems = coldChainRepository.getNewEquipmentLineItems(programId, facilityId);
List<Vitamin> vitamins = vitaminRepository.getAll();
List<VitaminSupplementationAgeGroup> ageGroups = ageGroupRepository.getAll();
VaccineReport previousReport = this.getPreviousReport(facilityId, programId, periodId);
report = new VaccineReport();
report.setFacilityId(facilityId);
report.setProgramId(programId);
report.setPeriodId(periodId);
report.setStatus(ReportStatus.DRAFT);
Boolean defaultFieldsToZero = (configService != null) ? configService.getBoolValue(ConfigurationSettingKey.DEFAULT_ZERO) : false;
// 1. copy the products list and initiate the logistics tab.
report.initializeLogisticsLineItems(programProducts, previousReport, defaultFieldsToZero);
// 2. copy the product + dosage settings and initiate the coverage tab.
report.initializeCoverageLineItems(dosesToCover, defaultFieldsToZero);
// 3. copy the disease list and initiate the disease tab.
report.initializeDiseaseLineItems(diseases, defaultFieldsToZero);
// 4. initialize the cold chain line items.
report.initializeColdChainLineItems(coldChainLineItems, defaultFieldsToZero);
report.initializeVitaminLineItems(vitamins, ageGroups, defaultFieldsToZero);
return report;
}
private VaccineReport getPreviousReport(Long facilityId, Long programId, Long periodId) {
Long reportId = repository.findLastReportBeforePeriod(facilityId, programId, periodId);
return repository.getByIdWithFullDetails(reportId);
}
public List<ReportStatusDTO> getReportedPeriodsFor(Long facilityId, Long programId) {
return repository.getReportedPeriodsForFacility(facilityId, programId);
}
public List<ReportStatusDTO> getPeriodsFor(Long facilityId, Long programId, Date endDate) {
Date startDate;
try {
startDate = programService.getProgramStartDate(facilityId, programId);
} catch (Exception exp) {
throw new ProgramNotSupportedException("ivd.form.program.not.supported");
}
// find out which schedule this facility is in?
Long scheduleId = repository.getScheduleFor(facilityId, programId);
VaccineReport lastRequest = repository.getLastReport(facilityId, programId);
if (lastRequest != null) {
lastRequest.setPeriod(periodService.getById(lastRequest.getPeriodId()));
startDate = lastRequest.getPeriod().getStartDate();
}
List<ReportStatusDTO> results = new ArrayList<>();
List<ProcessingPeriod> periods = periodService.getAllPeriodsForDateRange(scheduleId, startDate, endDate);
if (lastRequest != null) {
List<VaccineReport> rejectedReports = repository.getRejectedReports(facilityId, programId);
for (VaccineReport rReport : rejectedReports) {
results.add(createReportStatusDto(facilityId, programId, rReport));
}
if (lastRequest.getStatus().equals(ReportStatus.DRAFT)) {
results.add(createReportStatusDto(facilityId, programId, lastRequest));
}
}
for (ProcessingPeriod period : emptyIfNull(periods)) {
if (lastRequest == null || !lastRequest.getPeriodId().equals(period.getId())) {
ReportStatusDTO reportStatusDTO = new ReportStatusDTO();
reportStatusDTO.setPeriodName(period.getName());
reportStatusDTO.setPeriodId(period.getId());
reportStatusDTO.setProgramId(programId);
reportStatusDTO.setFacilityId(facilityId);
results.add(reportStatusDTO);
}
}
return results;
}
private static ReportStatusDTO createReportStatusDto(Long facilityId, Long programId, VaccineReport report) {
ReportStatusDTO reportStatusDTO = new ReportStatusDTO();
reportStatusDTO.setPeriodName(report.getPeriod().getName());
reportStatusDTO.setPeriodId(report.getPeriod().getId());
reportStatusDTO.setStatus(report.getStatus().toString());
reportStatusDTO.setProgramId(programId);
reportStatusDTO.setFacilityId(facilityId);
reportStatusDTO.setId(report.getId());
return reportStatusDTO;
}
public VaccineReport getById(Long id) {
VaccineReport report = repository.getByIdWithFullDetails(id);
report.setTabVisibilitySettings(tabVisibilityService.getVisibilityForProgram(report.getProgramId()));
DateTime periodStartDate = new DateTime(report.getPeriod().getStartDate());
report.setFacilityDemographicEstimates(demographicsService.getEstimateValuesForFacility(report.getFacilityId(), report.getProgramId(), periodStartDate.getYear()));
return report;
}
public Long getReportIdForFacilityAndPeriod(Long facilityId, Long periodId) {
return repository.getReportIdForFacilityAndPeriod(facilityId, periodId);
}
public List<RoutineReportDTO> getApprovalPendingForms(Long userId, Long programId) {
String facilityIds = commaSeparator.commaSeparateIds(facilityService.getUserSupervisedFacilities(userId, programId, RightName.APPROVE_IVD));
return repository.getApprovalPendingForms(facilityIds);
}
public void approve(VaccineReport report, Long userId) {
Long reportSubmitterUserId = getReportSubmitterUserId(report.getId());
repository.changeStatus(report, ReportStatus.APPROVED, userId);
notificationService.sendIVDStatusChangeNotification(report, reportSubmitterUserId);
}
public void reject(VaccineReport report, Long userId) {
Long reportSubmitterUserId = getReportSubmitterUserId(report.getId());
repository.changeStatus(report, ReportStatus.REJECTED, userId);
notificationService.sendIVDStatusChangeNotification(report, reportSubmitterUserId);
}
public FacilityIvdSummary getStockStatusForAllProductsInFacility(String facilityCode, String programCode, Long periodId) {
FacilityIvdSummary summary = new FacilityIvdSummary(facilityCode, programCode, periodId);
List<LogisticsLineItem> list = logisticsLineItemRepository.getApprovedLineItemListFor(programCode, facilityCode, periodId);
if (!emptyIfNull(list).isEmpty()) {
Facility facility = facilityService.getFacilityByCode(facilityCode);
Long reportId = this.getReportIdForFacilityAndPeriod(facility.getId(), periodId);
VaccineReport report = repository.getByIdWithFullDetails(reportId);
summary.setEquipments(report.getColdChainLineItems());
summary.setStatus(STOCK_STATUS_FOUND);
summary.setProducts(new ArrayList<StockStatusSummary>());
for (LogisticsLineItem item : list) {
summary.getProducts().add(populateStockStatusSummary(facilityCode, item.getProductCode(), programCode, periodId, item));
}
} else {
summary.setStatus(STOCK_STATUS_NOT_FOUND);
}
return summary;
}
public StockStatusSummary getStockStatusForProductInFacility(String facilityCode, String productCode, String programCode, Long periodId) {
LogisticsLineItem periodicLLI = logisticsLineItemRepository.getApprovedLineItemFor(programCode, productCode, facilityCode, periodId);
return populateStockStatusSummary(facilityCode, productCode, programCode, periodId, periodicLLI);
}
private StockStatusSummary populateStockStatusSummary(String facilityCode, String productCode, String programCode, Long periodId, LogisticsLineItem periodicLLI) {
StockStatusSummary response = new StockStatusSummary();
response.setPeriodId(periodId);
response.setProductCode(productCode);
if (periodicLLI != null) {
response.setDaysOutOfStock(periodicLLI.getDaysStockedOut());
response.setStockStatus(periodicLLI.getClosingBalance());
response.setProductId(periodicLLI.getProductId());
response.setStatus(STOCK_STATUS_FOUND);
List<LogisticsLineItem> previousThreeSubmissions = logisticsLineItemRepository.getUpTo3PreviousPeriodLineItemsFor(programCode, productCode, facilityCode, periodId);
response.setAmc(calculateAMC(previousThreeSubmissions));
} else {
response.setStatus(STOCK_STATUS_NOT_FOUND);
}
return response;
}
private Long getReportSubmitterUserId(Long vaccineReportId) {
ReportStatusChange change = reportStatusChangeRepository.getOperation(vaccineReportId, ReportStatus.SUBMITTED);
return (change != null) ? change.getCreatedBy() : null;
}
private static Long calculateAMC(List<LogisticsLineItem> previousThree) {
Long sum = 0L;
int count = 0;
for (LogisticsLineItem lineItem : emptyIfNull(previousThree)) {
if (lineItem.getQuantityIssued() != null) {
sum += lineItem.getQuantityIssued();
count++;
}
}
return (count == 0) ? 0L : sum / count;
}
@Transactional
public void submitFromOtherApplications(VaccineReport report, Long userId) {
report.validateBasicHeaders();
Long id = this.getReportIdForFacilityAndPeriod(report.getFacilityId(), report.getPeriodId());
if (id == null) {
this.initialize(report.getFacilityId(), report.getProgramId(), report.getPeriodId(), userId);
}
report.setSubmissionDate(new Date());
this.submit(report, userId);
}
}