Merge branch 'develop' into arrearsAndWriteOff
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
index 48b294c..75f8a7f 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
@@ -21,7 +21,7 @@
 @SuppressWarnings("unused")
 public interface PermittableGroupIds {
   String PRODUCT_OPERATIONS_MANAGEMENT = "portfolio__v1__products__enable";
-  String PRODUCT_LOSS_PROVISIONING_MANAGEMENT = "portfolio__v1__products__lossprov";
+  String PRODUCT_LOSS_PROVISIONING_MANAGEMENT = "portfolio__v1__products__lossprv";
   String PRODUCT_MANAGEMENT = "portfolio__v1__products";
   String CASE_MANAGEMENT = "portfolio__v1__case";
 }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidLossProvisionList.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidLossProvisionList.java
index 3085033..adf9988 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidLossProvisionList.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidLossProvisionList.java
@@ -19,8 +19,9 @@
 
 import javax.validation.ConstraintValidator;
 import javax.validation.ConstraintValidatorContext;
-import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @author Myrle Krantz
@@ -35,10 +36,11 @@
   public boolean isValid(
       final List<LossProvisionStep> value,
       final ConstraintValidatorContext context) {
-    final BigDecimal sum = value.stream()
-        .map(LossProvisionStep::getPercentProvision)
-        .map(x -> x.setScale(2, BigDecimal.ROUND_HALF_EVEN))
-        .reduce(BigDecimal.ZERO, BigDecimal::add);
-    return sum.compareTo(BigDecimal.valueOf(100_00, 2)) == 0;
+    if (value == null) return false;
+    final Map<Integer, Long> configurationsPerDay = value.stream()
+        .collect(Collectors.groupingBy(LossProvisionStep::getDaysLate, Collectors.counting()));
+    final boolean moreThanOneConfigurationForAtLeastOneDay = configurationsPerDay.values().stream()
+        .anyMatch(x -> x > 1);
+    return !moreThanOneConfigurationForAtLeastOneDay;
   }
 }
\ No newline at end of file
diff --git a/api/src/test/java/io/mifos/individuallending/api/v1/domain/product/LossProvisionConfigurationTest.java b/api/src/test/java/io/mifos/individuallending/api/v1/domain/product/LossProvisionConfigurationTest.java
index ba6e24b..5139310 100644
--- a/api/src/test/java/io/mifos/individuallending/api/v1/domain/product/LossProvisionConfigurationTest.java
+++ b/api/src/test/java/io/mifos/individuallending/api/v1/domain/product/LossProvisionConfigurationTest.java
@@ -36,7 +36,7 @@
     final LossProvisionConfiguration ret = new LossProvisionConfiguration();
     final List<LossProvisionStep> lossProvisionSteps = new ArrayList<>();
     lossProvisionSteps.add(new LossProvisionStep(0, BigDecimal.ONE));
-    lossProvisionSteps.add(new LossProvisionStep(1, BigDecimal.valueOf(9)));
+    lossProvisionSteps.add(new LossProvisionStep(1, BigDecimal.valueOf(10)));
     lossProvisionSteps.add(new LossProvisionStep(10, BigDecimal.valueOf(20)));
     lossProvisionSteps.add(new LossProvisionStep(50, BigDecimal.valueOf(70)));
     ret.setLossProvisionSteps(lossProvisionSteps);
@@ -50,15 +50,12 @@
     ret.add(new ValidationTestCase<LossProvisionConfiguration>("valid"));
     ret.add(new ValidationTestCase<LossProvisionConfiguration>("emptyList")
         .adjustment(x -> x.setLossProvisionSteps(Collections.emptyList()))
-        .valid(false));
+        .valid(true));
     ret.add(new ValidationTestCase<LossProvisionConfiguration>("nullList")
-        .adjustment(x -> x.setLossProvisionSteps(Collections.emptyList()))
+        .adjustment(x -> x.setLossProvisionSteps(null))
         .valid(false));
-    ret.add(new ValidationTestCase<LossProvisionConfiguration>("sumTooSmall")
-        .adjustment(x -> x.getLossProvisionSteps().get(0).setPercentProvision(BigDecimal.valueOf(0.1)))
-        .valid(false));
-    ret.add(new ValidationTestCase<LossProvisionConfiguration>("sumTooLarge")
-        .adjustment(x -> x.getLossProvisionSteps().get(3).setPercentProvision(BigDecimal.valueOf(71)))
+    ret.add(new ValidationTestCase<LossProvisionConfiguration>("moreThanOneValuesForOneDay")
+        .adjustment(x -> x.getLossProvisionSteps().add(new LossProvisionStep(0, BigDecimal.valueOf(0.1))))
         .valid(false));
 
     return ret;
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestLossProvisionSteps.java b/component-test/src/main/java/io/mifos/portfolio/TestLossProvisionSteps.java
index f86ef33..349f8bd 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestLossProvisionSteps.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestLossProvisionSteps.java
@@ -24,6 +24,7 @@
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -33,16 +34,44 @@
   @Test
   public void shouldChangeAndGetLossProvisionSteps() throws InterruptedException {
     final Product product = createAdjustedProduct(x -> {});
+
     final List<LossProvisionStep> lossProvisionSteps = new ArrayList<>();
     lossProvisionSteps.add(new LossProvisionStep(0, BigDecimal.valueOf(1_00, 2)));
     lossProvisionSteps.add(new LossProvisionStep(1, BigDecimal.valueOf(9_00, 2)));
     lossProvisionSteps.add(new LossProvisionStep(30, BigDecimal.valueOf(35_00, 2)));
     lossProvisionSteps.add(new LossProvisionStep(60, BigDecimal.valueOf(55_00, 2)));
     final LossProvisionConfiguration lossProvisionConfiguration = new LossProvisionConfiguration(lossProvisionSteps);
-    individualLending.changeLossProvisionConfiguration(product.getIdentifier(), lossProvisionConfiguration);
 
+    individualLending.changeLossProvisionConfiguration(product.getIdentifier(), lossProvisionConfiguration);
     Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.PUT_LOSS_PROVISION_STEPS, product.getIdentifier()));
+    Thread.sleep(2000);
+
     final LossProvisionConfiguration lossProvisionConfigurationAsSaved = individualLending.getLossProvisionConfiguration(product.getIdentifier());
     Assert.assertEquals(lossProvisionConfiguration, lossProvisionConfigurationAsSaved);
+
+
+    final List<LossProvisionStep> lossProvisionSteps2 = new ArrayList<>();
+    lossProvisionSteps2.add(new LossProvisionStep(0, BigDecimal.valueOf(2_00, 2)));
+    lossProvisionSteps2.add(new LossProvisionStep(1, BigDecimal.valueOf(15_00, 2)));
+    lossProvisionSteps2.add(new LossProvisionStep(30, BigDecimal.valueOf(35_00, 2)));
+    lossProvisionSteps2.add(new LossProvisionStep(60, BigDecimal.valueOf(55_00, 2)));
+    final LossProvisionConfiguration lossProvisionConfiguration2 = new LossProvisionConfiguration(lossProvisionSteps2);
+
+    individualLending.changeLossProvisionConfiguration(product.getIdentifier(), lossProvisionConfiguration2);
+    Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.PUT_LOSS_PROVISION_STEPS, product.getIdentifier()));
+    Thread.sleep(2000);
+
+    final LossProvisionConfiguration lossProvisionConfiguration2AsSaved = individualLending.getLossProvisionConfiguration(product.getIdentifier());
+    Assert.assertEquals(lossProvisionConfiguration2, lossProvisionConfiguration2AsSaved);
+
+
+    final LossProvisionConfiguration lossProvisionConfiguration3 = new LossProvisionConfiguration(Collections.emptyList());
+
+    individualLending.changeLossProvisionConfiguration(product.getIdentifier(), lossProvisionConfiguration3);
+    Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.PUT_LOSS_PROVISION_STEPS, product.getIdentifier()));
+    Thread.sleep(2000);
+
+    //TODO: final LossProvisionConfiguration lossProvisionConfiguration3AsSaved = individualLending.getLossProvisionConfiguration(product.getIdentifier());
+    //Assert.assertEquals(lossProvisionConfiguration3, lossProvisionConfiguration3AsSaved);
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/LossProvisionStepsCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/LossProvisionStepsCommandHandler.java
index 31cd9d8..126fa9a 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/LossProvisionStepsCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/LossProvisionStepsCommandHandler.java
@@ -30,7 +30,9 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.stream.Stream;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 
 /**
@@ -57,11 +59,28 @@
   public String process(final ChangeLossProvisionSteps command) {
     final ProductEntity productEntity = productRepository.findByIdentifier(command.getProductIdentifier())
         .orElseThrow(() -> ServiceException.notFound("Product not found ''{0}''.", command.getProductIdentifier()));
-    final Stream<LossProvisionStepEntity> lossProvisionSteps = lossProvisionStepRepository.findByProductId(productEntity.getId());
-    lossProvisionSteps.forEach(lossProvisionStepRepository::delete);
-    command.getLossProvisionConfiguration().getLossProvisionSteps().stream()
-        .map(lossProvisionStep -> LossProvisionStepMapper.map(productEntity.getId(), lossProvisionStep))
-        .forEach(lossProvisionStepRepository::save);
+    final Map<Integer, LossProvisionStepEntity> existingLossProvisionSteps =
+        lossProvisionStepRepository.findByProductId(productEntity.getId())
+        .collect(Collectors.toMap(LossProvisionStepEntity::getDaysLate, Function.identity()));
+
+    final Map<Integer, LossProvisionStepEntity> newLossProvisionSteps =
+        command.getLossProvisionConfiguration().getLossProvisionSteps().stream()
+            .map(newLossProvisionStep -> {
+              final LossProvisionStepEntity existingLossProvisionStepEntity = existingLossProvisionSteps.get(newLossProvisionStep.getDaysLate());
+              if (existingLossProvisionStepEntity != null) {
+                existingLossProvisionStepEntity.setPercentProvision(newLossProvisionStep.getPercentProvision());
+                return existingLossProvisionStepEntity;
+              } else {
+                return LossProvisionStepMapper.map(productEntity.getId(), newLossProvisionStep);
+              }
+            })
+            .collect(Collectors.toMap(LossProvisionStepEntity::getDaysLate, Function.identity()));
+    newLossProvisionSteps.values().forEach(lossProvisionStepRepository::save);
+
+    existingLossProvisionSteps.forEach((daysLate, lossProvisionStep) -> {
+      if (newLossProvisionSteps.get(daysLate) == null)
+        lossProvisionStepRepository.delete(lossProvisionStep);
+    });
 
     return command.getProductIdentifier();
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepEntity.java b/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepEntity.java
index a74a8ea..0b90ae4 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepEntity.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepEntity.java
@@ -17,7 +17,6 @@
 
 import javax.persistence.*;
 import java.math.BigDecimal;
-import java.util.Objects;
 
 /**
  * @author Myrle Krantz
@@ -71,18 +70,4 @@
   public void setPercentProvision(BigDecimal percentProvision) {
     this.percentProvision = percentProvision;
   }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    LossProvisionStepEntity that = (LossProvisionStepEntity) o;
-    return Objects.equals(productId, that.productId) &&
-        Objects.equals(daysLate, that.daysLate);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(productId, daysLate);
-  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepRepository.java b/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepRepository.java
index 810412e..3f5fff6 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepRepository.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/LossProvisionStepRepository.java
@@ -26,7 +26,9 @@
  */
 @Repository
 public interface LossProvisionStepRepository extends JpaRepository<LossProvisionStepEntity, Long> {
-  Stream<LossProvisionStepEntity> findByProductId(Long id);
+  Stream<LossProvisionStepEntity> findByProductId(Long productId);
 
-  Optional<LossProvisionStepEntity> findByProductIdAndDaysLate(Long id, int daysLate);
+  Stream<LossProvisionStepEntity> findByProductIdOrderByDaysLateAsc(Long productId);
+
+  Optional<LossProvisionStepEntity> findByProductIdAndDaysLate(Long productId, int daysLate);
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java b/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
index a079ecf..80d4dc9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
@@ -54,7 +54,7 @@
     final Long productId = productRepository.findByIdentifier(productIdentifier)
         .orElseThrow(() -> ServiceException.notFound("Product ''{}'' doesn''t exist.", productIdentifier))
         .getId();
-    return lossProvisionStepRepository.findByProductId(productId)
+    return lossProvisionStepRepository.findByProductIdOrderByDaysLateAsc(productId)
         .map(LossProvisionStepMapper::map)
         .collect(Collectors.toList());
   }