diff --git a/org.adempiere.base/src/org/compiere/model/MInOut.java b/org.adempiere.base/src/org/compiere/model/MInOut.java index 07e8cacc50..524c399c07 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOut.java +++ b/org.adempiere.base/src/org/compiere/model/MInOut.java @@ -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 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 diff --git a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java index 4bd3be0a4a..e50def8927 100644 --- a/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java +++ b/org.adempiere.base/src/org/compiere/model/MStorageOnHand.java @@ -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 params = new ArrayList(); + 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 params = new ArrayList(); + 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 params = new ArrayList(); + 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 params = new ArrayList(); + 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 diff --git a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java index 27978879de..701071ef62 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java @@ -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 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 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"); + } }