From 0395bafa6a09c9cb37ef7745b3b5852e9ed04182 Mon Sep 17 00:00:00 2001 From: dpansheriya Date: Tue, 16 Apr 2024 00:27:14 +0530 Subject: [PATCH] IDEMPIERE-3040: Stock Coverage --- .../src/org/compiere/acct/Doc_Invoice.java | 104 ++++++++---- .../src/org/compiere/acct/Doc_MatchInv.java | 152 ++++++++++++++---- 2 files changed, 195 insertions(+), 61 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 1948462568..9a3ce122ec 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java @@ -34,7 +34,9 @@ import org.compiere.model.MAcctSchema; import org.compiere.model.MClientInfo; import org.compiere.model.MConversionRate; +import org.compiere.model.MCost; import org.compiere.model.MCostDetail; +import org.compiere.model.MCostElement; import org.compiere.model.MCurrency; import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; @@ -1043,14 +1045,47 @@ else if (allocationAmt.signum() < 0) if (!dr) costAdjustmentAmt = costAdjustmentAmt.negate(); - boolean zeroQty = false; + BigDecimal amtAsset = Env.ZERO; + BigDecimal amtVariance = Env.ZERO; if (costAdjustmentAmt.signum() != 0) { Trx trx = Trx.get(getTrxName(), false); Savepoint savepoint = null; try { savepoint = trx.setSavepoint(null); - BigDecimal costDetailAmt = costAdjustmentAmt; + + BigDecimal qty = lca.getQty(); + amtVariance = Env.ZERO; + amtAsset = costAdjustmentAmt; + + if(X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) + { + int AD_Org_ID = lca.getAD_Org_ID(); + int M_AttributeSetInstance_ID = lca.getM_AttributeSetInstance_ID(); + + if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel())) + { + AD_Org_ID = 0; + M_AttributeSetInstance_ID = 0; + } + else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel())) + M_AttributeSetInstance_ID = 0; + else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel())) + AD_Org_ID = 0; + + MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), as.getCostingMethod(), + AD_Org_ID); + MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, lca.getM_Product_ID(), + as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(), + M_AttributeSetInstance_ID, null); + if (c != null && c.getCurrentQty().compareTo(qty) < 0) + {//TODO test reposting of landed cost invoice + amtAsset = c.getCurrentQty().multiply(costAdjustmentAmt.divide(lca.getQty())); + amtVariance = costAdjustmentAmt.subtract(amtAsset); + } + } + + BigDecimal costDetailAmt = amtAsset; //convert to accounting schema currency if (getC_Currency_ID() != as.getC_Currency_ID()) costDetailAmt = MConversionRate.convert(getCtx(), costDetailAmt, @@ -1076,8 +1111,7 @@ desc, getTrxName())) { } catch (SQLException e) { throw new RuntimeException(e.getLocalizedMessage(), e); } catch (AverageCostingZeroQtyException e) { - zeroQty = true; - try { + try { //TODO test zero qty scenario trx.rollback(savepoint); savepoint = null; } catch (SQLException e1) { @@ -1110,8 +1144,9 @@ desc, getTrxName())) { estimatedAmt = estimatedAmt.setScale(as.getStdPrecision(), RoundingMode.HALF_UP); } int compare = allocationAmt.compareTo(estimatedAmt); - if (compare > 0) + if (allocationAmt.compareTo(estimatedAmt)!=0) { + // TODO test more order allocation then landed costing drAmt = dr ? (reversal ? null : estimatedAmt): (reversal ? estimatedAmt : null); crAmt = dr ? (reversal ? estimatedAmt : null): (reversal ? null : estimatedAmt); account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); @@ -1120,33 +1155,38 @@ desc, getTrxName())) { fl.setM_Product_ID(lca.getM_Product_ID()); fl.setQty(line.getQty()); - BigDecimal overAmt = allocationAmt.subtract(estimatedAmt); - drAmt = dr ? (reversal ? null : overAmt) : (reversal ? overAmt : null); - crAmt = dr ? (reversal ? overAmt : null) : (reversal ? null : overAmt); - account = zeroQty ? pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as) : pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); - fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); - } - else if (compare < 0) - { - drAmt = dr ? (reversal ? null : estimatedAmt) : (reversal ? estimatedAmt : null); - crAmt = dr ? (reversal ? estimatedAmt : null) : (reversal ? null : estimatedAmt); - account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); - FactLine fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); - - BigDecimal underAmt = estimatedAmt.subtract(allocationAmt); - drAmt = dr ? (reversal ? underAmt : null) : (reversal ? null : underAmt); - crAmt = dr ? (reversal ? null : underAmt) : (reversal ? underAmt : null); - account = zeroQty ? pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as) : pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); - fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); + if (amtVariance.compareTo(Env.ZERO) != 0) { + if (amtVariance.compareTo(Env.ZERO) > 0) { + drAmt = dr ? (reversal ? null : amtVariance) : (reversal ? amtVariance : null); + crAmt = dr ? (reversal ? amtVariance : null) : (reversal ? null : amtVariance); + } else { + BigDecimal underAmt = amtVariance.negate(); + drAmt = dr ? (reversal ? underAmt : null) : (reversal ? null : underAmt); + crAmt = dr ? (reversal ? null : underAmt) : (reversal ? underAmt : null); + } + + account = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } + + if (amtAsset.compareTo(Env.ZERO) != 0) { + if (amtAsset.compareTo(Env.ZERO) > 0) { + drAmt = dr ? (reversal ? null : amtAsset) : (reversal ? amtAsset : null); + crAmt = dr ? (reversal ? amtAsset : null) : (reversal ? null : amtAsset); + } else { + BigDecimal underAmt = amtAsset.negate(); + drAmt = dr ? (reversal ? underAmt : null) : (reversal ? null : underAmt); + crAmt = dr ? (reversal ? null : underAmt) : (reversal ? underAmt : null); + } + account = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } } else { diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java index 4628d49620..77a6eee591 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java @@ -36,7 +36,9 @@ import org.compiere.model.MAcctSchema; import org.compiere.model.MAcctSchemaElement; import org.compiere.model.MConversionRate; +import org.compiere.model.MCost; import org.compiere.model.MCostDetail; +import org.compiere.model.MCostElement; import org.compiere.model.MCurrency; import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; @@ -389,7 +391,8 @@ public ArrayList createFacts (MAcctSchema as) // Invoice Price Variance difference BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate(); - processInvoicePriceVariance(as, fact, ipv); + BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate(); + processInvoicePriceVariance(as, fact, ipv, ipvSource); if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance()); String error = createMatchInvCostDetail(as); @@ -421,15 +424,60 @@ public ArrayList createFacts (MAcctSchema as) * @param ipv */ protected void processInvoicePriceVariance(MAcctSchema as, Fact fact, - BigDecimal ipv) { + BigDecimal ipv, BigDecimal ipvSource) { if (ipv.signum() == 0) return; - FactLine pv = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv); - updateFactLine(pv); - MMatchInv matchInv = (MMatchInv)getPO(); + String costingMethod = m_pc.getProduct().getCostingMethod(as); + BigDecimal amtVariance = Env.ZERO; + BigDecimal amtAsset = Env.ZERO; + BigDecimal qtyInv = m_invoiceLine.getQtyInvoiced(); + BigDecimal qtyCost = null; + Boolean isStockCoverage = false; + + if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod) && m_invoiceLine.getM_Product_ID() > 0) + { + + int AD_Org_ID = m_receiptLine.getAD_Org_ID(); + int M_AttributeSetInstance_ID = matchInv.getM_AttributeSetInstance_ID(); + + if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel())) + { + AD_Org_ID = 0; + M_AttributeSetInstance_ID = 0; + } + else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel())) + M_AttributeSetInstance_ID = 0; + else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel())) + AD_Org_ID = 0; + + MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), costingMethod, AD_Org_ID); + + MCostDetail cd = MCostDetail.get (as.getCtx(), "M_MatchInv_ID=? AND Coalesce(M_CostElement_ID,0)=0", + matchInv.getM_MatchInv_ID(), M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), getTrxName()); + if(cd!=null){ + qtyCost = cd.getCurrentQty(); + }else{ + MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, m_invoiceLine.getM_Product_ID(), + as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(), + M_AttributeSetInstance_ID, null); + qtyCost = (c!=null? c.getCurrentQty():Env.ZERO); + } + + isStockCoverage = true; + if (qtyCost != null && qtyCost.compareTo(qtyInv) < 0 ) + { + //If current cost qty < invoice qty + amtAsset = qtyCost.multiply(ipv).divide(qtyInv); + amtVariance = ipv.subtract(amtAsset); + + }else{ + //If current qty >= invoice qty + amtAsset = ipv; + } + + } + Trx trx = Trx.get(getTrxName(), false); Savepoint savepoint = null; boolean zeroQty = false; @@ -439,7 +487,7 @@ protected void processInvoicePriceVariance(MAcctSchema as, Fact fact, if (!MCostDetail.createMatchInvoice(as, m_invoiceLine.getAD_Org_ID(), m_invoiceLine.getM_Product_ID(), m_invoiceLine.getM_AttributeSetInstance_ID(), matchInv.getM_MatchInv_ID(), 0, - ipv, BigDecimal.ZERO, "Invoice Price Variance", getTrxName())) { + isStockCoverage ? amtAsset: ipv, BigDecimal.ZERO, "Invoice Price Variance", getTrxName())) { throw new RuntimeException("Failed to create cost detail record."); } } catch (SQLException e) { @@ -460,36 +508,58 @@ protected void processInvoicePriceVariance(MAcctSchema as, Fact fact, } } - String costingMethod = m_pc.getProduct().getCostingMethod(as); +// String costingMethod = m_pc.getProduct().getCostingMethod(as); MAccount account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); if (m_pc.isService()) account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) { - if (zeroQty) - account = m_pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); - FactLine line = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv.negate()); - updateFactLine(line); - line.setQty(getQty().negate()); - - line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); - updateFactLine(line); + FactLine varianceLine = null; + if (amtVariance.compareTo(Env.ZERO) != 0) + { + varianceLine = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as), as.getC_Currency_ID(), + amtVariance); + updateFactLine(varianceLine); + + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(varianceLine, ipvSource.multiply(amtVariance).divide(ipv)); + } + } + if (amtAsset.compareTo(Env.ZERO) != 0) + { + FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), amtAsset); + updateFactLine(line); + + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(line, ipvSource.multiply(amtAsset).divide(ipv)); + } + } } else if (X_M_Cost.COSTINGMETHOD_AverageInvoice.equals(costingMethod) && !zeroQty) { - FactLine line = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv.negate()); + //TODO test for avg Invoice costing method as here dropped posting of posting to IPV account + FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); updateFactLine(line); - line.setQty(getQty().negate()); - line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); - updateFactLine(line); + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(line, ipvSource); + } + }else{ + //For standard costing post to IPV account + FactLine pv = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), + as.getC_Currency_ID(), ipv); + updateFactLine(pv); + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(pv, ipvSource); + } } } - /** - * Verify if the posting involves two or more organizations - * @return true if there are more than one org involved on the posting + /** Verify if the posting involves two or more organizations + @return true if there are more than one org involved on the posting */ private boolean isInterOrg(MAcctSchema as) { MAcctSchemaElement elementorg = as.getAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_Organization); @@ -907,7 +977,8 @@ private ArrayList createMatShipmentFacts(MAcctSchema as) { // Invoice Price Variance difference BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate(); - processInvoicePriceVariance(as, fact, ipv); + BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate(); + processInvoicePriceVariance(as, fact, ipv, ipvSource); if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance()); String error = createMatchInvCostDetail(as); @@ -1258,6 +1329,29 @@ protected void updateFactLine(FactLine factLine) { factLine.setQty(getQty()); } + /** + * Invoice currency & acct schema currency are not same then update AmtSource value + * to avoid source not balanced error/ignore suspense balancing. + * + * @param factLine + * @param ipvSource + */ + protected void updateFactLineAmtSource(FactLine factLine, BigDecimal ipvSource) + { + // When only Rate differ then set Dr & Cr Source amount as zero. + factLine.setAmtSourceCr(Env.ZERO); + factLine.setAmtSourceDr(Env.ZERO); + + // Price is vary then set Source amount according to source variance + if (ipvSource.compareTo(Env.ZERO) != 0) + { + if (ipvSource.signum() < 0) + factLine.setAmtSourceCr(ipvSource); + else + factLine.setAmtSourceDr(ipvSource); + } + } + /** * Create Gain/Loss for invoice * @param as accounting schema