/*
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.item;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.sapienter.jbilling.server.item.event.ItemDeletedEvent;
import com.sapienter.jbilling.server.item.event.ItemUpdatedEvent;
import com.sapienter.jbilling.server.item.event.NewItemEvent;
import com.sapienter.jbilling.server.order.db.OrderDTO;
import com.sapienter.jbilling.server.system.event.EventManager;
import org.apache.log4j.Logger;
import org.springmodules.cache.provider.CacheProviderFacade;
import org.springmodules.cache.CachingModel;
import org.springmodules.cache.FlushingModel;
import com.sapienter.jbilling.common.SessionInternalError;
import com.sapienter.jbilling.server.item.db.ItemDAS;
import com.sapienter.jbilling.server.item.db.ItemDTO;
import com.sapienter.jbilling.server.item.db.ItemPriceDAS;
import com.sapienter.jbilling.server.item.db.ItemPriceDTO;
import com.sapienter.jbilling.server.item.db.ItemTypeDTO;
import com.sapienter.jbilling.server.item.tasks.IPricing;
import com.sapienter.jbilling.server.order.db.OrderLineDAS;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager;
import com.sapienter.jbilling.server.user.EntityBL;
import com.sapienter.jbilling.server.user.UserBL;
import com.sapienter.jbilling.server.user.db.CompanyDAS;
import com.sapienter.jbilling.server.user.db.CompanyDTO;
import com.sapienter.jbilling.server.util.Constants;
import com.sapienter.jbilling.server.util.Context;
import com.sapienter.jbilling.server.util.audit.EventLogger;
import com.sapienter.jbilling.server.util.db.CurrencyDAS;
import com.sapienter.jbilling.server.util.db.CurrencyDTO;
import java.math.BigDecimal;
import java.util.ArrayList;
public class ItemBL {
private static final Logger LOG = Logger.getLogger(ItemBL.class);
private ItemDAS itemDas = null;
private ItemDTO item = null;
private EventLogger eLogger = null;
private String priceCurrencySymbol = null;
private List<PricingField> pricingFields = null;
// item price cache for getPrice()
private CacheProviderFacade cache;
private CachingModel cacheModel;
private FlushingModel flushModel;
public ItemBL(Integer itemId)
throws SessionInternalError {
try {
init();
set(itemId);
} catch (Exception e) {
throw new SessionInternalError("Setting item", ItemBL.class, e);
}
}
public ItemBL() {
init();
}
public ItemBL(ItemDTO item) {
this.item = item;
init();
}
public void set(Integer itemId) {
item = itemDas.find(itemId);
}
private void init() {
eLogger = EventLogger.getInstance();
itemDas = new ItemDAS();
cache = (CacheProviderFacade) Context.getBean(Context.Name.CACHE);
cacheModel = (CachingModel) Context.getBean(
Context.Name.CACHE_MODEL_ITEM_PRICE);
flushModel = (FlushingModel) Context.getBean(
Context.Name.CACHE_FLUSH_MODEL_ITEM_PRICE);
}
public ItemDTO getEntity() {
return item;
}
public Integer create(ItemDTO dto, Integer languageId) {
EntityBL entity = new EntityBL(dto.getEntityId());
if (languageId == null) {
languageId = entity.getEntity().getLanguageId();
}
if (dto.getHasDecimals() != null) {
dto.setHasDecimals(dto.getHasDecimals());
} else {
dto.setHasDecimals(0);
}
dto.setDeleted(0);
item = itemDas.save(dto);
item.setDescription(dto.getDescription(), languageId);
updateTypes(dto);
updateExcludedTypes(dto);
updateCurrencies(dto);
// trigger internal event
EventManager.process(new NewItemEvent(item));
return item.getId();
}
public void update(Integer executorId, ItemDTO dto, Integer languageId) {
eLogger.audit(executorId, null, Constants.TABLE_ITEM, item.getId(),
EventLogger.MODULE_ITEM_MAINTENANCE,
EventLogger.ROW_UPDATED, null, null, null);
item.setNumber(dto.getNumber());
item.setPriceManual(dto.getPriceManual());
item.setDescription(dto.getDescription(), languageId);
item.setPercentage(dto.getPercentage());
item.setHasDecimals(dto.getHasDecimals());
updateTypes(dto);
updateExcludedTypes(dto);
if (item.getPercentage() == null) {
// update price currencies
updateCurrencies(dto);
} else {
// percentage items shouldn't have a price, remove all old prices
for (ItemPriceDTO price : item.getItemPrices()) {
new ItemPriceDAS().delete(price);
}
item.getItemPrices().clear();
}
itemDas.save(item);
invalidateCache();
// trigger internal event
EventManager.process(new ItemUpdatedEvent(item));
}
private void updateTypes(ItemDTO dto)
{
// update the types relationship
Collection types = item.getItemTypes();
types.clear();
ItemTypeBL typeBl = new ItemTypeBL();
// TODO verify that all the categories belong to the same
// order_line_type_id
for (int f=0; f < dto.getTypes().length; f++) {
typeBl.set(dto.getTypes()[f]);
types.add(typeBl.getEntity());
}
}
private void updateExcludedTypes(ItemDTO dto) {
item.getExcludedTypes().clear();
ItemTypeBL itemType = new ItemTypeBL();
for (Integer typeId : dto.getExcludedTypeIds()) {
itemType.set(typeId);
item.getExcludedTypes().add(itemType.getEntity());
}
}
private void updateCurrencies(ItemDTO dto) {
LOG.debug("updating prices. prices " + (dto.getPrices() != null) +
" price = " + dto.getPrice());
ItemPriceDAS itemPriceDas = new ItemPriceDAS();
// may be there's just one simple price
if (dto.getPrices() == null) {
if (dto.getPrice() != null) {
List prices = new ArrayList();
// get the defualt currency of the entity
CurrencyDTO currency = new CurrencyDAS().findNow(
dto.getCurrencyId());
if (currency == null) {
EntityBL entity = new EntityBL(dto.getEntityId());
currency = entity.getEntity().getCurrency();
}
ItemPriceDTO price = new ItemPriceDTO(null, dto, dto.getPrice(),
currency);
prices.add(price);
dto.setPrices(prices);
} else {
LOG.warn("updatedCurrencies was called, but this " +
"item has no price");
return;
}
}
// a call to clear() would simply set item_price.entity_id = null
// instead of removing the row
for (int f = 0; f < dto.getPrices().size(); f++) {
ItemPriceDTO price = (ItemPriceDTO) dto.getPrices().get(f);
ItemPriceDTO priceRow = null;
priceRow = itemPriceDas.find(dto.getId(),
price.getCurrencyId());
if (price.getPrice() != null) {
if (priceRow != null) {
// if there one there already, update it
priceRow.setPrice(price.getPrice());
} else {
// nothing there, create one
ItemPriceDTO itemPrice= new ItemPriceDTO(null, item,
price.getPrice(), price.getCurrency());
item.getItemPrices().add(itemPrice);
}
} else {
// this price should be removed if it is there
if (priceRow != null) {
itemPriceDas.delete(priceRow);
}
}
}
// invalidate item/price cache
invalidateCache();
}
public void delete(Integer executorId) {
item.setDeleted(new Integer(1));
eLogger.audit(executorId, null, Constants.TABLE_ITEM, item.getId(),
EventLogger.MODULE_ITEM_MAINTENANCE,
EventLogger.ROW_DELETED, null, null, null);
// trigger internal event
EventManager.process(new ItemDeletedEvent(item));
itemDas.flush();
itemDas.clear();
}
public boolean validateDecimals( Integer hasDecimals ){
if( hasDecimals == 0 ){
if(new OrderLineDAS().findLinesWithDecimals(item.getId()) > 0) {
return false;
}
}
return true;
}
/**
* This is the basic price, without any plug-ins applied.
* It only takes into account the currency and makes the necessary
* conversions.
* It uses a cache to avoid repeating this look-up too often
* @return The price in the requested currency
*/
public BigDecimal getPriceByCurrency(ItemDTO item, Integer entityId, Integer currencyId) {
BigDecimal retValue = null;
// try to get cached item price for this currency
retValue = (BigDecimal) cache.getFromCache(item.getId() +
currencyId.toString(), cacheModel);
if (retValue != null) {
return retValue;
}
// get the item's defualt price
int prices = 0;
BigDecimal aPrice = null;
Integer aCurrency = null;
// may be the item has a price in this currency
for (Iterator it = item.getItemPrices().iterator(); it.hasNext(); ) {
prices++;
ItemPriceDTO price = (ItemPriceDTO) it.next();
if (price.getCurrencyId().equals(currencyId)) {
// it is there!
retValue = price.getPrice();
break;
} else {
// the pivot has priority, for a more accurate conversion
if (aCurrency == null || aCurrency.intValue() != 1) {
aPrice = price.getPrice();
aCurrency = price.getCurrencyId();
}
}
}
if (prices > 0 && retValue == null) {
// there are prices defined, but not for the currency required
try {
CurrencyBL currencyBL = new CurrencyBL();
retValue = currencyBL.convert(aCurrency, currencyId, aPrice, entityId);
} catch (Exception e) {
throw new SessionInternalError(e);
}
} else {
if (retValue == null) {
throw new SessionInternalError("No price defined for item " +
item.getId());
}
}
cache.putInCache(item.getId() + currencyId.toString(), cacheModel,
retValue);
return retValue;
}
public BigDecimal getPrice(Integer userId, BigDecimal quantity, Integer entityId) throws SessionInternalError {
UserBL user = new UserBL(userId);
return getPrice(userId, user.getCurrencyId(), quantity, entityId, null);
}
public BigDecimal getPrice(Integer userId, Integer currencyId, BigDecimal quantity, Integer entityId) throws SessionInternalError {
UserBL user = new UserBL(userId);
return getPrice(userId, currencyId, quantity, entityId, null);
}
/**
* Will find the right price considering the user's special prices and which
* currencies had been entered in the prices table.
*
* @param userId user id
* @param currencyId currency id
* @param entityId entity id
* @param order order being created or edited, maybe used for additional pricing calculations
* @return The price in the requested currency. It always returns a price,
* otherwise an exception for lack of pricing for an item
*/
public BigDecimal getPrice(Integer userId, Integer currencyId, BigDecimal quantity, Integer entityId, OrderDTO order)
throws SessionInternalError {
if (currencyId == null || entityId == null) {
throw new SessionInternalError("Can't get a price with null parameters. "
+ "currencyId = " + currencyId
+ " entityId = " + entityId);
}
CurrencyBL currencyBL;
try {
currencyBL = new CurrencyBL(currencyId);
priceCurrencySymbol = currencyBL.getEntity().getSymbol();
} catch (Exception e) {
throw new SessionInternalError(e);
}
// default "simple" price
BigDecimal price = getPriceByCurrency(item, entityId, currencyId);
// run a plug-in with external logic (rules), if available
try {
PluggableTaskManager<IPricing> taskManager
= new PluggableTaskManager<IPricing>(entityId, Constants.PLUGGABLE_TASK_ITEM_PRICING);
IPricing myTask = taskManager.getNextClass();
while(myTask != null) {
price = myTask.getPrice(item.getId(), quantity, userId, currencyId, pricingFields, price, order);
myTask = taskManager.getNextClass();
}
} catch (Exception e) {
throw new SessionInternalError("Item pricing task error", ItemBL.class, e);
}
return price;
}
/**
* Returns an ItemDTO constructed for the given language and entity, priced for the
* given user and currency.
*
* @param languageId id of the users language
* @param userId id of the user purchasing the item
* @param entityId id of the entity
* @param currencyId id of the currency
* @return item dto
* @throws SessionInternalError if an internal exception occurs processing request
*/
public ItemDTO getDTO(Integer languageId, Integer userId, Integer entityId, Integer currencyId)
throws SessionInternalError {
return getDTO(languageId, userId, entityId, currencyId, BigDecimal.ONE, null);
}
/**
* Returns an ItemDTO constructed for the given language and entity, priced for the
* given user, currency and the amount being purchased.
*
* @param languageId id of the users language
* @param userId id of the user purchasing the item
* @param entityId id of the entity
* @param currencyId id of the currency
* @param quantity quantity being purchased
* @return item dto
* @throws SessionInternalError if an internal exception occurs processing request
*/
public ItemDTO getDTO(Integer languageId, Integer userId, Integer entityId, Integer currencyId, BigDecimal quantity)
throws SessionInternalError {
return getDTO(languageId, userId, entityId, currencyId, quantity, null);
}
/**
* Returns an ItemDTO constructed for the given language and entity, priced for the
* given user, currency and the amount being purchased.
*
* @param languageId id of the users language
* @param userId id of the user purchasing the item
* @param entityId id of the entity
* @param currencyId id of the currency
* @param quantity quantity being purchased
* @param order order that this item is to be added to. may be null if no order operation.
* @return item dto
* @throws SessionInternalError if an internal exception occurs processing request
*/
public ItemDTO getDTO(Integer languageId, Integer userId, Integer entityId, Integer currencyId, BigDecimal quantity,
OrderDTO order) throws SessionInternalError {
ItemDTO dto = new ItemDTO(
item.getId(),
item.getInternalNumber(),
item.getGlCode(),
item.getEntity(),
item.getDescription(languageId),
item.getPriceManual(),
item.getDeleted(),
currencyId,
null,
item.getPercentage(),
null, // to be set right after
item.getHasDecimals() );
// add all the prices for each currency
// if this is a percenteage, we still need an array with empty prices
dto.setPrices(findPrices(entityId, languageId));
if (currencyId != null && dto.getPercentage() == null) {
dto.setPrice(getPrice(userId, currencyId, quantity, entityId, order));
}
// set the types
Integer types[] = new Integer[item.getItemTypes().size()];
int n = 0;
for (ItemTypeDTO type : item.getItemTypes()) {
types[n++] = type.getId();
dto.setOrderLineTypeId(type.getOrderLineTypeId());
}
dto.setTypes(types);
// set excluded types
Integer excludedTypes[] = new Integer[item.getExcludedTypes().size()];
int i = 0;
for (ItemTypeDTO type : item.getExcludedTypes()) {
excludedTypes[i++] = type.getId();
}
dto.setExcludedTypeIds(excludedTypes);
LOG.debug("Got item: " + dto.getId() + ", price: " + dto.getPrice());
return dto;
}
public ItemDTO getDTO(ItemDTOEx other) {
ItemDTO retValue = new ItemDTO();
if (other.getId() != null) {
retValue.setId(other.getId());
}
retValue.setEntity(new CompanyDAS().find(other.getEntityId()));
retValue.setNumber(other.getNumber());
retValue.setGlCode(other.getGlCode());
retValue.setPercentage(other.getPercentageAsDecimal());
retValue.setPriceManual(other.getPriceManual());
retValue.setDeleted(other.getDeleted());
retValue.setHasDecimals(other.getHasDecimals());
retValue.setDescription(other.getDescription());
retValue.setTypes(other.getTypes());
retValue.setExcludedTypeIds(other.getExcludedTypes());
retValue.setPromoCode(other.getPromoCode());
retValue.setCurrencyId(other.getCurrencyId());
retValue.setPrice(other.getPriceAsDecimal());
retValue.setOrderLineTypeId(other.getOrderLineTypeId());
// convert prices between DTO and DTOEx (WS)
List otherPrices = other.getPrices();
if (otherPrices != null) {
List prices = new ArrayList(otherPrices.size());
for (int i = 0; i < otherPrices.size(); i++) {
ItemPriceDTO itemPrice = new ItemPriceDTO();
ItemPriceDTOEx otherPrice = (ItemPriceDTOEx) otherPrices.get(i);
itemPrice.setId(otherPrice.getId());
itemPrice.setCurrency(new CurrencyDAS().find(
otherPrice.getCurrencyId()));
itemPrice.setPrice(otherPrice.getPriceAsDecimal());
itemPrice.setName(otherPrice.getName());
itemPrice.setPriceForm(otherPrice.getPriceForm());
prices.add(itemPrice);
}
retValue.setPrices(prices);
}
return retValue;
}
public ItemDTOEx getWS(ItemDTO other) {
if (other == null) {
other = item;
}
ItemDTOEx retValue = new ItemDTOEx();
retValue.setId(other.getId());
retValue.setEntityId(other.getEntity().getId());
retValue.setNumber(other.getInternalNumber());
retValue.setGlCode(other.getGlCode());
retValue.setPercentage(other.getPercentage());
retValue.setPriceManual(other.getPriceManual());
retValue.setDeleted(other.getDeleted());
retValue.setHasDecimals(other.getHasDecimals());
retValue.setDescription(other.getDescription());
retValue.setTypes(other.getTypes());
retValue.setExcludedTypes(other.getExcludedTypeIds());
retValue.setPromoCode(other.getPromoCode());
retValue.setCurrencyId(other.getCurrencyId());
retValue.setPrice(other.getPrice());
retValue.setOrderLineTypeId(other.getOrderLineTypeId());
retValue.setPrices(other.getPrices());
// convert prices between DTOEx (WS) and DTO
List otherPrices = other.getPrices();
if (otherPrices != null) {
List prices = new ArrayList(otherPrices.size());
for (int i = 0; i < otherPrices.size(); i++) {
ItemPriceDTOEx itemPrice = new ItemPriceDTOEx();
ItemPriceDTO otherPrice = (ItemPriceDTO) otherPrices.get(i);
itemPrice.setId(otherPrice.getId());
itemPrice.setCurrencyId(otherPrice.getCurrency().getId());
itemPrice.setPrice(otherPrice.getPrice());
itemPrice.setName(otherPrice.getName());
itemPrice.setPriceForm(otherPrice.getPriceForm());
prices.add(itemPrice);
}
retValue.setPrices(prices);
}
return retValue;
}
/**
* This method will try to find a currency id for this item. It will
* give priority to the entity's default currency, otherwise anyone
* will do.
* @return
*/
private List findPrices(Integer entityId, Integer languageId) {
List retValue = new ArrayList();
// go over all the curencies of this entity
for (CurrencyDTO currency: item.getEntity().getCurrencies()) {
ItemPriceDTO price = new ItemPriceDTO();
price.setCurrency(currency);
price.setName(currency.getDescription(languageId));
// se if there's a price in this currency
ItemPriceDTO priceRow = new ItemPriceDAS().find(
item.getId(),currency.getId());
if (priceRow != null) {
price.setPrice(priceRow.getPrice());
price.setPriceForm(price.getPrice().toString());
}
retValue.add(price);
}
return retValue;
}
/**
* @return
*/
public String getPriceCurrencySymbol() {
return priceCurrencySymbol;
}
/**
* Returns all items for the given entity.
* @param entityId
* The id of the entity.
* @return an array of all items
*/
public ItemDTOEx[] getAllItems(Integer entityId) {
EntityBL entityBL = new EntityBL(entityId);
CompanyDTO entity = entityBL.getEntity();
Collection itemEntities = entity.getItems();
ItemDTOEx[] items = new ItemDTOEx[itemEntities.size()];
// iterate through returned item entities, converting them into a DTO
int index = 0;
for (ItemDTO item: entity.getItems()) {
set(item.getId());
items[index++] = getWS(getDTO(entity.getLanguageId(),
null, entityId, entity.getCurrencyId()));
}
return items;
}
/**
* Returns all items for the given item type (category) id. If no results
* are found an empty array is returned.
*
* @see ItemDAS#findAllByItemType(Integer)
*
* @param itemTypeId item type (category) id
* @return array of found items, empty if none found
*/
public ItemDTOEx[] getAllItemsByType(Integer itemTypeId) {
List<ItemDTO> results = new ItemDAS().findAllByItemType(itemTypeId);
ItemDTOEx[] items = new ItemDTOEx[results.size()];
int index = 0;
for (ItemDTO item : results)
items[index++] = getWS(item);
return items;
}
public void setPricingFields(List<PricingField> fields) {
pricingFields = fields;
}
public void invalidateCache() {
cache.flushCache(flushModel);
}
}