org.openbravo.costing.CostingUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.costing.CostingUtils.java

Source

/*
 *************************************************************************
 * The contents of this file are subject to the Openbravo  Public  License
 * Version  1.0  (the  "License"),  being   the  Mozilla   Public  License
 * Version 1.1  with a permitted attribution clause; you may not  use this
 * file except in compliance with the License. You  may  obtain  a copy of
 * the License at http://www.openbravo.com/legal/license.html
 * Software distributed under the License  is  distributed  on  an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific  language  governing  rights  and  limitations
 * under the License.
 * The Original Code is Openbravo ERP.
 * The Initial Developer of the Original Code is Openbravo SLU
 * All portions are Copyright (C) 2012-2015 Openbravo SLU
 * All Rights Reserved.
 * Contributor(s):  ______________________________________.
 *************************************************************************
 */
package org.openbravo.costing;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletException;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.criterion.Restrictions;
import org.openbravo.base.exception.OBException;
import org.openbravo.base.session.OBPropertiesProvider;
import org.openbravo.base.structure.BaseOBObject;
import org.openbravo.costing.CostingAlgorithm.CostDimension;
import org.openbravo.costing.CostingServer.TrxType;
import org.openbravo.dal.core.DalUtil;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.security.OrganizationStructureProvider;
import org.openbravo.dal.service.OBCriteria;
import org.openbravo.dal.service.OBDal;
import org.openbravo.dal.service.OBQuery;
import org.openbravo.erpCommon.utility.OBDateUtils;
import org.openbravo.erpCommon.utility.OBError;
import org.openbravo.erpCommon.utility.OBMessageUtils;
import org.openbravo.financial.FinancialUtils;
import org.openbravo.model.common.businesspartner.BusinessPartner;
import org.openbravo.model.common.currency.Currency;
import org.openbravo.model.common.enterprise.DocumentType;
import org.openbravo.model.common.enterprise.Locator;
import org.openbravo.model.common.enterprise.Organization;
import org.openbravo.model.common.enterprise.Warehouse;
import org.openbravo.model.common.order.Order;
import org.openbravo.model.common.order.OrderLine;
import org.openbravo.model.common.plm.Product;
import org.openbravo.model.financialmgmt.calendar.Period;
import org.openbravo.model.materialmgmt.cost.Costing;
import org.openbravo.model.materialmgmt.cost.CostingRule;
import org.openbravo.model.materialmgmt.cost.InvAmtUpdLnInventories;
import org.openbravo.model.materialmgmt.cost.TransactionCost;
import org.openbravo.model.materialmgmt.transaction.InventoryCount;
import org.openbravo.model.materialmgmt.transaction.InventoryCountLine;
import org.openbravo.model.materialmgmt.transaction.MaterialTransaction;
import org.openbravo.model.materialmgmt.transaction.ShipmentInOut;
import org.openbravo.model.materialmgmt.transaction.ShipmentInOutLine;
import org.openbravo.model.pricing.pricelist.PriceList;
import org.openbravo.model.pricing.pricelist.ProductPrice;
import org.openbravo.service.db.DalConnectionProvider;

public class CostingUtils {
    private static Logger log4j = Logger.getLogger(CostingUtils.class);

    /**
     * Calls {@link #getTransactionCost(MaterialTransaction, Date, boolean, Currency)} setting the
     * calculateTrx flag to false.
     */
    public static BigDecimal getTransactionCost(MaterialTransaction transaction, Date date, Currency currency) {
        return getTransactionCost(transaction, date, false, currency);
    }

    /**
     * Calculates the total cost amount of a transaction including the cost adjustments done until the
     * given date.
     * 
     * @param transaction
     *          MaterialTransaction to get its cost.
     * @param date
     *          The Date it is desired to know the cost.
     * @param calculateTrx
     *          boolean flag to force the calculation of the transaction cost if it is not calculated.
     * @param currency
     *          The Currency to calculate the amount.
     * @return The total cost amount.
     */
    public static BigDecimal getTransactionCost(MaterialTransaction transaction, Date date, boolean calculateTrx,
            Currency currency) {
        log4j.debug("Get Transaction Cost");
        OBError result = new OBError();
        try {
            OBContext.setAdminMode(true);
            result.setType("Success");
            result.setTitle(OBMessageUtils.messageBD("Success"));
            if (!transaction.isCostCalculated()) {
                // Transaction hasn't been calculated yet.
                if (calculateTrx) {
                    log4j.debug("  *** Cost for transaction will be calculated." + transaction.getIdentifier());
                    CostingServer transactionCost = new CostingServer(transaction);
                    transactionCost.process();
                    return transactionCost.getTransactionCost();
                }
                log4j.error("  *** No cost found for transaction " + transaction.getIdentifier() + " with id "
                        + transaction.getId() + " on date " + OBDateUtils.formatDate(date));
                throw new OBException("@NoCostFoundForTrxOnDate@ @Transaction@: " + transaction.getIdentifier()
                        + " @Date@ " + OBDateUtils.formatDate(date));
            }
            BigDecimal cost = BigDecimal.ZERO;
            for (TransactionCost trxCost : transaction.getTransactionCostList()) {
                if (!trxCost.getCostDate().after(date)) {
                    cost = cost.add(FinancialUtils.getConvertedAmount(trxCost.getCost(), trxCost.getCurrency(),
                            currency, trxCost.getCostDate(), trxCost.getOrganization(),
                            FinancialUtils.PRECISION_COSTING));
                }
            }
            return cost;
        } catch (OBException e) {
            OBDal.getInstance().rollbackAndClose();
            log4j.error(result.getMessage(), e);
            return null;
        } catch (Exception e) {
            OBDal.getInstance().rollbackAndClose();
            log4j.error(result.getMessage(), e);
            return null;
        } finally {
            OBContext.restorePreviousMode();
        }
    }

    public static BigDecimal getDefaultCost(Product product, BigDecimal qty, Organization org, Date costDate,
            Date movementDate, BusinessPartner bp, Currency currency,
            HashMap<CostDimension, BaseOBObject> costDimensions) {
        Costing stdCost = getStandardCostDefinition(product, org, costDate, costDimensions);
        PriceList pricelist = null;
        if (bp != null) {
            pricelist = bp.getPurchasePricelist();
        }
        ProductPrice pp = FinancialUtils.getProductPrice(product, movementDate, false, pricelist, false);
        if (stdCost == null && pp == null) {
            throw new OBException("@NoPriceListOrStandardCostForProduct@ @Organization@: " + org.getName()
                    + ", @Product@: " + product.getName() + ", @Date@: " + OBDateUtils.formatDate(costDate));
        } else if (stdCost != null && pp == null) {
            BigDecimal standardCost = getStandardCost(product, org, costDate, costDimensions, currency);
            return qty.abs().multiply(standardCost);
        } else if (stdCost == null && pp != null) {
            BigDecimal cost = pp.getStandardPrice().multiply(qty.abs());
            if (DalUtil.getId(pp.getPriceListVersion().getPriceList().getCurrency()).equals(currency.getId())) {
                // no conversion needed
                return cost;
            }
            return FinancialUtils.getConvertedAmount(cost, pp.getPriceListVersion().getPriceList().getCurrency(),
                    currency, movementDate, org, FinancialUtils.PRECISION_STANDARD);

        } else if (stdCost != null && pp != null
                && stdCost.getStartingDate().before(pp.getPriceListVersion().getValidFromDate())) {
            BigDecimal cost = pp.getStandardPrice().multiply(qty.abs());
            if (DalUtil.getId(pp.getPriceListVersion().getPriceList().getCurrency()).equals(currency.getId())) {
                // no conversion needed
                return cost;
            }
            return FinancialUtils.getConvertedAmount(cost, pp.getPriceListVersion().getPriceList().getCurrency(),
                    currency, movementDate, org, FinancialUtils.PRECISION_STANDARD);
        } else {
            BigDecimal standardCost = getStandardCost(product, org, costDate, costDimensions, currency);
            return qty.abs().multiply(standardCost);
        }
    }

    /**
     * Calls {@link #getStandardCost(Product, Organization, Date, HashMap, boolean, Currency)} setting
     * the recheckWithoutDimensions flag to true.
     */
    public static BigDecimal getStandardCost(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, Currency convCurrency) throws OBException {
        return getStandardCost(product, org, date, costDimensions, true, convCurrency);
    }

    /**
     * Calculates the standard cost of a product on the given date and cost dimensions.
     * 
     * @param product
     *          The Product to get its Standard Cost
     * @param date
     *          The Date to get the Standard Cost
     * @param costDimensions
     *          The cost dimensions to get the Standard Cost if it is defined by some of them.
     * @param recheckWithoutDimensions
     *          boolean flag to force a recall the method to get the Standard Cost at client level if
     *          no cost is found in the given cost dimensions.
     * @param convCurrency
     *          The Currency to calculate the amount.
     * @return the Standard Cost.
     * @throws OBException
     *           when no standard cost is found.
     */
    public static BigDecimal getStandardCost(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, boolean recheckWithoutDimensions,
            Currency convCurrency) throws OBException {
        Costing stdCost = getStandardCostDefinition(product, org, date, costDimensions, recheckWithoutDimensions);
        if (stdCost == null) {
            // If no standard cost is found throw an exception.
            throw new OBException("@NoStandardCostDefined@ @Organization@:" + org.getName() + ", @Product@: "
                    + product.getName() + ", @Date@: " + OBDateUtils.formatDate(date));
        }
        return FinancialUtils.getConvertedAmount(stdCost.getCost(), stdCost.getCurrency(), convCurrency, date, org,
                FinancialUtils.PRECISION_COSTING);
    }

    /**
     * Calls {@link #hasStandardCostDefinition(Product, Organization, Date, HashMap, boolean)} setting
     * the recheckWithoutDimensions flag to true.
     */
    public static boolean hasStandardCostDefinition(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions) {
        return hasStandardCostDefinition(product, org, date, costDimensions, true);
    }

    /**
     * Check the existence of a standard cost definition of a product on the given date and cost
     * dimensions.
     * 
     * @param product
     *          The Product to get its Standard Cost
     * @param date
     *          The Date to get the Standard Cost
     * @param costDimensions
     *          The cost dimensions to get the Standard Cost if it is defined by some of them.
     * @param recheckWithoutDimensions
     *          boolean flag to force a recall the method to get the Standard Cost at client level if
     *          no cost is found in the given cost dimensions.
     * @return the Standard Cost. Null when no definition is found.
     */
    public static boolean hasStandardCostDefinition(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, boolean recheckWithoutDimensions) {
        return getStandardCostDefinition(product, org, date, costDimensions, recheckWithoutDimensions) != null;
    }

    /**
     * Calls {@link #getStandardCostDefinition(Product, Organization, Date, HashMap, boolean)} setting
     * the recheckWithoutDimensions flag to true.
     */
    public static Costing getStandardCostDefinition(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions) {
        return getStandardCostDefinition(product, org, date, costDimensions, true);
    }

    /**
     * Calculates the standard cost definition of a product on the given date and cost dimensions.
     * 
     * @param product
     *          The Product to get its Standard Cost
     * @param date
     *          The Date to get the Standard Cost
     * @param costDimensions
     *          The cost dimensions to get the Standard Cost if it is defined by some of them.
     * @param recheckWithoutDimensions
     *          boolean flag to force a recall the method to get the Standard Cost at client level if
     *          no cost is found in the given cost dimensions.
     * @return the Standard Cost. Null when no definition is found.
     */
    public static Costing getStandardCostDefinition(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, boolean recheckWithoutDimensions) {
        Costing stdCost = getStandardCostDefinition(product, org, date, costDimensions, recheckWithoutDimensions,
                "STA");
        if (stdCost != null) {
            return stdCost;
        } else {
            // If no cost is found, search valid legacy cost
            return getStandardCostDefinition(product, org, date, costDimensions, recheckWithoutDimensions, "ST");
        }
    }

    /**
     * Calculates the standard cost definition of a product on the given date and cost dimensions.
     * 
     * @param product
     *          The Product to get its Standard Cost
     * @param date
     *          The Date to get the Standard Cost
     * @param costDimensions
     *          The cost dimensions to get the Standard Cost if it is defined by some of them.
     * @param recheckWithoutDimensions
     *          boolean flag to force a recall the method to get the Standard Cost at client level if
     *          no cost is found in the given cost dimensions.
     * @return the Standard Cost. Null when no definition is found.
     */
    public static Costing getStandardCostDefinition(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, boolean recheckWithoutDimensions,
            String costtype) {
        // Get cost from M_Costing for given date.
        OBCriteria<Costing> obcCosting = OBDal.getInstance().createCriteria(Costing.class);
        obcCosting.add(Restrictions.eq(Costing.PROPERTY_PRODUCT, product));
        obcCosting.add(Restrictions.le(Costing.PROPERTY_STARTINGDATE, date));
        obcCosting.add(Restrictions.gt(Costing.PROPERTY_ENDINGDATE, date));
        obcCosting.add(Restrictions.eq(Costing.PROPERTY_COSTTYPE, costtype));
        obcCosting.add(Restrictions.isNotNull(Costing.PROPERTY_COST));
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            obcCosting
                    .add(Restrictions.eq(Costing.PROPERTY_WAREHOUSE, costDimensions.get(CostDimension.Warehouse)));
        }
        obcCosting.add(Restrictions.eq(Costing.PROPERTY_ORGANIZATION, org));
        obcCosting.setFilterOnReadableOrganization(false);
        if (obcCosting.count() > 0) {
            if (obcCosting.count() > 1) {
                log4j.warn("More than one cost found for same date: " + OBDateUtils.formatDate(date)
                        + " for product: " + product.getName() + " (" + product.getId() + ")");
            }
            return obcCosting.list().get(0);
        } else if (recheckWithoutDimensions) {
            return getStandardCostDefinition(product, org, date, getEmptyDimensions(), false);
        }
        return null;
    }

    /**
     * @return The costDimensions HashMap with null values for the dimensions.
     */
    public static HashMap<CostDimension, BaseOBObject> getEmptyDimensions() {
        HashMap<CostDimension, BaseOBObject> costDimensions = new HashMap<CostDimension, BaseOBObject>();
        costDimensions.put(CostDimension.Warehouse, null);
        return costDimensions;
    }

    /**
     * Calculates the stock of the product on the given date and for the given cost dimensions. It
     * only takes transactions that have its cost calculated.
     */
    public static BigDecimal getCurrentStock(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions) {
        // Get child tree of organizations.
        Set<String> orgs = OBContext.getOBContext().getOrganizationStructureProvider().getChildTree(org.getId(),
                true);

        StringBuffer select = new StringBuffer();
        select.append(" select sum(trx." + MaterialTransaction.PROPERTY_MOVEMENTQUANTITY + ") as stock");
        select.append(" from " + MaterialTransaction.ENTITY_NAME + " as trx");
        select.append("   join trx." + MaterialTransaction.PROPERTY_STORAGEBIN + " as locator");
        select.append(" where trx." + MaterialTransaction.PROPERTY_PRODUCT + ".id = :product");
        select.append("   and trx." + MaterialTransaction.PROPERTY_TRANSACTIONPROCESSDATE + " <= :date");
        // Include only transactions that have its cost calculated
        select.append("   and trx." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + " = true");
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            select.append("  and locator." + Locator.PROPERTY_WAREHOUSE + ".id = :warehouse");
        }
        select.append("   and trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:orgs)");
        Query trxQry = OBDal.getInstance().getSession().createQuery(select.toString());
        trxQry.setParameter("product", product.getId());
        trxQry.setParameter("date", date);
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            trxQry.setParameter("warehouse", costDimensions.get(CostDimension.Warehouse).getId());
        }
        trxQry.setParameterList("orgs", orgs);
        Object stock = trxQry.uniqueResult();
        if (stock != null) {
            return (BigDecimal) stock;
        }
        return BigDecimal.ZERO;
    }

    /**
     * Calculates the value of the stock of the product on the given date, for the given cost
     * dimensions and for the given currency. It only takes transactions that have its cost
     * calculated.
     */
    public static BigDecimal getCurrentValuedStock(Product product, Organization org, Date date,
            HashMap<CostDimension, BaseOBObject> costDimensions, Currency currency) {
        // Get child tree of organizations.
        Set<String> orgs = OBContext.getOBContext().getOrganizationStructureProvider().getChildTree(org.getId(),
                true);

        StringBuffer select = new StringBuffer();
        select.append(" select sum(case");
        select.append("     when trx." + MaterialTransaction.PROPERTY_MOVEMENTQUANTITY + " < 0 then -tc."
                + TransactionCost.PROPERTY_COST);
        select.append("     else tc." + TransactionCost.PROPERTY_COST + " end ) as cost,");
        select.append("  tc." + TransactionCost.PROPERTY_CURRENCY + ".id as currency,");
        select.append("  coalesce(sr." + ShipmentInOut.PROPERTY_ACCOUNTINGDATE + ", trx."
                + MaterialTransaction.PROPERTY_MOVEMENTDATE + ") as mdate");

        select.append(" from " + TransactionCost.ENTITY_NAME + " as tc");
        select.append("  join tc." + TransactionCost.PROPERTY_INVENTORYTRANSACTION + " as trx");
        select.append("  join trx." + MaterialTransaction.PROPERTY_STORAGEBIN + " as locator");
        select.append("  left join trx." + MaterialTransaction.PROPERTY_GOODSSHIPMENTLINE + " as line");
        select.append("  left join line." + ShipmentInOutLine.PROPERTY_SHIPMENTRECEIPT + " as sr");

        select.append(" where trx." + MaterialTransaction.PROPERTY_PRODUCT + ".id = :product");
        select.append("  and trx." + MaterialTransaction.PROPERTY_TRANSACTIONPROCESSDATE + " <= :date");
        // Include only transactions that have its cost calculated
        select.append("   and trx." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + " = true");
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            select.append("  and locator." + Locator.PROPERTY_WAREHOUSE + ".id = :warehouse");
        }
        select.append("   and trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:orgs)");
        select.append(" group by tc." + TransactionCost.PROPERTY_CURRENCY + ",");
        select.append("   coalesce(sr." + ShipmentInOut.PROPERTY_ACCOUNTINGDATE + ", trx."
                + MaterialTransaction.PROPERTY_MOVEMENTDATE + ")");

        Query trxQry = OBDal.getInstance().getSession().createQuery(select.toString());
        trxQry.setParameter("product", product.getId());
        trxQry.setParameter("date", date);
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            trxQry.setParameter("warehouse", costDimensions.get(CostDimension.Warehouse).getId());
        }
        trxQry.setParameterList("orgs", orgs);
        @SuppressWarnings("unchecked")
        List<Object[]> o = trxQry.list();
        BigDecimal sum = BigDecimal.ZERO;
        if (o.size() == 0) {
            return sum;
        }
        for (Object[] resultSet : o) {
            BigDecimal origAmt = (BigDecimal) resultSet[0];
            Currency origCur = OBDal.getInstance().get(Currency.class, resultSet[1]);
            Date convDate = (Date) resultSet[2];

            if (origCur != currency) {
                sum = sum.add(FinancialUtils.getConvertedAmount(origAmt, origCur, currency, convDate, org,
                        FinancialUtils.PRECISION_COSTING));
            } else {
                sum = sum.add(origAmt);
            }
        }
        return sum;
    }

    public static BusinessPartner getTrxBusinessPartner(MaterialTransaction transaction, TrxType trxType) {
        switch (trxType) {
        case Receipt:
        case ReceiptNegative:
        case ReceiptReturn:
        case ReceiptVoid:
        case Shipment:
        case ShipmentNegative:
        case ShipmentReturn:
        case ShipmentVoid:
            return transaction.getGoodsShipmentLine().getShipmentReceipt().getBusinessPartner();
        default:
            return null;
        }
    }

    /**
     * Returns the newer order line for the given product, business partner and organization.
     */
    public static OrderLine getOrderLine(Product product, BusinessPartner bp, Organization org) {
        OrganizationStructureProvider osp = OBContext.getOBContext().getOrganizationStructureProvider();

        StringBuffer where = new StringBuffer();
        where.append(" as ol");
        where.append("   join ol." + OrderLine.PROPERTY_SALESORDER + " as o");
        where.append("   join o." + Order.PROPERTY_DOCUMENTTYPE + " as dt");
        where.append(" where o." + Order.PROPERTY_BUSINESSPARTNER + " = :bp");
        where.append("   and ol." + OrderLine.PROPERTY_PRODUCT + " = :product");
        where.append("   and o." + Order.PROPERTY_ORGANIZATION + ".id in :org");
        where.append("   and o." + Order.PROPERTY_DOCUMENTSTATUS + " in ('CO', 'CL')");
        where.append("   and o." + Order.PROPERTY_SALESTRANSACTION + " = false");
        where.append("   and dt." + DocumentType.PROPERTY_RETURN + " = false");
        where.append(" order by o." + Order.PROPERTY_ORDERDATE + " desc");
        OBQuery<OrderLine> olQry = OBDal.getInstance().createQuery(OrderLine.class, where.toString());
        olQry.setFilterOnReadableOrganization(false);
        olQry.setNamedParameter("bp", bp);
        olQry.setNamedParameter("product", product);
        olQry.setNamedParameter("org", osp.getChildTree(org.getId(), true));
        olQry.setMaxResult(1);
        return olQry.uniqueResult();
    }

    public static CostingRule getCostDimensionRule(Organization org, Date date) {
        StringBuffer where = new StringBuffer();
        where.append(CostingRule.PROPERTY_ORGANIZATION + ".id = :organization");
        where.append(" and (" + CostingRule.PROPERTY_STARTINGDATE + " is null ");
        where.append("   or " + CostingRule.PROPERTY_STARTINGDATE + " <= :startdate)");
        where.append(" and (" + CostingRule.PROPERTY_ENDINGDATE + " is null");
        where.append("   or " + CostingRule.PROPERTY_ENDINGDATE + " >= :enddate )");
        where.append(" and " + CostingRule.PROPERTY_VALIDATED + " = true");
        where.append(" order by case when " + CostingRule.PROPERTY_STARTINGDATE + " is null then 1 else 0 end, "
                + CostingRule.PROPERTY_STARTINGDATE + " desc");
        OBQuery<CostingRule> crQry = OBDal.getInstance().createQuery(CostingRule.class, where.toString());
        crQry.setFilterOnReadableOrganization(false);
        crQry.setNamedParameter("organization", org.getId());
        crQry.setNamedParameter("startdate", date);
        crQry.setNamedParameter("enddate", date);
        crQry.setMaxResult(1);
        List<CostingRule> costRules = crQry.list();
        if (costRules.size() == 0) {
            throw new OBException("@NoCostingRuleFoundForOrganizationAndDate@ @Organization@: " + org.getName()
                    + ", @Date@: " + OBDateUtils.formatDate(date));
        }
        return costRules.get(0);
    }

    /**
     * Returns the max transaction date with cost calculated
     */
    public static Date getMaxTransactionDate(Organization org) {
        // Get child tree of organizations.
        OrganizationStructureProvider osp = OBContext.getOBContext()
                .getOrganizationStructureProvider(org.getClient().getId());
        Set<String> orgs = osp.getChildTree(org.getId(), true);

        StringBuffer select = new StringBuffer();
        select.append(" select max(trx." + MaterialTransaction.PROPERTY_MOVEMENTDATE + ") as date");
        select.append(" from " + MaterialTransaction.ENTITY_NAME + " as trx");
        select.append(" where trx." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + " = true");
        select.append("   and trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:orgs)");
        Query trxQry = OBDal.getInstance().getSession().createQuery(select.toString());
        trxQry.setParameterList("orgs", orgs);
        Object maxDate = trxQry.uniqueResult();
        if (maxDate != null) {
            return (Date) maxDate;
        }
        return null;
    }

    /**
     * Search period control closed between dateFrom and dateTo
     */
    public static Period periodClosed(Organization org, Date dateFrom, Date dateTo, String docType)
            throws ServletException {
        String strDateFormat = OBPropertiesProvider.getInstance().getOpenbravoProperties()
                .getProperty("dateFormat.java");
        final SimpleDateFormat dateFormat = new SimpleDateFormat(strDateFormat);

        String strDateFrom = dateFormat.format(dateFrom);
        String strDateTo = dateFormat.format(dateTo);
        CostingUtilsData[] per = CostingUtilsData.periodClosed(new DalConnectionProvider(false), org.getId(),
                strDateFrom, strDateTo, org.getClient().getId(), docType);
        if (per.length > 0) {
            return OBDal.getInstance().get(Period.class, per[0].period);
        }
        return null;
    }

    /**
     * Returns the Starting Date of a Costing Rule, if is null returns 01/01/1900
     */
    public static Date getCostingRuleStartingDate(CostingRule rule) {
        if (rule.getStartingDate() == null) {
            SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy");
            try {
                return outputFormat.parse("01-01-1900");
            } catch (ParseException e) {
                // Error parsing the date.
                log4j.error("Error parsing the date.", e);
                return null;
            }
        }
        return rule.getStartingDate();
    }

    /**
     * Returns the Fix Backdated From of a Costing Rule, if is null returns 01/01/1900
     */
    public static Date getCostingRuleFixBackdatedFrom(CostingRule rule) {
        if (rule.getFixbackdatedfrom() == null) {
            SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy");
            try {
                return outputFormat.parse("01-01-1900");
            } catch (ParseException e) {
                // Error parsing the date.
                log4j.error("Error parsing the date.", e);
                return null;
            }
        }
        return rule.getFixbackdatedfrom();
    }

    /**
     * Throws an OBException when the processId is the CostingBackground and it is being executed by
     * an organization which has a legal entity as an ancestor.
     * 
     * @param processId
     *          This is the process Id being executed. The method only runs the validation when the
     *          process ID is equal to CostingBackground.AD_PROCESS_ID
     * @param scheduledOrg
     *          the organization that runs the process
     */
    public static void checkValidOrganization(final String processId, final Organization scheduledOrg) {
        if (StringUtils.equals(processId, CostingBackground.AD_PROCESS_ID)) {
            final Organization legalEntity = OBContext.getOBContext().getOrganizationStructureProvider()
                    .getLegalEntity(scheduledOrg);
            if (legalEntity != null && !StringUtils.equals(legalEntity.getId(), scheduledOrg.getId())) {
                throw new OBException(OBMessageUtils.messageBD("CostBackgroundWrongOrganization"));
            }
        }
    }

    /**
     * Check if exists processed transactions for this product
     */
    public static boolean existsProcessedTransactions(Product product,
            HashMap<CostDimension, BaseOBObject> _costDimensions, Organization costorg, MaterialTransaction trx,
            boolean isManufacturingProduct) {

        // Get child tree of organizations.
        OrganizationStructureProvider osp = OBContext.getOBContext()
                .getOrganizationStructureProvider(trx.getClient().getId());
        Set<String> orgs = osp.getChildTree(costorg.getId(), true);
        HashMap<CostDimension, BaseOBObject> costDimensions = _costDimensions;
        if (isManufacturingProduct) {
            orgs = osp.getChildTree("0", false);
            costDimensions = CostingUtils.getEmptyDimensions();
        }

        OBCriteria<MaterialTransaction> criteria = OBDal.getInstance().createCriteria(MaterialTransaction.class);
        criteria.add(Restrictions.eq(MaterialTransaction.PROPERTY_PRODUCT, product));
        criteria.add(Restrictions.eq(MaterialTransaction.PROPERTY_ISPROCESSED, true));
        criteria.add(Restrictions.in(MaterialTransaction.PROPERTY_ORGANIZATION + ".id", orgs));
        if (costDimensions.get(CostDimension.Warehouse) != null) {
            criteria.add(Restrictions.eq(
                    MaterialTransaction.PROPERTY_STORAGEBIN + "." + Locator.PROPERTY_WAREHOUSE + ".id",
                    costDimensions.get(CostDimension.Warehouse).getId()));
        }
        criteria.setFilterOnReadableOrganization(false);
        criteria.setMaxResults(1);
        return criteria.uniqueResult() != null;
    }

    /**
     * Check if trx is the last opening one of an Inventory Amount Update
     */
    public static boolean isLastOpeningTransaction(MaterialTransaction trx, boolean includeWarehouseDimension) {

        StringBuffer where = new StringBuffer();
        where.append(" select trx." + MaterialTransaction.PROPERTY_ID + " as trxid");
        where.append(" from " + MaterialTransaction.ENTITY_NAME + " as trx");
        where.append(" join trx." + MaterialTransaction.PROPERTY_PHYSICALINVENTORYLINE + " as il");
        where.append(" join il." + InventoryCountLine.PROPERTY_PHYSINVENTORY + " as i");
        where.append(" join i." + InventoryCount.PROPERTY_INVENTORYAMOUNTUPDATELINEINVENTORIESINITINVENTORYLIST
                + " as iaui");
        where.append(" join iaui." + InvAmtUpdLnInventories.PROPERTY_WAREHOUSE + " as w");
        where.append(" where i." + InventoryCount.PROPERTY_INVENTORYTYPE + " = 'O'");
        where.append(" and iaui." + InvAmtUpdLnInventories.PROPERTY_CAINVENTORYAMTLINE + " = :iaul");
        if (includeWarehouseDimension) {
            where.append(" and iaui." + InvAmtUpdLnInventories.PROPERTY_WAREHOUSE + " = :warehouse");
        }
        where.append(" order by w." + Warehouse.PROPERTY_NAME + " desc");
        where.append(" , il." + InventoryCountLine.PROPERTY_LINENO + " desc");

        Query qry = OBDal.getInstance().getSession().createQuery(where.toString());
        OBDal.getInstance().refresh(trx.getPhysicalInventoryLine().getPhysInventory());
        qry.setParameter("iaul", trx.getPhysicalInventoryLine().getPhysInventory()
                .getInventoryAmountUpdateLineInventoriesInitInventoryList().get(0).getCaInventoryamtline());
        if (includeWarehouseDimension) {
            qry.setParameter("warehouse", trx.getStorageBin().getWarehouse());
        }
        qry.setMaxResults(1);
        return StringUtils.equals(trx.getId(), (String) qry.uniqueResult());
    }
}