From 69ed0297c486312f3cbc042969c124e2d1b02aca Mon Sep 17 00:00:00 2001 From: Heng Sin Low Date: Mon, 15 Jul 2013 16:03:43 +0800 Subject: [PATCH] IDEMPIERE-1173 Fixed landed cost allocation for Average Costing. --- .../src/org/compiere/acct/Doc_Invoice.java | 82 +++++++---- .../src/org/compiere/model/MCost.java | 8 ++ .../src/org/compiere/model/MCostDetail.java | 133 +++++------------- 3 files changed, 99 insertions(+), 124 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java index fdb042e4a2..3444a4077c 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java @@ -20,12 +20,15 @@ import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Savepoint; import java.util.ArrayList; import java.util.logging.Level; +import org.adempiere.exceptions.AverageCostingZeroQtyException; import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; import org.compiere.model.MClientInfo; +import org.compiere.model.MConversionRate; import org.compiere.model.MCostDetail; import org.compiere.model.MCurrency; import org.compiere.model.MInvoice; @@ -33,8 +36,10 @@ import org.compiere.model.MInvoiceLine; import org.compiere.model.MLandedCostAllocation; import org.compiere.model.MTax; import org.compiere.model.ProductCost; +import org.compiere.model.X_M_Cost; import org.compiere.util.DB; import org.compiere.util.Env; +import org.compiere.util.Trx; /** * Post Invoice Documents. @@ -835,22 +840,8 @@ public class Doc_Invoice extends Doc if (line.getDescription() != null) desc += " - " + line.getDescription(); - // Accounting - ProductCost pc = new ProductCost (Env.getCtx(), - lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), getTrxName()); - BigDecimal drAmt = null; - BigDecimal crAmt = null; - if (dr) - drAmt = lca.getAmt(); - else - crAmt = lca.getAmt(); - FactLine fl = fact.createLine (line, pc.getAccount(ProductCost.ACCTTYPE_P_CostAdjustment, as), - getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - // Cost Detail - Convert to AcctCurrency - /* + BigDecimal allocationAmt = lca.getAmt(); if (getC_Currency_ID() != as.getC_Currency_ID()) allocationAmt = MConversionRate.convert(getCtx(), allocationAmt, @@ -861,15 +852,58 @@ public class Doc_Invoice extends Doc allocationAmt = allocationAmt.setScale(as.getCostingPrecision(), BigDecimal.ROUND_HALF_UP); if (!dr) allocationAmt = allocationAmt.negate(); - // AZ Goodwill - // use createInvoice to create/update non Material Cost Detail - MCostDetail.createInvoice(as, lca.getAD_Org_ID(), - lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), - C_InvoiceLine_ID, lca.getM_CostElement_ID(), - allocationAmt, lca.getQty(), - desc, getTrxName()); - */ - // end AZ + + Trx trx = Trx.get(getTrxName(), false); + Savepoint savepoint = null; + boolean zeroQty = false; + try { + savepoint = trx.setSavepoint(null); + + if (!MCostDetail.createInvoice(as, lca.getAD_Org_ID(), + lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), + C_InvoiceLine_ID, lca.getM_CostElement_ID(), + allocationAmt, lca.getQty(), + desc, getTrxName())) { + throw new RuntimeException("Failed to create cost detail record."); + } + } catch (SQLException e) { + throw new RuntimeException(e.getLocalizedMessage(), e); + } catch (AverageCostingZeroQtyException e) { + zeroQty = true; + try { + trx.rollback(savepoint); + savepoint = null; + } catch (SQLException e1) { + throw new RuntimeException(e1.getLocalizedMessage(), e1); + } + } finally { + if (savepoint != null) { + try { + trx.releaseSavepoint(savepoint); + } catch (SQLException e) {} + } + } + + // Accounting + ProductCost pc = new ProductCost (Env.getCtx(), + lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), getTrxName()); + BigDecimal drAmt = null; + BigDecimal crAmt = null; + if (dr) + drAmt = lca.getAmt(); + else + crAmt = lca.getAmt(); + String costingMethod = pc.getProduct().getCostingMethod(as); + MAccount account = null; + if (X_M_Cost.COSTINGMETHOD_AverageInvoice.equals(costingMethod) || X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) { + account = zeroQty ? pc.getAccount(ProductCost.ACCTTYPE_P_Cogs, as) : pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + } else { + account = pc.getAccount(ProductCost.ACCTTYPE_P_CostAdjustment, as); + } + + FactLine fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); } if (log.isLoggable(Level.CONFIG)) log.config("Created #" + lcas.length); diff --git a/org.adempiere.base/src/org/compiere/model/MCost.java b/org.adempiere.base/src/org/compiere/model/MCost.java index 33a4633321..b67fcbe2b0 100644 --- a/org.adempiere.base/src/org/compiere/model/MCost.java +++ b/org.adempiere.base/src/org/compiere/model/MCost.java @@ -28,6 +28,7 @@ import java.util.Properties; import java.util.logging.Level; import org.adempiere.exceptions.AverageCostingNegativeQtyException; +import org.adempiere.exceptions.AverageCostingZeroQtyException; import org.adempiere.exceptions.DBException; import org.compiere.Adempiere; import org.compiere.util.CLogger; @@ -1487,6 +1488,13 @@ public class MCost extends X_M_Cost amt = amt.negate(); } + //can't do cost adjustment if there's no stock left + if (qty.signum() == 0 && getCurrentQty().signum() <= 0) + { + throw new AverageCostingZeroQtyException("Product(ID)="+getM_Product_ID()+", Current Qty="+getCurrentQty()+", Trx Qty="+qty + +", CostElement="+getM_CostElement().getName()+", Schema="+getC_AcctSchema().getName()); + } + if (getCurrentQty().add(qty).signum() < 0) { throw new AverageCostingNegativeQtyException("Product(ID)="+getM_Product_ID()+", Current Qty="+getCurrentQty()+", Trx Qty="+qty diff --git a/org.adempiere.base/src/org/compiere/model/MCostDetail.java b/org.adempiere.base/src/org/compiere/model/MCostDetail.java index 735e4d46c4..ec8df41c4b 100644 --- a/org.adempiere.base/src/org/compiere/model/MCostDetail.java +++ b/org.adempiere.base/src/org/compiere/model/MCostDetail.java @@ -151,11 +151,13 @@ public class MCostDetail extends X_M_CostDetail .append("WHERE Processed='N' AND COALESCE(DeltaAmt,0)=0 AND COALESCE(DeltaQty,0)=0") .append(" AND C_InvoiceLine_ID=").append(C_InvoiceLine_ID) .append(" AND C_AcctSchema_ID =").append(as.getC_AcctSchema_ID()) - .append(" AND M_AttributeSetInstance_ID=").append(M_AttributeSetInstance_ID); + .append(" AND M_AttributeSetInstance_ID=").append(M_AttributeSetInstance_ID) + .append(" AND Coalesce(M_CostElement_ID,0)=").append(M_CostElement_ID); + int no = DB.executeUpdate(sql.toString(), trxName); if (no != 0) if (s_log.isLoggable(Level.CONFIG)) s_log.config("Deleted #" + no); - MCostDetail cd = get (as.getCtx(), "C_InvoiceLine_ID=?", + MCostDetail cd = get (as.getCtx(), "C_InvoiceLine_ID=? AND Coalesce(M_CostElement_ID,0)="+M_CostElement_ID, C_InvoiceLine_ID, M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), trxName); // if (cd == null) // createNew @@ -796,7 +798,20 @@ public class MCostDetail extends X_M_CostDetail else { MCostElement ce = MCostElement.get(getCtx(), getM_CostElement_ID()); - ok = process (as, product, ce, Org_ID, M_ASI_ID); + if (ce.getCostingMethod() == null) + { + MCostElement[] ces = MCostElement.getCostingMethods(this); + for (MCostElement costingElement : ces) + { + ok = process (as, product, costingElement, Org_ID, M_ASI_ID); + if (!ok) + break; + } + } + else + { + ok = process (as, product, ce, Org_ID, M_ASI_ID); + } } // Save it @@ -859,6 +874,14 @@ public class MCostDetail extends X_M_CostDetail amt = getAmt(); } // end MZ + + boolean costAdjustment = false; + //landed cost adjustment + if (this.getM_CostElement_ID() > 0 && this.getM_CostElement_ID() != ce.getM_CostElement_ID() && getC_InvoiceLine_ID() > 0) + { + qty = BigDecimal.ZERO; + costAdjustment = true; + } int precision = as.getCostingPrecision(); BigDecimal price = amt; @@ -942,6 +965,10 @@ public class MCostDetail extends X_M_CostDetail cost.setWeightedAverage(amt, qty); if (log.isLoggable(Level.FINER)) log.finer("Inv - AverageInv - " + cost); } + else if (ce.isAveragePO() && costAdjustment) + { + cost.setWeightedAverage(amt, qty); + } else if (ce.isFifo() || ce.isLifo()) { @@ -958,7 +985,7 @@ public class MCostDetail extends X_M_CostDetail cost.add(amt, qty); if (log.isLoggable(Level.FINER)) log.finer("Inv - FiFo/LiFo - " + cost); } - else if (ce.isLastInvoice()) + else if (ce.isLastInvoice() && !costAdjustment) { if (!isReturnTrx) { @@ -973,7 +1000,7 @@ public class MCostDetail extends X_M_CostDetail cost.add(amt, qty); if (log.isLoggable(Level.FINER)) log.finer("Inv - LastInv - " + cost); } - else if (ce.isStandardCosting()) + else if (ce.isStandardCosting() && !costAdjustment) { // Update cost record only if it is zero if (cost.getCurrentCostPrice().signum() == 0 @@ -996,54 +1023,7 @@ public class MCostDetail extends X_M_CostDetail // Interface cost.add(amt, qty); if (log.isLoggable(Level.FINER)) log.finer("Inv - UserDef - " + cost); - } - /* - else if (!ce.isCostingMethod()) // Cost Adjustments - { - // AZ Goodwill - //get costing method for product - String costingMethod = product.getCostingMethod(as); - if (MAcctSchema.COSTINGMETHOD_AveragePO.equals(costingMethod) || - MAcctSchema.COSTINGMETHOD_AverageInvoice.equals(costingMethod)) - { - // Problem with Landed Costs: certain cost element may not occur in every purchases, - // causing the average calculation of that cost element wrongly took the current qty. - // - // Solution: - // Make sure the current qty is reflecting the actual qty in storage - // - StringBuilder sql = new StringBuilder("SELECT COALESCE(SUM(QtyOnHand),0) FROM M_StorageOnHand") - .append(" WHERE AD_Client_ID=").append(cost.getAD_Client_ID()) - .append(" AND M_Product_ID=").append(cost.getM_Product_ID()); - //Costing Level - String CostingLevel = product.getCostingLevel(as); - if (MAcctSchema.COSTINGLEVEL_Organization.equals(CostingLevel)) - sql.append(" AND AD_Org_ID=").append(cost.getAD_Org_ID()); - else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel)) - sql.append(" AND M_AttributeSetInstance_ID=").append(M_ASI_ID); - // - BigDecimal qtyOnhand = DB.getSQLValueBD(get_TrxName(), sql.toString()); - if (qtyOnhand.signum() != 0) - { - BigDecimal oldSum = cost.getCurrentCostPrice().multiply(cost.getCurrentQty()); - BigDecimal sumAmt = oldSum.add(amt); // amt is total already - BigDecimal costs = sumAmt.divide(qtyOnhand, precision, BigDecimal.ROUND_HALF_UP); - cost.setCurrentCostPrice(costs); - } - cost.setCumulatedAmt(cost.getCumulatedAmt().add(amt)); - cost.setCumulatedQty(cost.getCumulatedQty().add(qty)); - cost.setCurrentQty(qtyOnhand); - } - else //original logic from Compiere - { - BigDecimal cCosts = cost.getCurrentCostPrice().add(amt); - cost.setCurrentCostPrice(cCosts); - cost.add(amt, qty); - } - // end AZ - if (log.isLoggable(Level.FINER)) log.finer("Inv - Landed Costs - " + cost); - } - */ + } // else // log.warning("Inv - " + ce + " - " + cost); } @@ -1177,53 +1157,6 @@ public class MCostDetail extends X_M_CostDetail else log.warning("QtyAdjust - " + ce + " - " + cost); - //AZ Goodwill - //Also update Landed Costs to reflect the actual qty in storage - /* - String costingMethod = ce.getCostingMethod(); - if (MAcctSchema.COSTINGMETHOD_AveragePO.equals(costingMethod) || - MAcctSchema.COSTINGMETHOD_AverageInvoice.equals(costingMethod)) - { - MCostElement[] lce = MCostElement.getNonCostingMethods(this); - if (lce.length > 0) - { - StringBuilder sql = new StringBuilder("SELECT COALESCE(SUM(QtyOnHand),0) FROM M_StorageOnHand") - .append(" WHERE AD_Client_ID=").append(cost.getAD_Client_ID()) - .append(" AND M_Product_ID=").append(cost.getM_Product_ID()); - //Costing Level - String CostingLevel = product.getCostingLevel(as); - if (MAcctSchema.COSTINGLEVEL_Organization.equals(CostingLevel)) - sql.append(" AND AD_Org_ID=").append(cost.getAD_Org_ID()); - else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel)) - sql.append(" AND M_AttributeSetInstance_ID=").append(M_ASI_ID); - // - BigDecimal qtyOnhand = DB.getSQLValueBD(get_TrxName(), sql.toString()); - for (int i = 0 ; i < lce.length ; i++) - { - MCost lCost = MCost.get(getCtx(), cost.getAD_Client_ID(), cost.getAD_Org_ID(), - cost.getM_Product_ID(), cost.getM_CostType_ID(), cost.getC_AcctSchema_ID(), - lce[i].getM_CostElement_ID(), cost.getM_AttributeSetInstance_ID(), get_TrxName()); - if (lCost != null) - { - if (qtyOnhand.signum() != 0) - { - // new average cost - BigDecimal oldSum = lCost.getCurrentCostPrice().multiply(lCost.getCurrentQty()); - BigDecimal costs = oldSum.divide(qtyOnhand, precision, BigDecimal.ROUND_HALF_UP); - lCost.setCurrentCostPrice(costs); - } - lCost.setCurrentQty(qtyOnhand); - if (!lCost.save()) - { - log.warning("Update Landed Costs (Qty) fail: " + lce + " - " + lCost); - return false; - } - } - } - }//end-if - } - */ - //end AZ } else // unknown or no id {