Java tutorial
/* ************************************************************************* * 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) 2014-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.HashMap; import java.util.List; import javax.enterprise.context.ApplicationScoped; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.criterion.Restrictions; 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.service.OBCriteria; import org.openbravo.dal.service.OBDal; import org.openbravo.dal.service.OBQuery; import org.openbravo.erpCommon.businessUtility.Preferences; import org.openbravo.erpCommon.utility.PropertyException; import org.openbravo.model.common.businesspartner.BusinessPartner; import org.openbravo.model.common.currency.Currency; import org.openbravo.model.common.enterprise.Organization; import org.openbravo.model.common.enterprise.Warehouse; import org.openbravo.model.common.order.OrderLine; import org.openbravo.model.common.plm.Product; 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.transaction.InternalConsumptionLine; import org.openbravo.model.materialmgmt.transaction.InventoryCountLine; import org.openbravo.model.materialmgmt.transaction.MaterialTransaction; import org.openbravo.model.materialmgmt.transaction.ProductionLine; import org.openbravo.model.materialmgmt.transaction.ShipmentInOutLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ApplicationScoped public abstract class CostingAlgorithmAdjustmentImp { private static final Logger log4j = LoggerFactory.getLogger(CostingAlgorithmAdjustmentImp.class); protected String strCostAdjLineId; protected String strCostAdjId; protected String strTransactionId; protected String strCostOrgId; protected String strCostCurrencyId; protected int costCurPrecission; protected int stdCurPrecission; protected TrxType trxType; protected String strCostingRuleId; protected Date startingDate; protected String strClientId; protected boolean isManufacturingProduct; protected boolean areBackdatedTrxFixed; protected boolean checkNegativeStockCorrection; protected HashMap<CostDimension, String> costDimensionIds = new HashMap<CostDimension, String>(); /** * Initializes class variables to perform the cost adjustment process. Variables are stored by the * ids instead of the BaseOBObject to be safe of session clearing. * * @param costAdjLine * The Cost Adjustment Line that it is processed. */ protected void init(CostAdjustmentLine costAdjLine) { strCostAdjLineId = costAdjLine.getId(); strCostAdjId = (String) DalUtil.getId(costAdjLine.getCostAdjustment()); MaterialTransaction transaction = costAdjLine.getInventoryTransaction(); strTransactionId = transaction.getId(); isManufacturingProduct = transaction.getProduct().isProduction(); CostingServer costingServer = new CostingServer(transaction); strCostOrgId = costingServer.getOrganization().getId(); strCostCurrencyId = transaction.getCurrency().getId(); costCurPrecission = transaction.getCurrency().getCostingPrecision().intValue(); stdCurPrecission = transaction.getCurrency().getStandardPrecision().intValue(); trxType = CostingServer.TrxType.getTrxType(transaction); CostingRule costingRule = costingServer.getCostingRule(); strCostingRuleId = costingRule.getId(); startingDate = CostingUtils.getCostingRuleStartingDate(costingRule); strClientId = costingRule.getClient().getId(); areBackdatedTrxFixed = costingRule.isBackdatedTransactionsFixed() && !transaction .getTransactionProcessDate().before(CostingUtils.getCostingRuleFixBackdatedFrom(costingRule)); HashMap<CostDimension, BaseOBObject> costDimensions = CostingUtils.getEmptyDimensions(); // Production products cannot be calculated by warehouse dimension. if (costingRule.isWarehouseDimension()) { costDimensions.put(CostDimension.Warehouse, transaction.getStorageBin().getWarehouse()); } for (CostDimension costDimension : costDimensions.keySet()) { String value = null; if (costDimensions.get(costDimension) != null) { value = (String) costDimensions.get(costDimension).getId(); } costDimensionIds.put(costDimension, value); } try { checkNegativeStockCorrection = 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) { checkNegativeStockCorrection = false; } } /** * Process to include in the Cost Adjustment the required lines of transactions whose cost needs * to be adjusted as a consequence of other lines already included. */ protected void searchRelatedTransactionCosts(CostAdjustmentLine _costAdjLine) { boolean searchRelatedTransactions = true; CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; searchRelatedTransactions = false; } else { costAdjLine = getCostAdjLine(); } // Backdated transactions are inserted with a null adjustment amount. if (costAdjLine.isBackdatedTrx()) { calculateBackdatedTrxAdjustment(costAdjLine); } // Negative stock correction are inserted with a null adjustment amount. if (costAdjLine.isNegativeStockCorrection()) { calculateNegativeStockCorrectionAdjustmentAmount(costAdjLine); } if (costAdjLine.isSource()) { addCostDependingTrx(null); if (BigDecimal.ZERO.compareTo(costAdjLine.getAdjustmentAmount()) == 0) { costAdjLine.setNeedsPosting(Boolean.FALSE); } } if (searchRelatedTransactions) { getRelatedTransactionsByAlgorithm(); } } protected void addCostDependingTrx(CostAdjustmentLine costAdjLine) { // Some transaction costs are directly related to other transaction costs. These relationships // must be kept when the original transaction cost is adjusted adjusting as well the dependent // transactions. TrxType _trxType = trxType; if (costAdjLine != null) { _trxType = TrxType.getTrxType(costAdjLine.getInventoryTransaction()); } switch (_trxType) { case Shipment: searchReturnShipments(costAdjLine); case Receipt: searchVoidInOut(costAdjLine); break; case IntMovementFrom: searchIntMovementTo(costAdjLine); break; case InternalCons: searchVoidInternalConsumption(costAdjLine); break; case BOMPart: searchBOMProducts(costAdjLine); break; case ManufacturingConsumed: searchManufacturingProduced(costAdjLine); break; case InventoryDecrease: case InventoryIncrease: searchOpeningInventory(costAdjLine); default: break; } } /** * Inserts a new cost adjustment line * * @param trx * Material transaction * @param adjustmentamt * Adjustment amount * @param _parentLine * Cost Adjustment Line * */ protected CostAdjustmentLine insertCostAdjustmentLine(MaterialTransaction trx, BigDecimal adjustmentamt, CostAdjustmentLine _parentLine) { Date dateAcct = trx.getMovementDate(); CostAdjustmentLine parentLine; if (_parentLine == null) { parentLine = getCostAdjLine(); } else { parentLine = _parentLine; } Date parentAcctDate = parentLine.getAccountingDate(); if (parentAcctDate == null) { parentAcctDate = parentLine.getInventoryTransaction().getMovementDate(); } if (dateAcct.before(parentAcctDate)) { dateAcct = parentAcctDate; } CostAdjustmentLine newCAL = CostAdjustmentUtils.insertCostAdjustmentLine(trx, (CostAdjustment) OBDal.getInstance().getProxy(CostAdjustment.ENTITY_NAME, strCostAdjId), adjustmentamt, false, dateAcct); newCAL.setRelatedTransactionAdjusted(false); newCAL.setParentCostAdjustmentLine(parentLine); OBDal.getInstance().save(newCAL); OBDal.getInstance().flush(); addCostDependingTrx(newCAL); return newCAL; } /** * When the cost of a Closing Inventory is adjusted it is needed to adjust with the same amount * the related Opening Inventory. */ protected void searchOpeningInventory(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } InventoryCountLine invline = costAdjLine.getInventoryTransaction().getPhysicalInventoryLine() .getRelatedInventory(); if (invline == null) { return; } MaterialTransaction deptrx = invline.getMaterialMgmtMaterialTransactionList().get(0); if (!deptrx.isCostCalculated()) { return; } insertCostAdjustmentLine(deptrx, costAdjLine.getAdjustmentAmount(), _costAdjLine); } protected void searchManufacturingProduced(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } ProductionLine pl = costAdjLine.getInventoryTransaction().getProductionLine(); OBCriteria<ProductionLine> critPL = OBDal.getInstance().createCriteria(ProductionLine.class); critPL.createAlias(ProductionLine.PROPERTY_PRODUCT, "pr"); critPL.add(Restrictions.eq(ProductionLine.PROPERTY_PRODUCTIONPLAN, pl.getProductionPlan())); critPL.add(Restrictions.eq(ProductionLine.PROPERTY_PRODUCTIONTYPE, "+")); critPL.addOrderBy(ProductionLine.PROPERTY_COMPONENTCOST, true); BigDecimal pendingAmt = costAdjLine.getAdjustmentAmount(); CostAdjustmentLine lastAdjLine = null; for (ProductionLine pline : critPL.list()) { BigDecimal adjAmt = costAdjLine.getAdjustmentAmount().multiply(pline.getComponentCost()); pendingAmt = pendingAmt.subtract(adjAmt); if (!pline.getProduct().isStocked() || !"I".equals(pline.getProduct().getProductType())) { continue; } if (pline.getMaterialMgmtMaterialTransactionList().isEmpty()) { log4j.error("Production Line with id {} has no related transaction (M_Transaction).", pline.getId()); continue; } MaterialTransaction prodtrx = pline.getMaterialMgmtMaterialTransactionList().get(0); if (!prodtrx.isCostCalculated()) { continue; } CostAdjustmentLine newCAL = insertCostAdjustmentLine(prodtrx, adjAmt, _costAdjLine); lastAdjLine = newCAL; } // If there is more than one P+ product there can be some amount left to assign due to rounding. if (pendingAmt.signum() != 0 && lastAdjLine != null) { lastAdjLine.setAdjustmentAmount(lastAdjLine.getAdjustmentAmount().add(pendingAmt)); OBDal.getInstance().save(lastAdjLine); } } protected void searchBOMProducts(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } ProductionLine pl = costAdjLine.getInventoryTransaction().getProductionLine(); OBCriteria<ProductionLine> critBOM = OBDal.getInstance().createCriteria(ProductionLine.class); critBOM.createAlias(ProductionLine.PROPERTY_PRODUCT, "pr"); critBOM.add(Restrictions.eq(ProductionLine.PROPERTY_PRODUCTIONPLAN, pl.getProductionPlan())); critBOM.add(Restrictions.gt(ProductionLine.PROPERTY_MOVEMENTQUANTITY, BigDecimal.ZERO)); critBOM.add(Restrictions.eq("pr." + Product.PROPERTY_STOCKED, true)); critBOM.add(Restrictions.eq("pr." + Product.PROPERTY_PRODUCTTYPE, "I")); for (ProductionLine pline : critBOM.list()) { if (pline.getMaterialMgmtMaterialTransactionList().isEmpty()) { log4j.error("BOM Produced with id {} has no related transaction (M_Transaction).", pline.getId()); continue; } MaterialTransaction prodtrx = pline.getMaterialMgmtMaterialTransactionList().get(0); if (!prodtrx.isCostCalculated()) { continue; } insertCostAdjustmentLine(prodtrx, costAdjLine.getAdjustmentAmount(), _costAdjLine); } } protected void searchVoidInternalConsumption(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } List<InternalConsumptionLine> intConsVoidedList = costAdjLine.getInventoryTransaction() .getInternalConsumptionLine() .getMaterialMgmtInternalConsumptionLineVoidedInternalConsumptionLineList(); if (intConsVoidedList.isEmpty()) { return; } InternalConsumptionLine intCons = intConsVoidedList.get(0); MaterialTransaction voidedTrx = intCons.getMaterialMgmtMaterialTransactionList().get(0); if (!voidedTrx.isCostCalculated()) { return; } insertCostAdjustmentLine(voidedTrx, costAdjLine.getAdjustmentAmount(), _costAdjLine); } protected void searchIntMovementTo(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } MaterialTransaction transaction = costAdjLine.getInventoryTransaction(); for (MaterialTransaction movementTransaction : transaction.getMovementLine() .getMaterialMgmtMaterialTransactionList()) { if (movementTransaction.getId().equals(transaction.getId())) { continue; } if (!movementTransaction.isCostCalculated()) { continue; } insertCostAdjustmentLine(movementTransaction, costAdjLine.getAdjustmentAmount(), _costAdjLine); } } protected void searchVoidInOut(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } ShipmentInOutLine voidedinoutline = costAdjLine.getInventoryTransaction().getGoodsShipmentLine() .getCanceledInoutLine(); if (voidedinoutline == null) { return; } for (MaterialTransaction trx : voidedinoutline.getMaterialMgmtMaterialTransactionList()) { if (!trx.isCostCalculated()) { continue; } insertCostAdjustmentLine(trx, costAdjLine.getAdjustmentAmount(), _costAdjLine); } } protected void searchReturnShipments(CostAdjustmentLine _costAdjLine) { CostAdjustmentLine costAdjLine; if (_costAdjLine != null) { costAdjLine = _costAdjLine; } else { costAdjLine = getCostAdjLine(); } ShipmentInOutLine inoutline = costAdjLine.getInventoryTransaction().getGoodsShipmentLine(); BigDecimal costAdjAmt = costAdjLine.getAdjustmentAmount().negate(); int precission = getCostCurrency().getStandardPrecision().intValue(); StringBuffer where = new StringBuffer(); where.append(" as trx"); where.append(" join trx." + MaterialTransaction.PROPERTY_GOODSSHIPMENTLINE + " as iol"); where.append(" join iol." + ShipmentInOutLine.PROPERTY_SALESORDERLINE + " as ol"); where.append(" where ol." + OrderLine.PROPERTY_GOODSSHIPMENTLINE + " = :shipment"); OBQuery<MaterialTransaction> qryTrx = OBDal.getInstance().createQuery(MaterialTransaction.class, where.toString()); qryTrx.setFilterOnReadableOrganization(false); qryTrx.setNamedParameter("shipment", inoutline); ScrollableResults trxs = qryTrx.scroll(ScrollMode.FORWARD_ONLY); try { int counter = 0; while (trxs.next()) { counter++; MaterialTransaction trx = (MaterialTransaction) trxs.get()[0]; if (trx.isCostCalculated()) { BigDecimal adjAmt = costAdjAmt.multiply(trx.getMovementQuantity().abs()) .divide(inoutline.getMovementQuantity().abs(), precission, RoundingMode.HALF_UP); insertCostAdjustmentLine(trx, adjAmt, _costAdjLine); } if (counter % 1000 == 0) { OBDal.getInstance().flush(); OBDal.getInstance().getSession().clear(); } } } finally { trxs.close(); } } protected abstract void calculateNegativeStockCorrectionAdjustmentAmount(CostAdjustmentLine costAdjLine); protected abstract void getRelatedTransactionsByAlgorithm(); protected void calculateBackdatedTrxAdjustment(CostAdjustmentLine costAdjLine) { BigDecimal adjAmt = BigDecimal.ZERO; TrxType calTrxType = TrxType.getTrxType(costAdjLine.getInventoryTransaction()); if (costAdjLine.getInventoryTransaction().isCostPermanent() && costAdjLine.isUnitCost()) { costAdjLine .setCurrency((Currency) OBDal.getInstance().getProxy(Currency.ENTITY_NAME, strCostCurrencyId)); costAdjLine.setAdjustmentAmount(BigDecimal.ZERO); OBDal.getInstance().save(costAdjLine); return; } // Incoming transactions does not modify the calculated cost switch (calTrxType) { case ShipmentVoid: case ReceiptVoid: case IntMovementTo: case InternalConsVoid: case BOMProduct: case ManufacturingProduced: // The cost of these transaction types does not depend on the date it is calculated. break; case Receipt: if (hasOrder(costAdjLine)) { // If the receipt has a related order the cost amount does not depend on the date. break; } // Check receipt default on backdated date. adjAmt = getDefaultCostDifference(calTrxType, costAdjLine); break; case ShipmentReturn: if (hasReturnedReceipt(costAdjLine)) { // If the return receipt has a original receipt the cost amount does not depend on the date. break; } case ShipmentNegative: // These transaction types are calculated using the default cost. Check if there is a // difference. adjAmt = getDefaultCostDifference(calTrxType, costAdjLine); break; case InventoryIncrease: case InventoryOpening: if (inventoryHasCost(costAdjLine)) { // If the inventory line defines a unit cost it does not depend on the date. break; } case InternalConsNegative: // These transaction types are calculated using the default cost. Check if there is a // difference. adjAmt = getDefaultCostDifference(calTrxType, costAdjLine); break; case InventoryClosing: adjAmt = getInventoryClosingAmt(costAdjLine); break; case Shipment: case ReceiptReturn: case ReceiptNegative: case InventoryDecrease: case IntMovementFrom: case InternalCons: case BOMPart: case ManufacturingConsumed: // These transactions are calculated as regular outgoing transactions. The adjustment amount // needs to be calculated by the algorithm. adjAmt = getOutgoingBackdatedTrxAdjAmt(costAdjLine); default: break; } costAdjLine.setCurrency((Currency) OBDal.getInstance().getProxy(Currency.ENTITY_NAME, strCostCurrencyId)); costAdjLine.setAdjustmentAmount(adjAmt); OBDal.getInstance().save(costAdjLine); } protected abstract BigDecimal getOutgoingBackdatedTrxAdjAmt(CostAdjustmentLine costAdjLine); protected BigDecimal getDefaultCostDifference(TrxType calTrxType, CostAdjustmentLine costAdjLine) { MaterialTransaction trx = costAdjLine.getInventoryTransaction(); BusinessPartner bp = CostingUtils.getTrxBusinessPartner(trx, calTrxType); Organization costOrg = getCostOrg(); Date trxDate = CostAdjustmentUtils.getLastTrxDateOfMvmntDate(trx.getMovementDate(), trx.getProduct(), costOrg, getCostDimensions()); if (trxDate == null) { trxDate = trx.getTransactionProcessDate(); } BigDecimal defaultCost = CostingUtils.getDefaultCost(trx.getProduct(), trx.getMovementQuantity(), costOrg, trxDate, trx.getMovementDate(), bp, getCostCurrency(), getCostDimensions()); BigDecimal trxCalculatedCost = CostAdjustmentUtils.getTrxCost(trx, true, getCostCurrency()); return defaultCost.subtract(trxCalculatedCost); } private BigDecimal getInventoryClosingAmt(CostAdjustmentLine costAdjLine) { MaterialTransaction trx = costAdjLine.getInventoryTransaction(); // currentBalanceOnDate already includes the cost of the inventory closing. The balance after an // inventory closing should be zero, so the adjustment amount should be de current balance // negated. BigDecimal currentBalanceOnDate = CostAdjustmentUtils.getValuedStockOnMovementDateByAttrAndLocator( trx.getProduct(), getCostOrg(), trx.getMovementDate(), getCostDimensions(), trx.getStorageBin(), trx.getAttributeSetValue(), getCostCurrency(), true); return currentBalanceOnDate.negate(); } /** * Checks if the goods receipt line of the adjustment line has a related purchase order line. * * @param costAdjLine * the adjustment line to check. * @return true if there is a related order line. */ private boolean hasOrder(CostAdjustmentLine costAdjLine) { return costAdjLine.getInventoryTransaction().getGoodsShipmentLine() != null && costAdjLine.getInventoryTransaction().getGoodsShipmentLine().getSalesOrderLine() != null; } /** * Checks if the inventory line has a unit cost defined. * * @param costAdjLine * the adjustment line to check. * @return true if there is a unit cost. */ private boolean inventoryHasCost(CostAdjustmentLine costAdjLine) { return costAdjLine.getInventoryTransaction().getPhysicalInventoryLine() != null && costAdjLine.getInventoryTransaction().getPhysicalInventoryLine().getCost() != null; } /** * Checks if the returned receipt line has a related original shipment line. * * @param costAdjLine * the adjustment line to check. * @return true if there is a original shipment line. */ private boolean hasReturnedReceipt(CostAdjustmentLine costAdjLine) { OrderLine shipmentLine = costAdjLine.getInventoryTransaction().getGoodsShipmentLine().getSalesOrderLine(); return shipmentLine != null && shipmentLine.getGoodsShipmentLine() != null; } public CostAdjustmentLine getCostAdjLine() { return OBDal.getInstance().get(CostAdjustmentLine.class, strCostAdjLineId); } public CostAdjustment getCostAdj() { return OBDal.getInstance().get(CostAdjustment.class, strCostAdjId); } public MaterialTransaction getTransaction() { return OBDal.getInstance().get(MaterialTransaction.class, strTransactionId); } public Organization getCostOrg() { return OBDal.getInstance().get(Organization.class, strCostOrgId); } public Currency getCostCurrency() { return OBDal.getInstance().get(Currency.class, strCostCurrencyId); } public CostingRule getCostingRule() { return OBDal.getInstance().get(CostingRule.class, strCostingRuleId); } public HashMap<CostDimension, BaseOBObject> getCostDimensions() { HashMap<CostDimension, BaseOBObject> costDimensions = new HashMap<CostDimension, BaseOBObject>(); for (CostDimension costDimension : costDimensionIds.keySet()) { switch (costDimension) { case Warehouse: Warehouse warehouse = null; if (costDimensionIds.get(costDimension) != null) { warehouse = OBDal.getInstance().get(Warehouse.class, costDimensionIds.get(costDimension)); } costDimensions.put(costDimension, warehouse); break; default: break; } } return costDimensions; } }