org.broadleafcommerce.core.pricing.service.tax.provider.SimpleTaxProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.broadleafcommerce.core.pricing.service.tax.provider.SimpleTaxProvider.java

Source

/*
 * #%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.tax.provider;

import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.common.config.domain.ModuleConfiguration;
import org.broadleafcommerce.common.i18n.domain.ISOCountry;
import org.broadleafcommerce.common.persistence.EntityConfiguration;
import org.broadleafcommerce.core.order.domain.FulfillmentGroup;
import org.broadleafcommerce.core.order.domain.FulfillmentGroupFee;
import org.broadleafcommerce.core.order.domain.FulfillmentGroupItem;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.domain.TaxDetail;
import org.broadleafcommerce.core.order.domain.TaxType;
import org.broadleafcommerce.core.pricing.service.exception.TaxException;
import org.broadleafcommerce.profile.core.domain.Address;
import org.broadleafcommerce.profile.core.domain.Country;
import org.broadleafcommerce.profile.core.domain.State;

import java.math.BigDecimal;
import java.util.Map;

import javax.annotation.Resource;

/**
 * <p>
 * Simple factor-based tax module that can be configured by adding rates for
 * specific postalCodes, city, state, or country.
 *
 * <p>
 * Through configuration, this module can be used to set a specific tax rate for items and shipping for a given postal code,
 * city, state, or country.
 * 
 * <p>
 * Utilizes the fulfillment group's address to determine the tax location.
 * 
 * <p>
 * Useful for those with very simple tax needs that want to configure rates programmatically.
 * 
 * @author jfischer, brian polster
 * @author Phillip Verheyden (phillipuniverse)
 */
public class SimpleTaxProvider implements TaxProvider {

    protected Map<String, Double> itemPostalCodeTaxRateMap;
    protected Map<String, Double> itemCityTaxRateMap;
    protected Map<String, Double> itemStateTaxRateMap;
    protected Map<String, Double> itemCountryTaxRateMap;

    protected Map<String, Double> fulfillmentGroupPostalCodeTaxRateMap;
    protected Map<String, Double> fulfillmentGroupCityTaxRateMap;
    protected Map<String, Double> fulfillmentGroupStateTaxRateMap;
    protected Map<String, Double> fulfillmentGroupCountryTaxRateMap;

    protected Double defaultItemTaxRate;
    protected Double defaultFulfillmentGroupTaxRate;

    protected boolean taxFees;

    @Resource(name = "blEntityConfiguration")
    protected EntityConfiguration entityConfig;

    @Override
    public boolean canRespond(ModuleConfiguration config) {
        // this will only be executed with null module configurations
        return config == null;
    }

    @Override
    public Order calculateTaxForOrder(Order order, ModuleConfiguration config) throws TaxException {
        if (!order.getCustomer().isTaxExempt()) {
            for (FulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
                // Set taxes on the fulfillment group items
                for (FulfillmentGroupItem fgItem : fulfillmentGroup.getFulfillmentGroupItems()) {
                    if (isItemTaxable(fgItem)) {
                        BigDecimal factor = determineItemTaxRate(fulfillmentGroup.getAddress());
                        if (factor != null && factor.compareTo(BigDecimal.ZERO) != 0) {
                            TaxDetail tax;
                            checkDetail: {
                                for (TaxDetail detail : fgItem.getTaxes()) {
                                    if (detail.getType().equals(TaxType.COMBINED)) {
                                        tax = detail;
                                        break checkDetail;
                                    }
                                }
                                tax = entityConfig.createEntityInstance(TaxDetail.class.getName(), TaxDetail.class);
                                tax.setType(TaxType.COMBINED);
                                fgItem.getTaxes().add(tax);
                            }
                            tax.setRate(factor);
                            tax.setAmount(fgItem.getTotalItemTaxableAmount().multiply(factor));
                        }
                    }
                }

                for (FulfillmentGroupFee fgFee : fulfillmentGroup.getFulfillmentGroupFees()) {
                    if (isFeeTaxable(fgFee)) {
                        BigDecimal factor = determineItemTaxRate(fulfillmentGroup.getAddress());
                        if (factor != null && factor.compareTo(BigDecimal.ZERO) != 0) {
                            TaxDetail tax;
                            checkDetail: {
                                for (TaxDetail detail : fgFee.getTaxes()) {
                                    if (detail.getType().equals(TaxType.COMBINED)) {
                                        tax = detail;
                                        break checkDetail;
                                    }
                                }
                                tax = entityConfig.createEntityInstance(TaxDetail.class.getName(), TaxDetail.class);
                                tax.setType(TaxType.COMBINED);
                                fgFee.getTaxes().add(tax);
                            }
                            tax.setRate(factor);
                            tax.setAmount(fgFee.getAmount().multiply(factor));
                        }
                    }
                }

                BigDecimal factor = determineTaxRateForFulfillmentGroup(fulfillmentGroup);
                if (factor != null && factor.compareTo(BigDecimal.ZERO) != 0) {
                    TaxDetail tax;
                    checkDetail: {
                        for (TaxDetail detail : fulfillmentGroup.getTaxes()) {
                            if (detail.getType().equals(TaxType.COMBINED)) {
                                tax = detail;
                                break checkDetail;
                            }
                        }
                        tax = entityConfig.createEntityInstance(TaxDetail.class.getName(), TaxDetail.class);
                        tax.setType(TaxType.COMBINED);
                        fulfillmentGroup.getTaxes().add(tax);
                    }
                    tax.setRate(factor);
                    tax.setAmount(fulfillmentGroup.getFulfillmentPrice().multiply(factor));
                }
            }
        }

        return order;
    }

    @Override
    public Order commitTaxForOrder(Order order, ModuleConfiguration config) throws TaxException {
        // intentionally left blank; no tax needs to be committed as this already has the tax details on the order
        return order;
    }

    @Override
    public void cancelTax(Order order, ModuleConfiguration config) throws TaxException {
        // intentionally left blank; tax never got committed so it never gets cancelled
    }

    /**
     * Returns the taxAmount for the passed in postal code or
     * null if no match is found.
     *
     * @param postalCode
     * @return
     */
    public Double lookupPostalCodeRate(Map<String, Double> postalCodeTaxRateMap, String postalCode) {
        if (postalCodeTaxRateMap != null && postalCode != null) {
            return postalCodeTaxRateMap.get(postalCode);
        }
        return null;
    }

    /**
     * Changes the city to upper case before checking the
     * configuration.
     *
     * Return null if no match is found.
     *
     * @param cityTaxRateMap, city
     * @return
     */
    public Double lookupCityRate(Map<String, Double> cityTaxRateMap, String city) {
        if (cityTaxRateMap != null && city != null) {
            city = city.toUpperCase();
            return cityTaxRateMap.get(city);
        }
        return null;
    }

    /**
     * Returns the taxAmount for the passed in state or
     * null if no match is found.
     *
     * First checks the abbreviation (uppercase) followed by the name (uppercase).
     *
     * @param stateTaxRateMap, state
     * @return
     */
    public Double lookupStateRate(Map<String, Double> stateTaxRateMap, State state) {
        if (stateTaxRateMap != null && state != null && state.getAbbreviation() != null) {
            String stateAbbr = state.getAbbreviation().toUpperCase();
            Double rate = stateTaxRateMap.get(stateAbbr);
            if (rate == null && state.getName() != null) {
                String stateName = state.getName().toUpperCase();
                return stateTaxRateMap.get(stateName);
            } else {
                return rate;
            }
        }
        return null;
    }

    /**
     * Returns the taxAmount for the passed in stateProvinceRegion or
     * null if no match is found.
     *
     * First checks the abbreviation (uppercase) followed by the name (uppercase).
     *
     * @param stateTaxRateMap, stateProvinceRegion
     * @return
     */
    public Double lookupStateRate(Map<String, Double> stateTaxRateMap, String stateProvinceRegion) {
        if (stateTaxRateMap != null && StringUtils.isNotBlank(stateProvinceRegion)) {
            return stateTaxRateMap.get(stateProvinceRegion);
        }
        return null;
    }

    /**
     * Returns the taxAmount for the passed in country or
     * null if no match is found.
     *
     * First checks the abbreviation (uppercase) followed by the name (uppercase).
     *
     * @param countryTaxRateMap, country
     * @return
     */
    public Double lookupCountryRate(Map<String, Double> countryTaxRateMap, Country country) {
        if (countryTaxRateMap != null && country != null && country.getAbbreviation() != null) {
            String cntryAbbr = country.getAbbreviation().toUpperCase();
            Double rate = countryTaxRateMap.get(cntryAbbr);
            if (rate == null && country.getName() != null) {
                String countryName = country.getName().toUpperCase();
                return countryTaxRateMap.get(countryName);
            } else {
                return rate;
            }
        }
        return null;
    }

    /**
     * Returns the taxAmount for the passed in country or
     * null if no match is found.
     *
     * First checks the alpha2 (uppercase) followed by the name (uppercase).
     *
     * @param countryTaxRateMap, isoCountry
     * @return
     */
    public Double lookupCountryRate(Map<String, Double> countryTaxRateMap, ISOCountry isoCountry) {
        if (countryTaxRateMap != null && isoCountry != null && isoCountry.getAlpha2() != null) {
            String cntryAbbr = isoCountry.getAlpha2().toUpperCase();
            Double rate = countryTaxRateMap.get(cntryAbbr);
            if (rate == null && isoCountry.getName() != null) {
                String countryName = isoCountry.getName().toUpperCase();
                return countryTaxRateMap.get(countryName);
            } else {
                return rate;
            }
        }
        return null;
    }

    protected boolean isItemTaxable(FulfillmentGroupItem item) {
        return item.getOrderItem().isTaxable();
    }

    protected boolean isFeeTaxable(FulfillmentGroupFee fee) {
        return fee.isTaxable();
    }

    /**
     * Uses the passed in address to determine if the item is taxable.
     *
     * Checks the configured maps in order - (postal code, city, state, country)
     *
     * @param address
     * @return
     */
    public BigDecimal determineItemTaxRate(Address address) {
        if (address != null) {
            Double postalCodeRate = lookupPostalCodeRate(itemPostalCodeTaxRateMap, address.getPostalCode());
            if (postalCodeRate != null) {
                return BigDecimal.valueOf(postalCodeRate);
            }
            Double cityCodeRate = lookupCityRate(itemCityTaxRateMap, address.getCity());
            if (cityCodeRate != null) {
                return BigDecimal.valueOf(cityCodeRate);
            }

            Double stateCodeRate;
            if (StringUtils.isNotBlank(address.getStateProvinceRegion())) {
                stateCodeRate = lookupStateRate(itemStateTaxRateMap, address.getStateProvinceRegion());
            } else {
                stateCodeRate = lookupStateRate(itemStateTaxRateMap, address.getState());
            }

            if (stateCodeRate != null) {
                return BigDecimal.valueOf(stateCodeRate);
            }

            Double countryCodeRate;
            if (address.getIsoCountryAlpha2() != null) {
                countryCodeRate = lookupCountryRate(itemCountryTaxRateMap, address.getIsoCountryAlpha2());
            } else {
                countryCodeRate = lookupCountryRate(itemCountryTaxRateMap, address.getCountry());
            }

            if (countryCodeRate != null) {
                return BigDecimal.valueOf(countryCodeRate);
            }
        }

        if (defaultItemTaxRate != null) {
            return BigDecimal.valueOf(defaultItemTaxRate);
        } else {
            return BigDecimal.ZERO;
        }
    }

    /**
     * Uses the passed in address to determine if the item is taxable.
     *
     * Checks the configured maps in order - (postal code, city, state, country)
     *
     * @param fulfillmentGroup
     * @return
     */
    public BigDecimal determineTaxRateForFulfillmentGroup(FulfillmentGroup fulfillmentGroup) {
        boolean isTaxable = true;

        if (fulfillmentGroup.isShippingPriceTaxable() != null) {
            isTaxable = fulfillmentGroup.isShippingPriceTaxable();
        }

        if (isTaxable) {
            Address address = fulfillmentGroup.getAddress();
            if (address != null) {
                Double postalCodeRate = lookupPostalCodeRate(fulfillmentGroupPostalCodeTaxRateMap,
                        address.getPostalCode());
                if (postalCodeRate != null) {
                    return BigDecimal.valueOf(postalCodeRate);
                }
                Double cityCodeRate = lookupCityRate(fulfillmentGroupCityTaxRateMap, address.getCity());
                if (cityCodeRate != null) {
                    return BigDecimal.valueOf(cityCodeRate);
                }

                Double stateCodeRate;
                if (StringUtils.isNotBlank(address.getStateProvinceRegion())) {
                    stateCodeRate = lookupStateRate(fulfillmentGroupStateTaxRateMap,
                            address.getStateProvinceRegion());
                } else {
                    stateCodeRate = lookupStateRate(fulfillmentGroupStateTaxRateMap, address.getState());
                }

                if (stateCodeRate != null) {
                    return BigDecimal.valueOf(stateCodeRate);
                }

                Double countryCodeRate;
                if (address.getIsoCountryAlpha2() != null) {
                    countryCodeRate = lookupCountryRate(fulfillmentGroupCountryTaxRateMap,
                            address.getIsoCountryAlpha2());
                } else {
                    countryCodeRate = lookupCountryRate(fulfillmentGroupCountryTaxRateMap, address.getCountry());
                }

                if (countryCodeRate != null) {
                    return BigDecimal.valueOf(countryCodeRate);
                }
            }

            if (defaultFulfillmentGroupTaxRate != null) {
                return BigDecimal.valueOf(defaultFulfillmentGroupTaxRate);
            }
        }
        return BigDecimal.ZERO;
    }

    public Map<String, Double> getItemPostalCodeTaxRateMap() {
        return itemPostalCodeTaxRateMap;
    }

    public void setItemPostalCodeTaxRateMap(Map<String, Double> itemPostalCodeTaxRateMap) {
        this.itemPostalCodeTaxRateMap = itemPostalCodeTaxRateMap;
    }

    public Map<String, Double> getItemCityTaxRateMap() {
        return itemCityTaxRateMap;
    }

    public void setItemCityTaxRateMap(Map<String, Double> itemCityTaxRateMap) {
        this.itemCityTaxRateMap = itemCityTaxRateMap;
    }

    public Map<String, Double> getItemStateTaxRateMap() {
        return itemStateTaxRateMap;
    }

    public void setItemStateTaxRateMap(Map<String, Double> itemStateTaxRateMap) {
        this.itemStateTaxRateMap = itemStateTaxRateMap;
    }

    public Map<String, Double> getItemCountryTaxRateMap() {
        return itemCountryTaxRateMap;
    }

    public void setItemCountryTaxRateMap(Map<String, Double> itemCountryTaxRateMap) {
        this.itemCountryTaxRateMap = itemCountryTaxRateMap;
    }

    public Map<String, Double> getFulfillmentGroupPostalCodeTaxRateMap() {
        return fulfillmentGroupPostalCodeTaxRateMap;
    }

    public void setFulfillmentGroupPostalCodeTaxRateMap(Map<String, Double> fulfillmentGroupPostalCodeTaxRateMap) {
        this.fulfillmentGroupPostalCodeTaxRateMap = fulfillmentGroupPostalCodeTaxRateMap;
    }

    public Map<String, Double> getFulfillmentGroupCityTaxRateMap() {
        return fulfillmentGroupCityTaxRateMap;
    }

    public void setFulfillmentGroupCityTaxRateMap(Map<String, Double> fulfillmentGroupCityTaxRateMap) {
        this.fulfillmentGroupCityTaxRateMap = fulfillmentGroupCityTaxRateMap;
    }

    public Map<String, Double> getFulfillmentGroupStateTaxRateMap() {
        return fulfillmentGroupStateTaxRateMap;
    }

    public void setFulfillmentGroupStateTaxRateMap(Map<String, Double> fulfillmentGroupStateTaxRateMap) {
        this.fulfillmentGroupStateTaxRateMap = fulfillmentGroupStateTaxRateMap;
    }

    public Map<String, Double> getFulfillmentGroupCountryTaxRateMap() {
        return fulfillmentGroupCountryTaxRateMap;
    }

    public void setFulfillmentGroupCountryTaxRateMap(Map<String, Double> fulfillmentGroupCountryTaxRateMap) {
        this.fulfillmentGroupCountryTaxRateMap = fulfillmentGroupCountryTaxRateMap;
    }

    public Double getDefaultItemTaxRate() {
        return defaultItemTaxRate;
    }

    public void setDefaultItemTaxRate(Double defaultItemTaxRate) {
        this.defaultItemTaxRate = defaultItemTaxRate;
    }

    public Double getDefaultFulfillmentGroupTaxRate() {
        return defaultFulfillmentGroupTaxRate;
    }

    public void setDefaultFulfillmentGroupTaxRate(Double defaultFulfillmentGroupTaxRate) {
        this.defaultFulfillmentGroupTaxRate = defaultFulfillmentGroupTaxRate;
    }

}