Fix to accept Loan Charge amounts using locales (#2393)

Co-authored-by: Jose Alberto Hernandez <alberto@MacBook-Pro.local>
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
index 7ad6755..d93b555 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -122,9 +122,15 @@
             if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
                 modifiedParameters.add(parameterName);
                 final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
-                final String valueAsString = primitive.getAsString();
-                if (StringUtils.isNotBlank(valueAsString)) {
-                    value = convertFrom(valueAsString, parameterName, locale);
+                if (!primitive.isJsonNull()) {
+                    if (primitive.isNumber()) {
+                        value = primitive.getAsBigDecimal();
+                    } else {
+                        final String valueAsString = primitive.getAsString();
+                        if (StringUtils.isNotBlank(valueAsString)) {
+                            value = convertFrom(valueAsString, parameterName, locale);
+                        }
+                    }
                 }
             }
         }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
index 3ca6660..eb0e63d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
@@ -23,6 +23,7 @@
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import javax.persistence.Column;
@@ -390,7 +391,7 @@
     public Map<String, Object> update(final JsonCommand command) {
         final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
 
-        final String localeAsInput = command.locale();
+        final Locale locale = command.extractLocale();
 
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
         final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
@@ -411,9 +412,9 @@
 
         final String amountParamName = "amount";
         if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
-            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+            final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName, locale);
             actualChanges.put(amountParamName, newValue);
-            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("locale", locale.getLanguage());
             this.amount = newValue;
         }
 
@@ -421,7 +422,7 @@
         if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTimeType)) {
             final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName);
             actualChanges.put(chargeTimeParamName, newValue);
-            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("locale", locale.getLanguage());
             this.chargeTimeType = ChargeTimeType.fromInt(newValue).getValue();
 
             if (isSavingsCharge()) {
@@ -510,7 +511,7 @@
         if (command.isChangeInIntegerParameterNamed(chargeCalculationParamName, this.chargeCalculation)) {
             final Integer newValue = command.integerValueOfParameterNamed(chargeCalculationParamName);
             actualChanges.put(chargeCalculationParamName, newValue);
-            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("locale", locale.getLanguage());
             this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
 
             if (isSavingsCharge()) {
@@ -540,7 +541,7 @@
             if (command.isChangeInIntegerParameterNamed(paymentModeParamName, this.chargePaymentMode)) {
                 final Integer newValue = command.integerValueOfParameterNamed(paymentModeParamName);
                 actualChanges.put(paymentModeParamName, newValue);
-                actualChanges.put("locale", localeAsInput);
+                actualChanges.put("locale", locale.getLanguage());
                 this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue();
             }
         }
@@ -551,14 +552,14 @@
             final Integer dayOfMonthValue = monthDay.getDayOfMonth();
             if (!this.feeOnDay.equals(dayOfMonthValue)) {
                 actualChanges.put("feeOnMonthDay", actualValueEntered);
-                actualChanges.put("locale", localeAsInput);
+                actualChanges.put("locale", locale.getLanguage());
                 this.feeOnDay = dayOfMonthValue;
             }
 
             final Integer monthOfYear = monthDay.getMonthValue();
             if (!this.feeOnMonth.equals(monthOfYear)) {
                 actualChanges.put("feeOnMonthDay", actualValueEntered);
-                actualChanges.put("locale", localeAsInput);
+                actualChanges.put("locale", locale.getLanguage());
                 this.feeOnMonth = monthOfYear;
             }
         }
@@ -567,7 +568,7 @@
         if (command.isChangeInIntegerParameterNamed(feeInterval, this.feeInterval)) {
             final Integer newValue = command.integerValueOfParameterNamed(feeInterval);
             actualChanges.put(feeInterval, newValue);
-            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("locale", locale.getLanguage());
             this.feeInterval = newValue;
         }
 
@@ -575,7 +576,7 @@
         if (command.isChangeInIntegerParameterNamed(feeFrequency, this.feeFrequency)) {
             final Integer newValue = command.integerValueOfParameterNamed(feeFrequency);
             actualChanges.put(feeFrequency, newValue);
-            actualChanges.put("locale", localeAsInput);
+            actualChanges.put("locale", locale.getLanguage());
             this.feeFrequency = newValue;
         }
 
@@ -602,14 +603,14 @@
             if (command.isChangeInBigDecimalParameterNamed(minCapParamName, this.minCap)) {
                 final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minCapParamName);
                 actualChanges.put(minCapParamName, newValue);
-                actualChanges.put("locale", localeAsInput);
+                actualChanges.put("locale", locale.getLanguage());
                 this.minCap = newValue;
             }
             final String maxCapParamName = "maxCap";
             if (command.isChangeInBigDecimalParameterNamed(maxCapParamName, this.maxCap)) {
                 final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxCapParamName);
                 actualChanges.put(maxCapParamName, newValue);
-                actualChanges.put("locale", localeAsInput);
+                actualChanges.put("locale", locale.getLanguage());
                 this.maxCap = newValue;
             }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
index 22e5410..9d04250 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
@@ -27,6 +27,7 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import javax.persistence.CascadeType;
@@ -145,7 +146,8 @@
 
     public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,
             final LocalDate dueDate) {
-        final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
+        final Locale locale = command.extractLocale();
+        final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount", locale);
 
         final ChargeTimeType chargeTime = null;
         final ChargeCalculationType chargeCalculation = null;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 6c52a8d..632b24b 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -865,6 +865,39 @@
         validateChargeExcludePrecission(flatAccTransfer, loanCharges, "100.0", "300", "100.0", "0.0");
         validateChargeExcludePrecission(flat, loanCharges, "50.0", "150", "0.0", "50.0");
 
+        // Loan Charges with US Locale using the amount as a number in the JSON body
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flat), 50.05, Locale.US));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("200.05", String.valueOf(installment.get("feeChargesDue")));
+        }
+
+        // Loan Charges with other Locale using comma (,) as decimal delimiter
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flat), "50,05", Locale.GERMAN));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("250.10", String.valueOf(installment.get("feeChargesDue")));
+        }
+
+        // Loan Charges with German Locale (where the comma is the decimal delimiter) using the amount as a number in
+        // the JSON body
+        this.loanTransactionHelper.addChargesForLoan(loanID,
+                LoanTransactionHelper.getInstallmentChargesForLoanAsJSON(String.valueOf(flat), 50.05, Locale.GERMAN));
+        loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        loanSchedule.remove(0);
+        for (HashMap installment : loanSchedule) {
+            validateNumberForEqualExcludePrecission("300.15", String.valueOf(installment.get("feeChargesDue")));
+        }
     }
 
     @Test
@@ -1080,7 +1113,8 @@
     private void validateNumberForEqualExcludePrecission(String val, String val2) {
         DecimalFormat twoDForm = new DecimalFormat("#");
         Assertions.assertTrue(
-                Float.valueOf(twoDForm.format(Float.valueOf(val))).compareTo(Float.valueOf(twoDForm.format(Float.valueOf(val2)))) == 0);
+                Float.valueOf(twoDForm.format(Float.valueOf(val))).compareTo(Float.valueOf(twoDForm.format(Float.valueOf(val2)))) == 0,
+                String.format("%s is not equal to %s", val, val2));
     }
 
     private Integer createLoanProduct(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
index b2401d2..baf1cfa 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
@@ -415,7 +415,7 @@
     public static String getModifyChargeJSON() {
         final HashMap<String, Object> map = new HashMap<>();
         map.put("locale", CommonConstants.LOCALE);
-        map.put("amount", "200.0");
+        map.put("amount", 200.0);
         String json = new Gson().toJson(map);
         LOG.info("{}", json);
         return json;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index f568cba..326614b 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -674,8 +675,12 @@
     }
 
     public static String getInstallmentChargesForLoanAsJSON(final String chargeId, final String amount) {
-        final HashMap<String, String> map = new HashMap<>();
-        map.put("locale", "en_GB");
+        return getInstallmentChargesForLoanAsJSON(chargeId, amount, Locale.UK);
+    }
+
+    public static String getInstallmentChargesForLoanAsJSON(final String chargeId, final Object amount, final Locale locale) {
+        final HashMap<String, Object> map = new HashMap<>();
+        map.put("locale", locale.getLanguage());
         map.put("dateFormat", "dd MMMM yyyy");
         map.put("amount", amount);
         map.put("chargeId", chargeId);