RFQAllocationCalculation.java

package com.tradecloud.domain.model.requestforquote;

import com.tradecloud.domain.base.utils.MathUtils;
import org.apache.commons.collections.map.HashedMap;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class RFQAllocationCalculation {

    //    private final BigDecimal totalSupplied;
    private final RFQStyle rfqStyle;
    private final List<RFQProduct> awardedProducts = new ArrayList<>();
    private final Map<Long, BigDecimal> awardPerSupplierId = new HashedMap();
    private final List<RFQProduct> suppliedProducts;
    private BigDecimal allocationMaxValue;
    private BigDecimal totalAwarded = BigDecimal.ZERO;
    private final List<BigDecimal> percentRules;

    public RFQAllocationCalculation(RFQStyle rfqStyle, List<RFQProduct> suppliedProducts, List<BigDecimal> percentRule) {
        this.rfqStyle = rfqStyle;
        this.suppliedProducts = suppliedProducts;
        suppliedProducts.sort(Comparator.comparing(RFQProduct::getPrice).thenComparing(RFQProduct::getPosition));
        this.percentRules = percentRule;
    }

    private void setPercentRule(BigDecimal percentRule) {
        if(isStyleQuantity()) {
            allocationMaxValue = rfqStyle.getQuantity().multiply(MathUtils.toPercent(percentRule));
            allocationMaxValue = BigDecimal.valueOf(Math.min(allocationMaxValue.doubleValue(), rfqStyle.getQuantity()
                    .subtract(totalAwarded).doubleValue()));
        }
    }

    private void generateAwards() {
        for (RFQProduct suppliedProduct : suppliedProducts) {
            if(!isStyleQuantity()){
                if(suppliedProduct.getQuantitySupplied()!=null && suppliedProduct.getQuantitySupplied().intValue()>0) {
                    suppliedProduct.setAwardedQuantity(suppliedProduct.getQuantitySupplied());
                    awardedProducts.add(suppliedProduct);
                }
                continue;
            }
            if (allocationMaxValue.doubleValue() > 0
                    && isSupplierNotFullyAwardedBaseOnPercentageRule(suppliedProduct)
                    && productQuantityNotFullyAwarded(suppliedProduct)) {
                BigDecimal reamingQuantitySupplied = suppliedProduct.getReamingQuantitySupplied();
                if (reamingQuantitySupplied.doubleValue() <= allocationMaxValue.doubleValue()) {
                    suppliedProduct.setAwardedQuantity(suppliedProduct.getAwardedQuantity().add(reamingQuantitySupplied));
                    allocationMaxValue = allocationMaxValue.subtract(reamingQuantitySupplied);
                    totalAwarded = totalAwarded.add(reamingQuantitySupplied);
                } else {
                    suppliedProduct.setAwardedQuantity(suppliedProduct.getAwardedQuantity().add(allocationMaxValue));
                    totalAwarded = totalAwarded.add(allocationMaxValue);
                    allocationMaxValue = BigDecimal.ZERO;
                }
                if (!awardPerSupplierId.containsKey(suppliedProduct.getSupplierStyle().getRfqOrganisationalUnitSupplier().getId())) {
                    awardPerSupplierId.computeIfAbsent(suppliedProduct.getSupplierStyle().getRfqOrganisationalUnitSupplier().getId(),
                            rfqOrganisationalUnitSupplier -> suppliedProduct.getAwardedQuantity());
                } else {
                    awardPerSupplierId.computeIfPresent(suppliedProduct.getSupplierStyle().getRfqOrganisationalUnitSupplier().getId(),
                            (rfqOrganisationalUnitSupplier, awarded) -> awarded.add(suppliedProduct.getAwardedQuantity()));
                }

                addAwardedProduct(suppliedProduct);
            }
        }
    }

    private boolean isStyleQuantity() {
        return rfqStyle.getQuantity() != null && rfqStyle.getQuantity().doubleValue() > 0;
    }

    private boolean productQuantityNotFullyAwarded(RFQProduct suppliedProduct) {
        return suppliedProduct.getReamingQuantitySupplied().doubleValue() > 0;
    }

    private double getNotRewardedQuantity(RFQProduct suppliedProduct) {
        return suppliedProduct.getQuantitySupplied().doubleValue() - suppliedProduct.getAwardedQuantity().doubleValue();
    }

    private boolean isSupplierNotFullyAwardedBaseOnPercentageRule(RFQProduct suppliedProduct) {
        BigDecimal award = awardPerSupplierId.get(suppliedProduct.getSupplierStyle().getRfqOrganisationalUnitSupplier().getId());
        return award == null || award.doubleValue() < allocationMaxValue.doubleValue();
    }

    public List<RFQProduct> getAwardedProductsBasedOnRule() {
        if(!isStyleQuantity()){
            generateAwards();
            return awardedProducts;
        }
        for (BigDecimal rule : percentRules) {
            setPercentRule(rule);
            generateAwards();
        }
        BigDecimal styleNotAwarded = getStyleNotAwarded();
        if (styleNotAwarded.doubleValue() > 0) {
            assignStyleNotAwarded(styleNotAwarded);
        }
        return awardedProducts;
    }

    public BigDecimal getStyleNotAwarded() {
        return rfqStyle.getQuantity().subtract(totalAwarded);
    }

    private void assignStyleNotAwarded(BigDecimal styleNotAwarded) {
        for (RFQProduct suppliedProduct : suppliedProducts) {
            double notRewardedQuanity = getNotRewardedQuantity(suppliedProduct);
            if (notRewardedQuanity > 0 && notRewardedQuanity >= styleNotAwarded.doubleValue()) {
                suppliedProduct.setAwardedQuantity(suppliedProduct.getAwardedQuantity().add(styleNotAwarded));
                totalAwarded = totalAwarded.add(styleNotAwarded);
                addAwardedProduct(suppliedProduct);
                styleNotAwarded = BigDecimal.ZERO;
            } else if (notRewardedQuanity > 0 && notRewardedQuanity < styleNotAwarded.doubleValue()) {
                BigDecimal notRewarded = BigDecimal.valueOf(notRewardedQuanity);
                suppliedProduct.setAwardedQuantity(suppliedProduct.getAwardedQuantity().add(notRewarded));
                styleNotAwarded = styleNotAwarded.subtract(notRewarded);
                totalAwarded = totalAwarded.add(notRewarded);
                addAwardedProduct(suppliedProduct);
            }
        }
    }

    private void addAwardedProduct(RFQProduct rfqProduct) {

        if (totalAwarded.doubleValue() > rfqStyle.getQuantity().doubleValue()) {
            throw new IllegalStateException(String.format("Awarding of %s cannot exceed Style Quantity of %s"
                    , totalAwarded, rfqStyle.getQuantity()));
        }
        if (!awardedProducts.contains(rfqProduct)) {
            awardedProducts.add(rfqProduct);
        }
    }
}