Package org.broadleafcommerce.core.pricing.service.workflow

Source Code of org.broadleafcommerce.core.pricing.service.workflow.FulfillmentItemPricingActivity

/*
* #%L
* BroadleafCommerce Framework
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.core.pricing.service.workflow;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.currency.domain.BroadleafCurrency;
import org.broadleafcommerce.common.money.Money;
import org.broadleafcommerce.core.order.domain.FulfillmentGroup;
import org.broadleafcommerce.core.order.domain.FulfillmentGroupItem;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.domain.OrderItem;
import org.broadleafcommerce.core.workflow.BaseActivity;
import org.broadleafcommerce.core.workflow.ProcessContext;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Currency;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Called during the pricing workflow to set each item's merchandise total and taxable total
*
* @author Brian Polster
*/
public class FulfillmentItemPricingActivity extends BaseActivity<ProcessContext<Order>> {
   
    private static final Log LOG = LogFactory.getLog(FulfillmentItemPricingActivity.class);

    protected BroadleafCurrency getCurrency(FulfillmentGroup fg) {
        return fg.getOrder().getCurrency();
    }
   
    /**
     * Returns the order adjustment value or zero if none exists
     * @param order
     * @return
     */
    protected Money getOrderSavingsToDistribute(Order order) {
        if (order.getOrderAdjustmentsValue() == null) {
            return new Money(order.getCurrency());
        } else {
            Money adjustmentValue = order.getOrderAdjustmentsValue();
           
            Money orderSubTotal = order.getSubTotal();
            if (orderSubTotal == null || orderSubTotal.lessThan(adjustmentValue)) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Subtotal is null or less than orderSavings in DistributeOrderSavingsActivity.java.  " +
                            "No distribution is taking place.");
                }
                return new Money(order.getCurrency());
            }
            return adjustmentValue;
        }
    }
   
    @Override
    public ProcessContext<Order> execute(ProcessContext<Order> context) throws Exception {
        Order order = context.getSeedData();
        Map<OrderItem,List<FulfillmentGroupItem>> partialOrderItemMap = new HashMap<OrderItem,List<FulfillmentGroupItem>>();

        // Calculate the fulfillmentGroupItem total
        populateItemTotalAmount(order, partialOrderItemMap);
        fixItemTotalRoundingIssues(order, partialOrderItemMap);
       
        // Calculate the fulfillmentGroupItem prorated orderSavings
        Money totalAllItemsAmount = calculateTotalPriceForAllFulfillmentItems(order);
        Money totalOrderAdjustmentDistributed = distributeOrderSavingsToItems(order, totalAllItemsAmount.getAmount());
        fixOrderSavingsRoundingIssues(order, totalOrderAdjustmentDistributed);
       
        // Step 3: Finalize the taxable amounts
        updateTaxableAmountsOnItems(order);
        context.setSeedData(order);

        return context;
    }

    /**
     * Sets the fulfillment amount which includes the relative portion of the total price for
     * the corresponding order item.
     *
     * @param order
     * @param partialOrderItemMap
     */
    protected void populateItemTotalAmount(Order order, Map<OrderItem, List<FulfillmentGroupItem>> partialOrderItemMap) {
        for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
            for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                OrderItem orderItem = fgItem.getOrderItem();
                int fgItemQty = fgItem.getQuantity();
                int orderItemQty = orderItem.getQuantity();
                Money totalItemAmount = orderItem.getTotalPrice();

                if (fgItemQty != orderItemQty) {
                    // We need to keep track of all of these items in case we need to distribute a remainder
                    // to one or more of the items.
                    List<FulfillmentGroupItem> fgItemList = partialOrderItemMap.get(orderItem);
                    if (fgItemList == null) {
                        fgItemList = new ArrayList<FulfillmentGroupItem>();
                        partialOrderItemMap.put(orderItem, fgItemList);
                    }
                    fgItemList.add(fgItem);
                    fgItem.setTotalItemAmount(totalItemAmount.multiply(fgItemQty).divide(orderItemQty));
                } else {
                    fgItem.setTotalItemAmount(totalItemAmount);
                }
            }
        }
    }

    /**
     * Because an item may have multiple price details that don't round cleanly, we may have pennies
     * left over that need to be distributed.
     *
     * @param order
     * @param partialOrderItemMap
     */
    protected void fixItemTotalRoundingIssues(Order order, Map<OrderItem, List<FulfillmentGroupItem>> partialOrderItemMap) {
        for (OrderItem orderItem : partialOrderItemMap.keySet()) {
            Money totalItemAmount = orderItem.getTotalPrice();
            Money totalFGItemAmount = sumItemAmount(partialOrderItemMap.get(orderItem), order);
            Money amountDiff = totalItemAmount.subtract(totalFGItemAmount);

            if (!(amountDiff.getAmount().compareTo(BigDecimal.ZERO) == 0)) {
                long numApplicationsNeeded = countNumberOfUnits(amountDiff);
                Money unitAmount = getUnitAmount(amountDiff);
                for (FulfillmentGroupItem fgItem : partialOrderItemMap.get(orderItem)) {
                    numApplicationsNeeded = numApplicationsNeeded -
                            applyDifferenceToAmount(fgItem, numApplicationsNeeded, unitAmount);
                    if (numApplicationsNeeded == 0) {
                        break;
                    }
                }
            }
        }
    }

    /**
     * Returns the total price for all fulfillment items.
     * @param order
     * @return
     */
    protected Money calculateTotalPriceForAllFulfillmentItems(Order order) {
        Money totalAllItemsAmount = new Money(order.getCurrency());
        for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
            for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                totalAllItemsAmount = totalAllItemsAmount.add(fgItem.getTotalItemAmount());
            }
        }
        return totalAllItemsAmount;
    }

    /**
     * Distributes the order adjustments (if any) to the individual fulfillment group items.
     * @param order
     * @param totalAllItems
     * @return
     */
    protected Money distributeOrderSavingsToItems(Order order, BigDecimal totalAllItems) {
        Money returnAmount = new Money(order.getCurrency());

        BigDecimal orderAdjAmt = order.getOrderAdjustmentsValue().getAmount();

        for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
            for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                BigDecimal fgItemAmount = fgItem.getTotalItemAmount().getAmount();
                BigDecimal proratedAdjAmt = totalAllItems.compareTo(BigDecimal.ZERO) == 0 ? totalAllItems : orderAdjAmt.multiply(fgItemAmount).divide(totalAllItems, RoundingMode.FLOOR);
                fgItem.setProratedOrderAdjustmentAmount(new Money(proratedAdjAmt, order.getCurrency()));
                returnAmount = returnAmount.add(fgItem.getProratedOrderAdjustmentAmount());
            }
        }
        return returnAmount;
    }

    /**
     * It is possible due to rounding that the order adjustments do not match the
     * total.   This method fixes by adding or removing the pennies.
     * @param order
     * @param partialOrderItemMap
     */
    protected void fixOrderSavingsRoundingIssues(Order order, Money totalOrderAdjustmentDistributed) {
        if (!order.getHasOrderAdjustments()) {
            return;
        }

        Money orderAdjustmentTotal = order.getOrderAdjustmentsValue();
        Money amountDiff = totalOrderAdjustmentDistributed.subtract(orderAdjustmentTotal);

        if (!(amountDiff.getAmount().compareTo(BigDecimal.ZERO) == 0)) {
            long numApplicationsNeeded = countNumberOfUnits(amountDiff);
            Money unitAmount = getUnitAmount(amountDiff);

            for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
                for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                    numApplicationsNeeded = numApplicationsNeeded -
                            applyDifferenceToProratedAdj(fgItem, numApplicationsNeeded, unitAmount);
                    if (numApplicationsNeeded == 0) {
                        break;
                    }
                }
            }
        }
    }

    /**
     * Returns the total price for all fulfillment items.
     * @param order
     * @return
     */
    protected void updateTaxableAmountsOnItems(Order order) {
        Money zero = new Money(order.getCurrency());
        for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
            for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                if (fgItem.getOrderItem().isTaxable()) {
                    Money proratedOrderAdjAmt = fgItem.getProratedOrderAdjustmentAmount();
                    if (proratedOrderAdjAmt != null) {
                        fgItem.setTotalItemTaxableAmount(fgItem.getTotalItemAmount().subtract(proratedOrderAdjAmt));
                    } else {
                        fgItem.setTotalItemTaxableAmount(fgItem.getTotalItemAmount());
                    }
                } else {
                    fgItem.setTotalItemTaxableAmount(zero);
                }             
            }
        }       
    }

    protected Money sumItemAmount(List<FulfillmentGroupItem> items, Order order) {
        Money totalAmount = new Money(order.getCurrency());
        for (FulfillmentGroupItem fgItem : items) {
            totalAmount = totalAmount.add(fgItem.getTotalItemAmount());
        }
        return totalAmount;
    }

    protected Money sumTaxAmount(List<FulfillmentGroupItem> items, Order order) {
        Money taxAmount = new Money(order.getCurrency());
        for (FulfillmentGroupItem fgItem : items) {
            taxAmount = taxAmount.add(fgItem.getTotalItemTaxableAmount());
        }
        return taxAmount;
    }

    public long countNumberOfUnits(Money difference) {
        double numUnits = difference.multiply(Math.pow(10, difference.getCurrency().getDefaultFractionDigits())).doubleValue();
        return Math.round(numUnits);
    }

    /**
     * Returns the unit amount (e.g. .01 for US)
     * @param currency
     * @return
     */
    public Money getUnitAmount(Money difference) {
        Currency currency = difference.getCurrency();
        BigDecimal divisor = new BigDecimal(Math.pow(10, currency.getDefaultFractionDigits()));
        BigDecimal unitAmount = new BigDecimal("1").divide(divisor);

        if (difference.lessThan(BigDecimal.ZERO)) {
            unitAmount = unitAmount.negate();
        }
        return new Money(unitAmount, currency);
    }

    public long applyDifferenceToAmount(FulfillmentGroupItem fgItem, long numApplicationsNeeded, Money unitAmount) {
        BigDecimal numTimesToApply = new BigDecimal(Math.min(numApplicationsNeeded, fgItem.getQuantity()));

        Money oldAmount = fgItem.getTotalItemAmount();
        Money changeToAmount = unitAmount.multiply(numTimesToApply);

        fgItem.setTotalItemAmount(oldAmount.add(changeToAmount));
        return numTimesToApply.longValue();
    }

    public long applyDifferenceToProratedAdj(FulfillmentGroupItem fgItem, long numApplicationsNeeded, Money unitAmount) {
        BigDecimal numTimesToApply = new BigDecimal(Math.min(numApplicationsNeeded, fgItem.getQuantity()));

        Money oldAmount = fgItem.getProratedOrderAdjustmentAmount();
        Money changeToAmount = unitAmount.multiply(numTimesToApply);

        fgItem.setProratedOrderAdjustmentAmount(oldAmount.add(changeToAmount));
        return numTimesToApply.longValue();
    }

    public long applyTaxDifference(FulfillmentGroupItem fgItem, long numApplicationsNeeded, Money unitAmount) {
        BigDecimal numTimesToApply = new BigDecimal(Math.min(numApplicationsNeeded, fgItem.getQuantity()));

        Money oldAmount = fgItem.getTotalItemTaxableAmount();
        Money changeToAmount = unitAmount.multiply(numTimesToApply);

        fgItem.setTotalItemTaxableAmount(oldAmount.add(changeToAmount));
        return numTimesToApply.longValue();
    }

}
TOP

Related Classes of org.broadleafcommerce.core.pricing.service.workflow.FulfillmentItemPricingActivity

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.