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.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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -860,6 +875,14 @@ public class MCostDetail extends X_M_CostDetail
|
|||
}
|
||||
// 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;
|
||||
if (qty.signum() != 0)
|
||||
|
@ -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
|
||||
|
@ -997,53 +1024,6 @@ public class MCostDetail extends X_M_CostDetail
|
|||
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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue