org.openbravo.costing.CostingRuleProcess.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.costing.CostingRuleProcess.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.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.openbravo.base.exception.OBException;
import org.openbravo.base.provider.OBProvider;
import org.openbravo.dal.core.DalUtil;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.security.OrganizationStructureProvider;
import org.openbravo.dal.service.OBDal;
import org.openbravo.dal.service.OBQuery;
import org.openbravo.erpCommon.utility.OBError;
import org.openbravo.erpCommon.utility.OBMessageUtils;
import org.openbravo.financial.FinancialUtils;
import org.openbravo.materialmgmt.InventoryCountProcess;
import org.openbravo.model.ad.system.Client;
import org.openbravo.model.common.currency.Currency;
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.plm.AttributeSetInstance;
import org.openbravo.model.common.plm.Product;
import org.openbravo.model.common.plm.ProductUOM;
import org.openbravo.model.common.uom.UOM;
import org.openbravo.model.materialmgmt.cost.CostingRule;
import org.openbravo.model.materialmgmt.cost.CostingRuleInit;
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.scheduling.Process;
import org.openbravo.scheduling.ProcessBundle;
import org.openbravo.scheduling.ProcessLogger;
import org.openbravo.service.db.DbUtility;

public class CostingRuleProcess implements Process {
    private ProcessLogger logger;
    private static final Logger log4j = Logger.getLogger(CostingRuleProcess.class);

    @Override
    public void execute(ProcessBundle bundle) throws Exception {
        logger = bundle.getLogger();
        OBError msg = new OBError();
        msg.setType("Success");
        msg.setTitle(OBMessageUtils.messageBD("Success"));
        try {
            OBContext.setAdminMode(false);
            final String ruleId = (String) bundle.getParams().get("M_Costing_Rule_ID");
            CostingRule rule = OBDal.getInstance().get(CostingRule.class, ruleId);
            if (rule.getOrganization().getCurrency() == null) {
                throw new OBException("@NoCurrencyInCostingRuleOrg@");
            }
            OrganizationStructureProvider osp = OBContext.getOBContext()
                    .getOrganizationStructureProvider(rule.getClient().getId());
            final Set<String> childOrgs = osp.getChildTree(rule.getOrganization().getId(), true);
            final Set<String> naturalOrgs = osp.getNaturalTree(rule.getOrganization().getId());

            // Checks
            migrationCheck();
            CostingRule prevCostingRule = getPreviousRule(rule);
            boolean existsPreviousRule = prevCostingRule != null;
            boolean existsTransactions = existsTransactions(naturalOrgs, childOrgs);
            if (existsPreviousRule) {
                // Product with costing rule. All trx must be calculated.
                checkAllTrxCalculated(naturalOrgs, childOrgs);
            } else if (existsTransactions) {
                // Product configured to have cost not calculated cannot have transactions with cost
                // calculated.
                checkNoTrxWithCostCalculated(naturalOrgs, childOrgs);
                if (rule.getStartingDate() != null) {
                    // First rule of an instance that does not need migration. Old transactions costs are not
                    // calculated. They are initialized with ZERO cost.
                    initializeOldTrx(childOrgs, rule.getStartingDate());
                }
            }
            // Inventories are only needed:
            // - if the costing rule is updating a previous rule
            // - or legacy cost was never used and the first validated rule has a starting date different
            // than null. If the date is old enough that there are not prior transactions no inventories
            // are created.
            if (existsPreviousRule || rule.getStartingDate() != null) {
                Date startingDate = rule.getStartingDate();
                if (existsPreviousRule) {
                    // Set valid from date
                    startingDate = DateUtils.truncate(new Date(), Calendar.SECOND);
                    rule.setStartingDate(startingDate);
                    log4j.debug("setting starting date " + startingDate);
                    prevCostingRule.setEndingDate(startingDate);
                    OBDal.getInstance().save(prevCostingRule);
                    OBDal.getInstance().flush();
                }
                if (rule.getFixbackdatedfrom() == null && rule.isBackdatedTransactionsFixed()) {
                    rule.setFixbackdatedfrom(startingDate);
                }
                createCostingRuleInits(ruleId, childOrgs, startingDate);

                // Update cost of inventories and process starting physical inventories.
                updateInventoriesCostAndProcessInitInventories(ruleId, startingDate, existsPreviousRule);
            }

            if (rule.getStartingDate() != null && rule.getFixbackdatedfrom() != null
                    && rule.isBackdatedTransactionsFixed()
                    && rule.getFixbackdatedfrom().before(rule.getStartingDate())) {
                throw new OBException("@FixBackdateFromBeforeStartingDate@");
            }

            // Reload rule after possible session clear.
            rule = OBDal.getInstance().get(CostingRule.class, ruleId);
            rule.setValidated(true);
            CostingStatus.getInstance().setMigrated();
            OBDal.getInstance().save(rule);
        } catch (final OBException e) {
            OBDal.getInstance().rollbackAndClose();
            String resultMsg = OBMessageUtils.parseTranslation(e.getMessage());
            logger.log(resultMsg);
            log4j.error(e);
            msg.setType("Error");
            msg.setTitle(OBMessageUtils.messageBD("Error"));
            msg.setMessage(resultMsg);
            bundle.setResult(msg);

        } catch (final Exception e) {
            OBDal.getInstance().rollbackAndClose();
            String message = DbUtility.getUnderlyingSQLException(e).getMessage();
            logger.log(message);
            log4j.error(message, e);
            msg.setType("Error");
            msg.setTitle(OBMessageUtils.messageBD("Error"));
            msg.setMessage(message);
            bundle.setResult(msg);
        } finally {
            OBContext.restorePreviousMode();
        }
        bundle.setResult(msg);
    }

    private void migrationCheck() {
        if (!CostingStatus.getInstance().isMigrated()) {
            throw new OBException("@CostMigrationNotDone@");
        }
    }

    private CostingRule getPreviousRule(CostingRule rule) {
        StringBuffer where = new StringBuffer();
        where.append(" as cr");
        where.append(" where cr." + CostingRule.PROPERTY_ORGANIZATION + " = :ruleOrg");
        where.append("   and cr." + CostingRule.PROPERTY_VALIDATED + " = true");
        where.append("   order by cr." + CostingRule.PROPERTY_STARTINGDATE + " desc");

        OBQuery<CostingRule> crQry = OBDal.getInstance().createQuery(CostingRule.class, where.toString());
        crQry.setFilterOnReadableOrganization(false);
        crQry.setNamedParameter("ruleOrg", rule.getOrganization());
        crQry.setMaxResult(1);
        return crQry.uniqueResult();
    }

    private boolean existsTransactions(Set<String> naturalOrgs, Set<String> childOrgs) {
        StringBuffer where = new StringBuffer();
        where.append(" as p");
        where.append(" where p." + Product.PROPERTY_PRODUCTTYPE + " = 'I'");
        where.append("   and p." + Product.PROPERTY_STOCKED + " = true");
        where.append("   and p." + Product.PROPERTY_ORGANIZATION + ".id in (:porgs)");
        where.append("   and exists (select 1 from " + MaterialTransaction.ENTITY_NAME);
        where.append("     where " + MaterialTransaction.PROPERTY_PRODUCT + " = p");
        where.append("      and " + MaterialTransaction.PROPERTY_ORGANIZATION + " .id in (:childOrgs))");

        OBQuery<Product> pQry = OBDal.getInstance().createQuery(Product.class, where.toString());
        pQry.setFilterOnReadableOrganization(false);
        pQry.setNamedParameter("porgs", naturalOrgs);
        pQry.setNamedParameter("childOrgs", childOrgs);
        return pQry.count() > 0;
    }

    private void checkAllTrxCalculated(Set<String> naturalOrgs, Set<String> childOrgs) {
        StringBuffer where = new StringBuffer();
        where.append(" as p");
        where.append(" where p." + Product.PROPERTY_PRODUCTTYPE + " = 'I'");
        where.append("  and p." + Product.PROPERTY_STOCKED + " = true");
        where.append("  and p." + Product.PROPERTY_ORGANIZATION + ".id in (:porgs)");
        where.append("  and exists (select 1 from " + MaterialTransaction.ENTITY_NAME + " as trx ");
        where.append("   where trx." + MaterialTransaction.PROPERTY_PRODUCT + " = p");
        where.append("     and trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:childOrgs)");
        where.append("     and trx." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + " = false");
        where.append("   )");
        OBQuery<Product> pQry = OBDal.getInstance().createQuery(Product.class, where.toString());
        pQry.setFilterOnReadableOrganization(false);
        pQry.setNamedParameter("porgs", naturalOrgs);
        pQry.setNamedParameter("childOrgs", childOrgs);
        if (pQry.count() > 0) {
            throw new OBException("@TrxWithCostNoCalculated@");
        }
    }

    private void checkNoTrxWithCostCalculated(Set<String> naturalOrgs, Set<String> childOrgs) {
        StringBuffer where = new StringBuffer();
        where.append(" as p");
        where.append(" where p." + Product.PROPERTY_PRODUCTTYPE + " = 'I'");
        where.append("  and p." + Product.PROPERTY_STOCKED + " = true");
        where.append("  and p." + Product.PROPERTY_ORGANIZATION + ".id in (:porgs)");
        where.append("  and exists (select 1 from " + MaterialTransaction.ENTITY_NAME + " as trx ");
        where.append("   where trx." + MaterialTransaction.PROPERTY_PRODUCT + " = p");
        where.append("     and trx." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + " = true");
        where.append("     and trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:childOrgs)");
        where.append("   )");
        OBQuery<Product> pQry = OBDal.getInstance().createQuery(Product.class, where.toString());
        pQry.setFilterOnReadableOrganization(false);
        pQry.setNamedParameter("porgs", naturalOrgs);
        pQry.setNamedParameter("childOrgs", childOrgs);
        if (pQry.count() > 0) {
            throw new OBException("@ProductsWithTrxCalculated@");
        }
    }

    private void initializeOldTrx(Set<String> childOrgs, Date date) {
        StringBuffer where = new StringBuffer();
        where.append(" where " + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:orgs)");
        where.append("   and " + MaterialTransaction.PROPERTY_MOVEMENTDATE + " < :date");
        OBQuery<MaterialTransaction> trxQry = OBDal.getInstance().createQuery(MaterialTransaction.class,
                where.toString());
        trxQry.setFilterOnReadableOrganization(false);
        trxQry.setNamedParameter("orgs", childOrgs);
        trxQry.setNamedParameter("date", date);
        trxQry.setFetchSize(1000);
        ScrollableResults trxs = trxQry.scroll(ScrollMode.FORWARD_ONLY);
        int i = 1;
        try {
            while (trxs.next()) {
                MaterialTransaction trx = (MaterialTransaction) trxs.get(0);

                TransactionCost transactionCost = OBProvider.getInstance().get(TransactionCost.class);
                transactionCost.setInventoryTransaction(trx);
                transactionCost.setCostDate(trx.getTransactionProcessDate());
                transactionCost.setClient(trx.getClient());
                transactionCost.setOrganization(trx.getOrganization());
                transactionCost.setCost(BigDecimal.ZERO);
                transactionCost.setCurrency(trx.getClient().getCurrency());
                transactionCost.setAccountingDate(trx.getGoodsShipmentLine() != null
                        ? trx.getGoodsShipmentLine().getShipmentReceipt().getAccountingDate()
                        : trx.getMovementDate());
                List<TransactionCost> trxCosts = trx.getTransactionCostList();
                trxCosts.add(transactionCost);
                trx.setTransactionCostList(trxCosts);

                trx.setCostCalculated(true);
                trx.setCostingStatus("CC");
                trx.setTransactionCost(BigDecimal.ZERO);
                trx.setCurrency(trx.getClient().getCurrency());
                OBDal.getInstance().save(trx);

                if ((i % 100) == 0) {
                    OBDal.getInstance().flush();
                    OBDal.getInstance().getSession().clear();
                }
                i++;
            }
        } finally {
            trxs.close();
        }
    }

    @Deprecated
    protected void createCostingRuleInits(CostingRule rule, Set<String> childOrgs) {
        createCostingRuleInits(rule.getId(), childOrgs, null);
    }

    protected void createCostingRuleInits(String ruleId, Set<String> childOrgs, Date date) {
        CostingRule rule = OBDal.getInstance().get(CostingRule.class, ruleId);
        ScrollableResults stockLines = getStockLines(childOrgs, date);
        // The key of the Map is the concatenation of orgId and warehouseId
        Map<String, String> initLines = new HashMap<String, String>();
        Map<String, Long> maxLineNumbers = new HashMap<String, Long>();
        InventoryCountLine closingInventoryLine = null;
        InventoryCountLine openInventoryLine = null;
        int i = 1;
        try {
            while (stockLines.next()) {
                Object[] stockLine = stockLines.get();
                String productId = (String) stockLine[0];
                String attrSetInsId = (String) stockLine[1];
                String uomId = (String) stockLine[2];
                String orderUOMId = (String) stockLine[3];
                String locatorId = (String) stockLine[4];
                String warehouseId = (String) stockLine[5];
                BigDecimal qty = (BigDecimal) stockLine[6];
                BigDecimal orderQty = (BigDecimal) stockLine[7];

                String criId = initLines.get(warehouseId);
                CostingRuleInit cri = null;
                if (criId == null) {
                    cri = createCostingRuleInitLine(rule, warehouseId, date);

                    initLines.put(warehouseId, cri.getId());
                } else {
                    cri = OBDal.getInstance().get(CostingRuleInit.class, criId);
                }
                Long lineNo = (maxLineNumbers.get(criId) == null ? 0L : maxLineNumbers.get(criId)) + 10L;
                maxLineNumbers.put(criId, lineNo);

                if (BigDecimal.ZERO.compareTo(qty) < 0) {
                    // Do not insert negative values in Inventory lines, instead reverse the Quantity Count
                    // and the Book Quantity. For example:
                    // Instead of CountQty=0 and BookQty=-5 insert CountQty=5 and BookQty=0
                    // By doing so the difference between both quantities remains the same and no negative
                    // values have been inserted.

                    openInventoryLine = insertInventoryLine(cri.getInitInventory(), productId, attrSetInsId, uomId,
                            orderUOMId, locatorId, qty, BigDecimal.ZERO, orderQty, BigDecimal.ZERO, lineNo, null);
                    insertInventoryLine(cri.getCloseInventory(), productId, attrSetInsId, uomId, orderUOMId,
                            locatorId, BigDecimal.ZERO, qty, BigDecimal.ZERO, orderQty, lineNo, openInventoryLine);

                } else {
                    openInventoryLine = insertInventoryLine(cri.getInitInventory(), productId, attrSetInsId, uomId,
                            orderUOMId, locatorId, BigDecimal.ZERO, qty.abs(), BigDecimal.ZERO,
                            orderQty == null ? null : orderQty.abs(), lineNo, closingInventoryLine);
                    insertInventoryLine(cri.getCloseInventory(), productId, attrSetInsId, uomId, orderUOMId,
                            locatorId, qty == null ? null : qty.abs(), BigDecimal.ZERO,
                            orderQty == null ? null : orderQty.abs(), BigDecimal.ZERO, lineNo, openInventoryLine);

                }

                if ((i % 100) == 0) {
                    OBDal.getInstance().flush();
                    OBDal.getInstance().getSession().clear();
                    // Reload rule after clear session.
                    rule = OBDal.getInstance().get(CostingRule.class, ruleId);
                }
                i++;
            }
        } finally {
            stockLines.close();
        }
        // Process closing physical inventories.
        for (CostingRuleInit cri : rule.getCostingRuleInitList()) {
            new InventoryCountProcess().processInventory(cri.getCloseInventory(), false);
        }
    }

    private ScrollableResults getStockLines(Set<String> childOrgs, Date date) {
        StringBuffer select = new StringBuffer();
        select.append("select trx." + MaterialTransaction.PROPERTY_PRODUCT + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ATTRIBUTESETVALUE + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_UOM + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ORDERUOM + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_STORAGEBIN + ".id");
        select.append(", loc." + Locator.PROPERTY_WAREHOUSE + ".id");
        select.append(", sum(trx." + MaterialTransaction.PROPERTY_MOVEMENTQUANTITY + ")");
        select.append(", sum(trx." + MaterialTransaction.PROPERTY_ORDERQUANTITY + ")");
        select.append(" from " + MaterialTransaction.ENTITY_NAME + " as trx");
        select.append("    join trx." + MaterialTransaction.PROPERTY_STORAGEBIN + " as loc");
        select.append(" where trx." + MaterialTransaction.PROPERTY_ORGANIZATION + ".id in (:orgs)");
        if (date != null) {
            select.append("   and trx." + MaterialTransaction.PROPERTY_MOVEMENTDATE + " < :date");
        }
        select.append("   and trx." + MaterialTransaction.PROPERTY_PRODUCT + ".productType = 'I'");
        select.append("   and trx." + MaterialTransaction.PROPERTY_PRODUCT + ".stocked = true");
        select.append(" group by trx." + MaterialTransaction.PROPERTY_PRODUCT + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ATTRIBUTESETVALUE + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_UOM + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ORDERUOM + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_STORAGEBIN + ".id");
        select.append(", loc." + Locator.PROPERTY_WAREHOUSE + ".id");
        select.append(" having ");
        select.append(" sum(trx." + MaterialTransaction.PROPERTY_MOVEMENTQUANTITY + ") <> 0");
        select.append(" or sum(trx." + MaterialTransaction.PROPERTY_ORDERQUANTITY + ") <> 0");
        select.append(" order by loc." + Locator.PROPERTY_WAREHOUSE + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_PRODUCT + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_STORAGEBIN + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ATTRIBUTESETVALUE + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_UOM + ".id");
        select.append(", trx." + MaterialTransaction.PROPERTY_ORDERUOM + ".id");

        Query stockLinesQry = OBDal.getInstance().getSession().createQuery(select.toString());
        stockLinesQry.setParameterList("orgs", childOrgs);
        if (date != null) {
            stockLinesQry.setTimestamp("date", date);
        }
        stockLinesQry.setFetchSize(1000);
        ScrollableResults stockLines = stockLinesQry.scroll(ScrollMode.FORWARD_ONLY);
        return stockLines;
    }

    private CostingRuleInit createCostingRuleInitLine(CostingRule rule, String warehouseId, Date date) {
        Date localDate = date;
        if (localDate == null) {
            localDate = new Date();
        }
        String clientId = (String) DalUtil.getId(rule.getClient());
        String orgId = (String) DalUtil.getId(rule.getOrganization());
        CostingRuleInit cri = OBProvider.getInstance().get(CostingRuleInit.class);
        cri.setClient((Client) OBDal.getInstance().getProxy(Client.ENTITY_NAME, clientId));
        cri.setOrganization((Organization) OBDal.getInstance().getProxy(Organization.ENTITY_NAME, orgId));
        cri.setWarehouse((Warehouse) OBDal.getInstance().getProxy(Warehouse.ENTITY_NAME, warehouseId));
        cri.setCostingRule(rule);
        List<CostingRuleInit> criList = rule.getCostingRuleInitList();
        criList.add(cri);
        rule.setCostingRuleInitList(criList);

        InventoryCount closeInv = OBProvider.getInstance().get(InventoryCount.class);
        closeInv.setClient((Client) OBDal.getInstance().getProxy(Client.ENTITY_NAME, clientId));
        closeInv.setOrganization((Organization) OBDal.getInstance().getProxy(Organization.ENTITY_NAME, orgId));
        closeInv.setName(OBMessageUtils.messageBD("CostCloseInventory"));
        closeInv.setWarehouse((Warehouse) OBDal.getInstance().getProxy(Warehouse.ENTITY_NAME, warehouseId));
        closeInv.setMovementDate(localDate);
        closeInv.setInventoryType("C");
        cri.setCloseInventory(closeInv);

        InventoryCount initInv = OBProvider.getInstance().get(InventoryCount.class);
        initInv.setClient((Client) OBDal.getInstance().getProxy(Client.ENTITY_NAME, clientId));
        initInv.setOrganization((Organization) OBDal.getInstance().getProxy(Organization.ENTITY_NAME, orgId));
        initInv.setName(OBMessageUtils.messageBD("CostInitInventory"));
        initInv.setWarehouse((Warehouse) OBDal.getInstance().getProxy(Warehouse.ENTITY_NAME, warehouseId));
        initInv.setMovementDate(localDate);
        initInv.setInventoryType("O");
        cri.setInitInventory(initInv);
        OBDal.getInstance().save(rule);
        OBDal.getInstance().save(closeInv);
        OBDal.getInstance().save(initInv);

        OBDal.getInstance().flush();

        return cri;
    }

    private InventoryCountLine insertInventoryLine(InventoryCount inventory, String productId, String attrSetInsId,
            String uomId, String orderUOMId, String locatorId, BigDecimal qtyCount, BigDecimal qtyBook,
            BigDecimal orderQtyCount, BigDecimal orderQtyBook, Long lineNo,
            InventoryCountLine relatedInventoryLine) {
        InventoryCountLine icl = OBProvider.getInstance().get(InventoryCountLine.class);
        icl.setClient(inventory.getClient());
        icl.setOrganization(inventory.getOrganization());
        icl.setPhysInventory(inventory);
        icl.setLineNo(lineNo);
        icl.setStorageBin((Locator) OBDal.getInstance().getProxy(Locator.ENTITY_NAME, locatorId));
        icl.setProduct((Product) OBDal.getInstance().getProxy(Product.ENTITY_NAME, productId));
        icl.setAttributeSetValue((AttributeSetInstance) OBDal.getInstance()
                .getProxy(AttributeSetInstance.ENTITY_NAME, attrSetInsId));
        icl.setQuantityCount(qtyCount);
        icl.setBookQuantity(qtyBook);
        icl.setUOM((UOM) OBDal.getInstance().getProxy(UOM.ENTITY_NAME, uomId));
        if (orderUOMId != null) {
            icl.setOrderQuantity(orderQtyCount);
            icl.setQuantityOrderBook(orderQtyBook);
            icl.setOrderUOM((ProductUOM) OBDal.getInstance().getProxy(ProductUOM.ENTITY_NAME, orderUOMId));
        }
        icl.setRelatedInventory(relatedInventoryLine);
        List<InventoryCountLine> invLines = inventory.getMaterialMgmtInventoryCountLineList();
        invLines.add(icl);
        inventory.setMaterialMgmtInventoryCountLineList(invLines);
        OBDal.getInstance().save(inventory);
        OBDal.getInstance().flush();
        return icl;
    }

    private void updateInventoriesCostAndProcessInitInventories(String ruleId, Date startingDate,
            boolean existsPreviousRule) {
        CostingRule rule = OBDal.getInstance().get(CostingRule.class, ruleId);
        for (CostingRuleInit cri : rule.getCostingRuleInitList()) {
            for (InventoryCountLine icl : cri.getCloseInventory().getMaterialMgmtInventoryCountLineList()) {
                MaterialTransaction trx = getInventoryLineTransaction(icl);
                // Remove 1 second from transaction date to ensure that cost is calculated with previous
                // costing rule.
                trx.setTransactionProcessDate(DateUtils.addSeconds(startingDate, -1));
                BigDecimal trxCost = BigDecimal.ZERO;
                BigDecimal cost = null;
                Currency cur = FinancialUtils.getLegalEntityCurrency(trx.getOrganization());
                if (existsPreviousRule) {
                    trxCost = CostingUtils.getTransactionCost(trx, startingDate, true, cur);
                    if (trx.getMovementQuantity().compareTo(BigDecimal.ZERO) != 0) {
                        cost = trxCost.divide(trx.getMovementQuantity().abs(), cur.getCostingPrecision().intValue(),
                                RoundingMode.HALF_UP);
                    }
                } else {
                    // Insert transaction cost record big ZERO cost.
                    cur = trx.getClient().getCurrency();
                    TransactionCost transactionCost = OBProvider.getInstance().get(TransactionCost.class);
                    transactionCost.setInventoryTransaction(trx);
                    transactionCost.setCostDate(trx.getTransactionProcessDate());
                    transactionCost.setClient(trx.getClient());
                    transactionCost.setOrganization(trx.getOrganization());
                    transactionCost.setCost(BigDecimal.ZERO);
                    transactionCost.setCurrency(trx.getClient().getCurrency());
                    transactionCost.setAccountingDate(trx.getGoodsShipmentLine() != null
                            ? trx.getGoodsShipmentLine().getShipmentReceipt().getAccountingDate()
                            : trx.getMovementDate());
                    List<TransactionCost> trxCosts = trx.getTransactionCostList();
                    trxCosts.add(transactionCost);
                    trx.setTransactionCostList(trxCosts);
                    OBDal.getInstance().save(trx);
                }

                trx.setCostCalculated(true);
                trx.setCostingStatus("CC");
                trx.setCurrency(cur);
                trx.setTransactionCost(trxCost);
                OBDal.getInstance().save(trx);
                InventoryCountLine initICL = getInitIcl(cri.getInitInventory(), icl);
                initICL.setCost(cost);
                OBDal.getInstance().save(initICL);
            }
            OBDal.getInstance().flush();
            new InventoryCountProcess().processInventory(cri.getInitInventory(), false);
        }
        if (!existsPreviousRule) {
            updateInitInventoriesTrxDate(startingDate, ruleId);
        }
    }

    protected MaterialTransaction getInventoryLineTransaction(InventoryCountLine icl) {
        OBQuery<MaterialTransaction> trxQry = OBDal.getInstance().createQuery(MaterialTransaction.class,
                MaterialTransaction.PROPERTY_PHYSICALINVENTORYLINE + ".id = :invline");
        trxQry.setFilterOnReadableClients(false);
        trxQry.setFilterOnReadableOrganization(false);
        trxQry.setNamedParameter("invline", icl.getId());
        MaterialTransaction trx = trxQry.uniqueResult();
        return trx;
    }

    protected InventoryCountLine getInitIcl(InventoryCount initInventory, InventoryCountLine icl) {
        StringBuffer where = new StringBuffer();
        where.append(InventoryCountLine.PROPERTY_PHYSINVENTORY + ".id = :inventory");
        where.append(" and " + InventoryCountLine.PROPERTY_PRODUCT + ".id = :product");
        where.append(" and " + InventoryCountLine.PROPERTY_ATTRIBUTESETVALUE + ".id = :asi");
        where.append(" and " + InventoryCountLine.PROPERTY_STORAGEBIN + ".id = :locator");
        if (icl.getOrderUOM() == null) {
            where.append(" and " + InventoryCountLine.PROPERTY_ORDERUOM + " is null");
        } else {
            where.append(" and " + InventoryCountLine.PROPERTY_ORDERUOM + ".id = :orderuom");
        }
        OBQuery<InventoryCountLine> iclQry = OBDal.getInstance().createQuery(InventoryCountLine.class,
                where.toString());
        iclQry.setFilterOnReadableClients(false);
        iclQry.setFilterOnReadableOrganization(false);
        iclQry.setNamedParameter("inventory", initInventory.getId());
        iclQry.setNamedParameter("product", icl.getProduct().getId());
        iclQry.setNamedParameter("asi", icl.getAttributeSetValue().getId());
        iclQry.setNamedParameter("locator", icl.getStorageBin().getId());
        if (icl.getOrderUOM() != null) {
            iclQry.setNamedParameter("orderuom", icl.getOrderUOM().getId());
        }
        return iclQry.uniqueResult();
    }

    private void updateInitInventoriesTrxDate(Date startingDate, String ruleId) {
        StringBuffer where = new StringBuffer();
        where.append(" as trx");
        where.append("   join trx." + MaterialTransaction.PROPERTY_PHYSICALINVENTORYLINE + " as il");
        where.append(" where il." + InventoryCountLine.PROPERTY_PHYSINVENTORY + ".id IN (");
        where.append("    select cri." + CostingRuleInit.PROPERTY_INITINVENTORY + ".id");
        where.append("    from " + CostingRuleInit.ENTITY_NAME + " as cri");
        where.append("    where cri." + CostingRuleInit.PROPERTY_COSTINGRULE + ".id = :cr");
        where.append("    )");
        OBQuery<MaterialTransaction> trxQry = OBDal.getInstance().createQuery(MaterialTransaction.class,
                where.toString());
        trxQry.setNamedParameter("cr", ruleId);
        trxQry.setFetchSize(1000);
        ScrollableResults trxs = trxQry.scroll(ScrollMode.FORWARD_ONLY);
        int i = 0;
        while (trxs.next()) {
            MaterialTransaction trx = (MaterialTransaction) trxs.get(0);
            trx.setTransactionProcessDate(startingDate);
            OBDal.getInstance().save(trx);
            if ((i % 100) == 0) {
                OBDal.getInstance().flush();
                OBDal.getInstance().getSession().clear();
            }
        }
        trxs.close();
    }
}