IDEMPIERE-1173 Fixed landed cost allocation for Average Costing.
This commit is contained in:
parent
5c31c19036
commit
69ed0297c4
|
@ -20,12 +20,15 @@ import java.math.BigDecimal;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Savepoint;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.adempiere.exceptions.AverageCostingZeroQtyException;
|
||||||
import org.compiere.model.MAccount;
|
import org.compiere.model.MAccount;
|
||||||
import org.compiere.model.MAcctSchema;
|
import org.compiere.model.MAcctSchema;
|
||||||
import org.compiere.model.MClientInfo;
|
import org.compiere.model.MClientInfo;
|
||||||
|
import org.compiere.model.MConversionRate;
|
||||||
import org.compiere.model.MCostDetail;
|
import org.compiere.model.MCostDetail;
|
||||||
import org.compiere.model.MCurrency;
|
import org.compiere.model.MCurrency;
|
||||||
import org.compiere.model.MInvoice;
|
import org.compiere.model.MInvoice;
|
||||||
|
@ -33,8 +36,10 @@ import org.compiere.model.MInvoiceLine;
|
||||||
import org.compiere.model.MLandedCostAllocation;
|
import org.compiere.model.MLandedCostAllocation;
|
||||||
import org.compiere.model.MTax;
|
import org.compiere.model.MTax;
|
||||||
import org.compiere.model.ProductCost;
|
import org.compiere.model.ProductCost;
|
||||||
|
import org.compiere.model.X_M_Cost;
|
||||||
import org.compiere.util.DB;
|
import org.compiere.util.DB;
|
||||||
import org.compiere.util.Env;
|
import org.compiere.util.Env;
|
||||||
|
import org.compiere.util.Trx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post Invoice Documents.
|
* Post Invoice Documents.
|
||||||
|
@ -835,22 +840,8 @@ public class Doc_Invoice extends Doc
|
||||||
if (line.getDescription() != null)
|
if (line.getDescription() != null)
|
||||||
desc += " - " + line.getDescription();
|
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
|
// Cost Detail - Convert to AcctCurrency
|
||||||
/*
|
|
||||||
BigDecimal allocationAmt = lca.getAmt();
|
BigDecimal allocationAmt = lca.getAmt();
|
||||||
if (getC_Currency_ID() != as.getC_Currency_ID())
|
if (getC_Currency_ID() != as.getC_Currency_ID())
|
||||||
allocationAmt = MConversionRate.convert(getCtx(), allocationAmt,
|
allocationAmt = MConversionRate.convert(getCtx(), allocationAmt,
|
||||||
|
@ -861,15 +852,58 @@ public class Doc_Invoice extends Doc
|
||||||
allocationAmt = allocationAmt.setScale(as.getCostingPrecision(), BigDecimal.ROUND_HALF_UP);
|
allocationAmt = allocationAmt.setScale(as.getCostingPrecision(), BigDecimal.ROUND_HALF_UP);
|
||||||
if (!dr)
|
if (!dr)
|
||||||
allocationAmt = allocationAmt.negate();
|
allocationAmt = allocationAmt.negate();
|
||||||
// AZ Goodwill
|
|
||||||
// use createInvoice to create/update non Material Cost Detail
|
Trx trx = Trx.get(getTrxName(), false);
|
||||||
MCostDetail.createInvoice(as, lca.getAD_Org_ID(),
|
Savepoint savepoint = null;
|
||||||
lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(),
|
boolean zeroQty = false;
|
||||||
C_InvoiceLine_ID, lca.getM_CostElement_ID(),
|
try {
|
||||||
allocationAmt, lca.getQty(),
|
savepoint = trx.setSavepoint(null);
|
||||||
desc, getTrxName());
|
|
||||||
*/
|
if (!MCostDetail.createInvoice(as, lca.getAD_Org_ID(),
|
||||||
// end AZ
|
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);
|
if (log.isLoggable(Level.CONFIG)) log.config("Created #" + lcas.length);
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Properties;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.adempiere.exceptions.AverageCostingNegativeQtyException;
|
import org.adempiere.exceptions.AverageCostingNegativeQtyException;
|
||||||
|
import org.adempiere.exceptions.AverageCostingZeroQtyException;
|
||||||
import org.adempiere.exceptions.DBException;
|
import org.adempiere.exceptions.DBException;
|
||||||
import org.compiere.Adempiere;
|
import org.compiere.Adempiere;
|
||||||
import org.compiere.util.CLogger;
|
import org.compiere.util.CLogger;
|
||||||
|
@ -1487,6 +1488,13 @@ public class MCost extends X_M_Cost
|
||||||
amt = amt.negate();
|
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)
|
if (getCurrentQty().add(qty).signum() < 0)
|
||||||
{
|
{
|
||||||
throw new AverageCostingNegativeQtyException("Product(ID)="+getM_Product_ID()+", Current Qty="+getCurrentQty()+", Trx Qty="+qty
|
throw new AverageCostingNegativeQtyException("Product(ID)="+getM_Product_ID()+", Current Qty="+getCurrentQty()+", Trx Qty="+qty
|
||||||
|
|
|
@ -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("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_InvoiceLine_ID=").append(C_InvoiceLine_ID)
|
||||||
.append(" AND C_AcctSchema_ID =").append(as.getC_AcctSchema_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);
|
int no = DB.executeUpdate(sql.toString(), trxName);
|
||||||
if (no != 0)
|
if (no != 0)
|
||||||
if (s_log.isLoggable(Level.CONFIG)) s_log.config("Deleted #" + no);
|
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);
|
C_InvoiceLine_ID, M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), trxName);
|
||||||
//
|
//
|
||||||
if (cd == null) // createNew
|
if (cd == null) // createNew
|
||||||
|
@ -796,7 +798,20 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MCostElement ce = MCostElement.get(getCtx(), getM_CostElement_ID());
|
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
|
// Save it
|
||||||
|
@ -859,6 +874,14 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
amt = getAmt();
|
amt = getAmt();
|
||||||
}
|
}
|
||||||
// end MZ
|
// 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();
|
int precision = as.getCostingPrecision();
|
||||||
BigDecimal price = amt;
|
BigDecimal price = amt;
|
||||||
|
@ -942,6 +965,10 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
cost.setWeightedAverage(amt, qty);
|
cost.setWeightedAverage(amt, qty);
|
||||||
if (log.isLoggable(Level.FINER)) log.finer("Inv - AverageInv - " + cost);
|
if (log.isLoggable(Level.FINER)) log.finer("Inv - AverageInv - " + cost);
|
||||||
}
|
}
|
||||||
|
else if (ce.isAveragePO() && costAdjustment)
|
||||||
|
{
|
||||||
|
cost.setWeightedAverage(amt, qty);
|
||||||
|
}
|
||||||
else if (ce.isFifo()
|
else if (ce.isFifo()
|
||||||
|| ce.isLifo())
|
|| ce.isLifo())
|
||||||
{
|
{
|
||||||
|
@ -958,7 +985,7 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
cost.add(amt, qty);
|
cost.add(amt, qty);
|
||||||
if (log.isLoggable(Level.FINER)) log.finer("Inv - FiFo/LiFo - " + cost);
|
if (log.isLoggable(Level.FINER)) log.finer("Inv - FiFo/LiFo - " + cost);
|
||||||
}
|
}
|
||||||
else if (ce.isLastInvoice())
|
else if (ce.isLastInvoice() && !costAdjustment)
|
||||||
{
|
{
|
||||||
if (!isReturnTrx)
|
if (!isReturnTrx)
|
||||||
{
|
{
|
||||||
|
@ -973,7 +1000,7 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
cost.add(amt, qty);
|
cost.add(amt, qty);
|
||||||
if (log.isLoggable(Level.FINER)) log.finer("Inv - LastInv - " + cost);
|
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
|
// Update cost record only if it is zero
|
||||||
if (cost.getCurrentCostPrice().signum() == 0
|
if (cost.getCurrentCostPrice().signum() == 0
|
||||||
|
@ -996,54 +1023,7 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
// Interface
|
// Interface
|
||||||
cost.add(amt, qty);
|
cost.add(amt, qty);
|
||||||
if (log.isLoggable(Level.FINER)) log.finer("Inv - UserDef - " + cost);
|
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
|
// else
|
||||||
// log.warning("Inv - " + ce + " - " + cost);
|
// log.warning("Inv - " + ce + " - " + cost);
|
||||||
}
|
}
|
||||||
|
@ -1177,53 +1157,6 @@ public class MCostDetail extends X_M_CostDetail
|
||||||
else
|
else
|
||||||
log.warning("QtyAdjust - " + ce + " - " + cost);
|
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
|
else // unknown or no id
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue