Related to issue 30832: code review improvements
authorVíctor Martínez Romanos <victor.martinez@openbravo.com>
Wed, 07 Oct 2015 09:25:04 +0200
changeset 27916 e8cf3936f4c1
parent 27915 5fa35abaa27e
child 27917 ffa9a951cbae
child 27918 8ea1dea9deaf
Related to issue 30832: code review improvements

M_INOUTLINE_TRG:
+ Updated copyright
+ Added more conditions to the if clause when updating
+ Usage of MOVEMENTQTY and QUANTITYORDER instead of v_qtyold, v_qty, v_qtyorderold and v_qtyorder as these variables always make true the if condition (so the fix would useless)
+ In the update part, update first the inventory for the old product/locator as it was done before the refactor

M_inoutlinetrgTest.java:
+ Totally rewritten of the JUnit
+ Support for more scenarios: update locator, update attributesetinstance, work with several lines, purchase and sales flows, etc.
+ Fixed scenarios not properly working before
+ Avoid HQL injection
src-db/database/model/triggers/M_INOUTLINE_TRG.xml
src-test/src/org/openbravo/test/db/model/triggers/M_inoutlinetrgTest.java
--- a/src-db/database/model/triggers/M_INOUTLINE_TRG.xml	Mon Oct 05 14:56:31 2015 +0530
+++ b/src-db/database/model/triggers/M_INOUTLINE_TRG.xml	Wed Oct 07 09:25:04 2015 +0200
@@ -34,7 +34,7 @@
  * Portions created by Jorg Janke are Copyright (C) 1999-2001 Jorg Janke, parts
  * created by ComPiere are Copyright (C) ComPiere, Inc.;   All Rights Reserved.
  * Contributor(s): Openbravo SLU
- * Contributions are Copyright (C) 2001-2013 Openbravo S.L.U.
+ * Contributions are Copyright (C) 2001-2015 Openbravo S.L.U.
  ******************************************************************************/
      
 BEGIN
@@ -218,13 +218,28 @@
     END IF;
   END IF;
   IF (UPDATING) THEN
-    IF (:OLD.AD_ORG_ID <> :NEW.AD_ORG_ID
+    IF (:OLD.AD_CLIENT_ID <> :NEW.AD_CLIENT_ID
+      OR :OLD.AD_ORG_ID <> :NEW.AD_ORG_ID
+      OR :OLD.UPDATEDBY <> :NEW.UPDATEDBY
       OR COALESCE(:OLD.M_Product_ID,'0') <> COALESCE(:NEW.M_Product_ID,'0')
+      OR COALESCE(:OLD.M_LOCATOR_ID, '0') <> COALESCE(:NEW.M_LOCATOR_ID, '0')
+      OR COALESCE(:OLD.M_AttributeSetInstance_ID,'0') <> COALESCE(:NEW.M_AttributeSetInstance_ID,'0')
       OR :OLD.C_UOM_ID <> :NEW.C_UOM_ID
-      OR COALESCE(:OLD.M_AttributeSetInstance_ID,'0') <> COALESCE(:NEW.M_AttributeSetInstance_ID,'0')
-      OR v_qtyold <> v_qty
       OR COALESCE(:OLD.M_Product_UOM_ID,'0') <> COALESCE(:NEW.M_Product_UOM_ID,'0')
-      OR v_qtyorderold <> v_qtyorder) THEN
+      OR :OLD.MOVEMENTQTY <> :NEW.MOVEMENTQTY
+      OR COALESCE(:OLD.QUANTITYORDER, 0) <> COALESCE(:NEW.QUANTITYORDER, 0)) THEN
+      
+      IF (:OLD.M_PRODUCT_ID IS NOT NULL AND :OLD.M_LOCATOR_ID IS NOT NULL) THEN
+        SELECT COUNT(*) INTO V_STOCKED
+          FROM M_PRODUCT
+          WHERE M_Product_ID=:OLD.M_PRODUCT_ID
+          AND IsStocked = 'Y' AND ProductType = 'I';
+        IF V_STOCKED > 0 THEN
+          M_UPDATE_INVENTORY(:OLD.AD_CLIENT_ID, :OLD.AD_ORG_ID, :OLD.UPDATEDBY, :OLD.M_PRODUCT_ID, :OLD.M_LOCATOR_ID,
+          :OLD.M_ATTRIBUTESETINSTANCE_ID, :OLD.C_UOM_ID,
+          :OLD.M_PRODUCT_UOM_ID, NULL, NULL, NULL, v_qtyold, v_qtyorderold);
+        END IF;
+      END IF;
 
       IF (:NEW.M_PRODUCT_ID IS NOT NULL AND :NEW.M_LOCATOR_ID IS NOT NULL) THEN
         SELECT COUNT(*) INTO V_STOCKED
@@ -237,18 +252,7 @@
           :NEW.M_PRODUCT_UOM_ID, NULL, NULL, NULL, v_qty, v_qtyorder);
         END IF;
       END IF;
-
-      IF (:OLD.M_PRODUCT_ID IS NOT NULL AND :OLD.M_LOCATOR_ID IS NOT NULL) THEN
-        SELECT COUNT(*) INTO V_STOCKED
-          FROM M_PRODUCT
-          WHERE M_Product_ID=:OLD.M_PRODUCT_ID
-          AND IsStocked = 'Y' AND ProductType = 'I';
-        IF V_STOCKED > 0 THEN
-          M_UPDATE_INVENTORY(:OLD.AD_CLIENT_ID, :OLD.AD_ORG_ID, :OLD.UPDATEDBY, :OLD.M_PRODUCT_ID, :OLD.M_LOCATOR_ID,
-          :OLD.M_ATTRIBUTESETINSTANCE_ID, :OLD.C_UOM_ID,
-          :OLD.M_PRODUCT_UOM_ID, NULL, NULL, NULL, v_qtyold, v_qtyorderold);
-        END IF;
-      END IF;
+  
     END IF;
   END IF;
 
--- a/src-test/src/org/openbravo/test/db/model/triggers/M_inoutlinetrgTest.java	Mon Oct 05 14:56:31 2015 +0530
+++ b/src-test/src/org/openbravo/test/db/model/triggers/M_inoutlinetrgTest.java	Wed Oct 07 09:25:04 2015 +0200
@@ -24,6 +24,7 @@
 import java.math.BigDecimal;
 import java.sql.SQLException;
 import java.util.Date;
+import java.util.List;
 
 import org.hibernate.Query;
 import org.junit.Test;
@@ -63,206 +64,487 @@
   // Spain Organization
   private static final String OrganizationId = "357947E87C284935AD1D783CF6F099A1";
 
-  // MM Shipment Document Type, Document Sequence
-  private static final String Shipment_DocumentTypeId = "FF8080812C2ABFC6012C2B3BDF4A004E";
-  private static final String Shipment_DocSequenceId = "FF8080812C2ABFC6012C2B3BDF4A004D";
+  // Movement Quantity: 10
+  private static BigDecimal MovementQty = BigDecimal.TEN;
 
-  // Business Partner: Customer A
-  private static final String CustomerId = "4028E6C72959682B01295F40C3CB02EC";
-  private static final String CustomerAddressId = "4028E6C72959682B01295F40C43802EE";
-
-  // Warehouse: Spain Warehouse
-  private static String WarehouseId = "4028E6C72959682B01295ECFEF4502A0";
-
-  // Locator: spain111
-  private static String LocatorId = "4028E6C72959682B01295ECFEF6502A3";
-
-  // Movement Quantity: 10
-  private static BigDecimal MovementQty = new BigDecimal(10);
-
-  // Product: Distribution good A
-  private static String ProductId = "4028E6C72959682B01295ADC211E0237";
   private BigDecimal afterValue = BigDecimal.ZERO;
   private BigDecimal beforeValue = BigDecimal.ZERO;
-  private Date afterUpdatedDateTime = new Date();
-  private Date beforeUpdatedDateTime = new Date();
+
+  private StorageDetail storageDetail;
+  private ShipmentInOut inOut;
+  private ShipmentInOutLine inOutLine;
+  private Product product;
+  private Locator locator;
+  private AttributeSetInstance attributeSetInstance;
+
+  private void salesSetup() {
+    // Product: Soccer Ball
+    final String ProductId = "EBCD272DC37B4ABBB12B96139E5837BF";
+    // Locator: spain111
+    final String LocatorId = "4028E6C72959682B01295ECFEF6502A3";
+    // Orange
+    final String attributeSetInstanceId = "E2F81DE34D404177BB13E2B4198B83AB";
+
+    product = OBDal.getInstance().get(Product.class, ProductId);
+    locator = OBDal.getInstance().get(Locator.class, LocatorId);
+    attributeSetInstance = OBDal.getInstance().get(AttributeSetInstance.class,
+        attributeSetInstanceId);
+  }
+
+  private void purchaseSetup() {
+    // Product: T-Shirts
+    final String ProductId = "0CF7C882B8BD4D249F3BCC8727A736D1";
+    // Locator: M01
+    final String LocatorId = "96DEDCC179504711A81497DE68900F49";
+    //
+    final String attributeSetInstanceId = "0";
+
+    product = OBDal.getInstance().get(Product.class, ProductId);
+    locator = OBDal.getInstance().get(Locator.class, LocatorId);
+    attributeSetInstance = OBDal.getInstance().get(AttributeSetInstance.class,
+        attributeSetInstanceId);
+  }
 
   @Test
-  public void testM_InOutLineTrg() throws SQLException {
-    StorageDetail beforeUpdate = null;
-    StorageDetail afterUpdate = null;
-    Date afterUpdatedDate = new Date();
-    Date beforeUpdatedDate = new Date();
+  public void testM_InOutLineTrg_Sales1() throws SQLException {
+    salesSetup();
+    batchOfTests(true, 1);
+  }
 
-    ShipmentInOut inOut = insertMInOut();
+  @Test
+  public void testM_InOutLineTrg_Sales2() throws SQLException {
+    salesSetup();
+    batchOfTests(true, 2);
+  }
+
+  @Test
+  public void testM_InOutLineTrg_Sales4() throws SQLException {
+    salesSetup();
+    batchOfTests(true, 4);
+  }
+
+  @Test
+  public void testM_InOutLineTrg_Purchase1() throws SQLException {
+    purchaseSetup();
+    batchOfTests(false, 1);
+  }
+
+  @Test
+  public void testM_InOutLineTrg_Purchase2() throws SQLException {
+    purchaseSetup();
+    batchOfTests(false, 2);
+  }
+
+  @Test
+  public void testM_InOutLineTrg_Purchase4() throws SQLException {
+    purchaseSetup();
+    batchOfTests(false, 4);
+  }
+
+  private void batchOfTests(boolean isSales, int numberOfLines) throws SQLException {
+    log.info("START TEST for Sales = " + isSales + ". Number of lines: " + numberOfLines);
+
+    test_CreateLineWith10Products(isSales, numberOfLines);
+    test_SetProductAsNull();
+    test_SetAProductAgain();
+    test_UpdateDescription();
+    test_UpdateMovementQuantity();
+    test_UpdateMovementNegativeQuantity();
+    test_UpdateMovementQuantity();
+    test_UpdateMovementQuantityToSameQuantity();
+    // Return bin
+    test_UpdateLocator(OBDal.getInstance().get(Locator.class, "67C3E9C2ADF74AC7A48C0F94CE571AB9"));
+    test_UpdateLocator(locator);
+    if (isSales) {
+      // blue
+      test_UpdateAttribute(OBDal.getInstance().get(AttributeSetInstance.class,
+          "1B78D7E95FBC47788B4962B11E80002B"));
+      test_UpdateAttribute(attributeSetInstance);
+    }
+    test_DeleteLine();
+
+    OBDal.getInstance().remove(inOut);
+
+    log.info("END TEST for Sales = " + isSales + ". Number of lines: " + numberOfLines);
+  }
+
+  // Create a new shipment line with a Product (10 units)
+  private void test_CreateLineWith10Products(boolean isSales, int numberOfLines)
+      throws SQLException {
+    inOut = insertMInOut(isSales);
     assertTrue("M_Inout header not inserted successfully ", inOut != null);
 
-    // Case 1: Check whether update inventory called on M_Inoutline insertion with
-    // 1. Product is not null
-    // 2. Locator is not null
-    // 3. MovementQty is not zero
-    // 4. AttributeSetInstance is null
-    // 5. ProductUom is null
+    storageDetail = getStorageDetail(product, product.getUOM(), locator, null, attributeSetInstance);
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      beforeValue = BigDecimal.ZERO;
+    }
 
-    Product product = OBDal.getInstance().get(Product.class, ProductId);
-    Locator locator = OBDal.getInstance().get(Locator.class, LocatorId);
-
-    // Get Storage Detail for Product before inserting line in M_InOutLine
-
-    beforeUpdate = getStorageDetail(product, product.getUOM(), null, locator, null);
-    OBDal.getInstance().refresh(beforeUpdate);
-    beforeValue = beforeUpdate.getQuantityInDraftTransactions();
-    beforeUpdatedDate = beforeUpdate.getUpdated();
-
-    log.info("*************** Case I *****************");
+    log.info("*************** Create a new shipment line with a Product (10 units) *****************");
     log.info("Qty in Draft transaction before insertion: " + beforeValue);
 
-    ShipmentInOutLine inOutLine = insertMInOutLine(inOut);
-    assertTrue(inOutLine != null);
+    for (int i = 0; i < numberOfLines; i++) {
+      inOutLine = insertMInOutLine(inOut);
+      assertTrue(inOutLine != null);
+    }
 
-    afterUpdate = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getAttributeSetValue(),
-        inOutLine.getStorageBin(), inOutLine.getOrderUOM());
-    OBDal.getInstance().refresh(afterUpdate);
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
 
-    afterValue = afterUpdate.getQuantityInDraftTransactions();
-    afterUpdatedDate = afterUpdate.getUpdated();
+    afterValue = storageDetail.getQuantityInDraftTransactions();
     log.info("Qty in Draft transaction after insertion " + afterValue);
-    log.info("Updated DateTime before Case I " + beforeUpdatedDate);
-    log.info("Updated DateTime after Case I " + afterUpdatedDate);
 
-    assertTrue("Update inventory is not called on Case I", afterValue.compareTo(beforeValue) != 0);
-    assertTrue("Update inventory is not called on Case I",
-        !beforeUpdatedDate.equals(afterUpdatedDate));
+    assertTrue("Quantities should not be equal because a new line with a product has been saved",
+        afterValue.compareTo(beforeValue) != 0);
+  }
 
-    beforeUpdate = afterUpdate;
-    OBDal.getInstance().refresh(beforeUpdate);
-    beforeValue = beforeUpdate.getQuantityInDraftTransactions();
-    beforeUpdatedDate = beforeUpdate.getUpdated();
-    log.info("*************** Case II *****************");
+  // Set Product null
+  private void test_SetProductAsNull() throws SQLException {
+    OBDal.getInstance().refresh(storageDetail);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+
+    log.info("*************** Set Product null *****************");
     log.info("Qty in Draft transaction before setting product as null in m_inoutline "
         + beforeValue);
 
-    // Case 2 : Set Product null
-    inOutLine = updateMInOutLine(inOutLine, 1, null, null, null);
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_PRODUCT, null);
     assertTrue(inOutLine.getProduct() == null);
 
-    afterUpdate = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getAttributeSetValue(),
-        inOutLine.getStorageBin(), inOutLine.getOrderUOM());
-    OBDal.getInstance().refresh(afterUpdate);
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      afterValue = BigDecimal.ZERO;
+    }
 
-    afterValue = afterUpdate.getQuantityInDraftTransactions();
-    afterUpdatedDate = afterUpdate.getUpdated();
     log.info("Qty in Draft transaction after setting product as null in m_inoutline " + afterValue);
-    log.info("Updated DateTime before Case II " + beforeUpdatedDate);
-    log.info("Updated DateTime after Case II " + afterUpdatedDate);
 
-    assertTrue("Update inventory is not called on Case II", afterValue.compareTo(beforeValue) != 0);
-    assertTrue("Update inventory is not called on Case II",
-        !beforeUpdatedDate.equals(afterUpdatedDate));
+    assertTrue("Quantities should not be equal because product has been removed",
+        afterValue.compareTo(beforeValue) != 0);
+  }
 
-    beforeUpdate = afterUpdate;
-    OBDal.getInstance().refresh(beforeUpdate);
+  // Set blank product in InOutLine with Product: Distribution good A
+  private void test_SetAProductAgain() throws SQLException {
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      beforeValue = BigDecimal.ZERO;
+    }
 
-    // Case 3 : Set blank product in InOutLine with Product: Distribution good A
-
-    beforeValue = beforeUpdate.getQuantityInDraftTransactions();
-    beforeUpdatedDate = beforeUpdate.getUpdated();
-    log.info("*************** Case III *****************");
+    log.info("*************** Set Product not null *****************");
     log.info("Qty in Draft transaction before setting null product with value in m_inoutline "
         + beforeValue);
 
-    inOutLine = updateMInOutLine(inOutLine, 1, product, null, null);
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_PRODUCT, product);
 
-    afterUpdate = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getAttributeSetValue(),
-        inOutLine.getStorageBin(), inOutLine.getOrderUOM());
-    OBDal.getInstance().refresh(afterUpdate);
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
 
-    afterValue = afterUpdate.getQuantityInDraftTransactions();
-    afterUpdatedDate = afterUpdate.getUpdated();
+    afterValue = storageDetail.getQuantityInDraftTransactions();
 
     log.info("Qty in Draft transaction after setting null product with value in m_inoutline "
         + afterValue);
 
-    log.info("Updated DateTime before Case III " + beforeUpdatedDate);
-    log.info("Updated DateTime after Case III " + afterUpdatedDate);
+    assertTrue("Quantities should not be equal because a product has been set again",
+        afterValue.compareTo(beforeValue) != 0);
 
-    assertTrue("Update inventory is not called on Case III", afterValue.compareTo(beforeValue) != 0);
-    assertTrue("Update inventory is not called on Case III",
-        !beforeUpdatedDate.equals(afterUpdatedDate));
+  }
 
-    beforeUpdate = afterUpdate;
-    OBDal.getInstance().refresh(beforeUpdate);
+  // Update description
+  private void test_UpdateDescription() throws SQLException {
+    OBDal.getInstance().getConnection().commit();
+    OBDal.getInstance().refresh(storageDetail);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+    final Date previousDate = storageDetail.getUpdated();
 
-    // Case 4 : Update description for the m_inoutline and check that storage details is not updated
+    log.info("*************** Update description *****************");
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_DESCRIPTION, "description updated for this line");
 
-    beforeValue = beforeUpdate.getQuantityInDraftTransactions();
-    beforeUpdatedDate = beforeUpdate.getUpdated();
-    log.info("*************** Case IV *****************");
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
 
-    inOutLine = updateMInOutLine(inOutLine, 4, product, null, "description updated for this line");
-
-    afterUpdate = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getAttributeSetValue(),
-        inOutLine.getStorageBin(), inOutLine.getOrderUOM());
-    OBDal.getInstance().refresh(afterUpdate);
-
-    afterValue = afterUpdate.getQuantityInDraftTransactions();
-    afterUpdatedDate = afterUpdate.getUpdated();
+    afterValue = storageDetail.getQuantityInDraftTransactions();
+    final Date newDate = storageDetail.getUpdated();
 
     log.info("Qty in Draft transaction before updating description in m_inoutline " + beforeValue);
     log.info("Qty in Draft transaction after updating description in m_inoutline " + afterValue);
-    log.info("Updated DateTime before updating description in m_inoutline " + beforeUpdatedDate);
-    log.info("Updated DateTime after updating description in m_inoutline " + afterUpdatedDate);
 
-    assertTrue("Update inventory is called on Case IV, should not be",
+    assertTrue("Quantities should be equal because we have only updated the description",
         afterValue.compareTo(beforeValue) == 0);
-    assertTrue("Update inventory is called on Case IV, should not be",
-        afterUpdatedDate.equals(beforeUpdatedDate));
 
-    // Case V: Delete a M_InoutLine
+    log.info("Previous modification in Storage detail: " + previousDate);
+    log.info("Las modification in Storage detail: " + newDate);
 
-    beforeUpdate = afterUpdate;
-    OBDal.getInstance().refresh(beforeUpdate);
-    beforeValue = beforeUpdate.getQuantityInDraftTransactions();
-    beforeUpdatedDate = beforeUpdate.getUpdated();
+    // FIXME This assert usually is not able to detect a problem because the m_update_inventory set
+    // the updated column with UPDATED=to_date(now()), which seems to set always the same date
+    // because to_date() is declared as IMMUTABLE. To properly test it, it should be UPDATED=now().
+    // However the assert is kept as it doesn't create false positives
+    assertTrue(
+        "Storage detail is not updated because only description has been updated. Previous: "
+            + previousDate + ". newDate: " + newDate, previousDate.compareTo(newDate) == 0);
+  }
 
-    log.info("*************** Case V *****************");
-    log.info("Qty in Draft transaction before deletion: " + beforeValue);
-    log.info("Updated DateTime before deletion: " + beforeUpdatedDate);
+  // Update product quantity (positive qty)
+  private void test_UpdateMovementQuantity() throws SQLException {
+    OBDal.getInstance().refresh(storageDetail);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+    log.info("*************** Update Product quantity (1 Unit) *****************");
 
-    inOut = deleteMInOutLine(inOutLine);
-    assertTrue(inOut.getMaterialMgmtShipmentInOutLineList().isEmpty());
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_MOVEMENTQUANTITY, BigDecimal.ONE);
 
-    afterUpdate = getStorageDetail(product, product.getUOM(), null, locator, null);
-    OBDal.getInstance().refresh(beforeUpdate);
-    afterValue = afterUpdate.getQuantityInDraftTransactions();
-    afterUpdatedDate = afterUpdate.getUpdated();
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
 
-    log.info("Qty in Draft transaction after deletion: " + afterValue);
-    log.info("Updated DateTime after deletion: " + afterUpdatedDate);
+    afterValue = storageDetail.getQuantityInDraftTransactions();
 
-    assertTrue("Update inventory is not called on Case V", afterValue.compareTo(beforeValue) != 0);
-    assertTrue("Update inventory is not called on Case V",
-        !beforeUpdatedDate.equals(afterUpdatedDate));
+    log.info("Qty in Draft transaction before updating quantity " + beforeValue);
+    log.info("Qty in Draft transaction after updating quantity " + afterValue);
+
+    assertTrue("Quantities should not be equal because quantity has been updated",
+        afterValue.compareTo(beforeValue) != 0);
+  }
+
+  // Update product quantity to the same quantity
+  private void test_UpdateMovementQuantityToSameQuantity() throws SQLException {
+    OBDal.getInstance().refresh(storageDetail);
+    OBDal.getInstance().refresh(inOutLine);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+    log.info("*************** Update Product quantity to the same quantity (no change) *****************");
+
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_MOVEMENTQUANTITY, inOutLine.getMovementQuantity());
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
+
+    afterValue = storageDetail.getQuantityInDraftTransactions();
+
+    log.info("Qty in Draft transaction before updating quantity " + beforeValue);
+    log.info("Qty in Draft transaction after updating quantity " + afterValue);
+
+    assertTrue("Quantities should be equal because quantity has not been actually updated",
+        afterValue.compareTo(beforeValue) == 0);
+  }
+
+  // Update product quantity (negative qty)
+  private void test_UpdateMovementNegativeQuantity() throws SQLException {
+    OBDal.getInstance().refresh(storageDetail);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+    log.info("*************** Update Product negative quantity (-2 Unit) *****************");
+
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_MOVEMENTQUANTITY, new BigDecimal("-2"));
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), attributeSetInstance);
+    OBDal.getInstance().refresh(storageDetail);
+
+    afterValue = storageDetail.getQuantityInDraftTransactions();
+
+    log.info("Qty in Draft transaction before updating quantity " + beforeValue);
+    log.info("Qty in Draft transaction after updating quantity " + afterValue);
+
+    assertTrue("Quantities should not be equal because quantity has been updated",
+        afterValue.compareTo(beforeValue) != 0);
+  }
+
+  // Update attribute
+  private void test_UpdateAttribute(AttributeSetInstance newAttributeSetInstance)
+      throws SQLException {
+    final AttributeSetInstance previousAttributeSetInstance = inOutLine.getAttributeSetValue();
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), previousAttributeSetInstance);
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      beforeValue = BigDecimal.ZERO;
+    }
+
+    log.info("*************** Update attribute *****************");
+
+    BigDecimal beforeValueNewAttribute;
+    try {
+      storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+          inOutLine.getOrderUOM(), newAttributeSetInstance);
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValueNewAttribute = storageDetail.getQuantityInDraftTransactions();
+    } catch (Exception notfound) {
+      beforeValueNewAttribute = BigDecimal.ZERO;
+    }
+
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_ATTRIBUTESETVALUE, newAttributeSetInstance);
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), previousAttributeSetInstance);
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      afterValue = BigDecimal.ZERO;
+    }
+
+    log.info("Qty in Draft transaction for old attributesetinstance before updating attributesetinstance "
+        + beforeValue);
+    log.info("Qty in Draft transaction for old attributesetinstance after updating attributesetinstance "
+        + afterValue);
+
+    assertTrue(
+        "Quantities should not be equal for old attributesetinstance because we have updated the attributesetinstance",
+        afterValue.compareTo(beforeValue) != 0);
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), inOutLine.getStorageBin(),
+        inOutLine.getOrderUOM(), newAttributeSetInstance);
+    BigDecimal afterValueNewAttribute = BigDecimal.ZERO;
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValueNewAttribute = storageDetail.getQuantityInDraftTransactions();
+    }
+
+    log.info("Qty in Draft transaction for new attributesetinstance before updating attributesetinstance "
+        + beforeValueNewAttribute);
+    log.info("Qty in Draft transaction for new attributesetinstance after updating attributesetinstance "
+        + afterValueNewAttribute);
+
+    assertTrue(
+        "Quantities should not be equal for new attributesetinstance because we have updated the attributesetinstance",
+        afterValueNewAttribute.compareTo(beforeValueNewAttribute) != 0);
+  }
+
+  // Update locator
+  private void test_UpdateLocator(Locator newLocator) throws SQLException {
+    final Locator previousLocator = inOutLine.getStorageBin();
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), previousLocator,
+        inOutLine.getOrderUOM(), inOutLine.getAttributeSetValue());
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      beforeValue = BigDecimal.ZERO;
+    }
+
+    log.info("*************** Update locator *****************");
+
+    BigDecimal beforeValueNewLocator;
+    try {
+      storageDetail = getStorageDetail(product, inOutLine.getUOM(), newLocator,
+          inOutLine.getOrderUOM(), inOutLine.getAttributeSetValue());
+      OBDal.getInstance().refresh(storageDetail);
+      beforeValueNewLocator = storageDetail.getQuantityInDraftTransactions();
+    } catch (Exception notfound) {
+      beforeValueNewLocator = BigDecimal.ZERO;
+    }
+
+    updateMInOutLine(ShipmentInOutLine.PROPERTY_STORAGEBIN, newLocator);
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), previousLocator,
+        inOutLine.getOrderUOM(), inOutLine.getAttributeSetValue());
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      afterValue = BigDecimal.ZERO;
+    }
+
+    log.info("Qty in Draft transaction for old locator before updating locator " + beforeValue);
+    log.info("Qty in Draft transaction for old locator after updating locator " + afterValue);
+
+    assertTrue(
+        "Quantities should not be equal for old locator because we have updated the locator",
+        afterValue.compareTo(beforeValue) != 0);
+
+    storageDetail = getStorageDetail(product, inOutLine.getUOM(), newLocator,
+        inOutLine.getOrderUOM(), inOutLine.getAttributeSetValue());
+    BigDecimal afterValueNewLocator = BigDecimal.ZERO;
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValueNewLocator = storageDetail.getQuantityInDraftTransactions();
+    }
+
+    log.info("Qty in Draft transaction for new locator before updating locator "
+        + beforeValueNewLocator);
+    log.info("Qty in Draft transaction for new locator after updating locator "
+        + afterValueNewLocator);
+
+    assertTrue(
+        "Quantities should not be equal for new locator because we have updated the locator",
+        afterValueNewLocator.compareTo(beforeValueNewLocator) != 0);
 
   }
 
-  private ShipmentInOut insertMInOut() throws SQLException {
+  // Delete a M_InoutLine
+  private void test_DeleteLine() throws SQLException {
+    OBDal.getInstance().refresh(storageDetail);
+    beforeValue = storageDetail.getQuantityInDraftTransactions();
+
+    log.info("*************** Delete line *****************");
+    log.info("Qty in Draft transaction before deletion: " + beforeValue);
+
+    deleteMInOutLines();
+    assertTrue(inOut.getMaterialMgmtShipmentInOutLineList().isEmpty());
+
+    storageDetail = getStorageDetail(product, product.getUOM(), locator, null, attributeSetInstance);
+    if (storageDetail != null) {
+      OBDal.getInstance().refresh(storageDetail);
+      afterValue = storageDetail.getQuantityInDraftTransactions();
+    } else {
+      afterValue = BigDecimal.ZERO;
+    }
+
+    log.info("Qty in Draft transaction after deletion: " + afterValue);
+
+    assertTrue("Quantities should not be equal because line has been deleted",
+        afterValue.compareTo(beforeValue) != 0);
+  }
+
+  private ShipmentInOut insertMInOut(boolean isSales) throws SQLException {
+    final String shipment_DocumentTypeId;
+    final String shipment_DocSequenceId = "FF8080812C2ABFC6012C2B3BDF4A004D";
+    final String customerId;
+    final String customerAddressId;
+    // Warehouse: Spain Warehouse
+    final String WarehouseId = "4D7B97565A024DB7B4C61650FA2B9560";
+    if (isSales) {
+      // MM Shipment Document Type, Document Sequence
+      shipment_DocumentTypeId = "FF8080812C2ABFC6012C2B3BDF4A004E";
+
+      // Business Partner: Customer A
+      customerId = "4028E6C72959682B01295F40C3CB02EC";
+      customerAddressId = "4028E6C72959682B01295F40C43802EE";
+    } else {
+      // MM Shipment Document Type
+      shipment_DocumentTypeId = "FF8080812C2ABFC6012C2B3BDF530078";
+
+      // Business Partner: Creditor
+      customerId = "4028E6C72959682B01295F40CA140307";
+      customerAddressId = "4028E6C72959682B01295F40CA620309";
+    }
+
     try {
       OBContext.setAdminMode(true);
       // Set QA context
       OBContext.setOBContext(USER_ID, ROLE_ID, ClientId, OrganizationId);
       ShipmentInOut shipmentInOut = OBProvider.getInstance().get(ShipmentInOut.class);
-      BusinessPartner bpartner = OBDal.getInstance().get(BusinessPartner.class, CustomerId);
-      Location bpLocation = OBDal.getInstance().get(Location.class, CustomerAddressId);
-      DocumentType doctype = OBDal.getInstance().get(DocumentType.class, Shipment_DocumentTypeId);
+      BusinessPartner bpartner = OBDal.getInstance().get(BusinessPartner.class, customerId);
+      Location bpLocation = OBDal.getInstance().get(Location.class, customerAddressId);
+      DocumentType doctype = OBDal.getInstance().get(DocumentType.class, shipment_DocumentTypeId);
       Warehouse warehouse = OBDal.getInstance().get(Warehouse.class, WarehouseId);
       shipmentInOut.setBusinessPartner(bpartner);
       shipmentInOut.setDocumentType(doctype);
       shipmentInOut.setPartnerAddress(bpLocation);
-      shipmentInOut.setDocumentNo(getDocumentNo(Shipment_DocSequenceId));
+      shipmentInOut.setDocumentNo(getDocumentNo(shipment_DocSequenceId));
       shipmentInOut.setMovementDate(new Date());
       shipmentInOut.setAccountingDate(new Date());
       shipmentInOut.setWarehouse(warehouse);
+      shipmentInOut.setSalesTransaction(isSales);
 
       OBDal.getInstance().save(shipmentInOut);
       OBDal.getInstance().getConnection().commit();
@@ -281,14 +563,13 @@
       // Set QA context
       OBContext.setOBContext(USER_ID, ROLE_ID, ClientId, OrganizationId);
       ShipmentInOutLine shipmentInOutLine = OBProvider.getInstance().get(ShipmentInOutLine.class);
-      Product product = OBDal.getInstance().get(Product.class, ProductId);
-      Locator locator = OBDal.getInstance().get(Locator.class, LocatorId);
       shipmentInOutLine.setShipmentReceipt(shipmentInOut);
       shipmentInOutLine.setLineNo(10L);
       shipmentInOutLine.setProduct(product);
       shipmentInOutLine.setMovementQuantity(MovementQty);
       shipmentInOutLine.setStorageBin(locator);
       shipmentInOutLine.setUOM(product.getUOM());
+      shipmentInOutLine.setAttributeSetValue(attributeSetInstance);
       OBDal.getInstance().save(shipmentInOutLine);
       OBDal.getInstance().getConnection().commit();
       OBDal.getInstance().refresh(shipmentInOutLine);
@@ -300,46 +581,23 @@
     }
   }
 
-  private ShipmentInOutLine updateMInOutLine(ShipmentInOutLine shipmentInOutLine, int i,
-      Product product, BigDecimal movementQty, String strDescription) throws SQLException {
-    ShipmentInOutLine InOutLine = shipmentInOutLine;
-    switch (i) {
+  private void updateMInOutLine(String propertyName, Object value) throws SQLException {
+    inOutLine.set(propertyName, value);
+    OBDal.getInstance().save(inOutLine);
+    OBDal.getInstance().getConnection().commit();
+    OBDal.getInstance().refresh(inOutLine);
 
-    case 1:
-      InOutLine.setProduct(product);
-      OBDal.getInstance().save(InOutLine);
-      OBDal.getInstance().getConnection().commit();
-      OBDal.getInstance().refresh(InOutLine);
-      break;
-
-    case 2:
-      InOutLine.setMovementQuantity(movementQty);
-      OBDal.getInstance().save(InOutLine);
-      OBDal.getInstance().getConnection().commit();
-      OBDal.getInstance().refresh(InOutLine);
-      break;
-
-    case 4:
-      InOutLine.setDescription(strDescription);
-      OBDal.getInstance().save(InOutLine);
-      OBDal.getInstance().getConnection().commit();
-      OBDal.getInstance().refresh(InOutLine);
-      break;
-
-    default:
-    }
-
-    return InOutLine;
   }
 
-  private ShipmentInOut deleteMInOutLine(ShipmentInOutLine shipmentInOutLine) throws SQLException {
-    ShipmentInOut shipmentInOut = shipmentInOutLine.getShipmentReceipt();
-    OBDal.getInstance().remove(shipmentInOutLine);
-    OBDal.getInstance().save(shipmentInOut);
+  private void deleteMInOutLines() throws SQLException {
+    final StringBuffer hqlString = new StringBuffer();
+    hqlString
+        .append(" delete from MaterialMgmtShipmentInOutLine where shipmentReceipt.id = :mInOutId ");
+    Query deleteQry = OBDal.getInstance().getSession().createQuery(hqlString.toString());
+    deleteQry.setString("mInOutId", inOut.getId());
+    deleteQry.executeUpdate();
     OBDal.getInstance().getConnection().commit();
-    OBDal.getInstance().refresh(shipmentInOut);
-    return shipmentInOut;
-
+    OBDal.getInstance().refresh(inOut);
   }
 
   // Calculates the next document number for this sequence
@@ -356,28 +614,34 @@
     }
   }
 
-  private StorageDetail getStorageDetail(Product product, UOM uom,
-      AttributeSetInstance attributeSetInstance, Locator locator, ProductUOM productUom) {
-    StorageDetail storageDetail = null;
-    String hqlString = " select sd from  MaterialMgmtStorageDetail sd " + " where sd.product.id ='"
-        + product.getId() + "' and sd.storageBin.id = '" + locator.getId() + "'"
-        + " and sd.uOM.id = '" + uom.getId() + "'";
-    if (attributeSetInstance != null) {
-      hqlString = hqlString + " and sd.attributeSetValue.id = '" + attributeSetInstance.getId()
-          + "'";
+  private StorageDetail getStorageDetail(Product _product, UOM uom, Locator _locator,
+      ProductUOM productUom, AttributeSetInstance _attributeInstance) {
+    final StringBuffer hqlString = new StringBuffer();
+    hqlString.append(" select sd from  MaterialMgmtStorageDetail sd ");
+    hqlString.append(" where sd.product.id = :productId ");
+    hqlString.append(" and sd.storageBin.id = :locatorId ");
+    hqlString.append(" and sd.uOM.id = :uomId ");
+    hqlString.append(" and sd.attributeSetValue.id = :attributeSetInstanceId ");
+    if (productUom != null) {
+      hqlString.append(" and sd.orderUOM.id = :productUOMId ");
     } else {
-      hqlString = hqlString + " and sd.attributeSetValue.id = '0'";
+      hqlString.append(" and sd.orderUOM.id is null ");
     }
+
+    Query query = OBDal.getInstance().getSession().createQuery(hqlString.toString());
+    query.setParameter("productId", _product.getId());
+    query.setParameter("locatorId", _locator.getId());
+    query.setParameter("uomId", uom.getId());
+    query.setParameter("attributeSetInstanceId", _attributeInstance.getId());
     if (productUom != null) {
-      hqlString = hqlString + " and sd.orderUOM.id = '" + productUom.getId() + "'";
-    } else {
-      hqlString = hqlString + " and sd.orderUOM.id is null ";
+      query.setParameter("productUOMId", productUom.getId());
     }
-    Query query = OBDal.getInstance().getSession().createQuery(hqlString);
-    query.uniqueResult();
-    if (!query.list().isEmpty()) {
-      storageDetail = (StorageDetail) query.list().get(0);
+    query.setMaxResults(1);
+
+    List<?> queryList = query.list();
+    if (!queryList.isEmpty()) {
+      return (StorageDetail) queryList.get(0);
     }
-    return storageDetail;
+    return null;
   }
 }