IDEMPIERE-1173 Fixed landed cost allocation for Average Costing.

This commit is contained in:
Heng Sin Low 2013-07-15 16:03:43 +08:00
parent 5c31c19036
commit 69ed0297c4
3 changed files with 99 additions and 124 deletions

View File

@ -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);

View File

@ -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

View File

@ -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
{ {