org.openbravo.costing.CostingServer.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.costing.CostingServer.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.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.openbravo.advpaymentmngt.utility.FIN_Utility;
import org.openbravo.base.exception.OBException;
import org.openbravo.base.provider.OBProvider;
import org.openbravo.base.util.OBClassLoader;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.service.OBDal;
import org.openbravo.dal.service.OBQuery;
import org.openbravo.erpCommon.businessUtility.Preferences;
import org.openbravo.erpCommon.utility.OBDateUtils;
import org.openbravo.erpCommon.utility.OBMessageUtils;
import org.openbravo.erpCommon.utility.PropertyException;
import org.openbravo.model.common.currency.Currency;
import org.openbravo.model.common.enterprise.DocumentType;
import org.openbravo.model.common.enterprise.Organization;
import org.openbravo.model.materialmgmt.cost.CostAdjustment;
import org.openbravo.model.materialmgmt.cost.CostAdjustmentLine;
import org.openbravo.model.materialmgmt.cost.CostingRule;
import org.openbravo.model.materialmgmt.cost.LCReceipt;
import org.openbravo.model.materialmgmt.cost.LandedCost;
import org.openbravo.model.materialmgmt.cost.LandedCostCost;
import org.openbravo.model.materialmgmt.cost.TransactionCost;
import org.openbravo.model.materialmgmt.transaction.InternalConsumption;
import org.openbravo.model.materialmgmt.transaction.InternalMovement;
import org.openbravo.model.materialmgmt.transaction.InventoryCount;
import org.openbravo.model.materialmgmt.transaction.MaterialTransaction;
import org.openbravo.model.materialmgmt.transaction.ProductionTransaction;
import org.openbravo.model.materialmgmt.transaction.ShipmentInOutLine;
import org.openbravo.model.procurement.ReceiptInvoiceMatch;

/**
 * @author gorkaion
 * 
 */
public class CostingServer {
    private MaterialTransaction transaction;
    private BigDecimal trxCost;
    protected static Logger log4j = Logger.getLogger(CostingServer.class);
    private CostingRule costingRule;
    private Currency currency;
    private Organization organization;
    final static String strCategoryLandedCost = "LDC";
    final static String strTableLandedCost = "M_LandedCost";

    public CostingServer(MaterialTransaction transaction) {
        this.transaction = transaction;
        init();
    }

    private void init() {
        organization = getOrganization();
        costingRule = getCostDimensionRule();
        currency = getCostCurrency();
        trxCost = transaction.getTransactionCost();
    }

    /**
     * Calculates and stores in the database the cost of the transaction.
     * 
     * 
     */
    public void process() {
        try {
            if (trxCost != null) {
                // Transaction cost has already been calculated. Nothing to do.
                return;
            }
            log4j.debug("Process cost");
            try {
                OBContext.setAdminMode(false);
                // Get needed algorithm. And set it in the M_Transaction.
                CostingAlgorithm costingAlgorithm = getCostingAlgorithm();
                costingAlgorithm.init(this);
                log4j.debug("  *** Algorithm initializated: " + costingAlgorithm.getClass());

                trxCost = costingAlgorithm.getTransactionCost();
                if (trxCost == null && !transaction.getCostingStatus().equals("P")) {
                    throw new OBException("@NoCostCalculated@: " + transaction.getIdentifier());
                }
                if (transaction.getCostingStatus().equals("P")) {
                    return;
                }

                trxCost = trxCost.setScale(costingAlgorithm.getCostCurrency().getStandardPrecision().intValue(),
                        RoundingMode.HALF_UP);
                log4j.debug("  *** Transaction cost amount: " + trxCost.toString());
                // Save calculated cost on M_Transaction.
                transaction = OBDal.getInstance().get(MaterialTransaction.class, transaction.getId());
                transaction.setTransactionCost(trxCost);
                transaction.setCurrency(currency);
                transaction.setCostCalculated(true);
                transaction.setCostingStatus("CC");
                // insert on m_transaction_cost
                createTransactionCost();
                OBDal.getInstance().flush();

                setNotPostedTransaction();
                checkCostAdjustments();
            } finally {
                OBContext.restorePreviousMode();
            }
            return;
        } finally {
            // Every Transaction must be set as Processed = 'Y' after going through this method
            transaction.setProcessed(true);
            OBDal.getInstance().flush();
        }
    }

    private void checkCostAdjustments() {
        TrxType trxType = TrxType.getTrxType(transaction);
        boolean adjustmentAlreadyCreated = false;

        boolean checkPriceCorrectionTrxs = false;
        boolean checkNegativeStockCorrectionTrxs = false;
        // check if price correction is needed
        try {
            checkPriceCorrectionTrxs = Preferences.getPreferenceValue(
                    CostAdjustmentUtils.ENABLE_AUTO_PRICE_CORRECTION_PREF, true,
                    OBContext.getOBContext().getCurrentClient(), OBContext.getOBContext().getCurrentOrganization(),
                    OBContext.getOBContext().getUser(), OBContext.getOBContext().getRole(), null).equals("Y");
        } catch (PropertyException e1) {
            checkPriceCorrectionTrxs = false;
        }
        if (checkPriceCorrectionTrxs && transaction.isCheckpricedifference()
                && !StringUtils.equals(transaction.getCostingAlgorithm().getJavaClassName(),
                        "org.openbravo.costing.StandardAlgorithm")) {
            JSONObject message = PriceDifferenceProcess.processPriceDifferenceTransaction(transaction);
            if (message.has("documentNo")) {
                adjustmentAlreadyCreated = true;
            }
        }

        // check if landed cost need to be processed
        if (trxType == TrxType.Receipt || trxType == TrxType.ReceiptReturn || trxType == TrxType.ReceiptNegative) {
            StringBuffer where = new StringBuffer();
            where.append(" as lc");
            where.append(" where not exists ");
            where.append("   (select 1 from " + MaterialTransaction.ENTITY_NAME + " mtrans");
            where.append("     join mtrans." + MaterialTransaction.PROPERTY_GOODSSHIPMENTLINE + " iol");
            where.append("   where iol." + ShipmentInOutLine.PROPERTY_SHIPMENTRECEIPT + ".id = :inoutId");
            where.append("     and mtrans." + MaterialTransaction.PROPERTY_ISCOSTCALCULATED + "= false");
            where.append("   )");
            where.append("   and lc." + LandedCostCost.PROPERTY_LANDEDCOST + " is null");
            where.append("   and lc." + LandedCostCost.PROPERTY_GOODSSHIPMENT + ".id = :inoutId");
            OBQuery<LandedCostCost> qry = OBDal.getInstance().createQuery(LandedCostCost.class, where.toString());
            qry.setNamedParameter("inoutId", transaction.getGoodsShipmentLine().getShipmentReceipt().getId());

            ScrollableResults lcLines = qry.scroll(ScrollMode.FORWARD_ONLY);
            try {
                LandedCost landedCost = null;

                while (lcLines.next()) {
                    if (landedCost == null) {
                        final DocumentType docType = FIN_Utility.getDocumentType(organization,
                                strCategoryLandedCost);
                        final String docNo = FIN_Utility.getDocumentNo(docType, strTableLandedCost);

                        landedCost = OBProvider.getInstance().get(LandedCost.class);
                        landedCost.setReferenceDate(new Date());
                        landedCost.setDocumentType(docType);
                        landedCost.setDocumentNo(docNo);
                        landedCost.setOrganization(organization);
                        OBDal.getInstance().save(landedCost);

                        LCReceipt lcReceipt = OBProvider.getInstance().get(LCReceipt.class);
                        lcReceipt.setLandedCost(landedCost);
                        lcReceipt.setOrganization(organization);
                        lcReceipt.setGoodsShipment(transaction.getGoodsShipmentLine().getShipmentReceipt());
                        OBDal.getInstance().save(lcReceipt);

                    }
                    final LandedCostCost landedCostCost = (LandedCostCost) lcLines.get()[0];
                    landedCostCost.setLandedCost(landedCost);
                    landedCost.getLandedCostCostList().add(landedCostCost);
                    OBDal.getInstance().save(landedCost);
                    OBDal.getInstance().save(landedCostCost);
                }

                if (landedCost != null) {
                    OBDal.getInstance().flush();
                    JSONObject message = LandedCostProcess.doProcessLandedCost(landedCost);
                    if (message.has("documentNo")) {
                        adjustmentAlreadyCreated = true;
                    }

                    if (message.get("severity") != "success") {
                        throw new OBException(OBMessageUtils.parseTranslation("@ErrorProcessingLandedCost@") + ": "
                                + landedCost.getDocumentNo() + " - " + message.getString("text"));
                    }
                }
            } catch (JSONException e) {
                throw new OBException(OBMessageUtils.parseTranslation("@ErrorProcessingLandedCost@"));
            } finally {
                lcLines.close();
            }
        }

        // With Standard Algorithm, only BDT are checked
        if (StringUtils.equals(transaction.getCostingAlgorithm().getJavaClassName(),
                "org.openbravo.costing.StandardAlgorithm")) {

            if (CostAdjustmentUtils.isNeededBackdatedCostAdjustment(transaction,
                    getCostingRule().isWarehouseDimension(),
                    CostingUtils.getCostingRuleStartingDate(getCostingRule()))) {

                // Case transaction backdated (modifying the stock in the past)
                if (trxType != TrxType.InventoryClosing && trxType != TrxType.InventoryOpening
                        && getCostingRule().isBackdatedTransactionsFixed() && transaction.getMovementDate()
                                .compareTo(CostingUtils.getCostingRuleFixBackdatedFrom(getCostingRule())) >= 0) {
                    // BDT = Backdated transaction
                    createAdjustment("BDT", BigDecimal.ZERO);
                }

                // Case Inventory Amount Update backdated (modifying the cost in the past)
                if (trxType == TrxType.InventoryOpening && transaction.getMovementDate()
                        .compareTo(CostingUtils.getCostingRuleStartingDate(getCostingRule())) >= 0) {
                    OBDal.getInstance().refresh(transaction.getPhysicalInventoryLine().getPhysInventory());
                    if (transaction.getPhysicalInventoryLine().getPhysInventory()
                            .getInventoryAmountUpdateLineInventoriesInitInventoryList().size() > 0
                            && CostingUtils.isLastOpeningTransaction(transaction,
                                    getCostingRule().isWarehouseDimension())) {
                        // BDT = Backdated transaction
                        createAdjustment("BDT", BigDecimal.ZERO);
                    }
                }
            }

            // update trxCost after cost adjustments
            transaction = OBDal.getInstance().get(MaterialTransaction.class, transaction.getId());
            trxCost = CostAdjustmentUtils.getTrxCost(transaction, false, getCostCurrency());

            // With Standard Algorithm, no more adjustments are needed
            return;
        }

        if (getCostingRule().isBackdatedTransactionsFixed()
                && transaction.getMovementDate()
                        .compareTo(CostingUtils.getCostingRuleFixBackdatedFrom(getCostingRule())) >= 0
                && CostAdjustmentUtils.isNeededBackdatedCostAdjustment(transaction,
                        getCostingRule().isWarehouseDimension(),
                        CostingUtils.getCostingRuleStartingDate(getCostingRule()))) {
            // BDT = Backdated transaction
            adjustmentAlreadyCreated = createAdjustment("BDT", null);
        }

        // check if negative stock correction should be done
        try {
            checkNegativeStockCorrectionTrxs = Preferences.getPreferenceValue(
                    CostAdjustmentUtils.ENABLE_NEGATIVE_STOCK_CORRECTION_PREF, true,
                    OBContext.getOBContext().getCurrentClient(), OBContext.getOBContext().getCurrentOrganization(),
                    OBContext.getOBContext().getUser(), OBContext.getOBContext().getRole(), null).equals("Y");
        } catch (PropertyException e1) {
            checkNegativeStockCorrectionTrxs = false;
        }

        boolean modifiesAvg = AverageAlgorithm.modifiesAverage(TrxType.getTrxType(transaction));
        BigDecimal currentStock = CostAdjustmentUtils.getStockOnTransactionDate(getOrganization(), transaction,
                getCostingAlgorithm().costDimensions, transaction.getProduct().isProduction(),
                costingRule.isBackdatedTransactionsFixed());
        // the stock previous to transaction was zero or negative
        if (checkNegativeStockCorrectionTrxs && modifiesAvg && !adjustmentAlreadyCreated
                && (currentStock.compareTo(transaction.getMovementQuantity()) < 0
                        || (trxType != TrxType.InventoryOpening
                                && currentStock.compareTo(transaction.getMovementQuantity()) == 0
                                && CostingUtils.existsProcessedTransactions(transaction.getProduct(),
                                        getCostingAlgorithm().costDimensions, getOrganization(), transaction,
                                        transaction.getProduct().isProduction())))) {

            // NSC = Negative Stock Correction
            createAdjustment("NSC", null);
        }

        // check if closing inventory needs to be adjusted due to a remainder value
        if (trxType == TrxType.InventoryClosing && BigDecimal.ZERO.compareTo(currentStock) == 0) {

            BigDecimal currentValuedStock = CostAdjustmentUtils.getValuedStockOnTransactionDate(getOrganization(),
                    transaction, getCostingAlgorithm().costDimensions, transaction.getProduct().isProduction(),
                    costingRule.isBackdatedTransactionsFixed(), transaction.getCurrency());

            if (BigDecimal.ZERO.compareTo(currentValuedStock) != 0) {
                // NSC = Negative Stock Correction
                createAdjustment("NSC", currentValuedStock);
            }
        }

        // update trxCost after cost adjustments
        transaction = OBDal.getInstance().get(MaterialTransaction.class, transaction.getId());
        trxCost = CostAdjustmentUtils.getTrxCost(transaction, false, getCostCurrency());
    }

    private boolean createAdjustment(String type, BigDecimal amount) {

        CostAdjustment costAdjustmentHeader = CostAdjustmentUtils
                .insertCostAdjustmentHeader(transaction.getOrganization(), type);
        CostAdjustmentLine cal = CostAdjustmentUtils.insertCostAdjustmentLine(transaction, costAdjustmentHeader,
                amount, Boolean.TRUE, transaction.getMovementDate());

        if (StringUtils.equals(type, "BDT")) {
            cal.setBackdatedTrx(Boolean.TRUE);
        } else if (StringUtils.equals(type, "NSC")) {
            cal.setNegativeStockCorrection(Boolean.TRUE);
            cal.setUnitCost(Boolean.FALSE);
        }

        OBDal.getInstance().save(cal);
        OBDal.getInstance().flush();
        JSONObject message = CostAdjustmentProcess.doProcessCostAdjustment(costAdjustmentHeader);

        try {
            if (message.get("severity") != "success") {
                throw new OBException(OBMessageUtils.parseTranslation("@ErrorProcessingCostAdj@") + ": "
                        + costAdjustmentHeader.getDocumentNo() + " - " + message.getString("text"));
            }
            return true;
        } catch (JSONException ignore) {
            throw new OBException(OBMessageUtils.parseTranslation("@ErrorProcessingCostAdj@"));
        }
    }

    private void setNotPostedTransaction() {
        TrxType trxType = TrxType.getTrxType(transaction);
        switch (trxType) {
        case Shipment:
        case ShipmentReturn:
        case ShipmentVoid:
        case ShipmentNegative:
        case Receipt:
        case ReceiptReturn:
        case ReceiptVoid:
        case ReceiptNegative: {
            org.openbravo.model.materialmgmt.transaction.ShipmentInOut inout = transaction.getGoodsShipmentLine()
                    .getShipmentReceipt();
            if (!"N".equals(inout.getPosted()) || !"Y".equals(inout.getPosted())) {
                inout.setPosted("N");
                OBDal.getInstance().save(inout);
                // Set for the Match Invoices associated
                List<ReceiptInvoiceMatch> invoiceMatchList = transaction.getGoodsShipmentLine()
                        .getProcurementReceiptInvoiceMatchList();
                if (invoiceMatchList != null && !invoiceMatchList.isEmpty()) {
                    for (ReceiptInvoiceMatch invoiceMatch : invoiceMatchList) {
                        if (!"N".equals(invoiceMatch.getPosted()) || !"Y".equals(invoiceMatch.getPosted())) {
                            invoiceMatch.setPosted("N");
                            OBDal.getInstance().save(invoiceMatch);
                        }
                    }
                }
            }
            break;
        }
        case InventoryDecrease:
        case InventoryIncrease:
        case InventoryOpening:
        case InventoryClosing: {
            InventoryCount inventory = transaction.getPhysicalInventoryLine().getPhysInventory();
            if (!"N".equals(inventory.getPosted()) || !"Y".equals(inventory.getPosted())) {
                inventory.setPosted("N");
                OBDal.getInstance().save(inventory);
            }
            break;
        }
        case IntMovementFrom:
        case IntMovementTo: {
            InternalMovement movement = transaction.getMovementLine().getMovement();
            if (!"N".equals(movement.getPosted()) || !"Y".equals(movement.getPosted())) {
                movement.setPosted("N");
                OBDal.getInstance().save(movement);
            }
            break;
        }
        case InternalCons:
        case InternalConsNegative:
        case InternalConsVoid: {
            InternalConsumption consumption = transaction.getInternalConsumptionLine().getInternalConsumption();
            if (!"N".equals(consumption.getPosted()) || !"Y".equals(consumption.getPosted())) {
                consumption.setPosted("N");
                OBDal.getInstance().save(consumption);
            }
            break;
        }
        case BOMPart:
        case BOMProduct:
        case ManufacturingConsumed:
        case ManufacturingProduced: {
            ProductionTransaction production = transaction.getProductionLine().getProductionPlan().getProduction();
            if (!"N".equals(production.getPosted()) || !"Y".equals(production.getPosted())) {
                production.setPosted("N");
                OBDal.getInstance().save(production);
            }
            break;
        }
        case Unknown:
            throw new OBException("@UnknownTrxType@: " + transaction.getIdentifier());
        default:
            throw new OBException("@UnknownTrxType@: " + transaction.getIdentifier());
        }
    }

    private CostingAlgorithm getCostingAlgorithm() {
        // Algorithm class is retrieved from costDimensionRule
        org.openbravo.model.materialmgmt.cost.CostingAlgorithm costAlgorithm = costingRule.getCostingAlgorithm();
        // FIXME: remove when manufacturing costs are fully migrated
        // In case the product is Manufacturing type it is forced to use Average Algorithm
        if (transaction.getProduct().isProduction()
                && !"org.openbravo.costing.StandardAlgorithm".equals(costAlgorithm.getJavaClassName())) {
            OBQuery<org.openbravo.model.materialmgmt.cost.CostingAlgorithm> caQry = OBDal.getInstance().createQuery(
                    org.openbravo.model.materialmgmt.cost.CostingAlgorithm.class,
                    org.openbravo.model.materialmgmt.cost.CostingAlgorithm.PROPERTY_JAVACLASSNAME
                            + " = 'org.openbravo.costing.AverageAlgorithm'");
            caQry.setFilterOnReadableClients(false);
            caQry.setFilterOnReadableOrganization(false);
            costAlgorithm = caQry.uniqueResult();
        }
        transaction.setCostingAlgorithm(costAlgorithm);

        try {
            final Class<?> clz = OBClassLoader.getInstance().loadClass(costAlgorithm.getJavaClassName());
            return (CostingAlgorithm) clz.newInstance();
        } catch (Exception e) {
            log4j.error("Exception loading Algorithm class: " + costAlgorithm.getJavaClassName() + " algorithm: "
                    + costAlgorithm.getIdentifier());
            throw new OBException("@AlgorithmClassNotLoaded@: " + costAlgorithm.getName(), e);
        }
    }

    private void createTransactionCost() {
        TransactionCost transactionCost = OBProvider.getInstance().get(TransactionCost.class);
        transactionCost.setInventoryTransaction(transaction);
        transactionCost.setOrganization(transaction.getOrganization());
        transactionCost.setCost(trxCost);
        transactionCost.setCurrency(currency);
        transactionCost.setCostDate(transaction.getTransactionProcessDate());
        transactionCost.setAccountingDate(transaction.getMovementDate());
        transactionCost.setUnitCost(Boolean.TRUE);
        OBDal.getInstance().save(transactionCost);
        transaction.getTransactionCostList().add(transactionCost);
    }

    public BigDecimal getTransactionCost() {
        return trxCost;
    }

    private CostingRule getCostDimensionRule() {
        StringBuffer where = new StringBuffer();
        where.append(CostingRule.PROPERTY_ORGANIZATION + " = :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", organization);
        crQry.setNamedParameter("startdate", transaction.getTransactionProcessDate());
        crQry.setNamedParameter("enddate", transaction.getTransactionProcessDate());
        crQry.setMaxResult(1);
        List<CostingRule> costRules = crQry.list();
        if (costRules.size() == 0) {
            throw new OBException(
                    "@NoCostingRuleFoundForOrganizationAndDate@ @Organization@: " + organization.getName()
                            + ", @Date@: " + OBDateUtils.formatDate(transaction.getTransactionProcessDate()));
        }
        return costRules.get(0);
    }

    public Currency getCostCurrency() {
        if (currency != null) {
            return currency;
        }
        // FIXME: remove when manufacturing costs are fully migrated
        // Production product costs are calculated in clients currency
        if (transaction.getProduct().isProduction()) {
            return transaction.getClient().getCurrency();
        }
        if (organization != null) {
            if (organization.getCurrency() != null) {
                return organization.getCurrency();
            }
            return organization.getClient().getCurrency();
        } else {
            init();
            return getCostCurrency();
        }
    }

    public Organization getOrganization() {
        if (organization != null) {
            return organization;
        }
        Organization org = OBContext.getOBContext()
                .getOrganizationStructureProvider(transaction.getClient().getId())
                .getLegalEntity(transaction.getOrganization());
        if (org == null) {
            throw new OBException("@WrongCostOrganization@" + transaction.getIdentifier());
        }
        return org;
    }

    public CostingRule getCostingRule() {
        return costingRule;
    }

    public MaterialTransaction getTransaction() {
        return transaction;
    }

    /**
     * Transaction types implemented on the cost engine.
     */
    public enum TrxType {
        Shipment, ShipmentReturn, ShipmentVoid, ShipmentNegative, Receipt, ReceiptReturn, ReceiptVoid, ReceiptNegative, InventoryIncrease, InventoryDecrease, InventoryOpening, InventoryClosing, IntMovementFrom, IntMovementTo, InternalCons, InternalConsNegative, InternalConsVoid, BOMPart, BOMProduct, ManufacturingConsumed, ManufacturingProduced, Unknown;
        /**
         * Given a Material Management transaction returns its type.
         */
        public static TrxType getTrxType(MaterialTransaction transaction) {
            if (transaction.getGoodsShipmentLine() != null) {
                // Receipt / Shipment
                org.openbravo.model.materialmgmt.transaction.ShipmentInOut inout = transaction
                        .getGoodsShipmentLine().getShipmentReceipt();
                if (inout.isSalesTransaction()) {
                    // Shipment
                    if (inout.getDocumentStatus().equals("VO")
                            && transaction.getGoodsShipmentLine().getCanceledInoutLine() != null) {
                        log4j.debug("Void shipment: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ShipmentVoid;
                    } else if (inout.getDocumentType().isReturn()) {
                        log4j.debug("Reversal shipment: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ShipmentReturn;
                    } else if (transaction.getGoodsShipmentLine().getMovementQuantity()
                            .compareTo(BigDecimal.ZERO) < 0) {
                        log4j.debug("Negative Shipment: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ShipmentNegative;
                    } else {
                        log4j.debug("Shipment: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return Shipment;
                    }
                } else {
                    // Receipt
                    if (inout.getDocumentStatus().equals("VO")
                            && transaction.getGoodsShipmentLine().getCanceledInoutLine() != null) {
                        log4j.debug("Void receipt: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ReceiptVoid;
                    } else if (inout.getDocumentType().isReturn()) {
                        log4j.debug("Reversal Receipt: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ReceiptReturn;
                    } else if (transaction.getGoodsShipmentLine().getMovementQuantity()
                            .compareTo(BigDecimal.ZERO) < 0) {
                        log4j.debug("Negative Receipt: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return ReceiptNegative;
                    } else {
                        log4j.debug("Receipt: " + transaction.getGoodsShipmentLine().getIdentifier());
                        return Receipt;
                    }
                }
            } else if (transaction.getPhysicalInventoryLine() != null) {
                // Physical Inventory
                String invType = transaction.getPhysicalInventoryLine().getPhysInventory().getInventoryType();
                if ("O".equals(invType)) {
                    return InventoryOpening;
                } else if ("C".equals(invType)) {
                    return InventoryClosing;
                }
                if (transaction.getMovementQuantity().compareTo(BigDecimal.ZERO) > 0) {
                    log4j.debug("Physical inventory, increments stock: "
                            + transaction.getPhysicalInventoryLine().getIdentifier());
                    return InventoryIncrease;
                } else {
                    log4j.debug("Physical inventory, decreases stock "
                            + transaction.getPhysicalInventoryLine().getIdentifier());
                    return InventoryDecrease;
                }
            } else if (transaction.getMovementLine() != null) {
                // Internal movement
                if (transaction.getMovementQuantity().compareTo(BigDecimal.ZERO) > 0) {
                    log4j.debug("Internal Movement to: " + transaction.getMovementLine().getIdentifier());
                    return IntMovementTo;
                } else {
                    log4j.debug("Internal Movement from: " + transaction.getMovementLine().getIdentifier());
                    return IntMovementFrom;
                }
            } else if (transaction.getInternalConsumptionLine() != null) {
                if (transaction.getInternalConsumptionLine().getVoidedInternalConsumptionLine() != null) {
                    return InternalConsVoid;
                } else if (transaction.getMovementQuantity().compareTo(BigDecimal.ZERO) > 0) {
                    log4j.debug("Negative Internal Consumption: "
                            + transaction.getInternalConsumptionLine().getIdentifier());
                    return InternalConsNegative;
                } else {
                    log4j.debug(
                            "Internal Consumption: " + transaction.getInternalConsumptionLine().getIdentifier());
                    return InternalCons;
                }
            } else if (transaction.getProductionLine() != null) {
                // Production Line
                if (transaction.getProductionLine().getProductionPlan().getProduction().isSalesTransaction()) {
                    // BOM Production
                    if (transaction.getMovementQuantity().compareTo(BigDecimal.ZERO) > 0) {
                        log4j.debug("Produced BOM product: " + transaction.getProductionLine().getIdentifier());
                        return BOMProduct;
                    } else {
                        log4j.debug("Used BOM Part: " + transaction.getProductionLine().getIdentifier());
                        // Used parts
                        return BOMPart;
                    }
                } else {
                    log4j.debug("Manufacturing Product");
                    // Work Effort
                    if ("+".equals(transaction.getProductionLine().getProductionType())) {
                        return ManufacturingProduced;
                    } else {
                        return ManufacturingConsumed;
                    }
                }
            }
            return Unknown;
        }
    }
}