Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDEMPIERE-3040: Stock Coverage #2317

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 72 additions & 32 deletions org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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
{
Expand Down
152 changes: 123 additions & 29 deletions org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -389,7 +391,8 @@ public ArrayList<Fact> 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);
Expand Down Expand Up @@ -421,15 +424,60 @@ public ArrayList<Fact> 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;
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -907,7 +977,8 @@ private ArrayList<Fact> 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);
Expand Down Expand Up @@ -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
Expand Down