/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.service;
import static org.kuali.kfs.sys.fixture.UserNameFixture.khuntley;
import java.math.BigDecimal;
import java.util.List;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.businessobject.TaxDetail;
import org.kuali.kfs.sys.businessobject.TaxRegion;
import org.kuali.kfs.sys.businessobject.TaxRegionPostalCode;
import org.kuali.kfs.sys.businessobject.TaxRegionRate;
import org.kuali.kfs.sys.businessobject.TaxRegionState;
import org.kuali.kfs.sys.context.KualiTestBase;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.krad.service.BusinessObjectService;
/**
* This class tests the Tax Service
*/
@ConfigureContext(session = khuntley)
public class TaxServiceTest extends KualiTestBase {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TaxServiceTest.class);
protected TaxService taxService;
protected DateTimeService dateTimeService;
protected BusinessObjectService businessObjectService;
protected final static String DATE_OF_TRANSACTION = "01/02/2008";
protected final static KualiDecimal AMOUNT = new KualiDecimal(100);
@Override
protected void setUp() throws Exception {
super.setUp();
taxService = SpringContext.getBean(TaxService.class);
dateTimeService = SpringContext.getBean(DateTimeService.class);
businessObjectService = SpringContext.getBean(BusinessObjectService.class);
}
/**
* This method tests that the sales tax details get set correctly
*
* @throws Exception
*/
public void testGetSalesTaxDetails() throws Exception {
TaxRegion taxRegion = TaxRegionFixture.TAX_REGION_NO_USE_TAX.createTaxRegion(new TaxRegionRateFixture[] { TaxRegionRateFixture.TAX_REGION_RATE_05 }, new TaxRegionPostalCodeFixture[] { TaxRegionPostalCodeFixture.PO_46113 }, null);
businessObjectService.save(taxRegion);
List<TaxDetail> taxDetails = taxService.getSalesTaxDetails(dateTimeService.convertToSqlDate(DATE_OF_TRANSACTION), TaxRegionPostalCodeFixture.PO_46113.postalCode, AMOUNT);
// there should only be one tax detail
assertEquals( "wrong number of details retrieved", 1, taxDetails.size());
// verify that tax detail was set correctly
TaxDetail taxDetail = taxDetails.get(0);
assertEquals( "code mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.taxRegionCode, taxDetail.getRateCode());
assertEquals( "name mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.taxRegionName, taxDetail.getRateName());
assertEquals( "chart mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.chartOfAccountsCode, taxDetail.getChartOfAccountsCode());
assertEquals( "account mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.accountNumber, taxDetail.getAccountNumber());
assertEquals( "object mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.financialObjectCode, taxDetail.getFinancialObjectCode());
assertEquals( "type code mismatch", TaxRegionFixture.TAX_REGION_NO_USE_TAX.taxRegionTypeCode, taxDetail.getTypeCode());
assertEquals( "tax rate mismatch", TaxRegionRateFixture.TAX_REGION_RATE_05.taxRate, taxDetail.getTaxRate());
assertEquals( "tax amount mismatch", AMOUNT.multiply(new KualiDecimal(TaxRegionRateFixture.TAX_REGION_RATE_05.taxRate)), taxDetail.getTaxAmount());
}
/**
* This method tests that only tax details are included that are tied to tax regions with the use tax indicator set to true
*
* @throws Exception
*/
public void testGetUseTaxDetails() throws Exception {
TaxRegion taxRegionNoUseTax = TaxRegionFixture.TAX_REGION_NO_USE_TAX.createTaxRegion(new TaxRegionRateFixture[] { TaxRegionRateFixture.TAX_REGION_RATE_05 }, new TaxRegionPostalCodeFixture[] { TaxRegionPostalCodeFixture.PO_46113 }, null);
TaxRegion taxRegionUseTax = TaxRegionFixture.TAX_REGION_WITH_USE_TAX.createTaxRegion(new TaxRegionRateFixture[] { TaxRegionRateFixture.TAX_REGION_RATE_05 }, new TaxRegionPostalCodeFixture[] { TaxRegionPostalCodeFixture.PO_46113 }, null);
businessObjectService.save(taxRegionNoUseTax);
businessObjectService.save(taxRegionUseTax);
List<TaxDetail> taxDetails = taxService.getUseTaxDetails(dateTimeService.convertToSqlDate(DATE_OF_TRANSACTION), TaxRegionPostalCodeFixture.PO_46113.postalCode, AMOUNT);
// there should only be one use tax detail, even though there are 2 tax regions tied to the same postal code
assertEquals( "wrong number of details retrieved", 1, taxDetails.size());
// verify that use tax detail was set correctly
TaxDetail taxDetail = taxDetails.get(0);
assertEquals( "code mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.taxRegionCode, taxDetail.getRateCode());
assertEquals( "name mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.taxRegionName, taxDetail.getRateName());
assertEquals( "chart mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.chartOfAccountsCode, taxDetail.getChartOfAccountsCode());
assertEquals( "account mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.accountNumber, taxDetail.getAccountNumber());
assertEquals( "object mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.financialObjectCode, taxDetail.getFinancialObjectCode());
assertEquals( "type code mismatch", TaxRegionFixture.TAX_REGION_WITH_USE_TAX.taxRegionTypeCode, taxDetail.getTypeCode());
assertEquals( "tax rate mismatch", TaxRegionRateFixture.TAX_REGION_RATE_05.taxRate, taxDetail.getTaxRate());
assertEquals( "tax amount mismatch", AMOUNT.multiply(new KualiDecimal(TaxRegionRateFixture.TAX_REGION_RATE_05.taxRate)), taxDetail.getTaxAmount());
}
/**
* This method tests that the total sales tax amount is correct.
*/
public void testGetTotalSalesTaxAmount() throws Exception {
TaxRegion taxRegionPostalCode = TaxRegionFixture.TAX_REGION_NO_USE_TAX.createTaxRegion(new TaxRegionRateFixture[] { TaxRegionRateFixture.TAX_REGION_RATE_05 }, new TaxRegionPostalCodeFixture[] { TaxRegionPostalCodeFixture.PO_46113 }, null);
TaxRegion taxRegionState = TaxRegionFixture.TAX_REGION_WITH_USE_TAX.createTaxRegion(new TaxRegionRateFixture[] { TaxRegionRateFixture.TAX_REGION_RATE_07 }, null, new TaxRegionStateFixture[] { TaxRegionStateFixture.IN });
businessObjectService.save(taxRegionPostalCode);
businessObjectService.save(taxRegionState);
//total sales tax amount
KualiDecimal totalSalesTaxAmount = taxService.getTotalSalesTaxAmount(dateTimeService.convertToSqlDate(DATE_OF_TRANSACTION), TaxRegionPostalCodeFixture.PO_46113.postalCode, AMOUNT);
BigDecimal totalTaxRate = TaxRegionRateFixture.TAX_REGION_RATE_05.taxRate.add(TaxRegionRateFixture.TAX_REGION_RATE_07.taxRate);
assertEquals( "tax amount mismatch", AMOUNT.multiply(new KualiDecimal(totalTaxRate)), totalSalesTaxAmount );
}
/**
* This class is a fixture for creating tax regions
*/
private enum TaxRegionFixture {
TAX_REGION_NO_USE_TAX("NOUSETAX", "NOUSETAX", "POST", "BL", "1031400", "1500", false, true),
TAX_REGION_WITH_USE_TAX("USETAX", "USETAX", "ST", "BA", "6044900", "5387", true, true), ;
public String taxRegionCode;
public String taxRegionName;
public String taxRegionTypeCode;
public String chartOfAccountsCode;
public String accountNumber;
public String financialObjectCode;
public boolean taxRegionUseTaxIndicator;
public boolean active;
private TaxRegionFixture(String taxRegionCode, String taxRegionName, String taxRegionTypeCode, String chartOfAccountsCode, String accountNumber, String financialObjectCode, boolean taxRegionUseTaxIndicator, boolean active) {
this.taxRegionCode = taxRegionCode;
this.taxRegionName = taxRegionName;
this.taxRegionTypeCode = taxRegionTypeCode;
this.chartOfAccountsCode = chartOfAccountsCode;
this.accountNumber = accountNumber;
this.financialObjectCode = financialObjectCode;
this.taxRegionUseTaxIndicator = taxRegionUseTaxIndicator;
this.active = active;
}
public TaxRegion createTaxRegion(TaxRegionRateFixture[] taxRegionRateFixtures, TaxRegionPostalCodeFixture[] taxRegionPostalCodeFixtures, TaxRegionStateFixture[] taxRegionStateFixtures) throws Exception {
TaxRegion taxRegion = new TaxRegion();
taxRegion.setTaxRegionCode(this.taxRegionCode);
taxRegion.setTaxRegionName(this.taxRegionName);
taxRegion.setTaxRegionTypeCode(this.taxRegionTypeCode);
taxRegion.setChartOfAccountsCode(this.chartOfAccountsCode);
taxRegion.setAccountNumber(this.accountNumber);
taxRegion.setFinancialObjectCode(this.financialObjectCode);
taxRegion.setTaxRegionUseTaxIndicator(this.taxRegionUseTaxIndicator);
taxRegion.setActive(this.active);
if (taxRegionRateFixtures != null) {
for (TaxRegionRateFixture taxRegionRateFixture : taxRegionRateFixtures) {
taxRegionRateFixture.addTo(taxRegion);
}
}
if (taxRegionPostalCodeFixtures != null) {
for (TaxRegionPostalCodeFixture taxRegionPostalCodeFixture : taxRegionPostalCodeFixtures) {
taxRegionPostalCodeFixture.addTo(taxRegion);
}
}
if (taxRegionStateFixtures != null) {
for (TaxRegionStateFixture taxRegionStateFixture : taxRegionStateFixtures) {
taxRegionStateFixture.addTo(taxRegion);
}
}
return taxRegion;
}
}
/**
* This class is a fixture for creating tax region rates
*/
private enum TaxRegionRateFixture {
TAX_REGION_RATE_05(DATE_OF_TRANSACTION, new BigDecimal("0.05")),
TAX_REGION_RATE_07(DATE_OF_TRANSACTION, new BigDecimal("0.07")), ;
public String effectiveDate;
public BigDecimal taxRate;
private TaxRegionRateFixture(String effectiveDate, BigDecimal taxRate) {
this.effectiveDate = effectiveDate;
this.taxRate = taxRate;
}
public TaxRegionRate createTaxRegionRate() throws Exception {
TaxRegionRate taxRegionRate = new TaxRegionRate();
taxRegionRate.setTaxRate(this.taxRate);
taxRegionRate.setEffectiveDate(SpringContext.getBean(DateTimeService.class).convertToSqlDate(this.effectiveDate));
return taxRegionRate;
}
public void addTo(TaxRegion taxRegion) throws Exception {
TaxRegionRate taxRegionRate = createTaxRegionRate();
taxRegionRate.setTaxRegionCode(taxRegion.getTaxRegionCode());
taxRegion.getTaxRegionRates().add(taxRegionRate);
}
}
private enum TaxRegionPostalCodeFixture {
PO_46113("46113", "US", true), ;
public String postalCode;
public String countryCode;
public boolean active;
private TaxRegionPostalCodeFixture(String postalCode, String countryCode, boolean active) {
this.postalCode = postalCode;
this.countryCode = countryCode;
this.active = active;
}
public TaxRegionPostalCode createTaxRegionPostalCode() {
TaxRegionPostalCode taxRegionPostalCode = new TaxRegionPostalCode();
taxRegionPostalCode.setPostalCode(this.postalCode);
taxRegionPostalCode.setPostalCountryCode(this.countryCode);
taxRegionPostalCode.setActive(this.active);
return taxRegionPostalCode;
}
public void addTo(TaxRegion taxRegion) {
TaxRegionPostalCode taxRegionPostalCode = this.createTaxRegionPostalCode();
taxRegionPostalCode.setTaxRegionCode(taxRegion.getTaxRegionCode());
taxRegion.getTaxRegionPostalCodes().add(taxRegionPostalCode);
}
}
private enum TaxRegionStateFixture {
IN("IN", "US", true), ;
public String stateCode;
public String countryCode;
public boolean active;
private TaxRegionStateFixture(String stateCode, String countryCode, boolean active) {
this.stateCode = stateCode;
this.countryCode = countryCode;
this.active = active;
}
public TaxRegionState createTaxRegionState() {
TaxRegionState taxRegionState = new TaxRegionState();
taxRegionState.setStateCode(this.stateCode);
taxRegionState.setPostalCountryCode(this.countryCode);
taxRegionState.setActive(this.active);
return taxRegionState;
}
public void addTo(TaxRegion taxRegion) {
TaxRegionState taxRegionState = this.createTaxRegionState();
taxRegionState.setTaxRegionCode(taxRegion.getTaxRegionCode());
taxRegion.getTaxRegionStates().add(taxRegionState);
}
}
}