org.yes.cart.web.support.service.impl.ShippingServiceFacadeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.yes.cart.web.support.service.impl.ShippingServiceFacadeImpl.java

Source

/*
 * Copyright 2009 Denys Pavlov, Igor Azarnyi
 *
 *    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.
 */

package org.yes.cart.web.support.service.impl;

import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.lang.StringUtils;
import org.yes.cart.constants.AttributeNamesKeys;
import org.yes.cart.constants.Constants;
import org.yes.cart.domain.entity.*;
import org.yes.cart.domain.entity.impl.ProductPriceModelImpl;
import org.yes.cart.domain.i18n.impl.FailoverStringI18NModel;
import org.yes.cart.domain.misc.Pair;
import org.yes.cart.service.domain.CarrierService;
import org.yes.cart.service.domain.CarrierSlaService;
import org.yes.cart.service.domain.ShopService;
import org.yes.cart.service.domain.WarehouseService;
import org.yes.cart.shoppingcart.*;
import org.yes.cart.shoppingcart.impl.ShoppingCartShippingCostContainerImpl;
import org.yes.cart.util.MoneyUtils;
import org.yes.cart.web.support.service.ShippingServiceFacade;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * User: denispavlov
 * Date: 11/01/2014
 * Time: 11:57
 */
public class ShippingServiceFacadeImpl implements ShippingServiceFacade {

    private final CarrierService carrierService;
    private final CarrierSlaService carrierSlaService;
    private final ShopService shopService;
    private final WarehouseService warehouseService;
    private final DeliveryCostCalculationStrategy deliveryCostCalculationStrategy;

    public ShippingServiceFacadeImpl(final CarrierService carrierService, final CarrierSlaService carrierSlaService,
            final ShopService shopService, final WarehouseService warehouseService,
            final DeliveryCostCalculationStrategy deliveryCostCalculationStrategy) {
        this.carrierService = carrierService;
        this.carrierSlaService = carrierSlaService;
        this.shopService = shopService;
        this.warehouseService = warehouseService;
        this.deliveryCostCalculationStrategy = deliveryCostCalculationStrategy;
    }

    /** {@inheritDoc} */
    @Override
    public Pair<Boolean, Boolean> isAddressNotRequired(final Collection<Long> carrierSlaIds) {

        boolean billing = true;
        boolean delivery = true;

        for (final Long carrierSlaId : carrierSlaIds) {
            final CarrierSla sla = this.carrierSlaService.getById(carrierSlaId);
            if (sla != null) {
                if (!sla.isBillingAddressNotRequired()) {
                    billing = false;
                }
                if (!sla.isDeliveryAddressNotRequired()) {
                    delivery = false;
                }
            }
            if (!billing && !delivery) {
                break; // if both are required, no point in searching further
            }
        }

        return new Pair<Boolean, Boolean>(billing, delivery);
    }

    /** {@inheritDoc} */
    @Override
    public boolean isSkippableAddress(final ShoppingCart shoppingCart) {
        final List<String> suppliers = shoppingCart.getCartItemsSuppliers();
        if (suppliers.isEmpty()) {
            return false; // default is must select address
        }
        for (final String supplier : suppliers) {
            boolean atLeastOneNotRequiredAvailable = false;
            final List<Carrier> available = findCarriers(shoppingCart, supplier);
            for (final Carrier carrier : available) {
                for (final CarrierSla sla : carrier.getCarrierSla()) {
                    if (sla.isBillingAddressNotRequired() && sla.isDeliveryAddressNotRequired()) {
                        atLeastOneNotRequiredAvailable = true;
                        break;
                    }
                }
                if (atLeastOneNotRequiredAvailable) {
                    break;
                }
            }
            if (!atLeastOneNotRequiredAvailable) {
                return false;
            }
        }
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public List<Carrier> findCarriers(final ShoppingCart shoppingCart, final String supplier) {
        final List<Carrier> cached = carrierService
                .getCarriersByShopId(shoppingCart.getShoppingContext().getCustomerShopId());
        final List<Carrier> all = deepCopyCarriers(cached);
        filterCarriersForShoppingCart(all, shoppingCart, supplier);
        return all;
    }

    private List<Carrier> deepCopyCarriers(final List<Carrier> cached) {

        final List<Carrier> carriers = new ArrayList<Carrier>(cached.size());
        for (final Carrier cache : cached) {

            carriers.add((Carrier) SerializationUtils.clone(cache));

        }

        return carriers;
    }

    /*
    // CPOINT: shipping logic in most cases it is very business specific and should be put into this method
    // CPOINT: recommended approach is to create Carrier filter strategy bean and delegate filtering to it
     */
    private void filterCarriersForShoppingCart(final List<Carrier> all, final ShoppingCart shoppingCart,
            final String supplier) {

        final Iterator<Carrier> carrierIt = all.iterator();
        while (carrierIt.hasNext()) {
            final Carrier carrier = carrierIt.next();
            final Iterator<CarrierSla> slaIt = carrier.getCarrierSla().iterator();
            while (slaIt.hasNext()) {
                final CarrierSla carrierSla = slaIt.next();

                // Check if this SLA is available for given supplier (empty supplier could be for ALWAYS+Digital, so need to allow all SLA for those TODO: revise for YC-668)
                if (StringUtils.isNotBlank(supplier)
                        && !carrierSla.getSupportedFulfilmentCentresAsList().contains(supplier)) {
                    slaIt.remove();
                    continue;
                }

                // Exclusions based on customer type
                final String customerType = shoppingCart.getOrderInfo()
                        .getDetailByKey(AttributeNamesKeys.Cart.ORDER_INFO_CUSTOMER_TYPE);
                if (StringUtils.isNotBlank(customerType)
                        && carrierSla.getExcludeCustomerTypesAsList().contains(customerType)) {
                    slaIt.remove();
                    continue;
                }

                // We use the same logic to determine regional availability for for shipping. All CarrierSla must have
                // corresponding SkuPrice for given basket, if this is not the case then they are considered
                // unavailable for this country/region.
                // For R(Free) and (E)External just use price 1.00. The actual delivery cost are calculated by specific
                // DeliveryCostCalculationStrategy, so this is just availability in region marker.
                if (!isSlaAvailable(shoppingCart, supplier, carrierSla.getCarrierslaId())) {
                    slaIt.remove(); // No price defined, so must not be available for given cart state
                    continue;
                }
            }
            if (carrier.getCarrierSla().isEmpty()) {
                carrierIt.remove(); // if no SLA available then this carrier is not available
            }
        }

    }

    protected boolean isSlaAvailable(final ShoppingCart cart, final String supplier, final Long carrierSlaId) {

        final MutableShoppingCart changeShippingView = new ShoppingCartShippingCostContainerImpl(cart, supplier,
                carrierSlaId);

        final Total cost = deliveryCostCalculationStrategy.calculate(changeShippingView);

        return cost != null;

    }

    /** {@inheritDoc} */
    @Override
    public Pair<Carrier, CarrierSla> getCarrierSla(final ShoppingCart shoppingCart, final String supplier,
            final List<Carrier> carriersChoices) {

        final Long slaId = shoppingCart.getCarrierSlaId().get(supplier);

        if (slaId != null) {
            for (Carrier carrier : carriersChoices) {
                for (CarrierSla carrierSla : carrier.getCarrierSla()) {
                    if (slaId == carrierSla.getCarrierslaId()) {
                        return new Pair<Carrier, CarrierSla>(carrier, carrierSla);
                    }
                }
            }
        }

        return new Pair<Carrier, CarrierSla>(null, null);
    }

    static final String CART_SHIPPING_TOTAL_REF = "yc-cart-shipping-total";

    /**
     * {@inheritDoc}
     */
    public ProductPriceModel getCartShippingTotal(final ShoppingCart cart) {

        final String currency = cart.getCurrencyCode();

        final BigDecimal deliveriesCount = new BigDecimal(cart.getShippingList().size());
        final BigDecimal list = cart.getTotal().getDeliveryListCost();
        final BigDecimal sale = cart.getTotal().getDeliveryCost();

        final boolean showTax = cart.getShoppingContext().isTaxInfoEnabled();
        final boolean showTaxNet = showTax && cart.getShoppingContext().isTaxInfoUseNet();
        final boolean showTaxAmount = showTax && cart.getShoppingContext().isTaxInfoShowAmount();

        if (showTax) {

            final BigDecimal costInclTax = cart.getTotal().getDeliveryCostAmount();

            if (MoneyUtils.isFirstBiggerThanSecond(costInclTax, Total.ZERO)) {

                final BigDecimal totalTax = cart.getTotal().getDeliveryTax();
                final BigDecimal net = costInclTax.subtract(totalTax);
                final BigDecimal gross = costInclTax;

                final BigDecimal totalAdjusted = showTaxNet ? net : gross;

                final Set<String> taxes = new TreeSet<String>(); // sorts and de-dup's
                final Set<BigDecimal> rates = new TreeSet<BigDecimal>();
                for (final CartItem item : cart.getShippingList()) {
                    if (StringUtils.isNotBlank(item.getTaxCode())) {
                        taxes.add(item.getTaxCode());
                    }
                    rates.add(item.getTaxRate());
                }

                final BigDecimal taxRate;
                if (MoneyUtils.isFirstBiggerThanSecond(totalTax, Total.ZERO) && rates.size() > 1) {
                    // mixed rates in cart we use average with round up so that tax is not reduced by rounding
                    taxRate = totalTax.multiply(MoneyUtils.HUNDRED).divide(net, Constants.DEFAULT_SCALE,
                            BigDecimal.ROUND_UP);
                } else {
                    // single rate for all items, use it to prevent rounding errors
                    taxRate = rates.iterator().next();
                }

                final String tax = StringUtils.join(taxes, ',');
                final boolean exclusiveTax = costInclTax.compareTo(sale) > 0;

                if (MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
                    // if we have discounts

                    final MoneyUtils.Money listMoney = MoneyUtils.getMoney(list, taxRate, !exclusiveTax);
                    final BigDecimal listAdjusted = showTaxNet ? listMoney.getNet() : listMoney.getGross();

                    return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount,
                            listAdjusted, totalAdjusted, showTax, showTaxNet, showTaxAmount, tax, taxRate,
                            exclusiveTax, totalTax);

                }
                // no discounts
                return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, totalAdjusted,
                        null, showTax, showTaxNet, showTaxAmount, tax, taxRate, exclusiveTax, totalTax);

            }

        }

        // standard "as is" prices

        if (MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
            // if we have discounts
            return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, list, sale);

        }
        // no discounts
        return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, sale, null);

    }

    /**
     * {@inheritDoc}
     */
    public ProductPriceModel getCartShippingSupplierTotal(final ShoppingCart cart, final String supplier) {

        final String currency = cart.getCurrencyCode();

        BigDecimal deliveriesCount = BigDecimal.ZERO;
        new BigDecimal(cart.getShippingList().size());
        BigDecimal list = BigDecimal.ZERO;
        BigDecimal sale = BigDecimal.ZERO;
        BigDecimal taxAmount = BigDecimal.ZERO;

        final Set<String> taxes = new TreeSet<String>(); // sorts and de-dup's
        final Set<BigDecimal> rates = new TreeSet<BigDecimal>();

        for (final CartItem shipping : cart.getShippingList()) {
            if (supplier.equals(shipping.getSupplierCode())) {
                deliveriesCount = deliveriesCount.add(BigDecimal.ONE);
                list = list.add(shipping.getListPrice().multiply(shipping.getQty())
                        .setScale(Constants.DEFAULT_SCALE, RoundingMode.HALF_UP));
                sale = sale.add(shipping.getPrice().multiply(shipping.getQty()).setScale(Constants.DEFAULT_SCALE,
                        RoundingMode.HALF_UP));
                taxAmount = taxAmount.add(shipping.getGrossPrice().subtract(shipping.getNetPrice())
                        .multiply(shipping.getQty()).setScale(Constants.DEFAULT_SCALE, RoundingMode.HALF_UP));
                if (StringUtils.isNotBlank(shipping.getTaxCode())) {
                    taxes.add(shipping.getTaxCode());
                }
                rates.add(shipping.getTaxRate());
            }
        }

        final boolean showTax = cart.getShoppingContext().isTaxInfoEnabled();
        final boolean showTaxNet = showTax && cart.getShoppingContext().isTaxInfoUseNet();
        final boolean showTaxAmount = showTax && cart.getShoppingContext().isTaxInfoShowAmount();

        if (showTax) {

            final BigDecimal costInclTax = sale;

            if (MoneyUtils.isFirstBiggerThanSecond(costInclTax, Total.ZERO)) {

                final BigDecimal totalTax = taxAmount;
                final BigDecimal net = costInclTax.subtract(totalTax);
                final BigDecimal gross = costInclTax;

                final BigDecimal totalAdjusted = showTaxNet ? net : gross;

                final BigDecimal taxRate;
                if (MoneyUtils.isFirstBiggerThanSecond(totalTax, Total.ZERO) && rates.size() > 1) {
                    // mixed rates in cart we use average with round up so that tax is not reduced by rounding
                    taxRate = totalTax.multiply(MoneyUtils.HUNDRED).divide(net, Constants.DEFAULT_SCALE,
                            BigDecimal.ROUND_UP);
                } else {
                    // single rate for all items, use it to prevent rounding errors
                    taxRate = rates.iterator().next();
                }

                final String tax = StringUtils.join(taxes, ',');
                final boolean exclusiveTax = costInclTax.compareTo(sale) > 0;

                if (MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
                    // if we have discounts

                    final MoneyUtils.Money listMoney = MoneyUtils.getMoney(list, taxRate, !exclusiveTax);
                    final BigDecimal listAdjusted = showTaxNet ? listMoney.getNet() : listMoney.getGross();

                    return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount,
                            listAdjusted, totalAdjusted, showTax, showTaxNet, showTaxAmount, tax, taxRate,
                            exclusiveTax, totalTax);

                }
                // no discounts
                return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, totalAdjusted,
                        null, showTax, showTaxNet, showTaxAmount, tax, taxRate, exclusiveTax, totalTax);

            }

        }

        // standard "as is" prices

        if (MoneyUtils.isFirstBiggerThanSecond(list, sale)) {
            // if we have discounts
            return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, list, sale);

        }
        // no discounts
        return new ProductPriceModelImpl(CART_SHIPPING_TOTAL_REF, currency, deliveriesCount, sale, null);

    }

    /**
     * {@inheritDoc}
     */
    public Map<String, String> getCartItemsSuppliers(final ShoppingCart cart) {

        final String lang = cart.getCurrentLocale();
        final List<Warehouse> suppliers = warehouseService
                .getByShopId(cart.getShoppingContext().getCustomerShopId(), false);

        final Map<String, String> namesByCode = new HashMap<String, String>();
        for (final Warehouse supplier : suppliers) {
            namesByCode.put(supplier.getCode(),
                    new FailoverStringI18NModel(supplier.getDisplayName(), supplier.getName()).getValue(lang));
        }

        // Default supplier is the shop
        final Shop shop = shopService.getById(cart.getShoppingContext().getShopId());
        namesByCode.put("", shop.getName());

        return namesByCode;
    }
}