001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.fp.businessobject.SalesTax;
025    import org.kuali.kfs.sys.businessobject.TaxDetail;
026    import org.kuali.kfs.sys.businessobject.TaxRegion;
027    import org.kuali.kfs.sys.service.TaxRegionService;
028    import org.kuali.kfs.sys.service.TaxService;
029    import org.kuali.rice.kns.service.ParameterService;
030    import org.kuali.rice.kns.util.KualiDecimal;
031    import org.kuali.rice.kns.util.ObjectUtils;
032    import org.springframework.transaction.annotation.Transactional;
033    
034    @Transactional
035    public class TaxServiceImpl implements TaxService {
036    
037        private static final String POSTAL_CODE_DIGITS_PASSED_TO_SALES_TAX_REGION_SERVICE = "POSTAL_CODE_DIGITS_PASSED_TO_SALES_TAX_REGION_SERVICE";
038        
039        private TaxRegionService taxRegionService;
040        private ParameterService parameterService;
041        
042        /**
043         * @see org.kuali.kfs.sys.service.TaxService#getSalesTaxDetails(java.lang.String, java.lang.String,
044         *      org.kuali.rice.kns.util.KualiDecimal)
045         */
046        public List<TaxDetail> getSalesTaxDetails(Date dateOfTransaction, String postalCode, KualiDecimal amount) {
047            List<TaxDetail> salesTaxDetails = new ArrayList<TaxDetail>();
048    
049            if (StringUtils.isNotEmpty(postalCode)) {
050                List<TaxRegion> salesTaxRegions = taxRegionService.getSalesTaxRegions(postalCode);
051                TaxDetail newTaxDetail = null;
052                for (TaxRegion taxRegion : salesTaxRegions) {
053                    if (taxRegion.isActive()) {
054                        newTaxDetail = populateTaxDetail(taxRegion, dateOfTransaction, amount);
055                        salesTaxDetails.add(newTaxDetail);
056                    }
057                }
058            }
059    
060            return salesTaxDetails;
061        }
062    
063        /**
064         * @see org.kuali.kfs.sys.service.TaxService#getUseTaxDetails(java.lang.String, java.lang.String,
065         *      org.kuali.rice.kns.util.KualiDecimal)
066         */
067        public List<TaxDetail> getUseTaxDetails(Date dateOfTransaction, String postalCode, KualiDecimal amount) {
068            List<TaxDetail> useTaxDetails = new ArrayList<TaxDetail>();
069    
070            if (StringUtils.isNotEmpty(postalCode)) {
071                //  strip digits from the postal code before passing it to the sales tax regions 
072                // if the parameters indicate to do so.
073                postalCode = truncatePostalCodeForSalesTaxRegionService(postalCode);
074                
075                for (TaxRegion taxRegion : taxRegionService.getUseTaxRegions(postalCode)) {
076                    useTaxDetails.add(populateTaxDetail(taxRegion, dateOfTransaction, amount));
077                }
078            }
079    
080            return useTaxDetails;
081        }
082    
083        /**
084         * @see org.kuali.kfs.sys.service.TaxService#getTotalSalesTaxAmount(java.lang.String, java.lang.String,
085         *      org.kuali.rice.kns.util.KualiDecimal)
086         */
087        public KualiDecimal getTotalSalesTaxAmount(Date dateOfTransaction, String postalCode, KualiDecimal amount) {
088            KualiDecimal totalSalesTaxAmount = KualiDecimal.ZERO;
089    
090            if (StringUtils.isNotEmpty(postalCode)) {
091    
092                //  strip digits from the postal code before passing it to the sales tax regions 
093                // if the parameters indicate to do so.
094                postalCode = truncatePostalCodeForSalesTaxRegionService(postalCode);
095                
096                List<TaxDetail> salesTaxDetails = getSalesTaxDetails(dateOfTransaction, postalCode, amount);
097                KualiDecimal newTaxAmount = KualiDecimal.ZERO;
098                for (TaxDetail taxDetail : salesTaxDetails) {
099                    newTaxAmount = taxDetail.getTaxAmount();
100                    totalSalesTaxAmount = totalSalesTaxAmount.add(newTaxAmount);
101                }
102            }
103    
104            return totalSalesTaxAmount;
105        }
106    
107        /**
108         * This method returns a preTax amount
109         * 
110         * @param dateOfTransaction
111         * @param postalCode
112         * @param amountWithTax
113         * @return
114         */
115    
116        public KualiDecimal getPretaxAmount(Date dateOfTransaction, String postalCode, KualiDecimal amountWithTax) {
117            BigDecimal totalTaxRate = BigDecimal.ZERO;
118    
119            // there is not tax amount
120            if (StringUtils.isEmpty(postalCode))
121                return amountWithTax;
122    
123            //  strip digits from the postal code before passing it to the sales tax regions 
124            // if the parameters indicate to do so.
125            postalCode = truncatePostalCodeForSalesTaxRegionService(postalCode);
126            
127            List<TaxRegion> salesTaxRegions = taxRegionService.getSalesTaxRegions(postalCode);
128            if (salesTaxRegions.size() == 0)
129                return amountWithTax;
130    
131            for (TaxRegion taxRegion : salesTaxRegions) {
132                if (ObjectUtils.isNotNull((taxRegion.getEffectiveTaxRegionRate(dateOfTransaction))))
133                    totalTaxRate = totalTaxRate.add(taxRegion.getEffectiveTaxRegionRate(dateOfTransaction).getTaxRate());
134            }
135    
136            KualiDecimal divisor = new KualiDecimal(totalTaxRate.add(BigDecimal.ONE));
137            KualiDecimal pretaxAmount = amountWithTax.divide(divisor);
138    
139            return pretaxAmount;
140        }
141    
142        /**
143         * This method returns a populated Tax Detail BO based on the Tax Region BO and amount
144         * 
145         * @param taxRegion
146         * @param amount
147         * @return
148         */
149        protected TaxDetail populateTaxDetail(TaxRegion taxRegion, Date dateOfTransaction, KualiDecimal amount) {
150            TaxDetail taxDetail = new TaxDetail();
151            taxDetail.setAccountNumber(taxRegion.getAccountNumber());
152            taxDetail.setChartOfAccountsCode(taxRegion.getChartOfAccountsCode());
153            taxDetail.setFinancialObjectCode(taxRegion.getFinancialObjectCode());
154            taxDetail.setRateCode(taxRegion.getTaxRegionCode());
155            taxDetail.setRateName(taxRegion.getTaxRegionName());
156            taxDetail.setTypeCode(taxRegion.getTaxRegionTypeCode());
157            if (ObjectUtils.isNotNull((taxRegion.getEffectiveTaxRegionRate(dateOfTransaction)))) {
158                taxDetail.setTaxRate(taxRegion.getEffectiveTaxRegionRate(dateOfTransaction).getTaxRate());
159                if (amount != null) {
160                    taxDetail.setTaxAmount(new KualiDecimal((amount.bigDecimalValue().multiply(taxDetail.getTaxRate())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
161                }
162            }
163            return taxDetail;
164        }
165    
166        protected String truncatePostalCodeForSalesTaxRegionService(String postalCode) {
167            Integer digitsToUse = postalCodeDigitsToUse();
168            if (digitsToUse != null) {
169                return postalCode.substring(0, digitsToUse.intValue());
170            } else {
171                return postalCode; // unchanged
172            }
173        }
174        
175        protected Integer postalCodeDigitsToUse() {
176            String digits = parameterService.getParameterValue(SalesTax.class, 
177                    POSTAL_CODE_DIGITS_PASSED_TO_SALES_TAX_REGION_SERVICE);
178            if (StringUtils.isBlank(digits)) { return null; }
179            Integer digitsToUse;
180            try {
181                digitsToUse = new Integer(digits);
182            }
183            catch (NumberFormatException ex) {
184                throw new RuntimeException("The value returned for Parameter " + POSTAL_CODE_DIGITS_PASSED_TO_SALES_TAX_REGION_SERVICE + " was non-numeric and cannot be processed.", ex);
185            }
186            return digitsToUse;
187        }
188        
189        public TaxRegionService getTaxRegionService() {
190            return taxRegionService;
191        }
192    
193        public void setTaxRegionService(TaxRegionService taxRegionService) {
194            this.taxRegionService = taxRegionService;
195        }
196    
197        public void setParameterService(ParameterService parameterService) {
198            this.parameterService = parameterService;
199        }
200    }