IDEMPIERE-2172 Attribute set with mandatory type = When Shipping not … (#1014)

* IDEMPIERE-2172 Attribute set with mandatory type = When Shipping not working

* IDEMPIERE-2172 Attribute set with mandatory type = When Shipping not working

- added MTransaction
- added reversal handling
This commit is contained in:
hengsin 2021-12-07 21:10:19 +08:00 committed by GitHub
parent 055c614336
commit db4087f878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 364 additions and 0 deletions

View File

@ -1421,6 +1421,15 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
if (MovementType.charAt(1) == '-') // C- Customer Shipment - V- Vendor Return
QtyMA = QtyMA.negate();
if (product != null && QtyMA.signum() < 0 && MovementType.equals("C-") && ma.getM_AttributeSetInstance_ID() > 0
&& oLine != null && oLine.getM_AttributeSetInstance_ID()==0 && !ma.isAutoGenerated() && !isReversal())
{
String status = moveOnHandToShipmentASI(product, sLine.getM_Locator_ID(), ma.getM_AttributeSetInstance_ID(), QtyMA.negate(), ma.getDateMaterialPolicy(),
sLine.get_ID(), false, get_TrxName());
if (status != null)
return status;
}
// Update Storage - see also VMatch.createMatchRecord
if (!MStorageOnHand.add(getCtx(), getM_Warehouse_ID(),
sLine.getM_Locator_ID(),
@ -1445,6 +1454,15 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
m_processMsg = "Could not create Material Transaction (MA) [" + product.getValue() + "]";
return DocAction.STATUS_Invalid;
}
if (product != null && QtyMA.signum() > 0 && MovementType.equals("C-") && ma.getM_AttributeSetInstance_ID() > 0
&& oLine != null && oLine.getM_AttributeSetInstance_ID()==0 && !ma.isAutoGenerated() && isReversal())
{
String status = moveOnHandToShipmentASI(product, sLine.getM_Locator_ID(), ma.getM_AttributeSetInstance_ID(), QtyMA.negate(), ma.getDateMaterialPolicy(),
sLine.get_ID(), true, get_TrxName());
if (status != null)
return status;
}
}
if (oLine!=null && mtrx!=null &&
@ -1478,6 +1496,14 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
if (mtrx == null)
{
if (product != null && MovementType.equals("C-") && sLine.getM_AttributeSetInstance_ID() > 0 && Qty.signum() < 0
&& oLine != null && oLine.getM_AttributeSetInstance_ID()==0 && !isReversal())
{
String status = moveOnHandToShipmentASI(product, sLine.getM_Locator_ID(), sLine.getM_AttributeSetInstance_ID(), Qty.negate(), null, sLine.get_ID(), false, get_TrxName());
if (status != null)
return status;
}
Timestamp dateMPolicy= null;
BigDecimal pendingQty = Qty;
if (pendingQty.signum() < 0) { // taking from inventory
@ -1569,6 +1595,14 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
m_processMsg = CLogger.retrieveErrorString("Could not create Material Transaction [" + product.getValue() + "]");
return DocAction.STATUS_Invalid;
}
if (product != null && MovementType.equals("C-") && sLine.getM_AttributeSetInstance_ID() > 0 && Qty.signum() > 0
&& oLine != null && oLine.getM_AttributeSetInstance_ID()==0 && isReversal())
{
String status = moveOnHandToShipmentASI(product, sLine.getM_Locator_ID(), sLine.getM_AttributeSetInstance_ID(), Qty.negate(), null, sLine.get_ID(), true, get_TrxName());
if (status != null)
return status;
}
}
} // stock movement
@ -2588,4 +2622,122 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
|| DOCSTATUS_Reversed.equals(ds);
} // isComplete
/**
* For product with mix of No ASI and ASI inventory, this move Non ASI on hand to the new ASI created at shipment line or shipment line ma
* @param product
* @param M_Locator_ID
* @param M_AttributeSetInstance_ID
* @param qty
* @param dateMaterialPolicy
* @param M_InOutLine_ID
* @param reversal
* @param trxName
* @return error doc status if there are any errors
*/
protected String moveOnHandToShipmentASI(MProduct product, int M_Locator_ID, int M_AttributeSetInstance_ID, BigDecimal qty,
Timestamp dateMaterialPolicy, int M_InOutLine_ID, boolean reversal, String trxName) {
if (qty.signum() == 0 || (qty.signum() < 0 && !reversal) || (qty.signum() > 0 && reversal))
return null;
if (M_AttributeSetInstance_ID == 0)
return null;
if (dateMaterialPolicy != null) {
MStorageOnHand asi = MStorageOnHand.get(getCtx(), M_Locator_ID, product.getM_Product_ID(), M_AttributeSetInstance_ID, dateMaterialPolicy, trxName);
if (asi != null && asi.getQtyOnHand().signum() != 0)
return null;
MStorageOnHand noasi = MStorageOnHand.get(getCtx(), M_Locator_ID, product.getM_Product_ID(), 0, dateMaterialPolicy, trxName);
if (noasi != null && noasi.getM_AttributeSetInstance_ID()==0 && (noasi.getQtyOnHand().compareTo(qty) >= 0 || reversal)) {
if (!(MStorageOnHand.add(getCtx(), getM_Warehouse_ID(), M_Locator_ID, product.getM_Product_ID(), 0, qty.negate(), dateMaterialPolicy, trxName))) {
String lastError = CLogger.retrieveErrorString("");
m_processMsg = "Cannot move Inventory OnHand (MA) to Shipment ASI [" + product.getValue() + "] - " + lastError;
return DocAction.STATUS_Invalid;
}
MTransaction trxFrom = new MTransaction (Env.getCtx(), getAD_Org_ID(), getMovementType(), M_Locator_ID, product.getM_Product_ID(), 0,
qty.negate(), getMovementDate(), trxName);
trxFrom.setM_InOutLine_ID(M_InOutLine_ID);
if (!trxFrom.save()) {
m_processMsg = "Transaction From not inserted (MA) [" + product.getValue() + "] - ";
return DocAction.STATUS_Invalid;
}
if (!(MStorageOnHand.add(getCtx(), getM_Warehouse_ID(), M_Locator_ID, product.getM_Product_ID(), M_AttributeSetInstance_ID, qty, dateMaterialPolicy, trxName))) {
String lastError = CLogger.retrieveErrorString("");
m_processMsg = "Cannot move Inventory OnHand (MA) to Shipment ASI [" + product.getValue() + "] - " + lastError;
return DocAction.STATUS_Invalid;
}
MTransaction trxTo = new MTransaction (Env.getCtx(), getAD_Org_ID(), getMovementType(), M_Locator_ID, product.getM_Product_ID(), M_AttributeSetInstance_ID,
qty, getMovementDate(), trxName);
trxTo.setM_InOutLine_ID(M_InOutLine_ID);
if (!trxTo.save()) {
m_processMsg = "Transaction To not inserted (MA) [" + product.getValue() + "] - ";
return DocAction.STATUS_Invalid;
}
}
} else {
BigDecimal totalASI = BigDecimal.ZERO;
BigDecimal totalOnHand = BigDecimal.ZERO;
MStorageOnHand[] storages = MStorageOnHand.getWarehouse(getCtx(), 0,
product.getM_Product_ID(), M_AttributeSetInstance_ID, null,
MClient.MMPOLICY_FiFo.equals(product.getMMPolicy()), false,
M_Locator_ID, get_TrxName());
for (MStorageOnHand onhand : storages) {
totalASI = totalASI.add(onhand.getQtyOnHand());
}
if (!reversal && totalASI.signum() != 0)
return null;
else if (reversal && (totalASI.compareTo(qty) < 0))
return null;
storages = MStorageOnHand.getWarehouse(getCtx(), 0,
product.getM_Product_ID(), 0, null,
MClient.MMPOLICY_FiFo.equals(product.getMMPolicy()), true,
M_Locator_ID, get_TrxName());
List<MStorageOnHand> nonASIList = new ArrayList<>();
for (MStorageOnHand storage : storages) {
if (storage.getM_AttributeSetInstance_ID() == 0) {
totalOnHand = totalOnHand.add(storage.getQtyOnHand());
nonASIList.add(storage);
}
}
if (totalOnHand.compareTo(qty) >= 0 || reversal) {
BigDecimal totalToMove = qty;
for (MStorageOnHand onhand : nonASIList) {
BigDecimal toMove = totalToMove;
if (!reversal && toMove.compareTo(onhand.getQtyOnHand()) >= 0) {
toMove = onhand.getQtyOnHand();
}
if (!MStorageOnHand.add(getCtx(), getM_Warehouse_ID(), M_Locator_ID, product.getM_Product_ID(), 0, toMove.negate(), onhand.getDateMaterialPolicy(), trxName)) {
String lastError = CLogger.retrieveErrorString("");
m_processMsg = "Cannot move Inventory OnHand to Shipment ASI [" + product.getValue() + "] - " + lastError;
return DocAction.STATUS_Invalid;
}
MTransaction trxFrom = new MTransaction (Env.getCtx(), getAD_Org_ID(), getMovementType(), M_Locator_ID, product.getM_Product_ID(), 0,
toMove.negate(), getMovementDate(), trxName);
trxFrom.setM_InOutLine_ID(M_InOutLine_ID);
if (!trxFrom.save()) {
m_processMsg = "Transaction From not inserted (MA) [" + product.getValue() + "] - ";
return DocAction.STATUS_Invalid;
}
dateMaterialPolicy = onhand.getDateMaterialPolicy();
totalToMove = totalToMove.subtract(toMove);
if ((!reversal && totalToMove.signum() <= 0) || (reversal && totalToMove.signum() >= 0))
break;
}
if (!MStorageOnHand.add(getCtx(), getM_Warehouse_ID(), M_Locator_ID, product.getM_Product_ID(), M_AttributeSetInstance_ID, qty, dateMaterialPolicy, trxName)) {
String lastError = CLogger.retrieveErrorString("");
m_processMsg = "Cannot move Inventory OnHand to Shipment ASI [" + product.getValue() + "] - " + lastError;
return DocAction.STATUS_Invalid;
}
MTransaction trxTo = new MTransaction (Env.getCtx(), getAD_Org_ID(), getMovementType(), M_Locator_ID, product.getM_Product_ID(), M_AttributeSetInstance_ID,
qty, getMovementDate(), trxName);
trxTo.setM_InOutLine_ID(M_InOutLine_ID);
if (!trxTo.save()) {
m_processMsg = "Transaction To not inserted (MA) [" + product.getValue() + "] - ";
return DocAction.STATUS_Invalid;
}
}
}
return null;
}
} // MInOut

View File

@ -947,6 +947,32 @@ public class MStorageOnHand extends X_M_StorageOnHand
return qty;
}
/**
* Get Quantity On Hand of Warehouse with ASI=0
* @param M_Product_ID
* @param M_Warehouse_ID
* @param trxName
* @return QtyOnHand
*/
public static BigDecimal getQtyOnHandWithASIZero(int M_Product_ID, int M_Warehouse_ID, String trxName) {
StringBuilder sql = new StringBuilder();
sql.append(" SELECT SUM(QtyOnHand) FROM M_StorageOnHand oh JOIN M_Locator loc ON (oh.M_Locator_ID=loc.M_Locator_ID)")
.append(" WHERE oh.M_Product_ID=?")
.append(" AND loc.M_Warehouse_ID=?")
.append(" AND oh.M_AttributeSetInstance_ID=0");
ArrayList<Object> params = new ArrayList<Object>();
params.add(M_Product_ID);
params.add(M_Warehouse_ID);
BigDecimal qty = DB.getSQLValueBD(trxName, sql.toString(), params);
if (qty == null)
qty = Env.ZERO;
return qty;
}
/**
* Get Quantity On Hand of Warehouse Available for Reservation
* @param M_Product_ID
@ -980,6 +1006,33 @@ public class MStorageOnHand extends X_M_StorageOnHand
return qty;
}
/**
* Get Quantity On Hand of Warehouse Available for Reservation with ASI=0
* @param M_Product_ID
* @param M_Warehouse_ID
* @param trxName
* @return QtyOnHand
*/
public static BigDecimal getQtyOnHandForReservationWithASIZero(int M_Product_ID, int M_Warehouse_ID, String trxName) {
StringBuilder sql = new StringBuilder();
sql.append(" SELECT SUM(QtyOnHand) FROM M_StorageOnHand oh"
+ " JOIN M_Locator loc ON (oh.M_Locator_ID=loc.M_Locator_ID)"
+ " LEFT JOIN M_LocatorType lt ON (loc.M_LocatorType_ID=lt.M_LocatorType_ID)")
.append(" WHERE oh.M_Product_ID=?")
.append(" AND loc.M_Warehouse_ID=? AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'")
.append(" AND oh.M_AttributeSetInstance_ID=0");
ArrayList<Object> params = new ArrayList<Object>();
params.add(M_Product_ID);
params.add(M_Warehouse_ID);
BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params);
if (qty == null)
qty = Env.ZERO;
return qty;
}
/**
* Get Quantity On Hand of Warehouse that's available for shipping
* @param M_Product_ID
@ -1012,6 +1065,32 @@ public class MStorageOnHand extends X_M_StorageOnHand
return qty;
}
/**
* Get Quantity On Hand of Warehouse that's available for shipping with ASI=0
* @param M_Product_ID
* @param M_Warehouse_ID
* @param trxName
* @return QtyOnHand
*/
public static BigDecimal getQtyOnHandForShippingWithASIZero(int M_Product_ID, int M_Warehouse_ID, String trxName) {
StringBuilder sql = new StringBuilder();
sql.append(" SELECT SUM(QtyOnHand) FROM M_StorageOnHand oh JOIN M_Locator loc ON (oh.M_Locator_ID=loc.M_Locator_ID)")
.append(" LEFT JOIN M_LocatorType lt ON (loc.M_LocatorType_ID=lt.M_LocatorType_ID)")
.append(" WHERE oh.M_Product_ID=?")
.append(" AND loc.M_Warehouse_ID=? AND COALESCE(lt.IsAvailableForShipping,'Y')='Y'")
.append(" AND oh.M_AttributeSetInstance_ID=0");
ArrayList<Object> params = new ArrayList<Object>();
params.add(M_Product_ID);
params.add(M_Warehouse_ID);
BigDecimal qty = DB.getSQLValueBDEx(trxName, sql.toString(), params);
if (qty == null)
qty = Env.ZERO;
return qty;
}
/**
* Get Quantity On Hand of Locator
* @param M_Product_ID
@ -1043,6 +1122,31 @@ public class MStorageOnHand extends X_M_StorageOnHand
return qty;
}
/**
* Get Quantity On Hand of Locator wtih ASI=0
* @param M_Product_ID
* @param M_Locator_ID
* @param trxName
* @return QtyOnHand
*/
public static BigDecimal getQtyOnHandForLocatorWithASIZero(int M_Product_ID, int M_Locator_ID, String trxName) {
StringBuilder sql = new StringBuilder();
sql.append(" SELECT SUM(oh.QtyOnHand) FROM M_StorageOnHand oh")
.append(" WHERE oh.M_Product_ID=?")
.append(" AND oh.M_Locator_ID=?")
.append(" AND oh.M_AttributeSetInstance_ID=0");
ArrayList<Object> params = new ArrayList<Object>();
params.add(M_Product_ID);
params.add(M_Locator_ID);
BigDecimal qty = DB.getSQLValueBD(trxName, sql.toString(), params);
if (qty == null)
qty = Env.ZERO;
return qty;
}
/**
* String Representation
* @return info

View File

@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import java.util.Properties;
import org.compiere.model.MAllocationHdr;
@ -50,6 +51,7 @@ import org.compiere.model.MProduct;
import org.compiere.model.MStorageOnHand;
import org.compiere.model.MStorageReservation;
import org.compiere.model.MStorageReservationLog;
import org.compiere.model.MTransaction;
import org.compiere.model.MUOM;
import org.compiere.model.MWarehouse;
import org.compiere.model.Query;
@ -78,6 +80,7 @@ public class SalesOrderTest extends AbstractTestCase {
private static final int PRODUCT_AZALEA = 128;
private static final int PRODUCT_FERT50 = 136;
private static final int PRODUCT_MARY = 132;
private static final int PRODUCT_PCHAIR = 133;
private static final int ORG_FERTILIZER = 50001;
private static final int WAREHOUSE_FERTILIZER = 50002;
private static final int WAREHOUSE_HQ_TRANSIT = 50000;
@ -1009,4 +1012,109 @@ public class SalesOrderTest extends AbstractTestCase {
success = order.save();
assertEquals(false, success);
}
@Test
public void testSetASIWhenShipping() {
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(MBPartner.get(Env.getCtx(), BP_JOE_BLOCK));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
line1.setProduct(MProduct.get(Env.getCtx(), PRODUCT_PCHAIR));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus(), "Unexpected Order document status");
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue(), "Unexpected order line qty reserved value");
int originalOnHand = MStorageOnHand.getQtyOnHandWithASIZero(PRODUCT_PCHAIR, getM_Warehouse_ID(), getTrxName()).intValue();
MInOut shipment = new MInOut(order, 120, order.getDateOrdered());
shipment.setDocStatus(DocAction.STATUS_Drafted);
shipment.setDocAction(DocAction.ACTION_Complete);
shipment.saveEx();
MInOutLine shipmentLine = new MInOutLine(shipment);
shipmentLine.setOrderLine(line1, 0, new BigDecimal("1"));
shipmentLine.setQty(new BigDecimal("1"));
MAttributeSetInstance asi = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi.setM_AttributeSet_ID(MProduct.get(PRODUCT_PCHAIR).getM_AttributeSet_ID());
asi.setSerNo("PChair Serial #1000000");
asi.saveEx();
shipmentLine.setM_AttributeSetInstance_ID(asi.getM_AttributeSetInstance_ID());
shipmentLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus(), "Unexpected Shipment document status");
int newOnHand = MStorageOnHand.getQtyOnHandWithASIZero(PRODUCT_PCHAIR, getM_Warehouse_ID(), getTrxName()).intValue();
assertEquals(originalOnHand-1, newOnHand, "Unexpected on hand quantity");
int asiOnHand = MStorageOnHand.getQtyOnHand(PRODUCT_PCHAIR, getM_Warehouse_ID(), asi.get_ID(), getTrxName()).intValue();
int asiRecords = 0;
MStorageOnHand[] storages = MStorageOnHand.getOfProduct(Env.getCtx(), PRODUCT_PCHAIR, getTrxName());
for (MStorageOnHand storage : storages) {
if (storage.getM_Warehouse_ID()==getM_Warehouse_ID() && storage.getM_AttributeSetInstance_ID()==asi.get_ID()) {
asiRecords++;
}
}
assertEquals(0, asiOnHand, "Unexpected on hand quantity for Serial ASI");
assertEquals(1, asiRecords, "Unexpected number of Serial ASI Storage records");
Query query = new Query(Env.getCtx(), MTransaction.Table_Name, "M_InOutLine_ID=? AND M_Product_ID=? AND M_AttributeSetInstance_ID=0", getTrxName());
MTransaction trxFrom = query.setParameters(shipmentLine.get_ID(), shipmentLine.getM_Product_ID()).first();
assertNotNull(trxFrom, "Can't find MTransaction record for no ASI MTransaction record");
assertEquals(-1, trxFrom.getMovementQty().intValue(), "Unexpected movement qty for no ASI MTransaction record");
query = new Query(Env.getCtx(), MTransaction.Table_Name, "M_InOutLine_ID=? AND M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
List<MTransaction> asiTrxs = query.setParameters(shipmentLine.get_ID(), shipmentLine.getM_Product_ID(), shipmentLine.getM_AttributeSetInstance_ID())
.setOrderBy("M_Transaction_ID")
.list();
assertEquals(2, asiTrxs.size(), "Unexpected number of records for ASI MTransaction");
assertEquals(1, asiTrxs.get(0).getMovementQty().intValue(), "Unexpected movement qty for first ASI MTransaction record");
assertEquals(-1, asiTrxs.get(1).getMovementQty().intValue(), "Unexpected movement qty for second ASI MTransaction record");
//reverse the MR
shipment.load(getTrxName());
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, shipment.getDocStatus(), "Unexpected Shipment document status");
newOnHand = MStorageOnHand.getQtyOnHandWithASIZero(PRODUCT_PCHAIR, getM_Warehouse_ID(), getTrxName()).intValue();
assertEquals(originalOnHand, newOnHand, "Unexpected on hand quantity no ASI");
asiOnHand = MStorageOnHand.getQtyOnHand(PRODUCT_PCHAIR, getM_Warehouse_ID(), asi.get_ID(), getTrxName()).intValue();
assertEquals(0, asiOnHand, "Unexpected on hand quantity for Serial ASI");
MInOut reversal = new MInOut(Env.getCtx(), shipment.getReversal_ID(), getTrxName());
MInOutLine[] reversalLines = reversal.getLines();
query = new Query(Env.getCtx(), MTransaction.Table_Name, "M_InOutLine_ID=? AND M_Product_ID=? AND M_AttributeSetInstance_ID=0", getTrxName());
List<MTransaction> noASITrxs = query.setParameters(reversalLines[0].get_ID(), reversalLines[0].getM_Product_ID())
.setOrderBy("M_Transaction_ID")
.list();
assertEquals(1, noASITrxs.size(), "Unexpected number of records for reversal no ASI MTransaction");
assertEquals(1, asiTrxs.get(0).getMovementQty().intValue(), "Unexpected reversal movement qty for no ASI MTransaction record");
query = new Query(Env.getCtx(), MTransaction.Table_Name, "M_InOutLine_ID=? AND M_Product_ID=? AND M_AttributeSetInstance_ID=?", getTrxName());
asiTrxs = query.setParameters(reversalLines[0].get_ID(), reversalLines[0].getM_Product_ID(), reversalLines[0].getM_AttributeSetInstance_ID())
.setOrderBy("M_Transaction_ID")
.list();
assertEquals(2, asiTrxs.size(), "Unexpected number of records for reversal ASI MTransaction");
assertEquals(1, asiTrxs.get(0).getMovementQty().intValue(), "Unexpected reversal movement qty for first ASI MTransaction record");
assertEquals(-1, asiTrxs.get(1).getMovementQty().intValue(), "Unexpected reversal movement qty for second ASI MTransaction record");
}
}