/*
* This program is part of the OpenLMIS logistics management information system platform software.
* Copyright © 2013 VillageReach
*
* 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. For additional information contact info@OpenLMIS.org.
*/
package org.openlmis.restapi.service;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import lombok.NoArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.Transformer;
import org.apache.log4j.Logger;
import org.openlmis.core.domain.*;
import org.openlmis.core.exception.DataException;
import org.openlmis.core.service.FacilityApprovedProductService;
import org.openlmis.core.service.FacilityService;
import org.openlmis.core.service.ProcessingPeriodService;
import org.openlmis.core.service.ProgramService;
import org.openlmis.order.service.OrderService;
import org.openlmis.restapi.domain.ReplenishmentDTO;
import org.openlmis.restapi.domain.Report;
import org.openlmis.rnr.domain.*;
import org.openlmis.rnr.search.criteria.RequisitionSearchCriteria;
import org.openlmis.rnr.service.RequisitionService;
import org.openlmis.rnr.service.RnrTemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static org.apache.commons.collections.CollectionUtils.find;
import static org.openlmis.restapi.domain.ReplenishmentDTO.prepareForREST;
/**
* This service exposes methods for creating, approving a requisition.
*/
@Service
@NoArgsConstructor
public class RestRequisitionService {
public static final boolean EMERGENCY = false;
private static final String SOURCE_APPLICATION_ELMIS_FE = "ELMIS_FE";
private static final String SOURCE_APPLICATION_OTHER = "OTHER";
private static final Logger logger = Logger.getLogger(RestRequisitionService.class);
@Autowired
private RequisitionService requisitionService;
@Autowired
private OrderService orderService;
@Autowired
private FacilityService facilityService;
@Autowired
private ProgramService programService;
@Autowired
private RnrTemplateService rnrTemplateService;
@Autowired
private RestRequisitionCalculator restRequisitionCalculator;
@Autowired
private ProcessingPeriodService processingPeriodService;
@Autowired
private FacilityApprovedProductService facilityApprovedProductService;
private List<FacilityTypeApprovedProduct> fullSupplyFacilityTypeApprovedProducts;
private List<FacilityTypeApprovedProduct> nonFullSupplyFacilityApprovedProducts;
@Transactional
public Rnr submitReport(Report report, Long userId) {
report.validate();
Facility reportingFacility = facilityService.getOperativeFacilityByCode(report.getAgentCode());
Program reportingProgram = programService.getValidatedProgramByCode(report.getProgramCode());
restRequisitionCalculator.validatePeriod(reportingFacility, reportingProgram);
Rnr rnr = requisitionService.initiate(reportingFacility, reportingProgram, userId, EMERGENCY, null, SOURCE_APPLICATION_OTHER);
restRequisitionCalculator.validateProducts(report.getProducts(), rnr);
markSkippedLineItems(rnr, report);
if (reportingFacility.getVirtualFacility())
restRequisitionCalculator.setDefaultValues(rnr);
copyRegimens(rnr, report);
requisitionService.save(rnr);
updateClientFields(report, rnr);
insertPatientQuantificationLineItems(report, rnr);
insertRnrSignatures(report, rnr, userId);
rnr = requisitionService.submit(rnr);
return requisitionService.authorize(rnr);
}
private void updateClientFields(Report report, Rnr rnr) {
Date clientSubmittedTime = report.getClientSubmittedTime();
rnr.setClientSubmittedTime(clientSubmittedTime);
String clientSubmittedNotes = report.getClientSubmittedNotes();
rnr.setClientSubmittedNotes(clientSubmittedNotes);
requisitionService.updateClientFields(rnr);
}
@Transactional
public Rnr submitSdpReport(Report report, Long userId) {
report.validate();
Facility reportingFacility = facilityService.getOperativeFacilityByCode(report.getAgentCode());
Program reportingProgram = programService.getValidatedProgramByCode(report.getProgramCode());
ProcessingPeriod period = processingPeriodService.getById(report.getPeriodId());
Rnr rnr;
List<Rnr> rnrs = null;
RequisitionSearchCriteria searchCriteria = new RequisitionSearchCriteria();
searchCriteria.setProgramId(reportingProgram.getId());
searchCriteria.setFacilityId(reportingFacility.getId());
searchCriteria.setWithoutLineItems(true);
searchCriteria.setUserId(userId);
if(report.getPeriodId() != null) {
//check if the requisition has already been initiated / submitted / authorized.
restRequisitionCalculator.validateCustomPeriod(reportingFacility, reportingProgram, period, userId);
rnrs = requisitionService.getRequisitionsFor(searchCriteria, asList(period));
}
if(rnrs != null && rnrs.size() > 0){
rnr = requisitionService.getFullRequisitionById( rnrs.get(0).getId() );
}else{
rnr = requisitionService.initiate(reportingFacility, reportingProgram, userId, report.getEmergency(), period, SOURCE_APPLICATION_ELMIS_FE);
}
List<RnrLineItem> fullSupplyProducts = new ArrayList<>();
List<RnrLineItem> nonFullSupplyProducts = new ArrayList<>();
fullSupplyFacilityTypeApprovedProducts = facilityApprovedProductService.getFullSupplyFacilityApprovedProductByFacilityAndProgram( reportingFacility.getId(), reportingProgram.getId() );
nonFullSupplyFacilityApprovedProducts = facilityApprovedProductService.getNonFullSupplyFacilityApprovedProductByFacilityAndProgram( reportingFacility.getId(), reportingProgram.getId() );
Collection<String> fullSupplyProductCodes = ( Collection<String> ) CollectionUtils.collect(fullSupplyFacilityTypeApprovedProducts, input -> ((FacilityTypeApprovedProduct) input).getProgramProduct().getProduct().getCode());
Collection<String> nonFullSupplyProductCodes = ( Collection<String> ) CollectionUtils.collect(nonFullSupplyFacilityApprovedProducts, input -> ((FacilityTypeApprovedProduct) input).getProgramProduct().getProduct().getCode());
fullSupplyProducts = report.getProducts().stream()
.filter(p-> fullSupplyProductCodes.contains(p.getProductCode()) )
.collect(Collectors.toList());
nonFullSupplyProducts = report.getProducts().stream()
.filter(p-> nonFullSupplyProductCodes.contains(p.getProductCode()) )
.collect(Collectors.toList());
for(RnrLineItem li : nonFullSupplyProducts){
setNonFullSupplyCreatorFields(li);
}
report.setProducts(fullSupplyProducts);
report.setNonFullSupplyProducts(nonFullSupplyProducts);
restRequisitionCalculator.validateProducts(report.getProducts(), rnr);
markSkippedLineItems(rnr, report);
copyRegimens(rnr, report);
// if you have come this far, then do it, it is your day. make the submission.
// i cannot believe we do all of these three at the same time.
// but then this is what zambia specifically asked.
requisitionService.save(rnr);
rnr = requisitionService.submit(rnr);
return requisitionService.authorize(rnr);
}
private void setNonFullSupplyCreatorFields(final RnrLineItem lineItem) {
FacilityTypeApprovedProduct facilityTypeApprovedProduct = (FacilityTypeApprovedProduct) find(nonFullSupplyFacilityApprovedProducts, new Predicate() {
@Override
public boolean evaluate(Object product) {
return ((FacilityTypeApprovedProduct) product).getProgramProduct().getProduct().getCode().equals(lineItem.getProductCode());
}
});
if(facilityTypeApprovedProduct == null){
return;
}
lineItem.populateFromProduct(facilityTypeApprovedProduct.getProgramProduct());
lineItem.setMaxMonthsOfStock(facilityTypeApprovedProduct.getMaxMonthsOfStock());
}
private void copyRegimens(Rnr rnr, Report report) {
if (report.getRegimens() != null) {
for (RegimenLineItem regimenLineItem : report.getRegimens()) {
RegimenLineItem correspondingRegimenLineItem = rnr.findCorrespondingRegimenLineItem(regimenLineItem);
if (correspondingRegimenLineItem == null)
throw new DataException("error.invalid.regimen");
correspondingRegimenLineItem.populate(regimenLineItem);
}
}
}
private void insertPatientQuantificationLineItems(Report report, Rnr rnr) {
if (report.getPatientQuantifications() != null) {
rnr.setPatientQuantifications(report.getPatientQuantifications());
requisitionService.insertPatientQuantificationLineItems(rnr);
}
}
private void insertRnrSignatures(Report report, Rnr rnr, final Long userId) {
if (report.getRnrSignatures() != null) {
List<Signature> rnrSignatures = new ArrayList(CollectionUtils.collect(report.getRnrSignatures(), new Transformer() {
@Override
public Object transform(Object input) {
((Signature)input).setCreatedBy(userId);
((Signature)input).setModifiedBy(userId);
return input;
}
}));
rnr.setRnrSignatures(rnrSignatures);
requisitionService.insertRnrSignatures(rnr);
}
}
@Transactional
public void approve(Report report, Long requisitionId, Long userId) {
Rnr requisition = report.getRequisition(requisitionId, userId);
Rnr savedRequisition = requisitionService.getFullRequisitionById(requisition.getId());
if (!savedRequisition.getFacility().getVirtualFacility()) {
throw new DataException("error.approval.not.allowed");
}
if (savedRequisition.getNonSkippedLineItems().size() != report.getProducts().size()) {
throw new DataException("error.number.of.line.items.mismatch");
}
restRequisitionCalculator.validateProducts(report.getProducts(), savedRequisition);
requisitionService.save(requisition);
requisitionService.approve(requisition, report.getApproverName());
}
public ReplenishmentDTO getReplenishmentDetails(Long id) {
Rnr requisition = requisitionService.getFullRequisitionById(id);
return prepareForREST(requisition, orderService.getOrder(id));
}
private void markSkippedLineItems(Rnr rnr, Report report) {
ProgramRnrTemplate rnrTemplate = rnrTemplateService.fetchProgramTemplateForRequisition(rnr.getProgram().getId());
List<RnrLineItem> savedLineItems = rnr.getFullSupplyLineItems();
List<RnrLineItem> reportedProducts = report.getProducts();
for (final RnrLineItem savedLineItem : savedLineItems) {
RnrLineItem reportedLineItem = (RnrLineItem) find(reportedProducts, new Predicate() {
@Override
public boolean evaluate(Object product) {
return ((RnrLineItem) product).getProductCode().equals(savedLineItem.getProductCode());
}
});
copyInto(savedLineItem, reportedLineItem, rnrTemplate);
}
savedLineItems = rnr.getNonFullSupplyLineItems();
reportedProducts = report.getNonFullSupplyProducts();
if(reportedProducts != null) {
for (final RnrLineItem reportedLineItem : reportedProducts) {
RnrLineItem savedLineItem = (RnrLineItem) find(savedLineItems, new Predicate() {
@Override
public boolean evaluate(Object product) {
return ((RnrLineItem) product).getProductCode().equals(reportedLineItem.getProductCode());
}
});
if (savedLineItem == null && reportedLineItem != null) {
rnr.getNonFullSupplyLineItems().add(reportedLineItem);
} else {
copyInto(savedLineItem, reportedLineItem, rnrTemplate);
}
}
}
}
private void copyInto(RnrLineItem savedLineItem, RnrLineItem reportedLineItem, ProgramRnrTemplate rnrTemplate) {
if (reportedLineItem == null) {
savedLineItem.setSkipped(true);
return;
}
for (Column column : rnrTemplate.getColumns()) {
if (!column.getVisible() || !rnrTemplate.columnsUserInput(column.getName()))
continue;
try {
Field field = RnrLineItem.class.getDeclaredField(column.getName());
field.setAccessible(true);
Object reportedValue = field.get(reportedLineItem);
Object toBeSavedValue = (reportedValue != null ? reportedValue : field.get(savedLineItem));
field.set(savedLineItem, toBeSavedValue);
} catch (Exception e) {
logger.error("could not copy field: " + column.getName());
}
}
}
public List<Report> getRequisitionsByFacility(String facilityCode) {
Facility facility = facilityService.getFacilityByCode(facilityCode);
if (facility == null) {
throw new DataException("error.facility.unknown");
}
List<Rnr> rnrList = requisitionService.getRequisitionsByFacility(facility);
return FluentIterable.from(rnrList).transform(new Function<Rnr, Report>() {
@Override
public Report apply(Rnr input) {
return Report.prepareForREST(input);
}
}).toList();
}
}