package org.openlmis.core.model.service;
import com.google.inject.Inject;
import org.joda.time.DateTime;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.exceptions.StockMovementIsNullException;
import org.openlmis.core.manager.MovementReasonManager;
import org.openlmis.core.manager.SharedPreferenceMgr;
import org.openlmis.core.model.Cmm;
import org.openlmis.core.model.Period;
import org.openlmis.core.model.StockCard;
import org.openlmis.core.model.StockMovementItem;
import org.openlmis.core.model.repository.CmmRepository;
import org.openlmis.core.model.repository.StockMovementRepository;
import org.openlmis.core.model.repository.StockRepository;
import org.openlmis.core.utils.DateUtil;
import org.roboguice.shaded.goole.common.base.Optional;
import org.roboguice.shaded.goole.common.base.Predicate;
import org.roboguice.shaded.goole.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import static org.openlmis.core.utils.DateUtil.today;
import static org.roboguice.shaded.goole.common.collect.FluentIterable.from;
public class StockService {
private final int LOW_STOCK_CALCULATE_MONTH_QUANTITY = 3;
@Inject
StockRepository stockRepository;
@Inject
CmmRepository cmmRepository;
@Inject
StockMovementRepository stockMovementRepository;
public StockService() {
}
protected Date queryFirstPeriodBegin(final StockCard stockCard) throws LMISException {
StockMovementItem stockMovementItem = stockMovementRepository.queryFirstStockMovementByStockCardId(stockCard.getId());
if (stockMovementItem == null) {
throw new StockMovementIsNullException(stockCard);
}
return stockMovementItem.getMovementPeriod().getBegin().toDate();
}
public void monthlyUpdateAvgMonthlyConsumption() {
DateTime recordLowStockAvgPeriod = SharedPreferenceMgr.getInstance().getLatestUpdateLowStockAvgTime();
Period period = Period.of(today());
if (recordLowStockAvgPeriod.isBefore(period.getBegin())) {
immediatelyUpdateAvgMonthlyConsumption();
}
}
public void immediatelyUpdateAvgMonthlyConsumption() {
try {
List<StockCard> stockCards = stockRepository.list();
for (StockCard stockCard : stockCards) {
stockCard.setAvgMonthlyConsumption(calculateAverageMonthlyConsumption(stockCard));
stockRepository.createOrUpdate(stockCard);
cmmRepository.save(Cmm.initWith(stockCard, Period.of(today())));
}
SharedPreferenceMgr.getInstance().updateLatestLowStockAvgTime();
} catch (LMISException e) {
e.reportToFabric();
}
}
protected float calculateAverageMonthlyConsumption(StockCard stockCard) {
Date firstPeriodBegin;
try {
firstPeriodBegin = queryFirstPeriodBegin(stockCard);
} catch (LMISException e) {
e.reportToFabric();
return -1;
}
List<Long> issuePerMonths = new ArrayList<>();
Period period = Period.of(today());
int periodQuantity = DateUtil.calculateDateMonthOffset(firstPeriodBegin, period.getBegin().toDate());
if (periodQuantity < LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
return -1;
}
for (int i = 0; i < periodQuantity; i++) {
period = period.previous();
Long totalIssuesEachMonth = calculateTotalIssuesPerPeriod(stockCard, period);
if (totalIssuesEachMonth == null) {
continue;
}
issuePerMonths.add(totalIssuesEachMonth);
if (issuePerMonths.size() == LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
break;
}
}
if (issuePerMonths.size() < LOW_STOCK_CALCULATE_MONTH_QUANTITY) {
return -1;
}
return getTotalIssues(issuePerMonths) * 1f / LOW_STOCK_CALCULATE_MONTH_QUANTITY;
}
private long getTotalIssues(List<Long> issuePerMonths) {
long total = 0;
for (Long totalIssues : issuePerMonths) {
total += totalIssues;
}
return total;
}
private Long calculateTotalIssuesPerPeriod(StockCard stockCard, Period period) {
long totalIssued = 0;
try {
List<StockMovementItem> stockMovementItems = stockMovementRepository.queryStockMovementsByMovementDate(stockCard.getId(), period.getBegin().toDate(), period.getEnd().toDate());
//the query above is actually wasteful, the movement items have already been queried and associated to the stock card
if (periodHasStockOut(stockCard, stockMovementItems, period)) {
return null;
}
for (StockMovementItem item : stockMovementItems) {
if (MovementReasonManager.MovementType.ISSUE == item.getMovementType()) {
totalIssued += item.getMovementQuantity();
}
}
return totalIssued;
} catch (LMISException e) {
e.reportToFabric();
return null;
}
}
private boolean periodHasStockOut(StockCard stockCard, List<StockMovementItem> stockMovementItems, final Period period) {
if (stockMovementItems.isEmpty()) {
return isStockOutStatusInherited(stockCard, period);
} else {
return hasStockOutInThisPeriod(stockMovementItems);
}
}
private boolean hasStockOutInThisPeriod(List<StockMovementItem> stockMovementItems) {
return from(stockMovementItems).anyMatch(new Predicate<StockMovementItem>() {
@Override
public boolean apply(StockMovementItem stockMovementItem) {
return stockMovementItem.getStockOnHand() == 0;
}
});
}
private boolean isStockOutStatusInherited(StockCard stockCard, final Period period) {
List<StockMovementItem> orderedMovements = Ordering.from(new Comparator<StockMovementItem>() {
@Override
public int compare(StockMovementItem lhs, StockMovementItem rhs) {
return lhs.getMovementDate().compareTo(rhs.getMovementDate());
}
}).sortedCopy(stockCard.getStockMovementItemsWrapper());
Optional<StockMovementItem> lastMovementBeforePeriod = from(orderedMovements)
.filter(new Predicate<StockMovementItem>() {
@Override
public boolean apply(StockMovementItem stockMovementItem) {
return new DateTime(stockMovementItem.getMovementDate()).isBefore(period.getBegin());
}
})
.last();
return lastMovementBeforePeriod.isPresent() && lastMovementBeforePeriod.get().getStockOnHand() == 0;
}
}