From bd404e8c7b88dad566692c01c1ec5a8630a35492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Alberto=20Hern=C3=A1ndez?= <44206706+josehernandezfintecheandomx@users.noreply.github.com> Date: Wed, 10 Aug 2022 13:31:19 -0500 Subject: [PATCH] Fix to accept Loan Charge amounts using locales (#2393) Co-authored-by: Jose Alberto Hernandez --- .../core/serialization/JsonParserHelper.java | 12 +++++-- .../portfolio/charge/domain/Charge.java | 25 ++++++------- .../loanaccount/domain/LoanCharge.java | 4 ++- .../ClientLoanIntegrationTest.java | 36 ++++++++++++++++++- .../common/charges/ChargesHelper.java | 2 +- .../common/loans/LoanTransactionHelper.java | 9 +++-- 6 files changed, 68 insertions(+), 20 deletions(-) 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 7ad675592..d93b55519 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 @@ public class JsonParserHelper { 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 3ca6660ed..eb0e63dbe 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.time.MonthDay; 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 class Charge extends AbstractPersistableCustom { public Map update(final JsonCommand command) { final Map actualChanges = new LinkedHashMap<>(7); - final String localeAsInput = command.locale(); + final Locale locale = command.extractLocale(); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges"); @@ -411,9 +412,9 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 @@ public class Charge extends AbstractPersistableCustom { 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 22e5410ef..9d04250cf 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.Collections; 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 class LoanCharge extends AbstractPersistableCustom { 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 6c52a8d13..632b24bbe 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 @@ public class ClientLoanIntegrationTest { 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 @@ public class ClientLoanIntegrationTest { 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 b2401d2e0..baf1cfa7f 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 final class ChargesHelper { public static String getModifyChargeJSON() { final HashMap 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 f568cba5a..326614bb7 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.time.LocalDate; 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 class LoanTransactionHelper { } public static String getInstallmentChargesForLoanAsJSON(final String chargeId, final String amount) { - final HashMap 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 map = new HashMap<>(); + map.put("locale", locale.getLanguage()); map.put("dateFormat", "dd MMMM yyyy"); map.put("amount", amount); map.put("chargeId", chargeId);