/*
jBilling - The Enterprise Open Source Billing System
Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde
This file is part of jbilling.
jbilling 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.
jbilling 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 jbilling. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sapienter.jbilling.server.pluggableTask;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import com.sapienter.jbilling.server.item.db.ItemTypeDTO;
import com.sapienter.jbilling.server.order.OrderLineComparator;
import org.apache.log4j.Logger;
import com.sapienter.jbilling.server.item.ItemDecimalsException;
import com.sapienter.jbilling.server.item.db.ItemDAS;
import com.sapienter.jbilling.server.item.db.ItemDTO;
import com.sapienter.jbilling.server.order.db.OrderDTO;
import com.sapienter.jbilling.server.order.db.OrderLineDTO;
import com.sapienter.jbilling.server.util.Constants;
/**
* Basic tasks that takes the quantity and multiplies it by the price to
* get the lines total. It also updates the order total with the addition
* of all line totals
*
*/
public class BasicLineTotalTask extends PluggableTask implements OrderProcessingTask {
private static final Logger LOG = Logger.getLogger(BasicLineTotalTask.class);
private static final BigDecimal ONE_HUNDRED = new BigDecimal("100.00");
public void doProcessing(OrderDTO order) throws TaskException {
validateLinesQuantity(order.getLines());
clearLineTotals(order.getLines());
ItemDAS itemDas = new ItemDAS();
/*
Calculate non-percentage items, calculating price as $/unit
*/
for (OrderLineDTO line : order.getLines()) {
if (line.getDeleted() == 1 || line.getTotalReadOnly()) continue;
// calculate line total
ItemDTO item = itemDas.find(line.getItemId());
if (item != null && item.getPercentage() == null) {
line.setAmount(line.getQuantity().multiply(line.getPrice()));
LOG.debug("normal line total: "
+ line.getQuantity() + " x " + line.getPrice() + " = " + line.getAmount());
}
}
/*
Calculate non-tax percentage items (fees).
Percentages are not compounded and charged only on normal item lines
*/
for (OrderLineDTO line : order.getLines()) {
if (line.getDeleted() == 1 || line.getTotalReadOnly()) continue;
// calculate line total
ItemDTO percentageItem = itemDas.find(line.getItemId());
if (percentageItem != null
&& percentageItem.getPercentage() != null
&& !line.getTypeId().equals(Constants.ORDER_LINE_TYPE_TAX)) {
// sum of applicable item charges * percentage
BigDecimal total = getTotalForPercentage(order.getLines(), percentageItem.getExcludedTypes());
line.setAmount(line.getPrice().divide(ONE_HUNDRED, Constants.BIGDECIMAL_ROUND).multiply(total));
LOG.debug("percentage line total: %" + line.getPrice() + "; "
+ "( " + line.getPrice() + " / 100 ) x " + total + " = " + line.getAmount());
}
}
/*
Calculate tax percentage items.
Taxes are not compounded and charged on all normal item lines and non-tax percentage amounts (fees).
*/
for (OrderLineDTO line : order.getLines()) {
if (line.getDeleted() == 1 || line.getTotalReadOnly()) continue;
// calculate line total
ItemDTO taxItem = itemDas.find(line.getItemId());
if (taxItem != null
&& taxItem.getPercentage() != null
&& line.getTypeId().equals(Constants.ORDER_LINE_TYPE_TAX)) {
// sum of applicable item charges + fees * percentage
BigDecimal total = getTotalForTax(order.getLines(), taxItem.getExcludedTypes());
line.setAmount(line.getPrice().divide(ONE_HUNDRED, BigDecimal.ROUND_HALF_EVEN).multiply(total));
LOG.debug("tax line total: %" + line.getPrice() + "; "
+ "( " + line.getPrice() + " / 100 ) x " + total + " = " + line.getAmount());
}
}
// order total
order.setTotal(getTotal(order.getLines()));
LOG.debug("Order total = " + order.getTotal());
}
/**
* Returns the sum total amount of all lines with items that do NOT belong to the given excluded type list.
*
* This total only includes normal item lines and not tax or penalty lines.
*
* @param lines order lines
* @param excludedTypes excluded item types
* @return total amount
*/
public BigDecimal getTotalForPercentage(List<OrderLineDTO> lines, Set<ItemTypeDTO> excludedTypes) {
BigDecimal total = BigDecimal.ZERO;
for (OrderLineDTO line : lines) {
if (line.getDeleted() == 1) continue;
// add line total for non-percentage & non-tax lines
if (line.getItem().getPercentage() == null && line.getTypeId().equals(Constants.ORDER_LINE_TYPE_ITEM)) {
// add if type is not in the excluded list
if (!isItemExcluded(line.getItem(), excludedTypes)) {
total = total.add(line.getAmount());
} else {
LOG.debug("item " + line.getItem().getId() + " excluded from percentage.");
}
}
}
LOG.debug("total amount applicable for percentage: " + total);
return total;
}
/**
* Returns the sum total amount of all lines with items that do NOT belong to the given excluded type list.
*
* This total includes all non tax lines (i.e., normal items, percentage fees and penalty lines).
*
* @param lines order lines
* @param excludedTypes excluded item types
* @return total amount
*/
public BigDecimal getTotalForTax(List<OrderLineDTO> lines, Set<ItemTypeDTO> excludedTypes) {
BigDecimal total = BigDecimal.ZERO;
for (OrderLineDTO line : lines) {
if (line.getDeleted() == 1) continue;
// add line total for all non-tax items
if (!line.getTypeId().equals(Constants.ORDER_LINE_TYPE_TAX)) {
// add if type is not in the excluded list
if (!isItemExcluded(line.getItem(), excludedTypes)) {
total = total.add(line.getAmount());
} else {
LOG.debug("item " + line.getItem().getId() + " excluded from tax.");
}
}
}
LOG.debug("total amount applicable for tax: " + total);
return total;
}
/**
* Returns true if the item is in the excluded item type list.
*
* @param item item to check
* @param excludedTypes list of excluded item types
* @return true if item is excluded, false if not
*/
private boolean isItemExcluded(ItemDTO item, Set<ItemTypeDTO> excludedTypes) {
for (ItemTypeDTO excludedType : excludedTypes) {
for (ItemTypeDTO itemType : item.getItemTypes()) {
if (itemType.getId() == excludedType.getId()) {
return true;
}
}
}
return false;
}
/**
* Returns the total of all given order lines.
*
* @param lines order lines
* @return total amount
*/
public BigDecimal getTotal(List<OrderLineDTO> lines) {
BigDecimal total = BigDecimal.ZERO;
for (OrderLineDTO line : lines) {
if (line.getDeleted() == 1) continue;
// add total
total = total.add(line.getAmount());
}
return total;
}
/**
* Sets all order line amounts to null.
*
* @param lines order lines to clear
*/
public void clearLineTotals(List<OrderLineDTO> lines) {
for (OrderLineDTO line : lines) {
if (line.getDeleted() == 1) continue;
// clear amount
line.setAmount(null);
}
}
/**
* Validates that only order line items with {@link ItemDTO#hasDecimals} set to true has
* a decimal quantity.
*
* @param lines order lines to validate
* @throws TaskException thrown if an order line has decimals without the item hasDecimals flag
*/
public void validateLinesQuantity(List<OrderLineDTO> lines) throws TaskException {
for (OrderLineDTO line : lines) {
if (line.getDeleted() == 1) continue;
// validate line quantity
if (line.getItem() != null
&& line.getQuantity().remainder(Constants.BIGDECIMAL_ONE).compareTo(BigDecimal.ZERO) != 0.0
&& line.getItem().getHasDecimals() == 0) {
throw new TaskException(new ItemDecimalsException("Item does not allow Decimals"));
}
}
}
}