diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java index 7ea034c07..ac6c94332 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; +import org.springframework.dao.EmptyResultDataAccessException; /** * {@link AbstractPlatformDomainRuleException} thrown when loan charge does not exist. @@ -41,6 +42,11 @@ public class LoanChargeNotFoundException extends AbstractPlatformResourceNotFoun id, loanId); } + public LoanChargeNotFoundException(final Long id, final Long loanId, final EmptyResultDataAccessException e) { + super("error.msg.loanCharge.id.invalid.for.given.loan", "Loan charge with identifier " + id + " does not exist for loan " + loanId, + id, loanId, e); + } + public LoanChargeNotFoundException(ExternalId externalId) { super("error.msg.loanCharge.external.id.invalid", "Loan Charge with external identifier " + ObjectUtils.defaultIfNull(externalId, ExternalId.empty()).getValue() + " does not exist", externalId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index 477735cc3..b7e38e592 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.domain.Charge; +import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; import org.apache.fineract.portfolio.charge.service.ChargeDropdownReadPlatformService; import org.apache.fineract.portfolio.charge.service.ChargeEnumerations; import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; @@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; @@ -169,20 +171,20 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform @Override public LoanChargeData retrieveLoanChargeDetails(final Long id, final Long loanId) { - final LoanChargeMapper rm = new LoanChargeMapper(); - - final String sql = "select " + rm.schema() + " where lc.id=? and lc.loan_id=?"; - - return this.jdbcTemplate.queryForObject(sql, rm, id, loanId); // NOSONAR + try { + final LoanChargeMapper rm = new LoanChargeMapper(); + final String sql = "select " + rm.schema() + " where lc.id=? and lc.loan_id=?"; + return this.jdbcTemplate.queryForObject(sql, rm, id, loanId); // NOSONAR + } catch (final EmptyResultDataAccessException e) { + throw new LoanChargeNotFoundException(id, loanId, e); + } } @Override public Collection retrieveLoanCharges(final Long loanId) { final LoanChargeMapper rm = new LoanChargeMapper(); - final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = true" + " order by coalesce(lc.due_for_collection_as_of_date,date(coalesce(dd.disbursedon_date,dd.expected_disburse_date))),lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; - return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java index f5c7b25b6..a0724f369 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java @@ -700,6 +700,23 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest { // Check whether the provided external id was retrieved assertEquals(txnExternalIdStr, disbursedLoanResult.get("resourceExternalId")); + // Second loan + final HashMap loan2 = applyForLoanApplication(client.getClientId().intValue(), loanProductID, null); + Integer loan2Id = (Integer) loan2.get("resourceId"); + this.loanTransactionHelper.approveLoan("02 September 2022", loan2Id); + final HashMap disbursedLoan2Result = this.loanTransactionHelper.disburseLoan("03 September 2022", loan2Id, "1000", null); + + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true)); + + LocalDate targetDate = LocalDate.of(2022, 9, 7); + final String penaltyCharge1AddedDate = dateFormatter.format(targetDate); + + String penalty1LoanChargeExternalId = UUID.randomUUID().toString(); + Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loan2Id, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10", + penalty1LoanChargeExternalId)); + // NEGATIVE SCENARIOS // GET @@ -762,6 +779,16 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest { assertEquals(404, exception.getResponse().code()); assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanCharge(-1L, (long) penalty1LoanChargeId)); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.id.invalid.for.given.loan")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanCharge(loanExternalIdStr, (long) penalty1LoanChargeId)); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.id.invalid.for.given.loan")); + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper.getLoanCharge(loanExternalIdStr, "randomNonExistingLoanChargeExternalId")); assertEquals(404, exception.getResponse().code()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java index fd4bc8036..a97df0641 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static java.time.temporal.ChronoUnit.DAYS; import static org.junit.jupiter.api.Assertions.assertEquals; import com.google.common.truth.Truth; @@ -31,7 +32,6 @@ import java.math.RoundingMode; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.LocalDate; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoUnit; @@ -1341,7 +1341,7 @@ public class FixedDepositTest { todaysDate = todaysDate.minusMonths(1); todaysDate = todaysDate.minusDays(1); - Float interestPerMonth = (float) (interestPerDay * principal * ChronoUnit.DAYS.between(todaysDate, Utils.getLocalDateOfTenant())); + Float interestPerMonth = (float) (interestPerDay * principal * DAYS.between(todaysDate, Utils.getLocalDateOfTenant())); principal += interestPerMonth; log.info("{}", Utils.dateFormatter.format(todaysDate)); log.info("IPM = {}", interestPerMonth); @@ -1441,26 +1441,25 @@ public class FixedDepositTest { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); - DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); - DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); - DateFormat currentDateFormat = new SimpleDateFormat("dd"); + DateTimeFormatter dateFormat = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); - Calendar todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -3); - final String VALID_FROM = dateFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.YEAR, 10); - final String VALID_TO = dateFormat.format(todaysDate.getTime()); + LocalDate todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(3); + final String VALID_FROM = dateFormat.format(todaysDate); + todaysDate = todaysDate.plusYears(10); + final String VALID_TO = dateFormat.format(todaysDate); - todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); - monthDayFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.MONTH, 1); - todaysDate.add(Calendar.DAY_OF_MONTH, 1); - final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime()); + todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.minusDays(1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + LocalDate activationDate = todaysDate; + todaysDate = todaysDate.plusMonths(1); + todaysDate = todaysDate.plusDays(1); + final String CLOSED_ON_DATE = dateFormat.format(todaysDate); + LocalDate closingDate = todaysDate; Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); Assertions.assertNotNull(clientId); @@ -1500,18 +1499,9 @@ public class FixedDepositTest { log.info("per day = {}", perDay); double interestPerDay = interestRateInFraction * perDay; - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime())); - Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); - daysInMonth = daysInMonth - currentDate + 1; - Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth); - principal += interestPerMonth; - todaysDate.add(Calendar.DATE, daysInMonth); - log.info("{}", monthDayFormat.format(todaysDate.getTime())); - interestPerMonth = (float) (interestPerDay * principal * currentDate); - log.info("IPM = {}", interestPerMonth); - principal += interestPerMonth; + long daysBetween = DAYS.between(activationDate, closingDate); + Float totalInterest = (float) (interestPerDay * principal * daysBetween); + principal += totalInterest; log.info("principal = {}", principal); this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE); @@ -1540,26 +1530,25 @@ public class FixedDepositTest { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); - DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); - DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); - DateFormat currentDateFormat = new SimpleDateFormat("dd"); + DateTimeFormatter dateFormat = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); - Calendar todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -3); - final String VALID_FROM = dateFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.YEAR, 10); - final String VALID_TO = dateFormat.format(todaysDate.getTime()); + LocalDate todaysDate = Utils.getLocalDateOfTenant().minusDays(32); + todaysDate = todaysDate.minusMonths(3); + final String VALID_FROM = dateFormat.format(todaysDate); + todaysDate = todaysDate.plusYears(10); + final String VALID_TO = dateFormat.format(todaysDate); - todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); - monthDayFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.MONTH, 1); - todaysDate.add(Calendar.DAY_OF_MONTH, 1); - final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime()); + todaysDate = Utils.getLocalDateOfTenant().minusDays(32); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.minusDays(1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + LocalDate activationDate = todaysDate; + + todaysDate = Utils.getLocalDateOfTenant(); + final String CLOSED_ON_DATE = dateFormat.format(todaysDate); + LocalDate closingDate = todaysDate; Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); Assertions.assertNotNull(clientId); @@ -1591,15 +1580,7 @@ public class FixedDepositTest { ArrayList> interestRateChartData = FixedDepositProductHelper .getInterestRateChartSlabsByProductId(this.requestSpec, this.responseSpec, fixedDepositProductId); - Calendar activationDate = Calendar.getInstance(); - activationDate.add(Calendar.MONTH, -1); - activationDate.add(Calendar.DAY_OF_MONTH, -1); - ZonedDateTime startDate = ZonedDateTime.ofInstant(activationDate.getTime().toInstant(), this.systemTimeZone.toZoneId()); - - Calendar prematureClosureDate = Calendar.getInstance(); - ZonedDateTime endDate = ZonedDateTime.ofInstant(prematureClosureDate.getTime().toInstant(), this.systemTimeZone.toZoneId()); - - Integer depositedPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(startDate.toLocalDate(), endDate.toLocalDate())); + Integer depositedPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(activationDate, closingDate)); Float interestRate = FixedDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod); interestRate -= preClosurePenalInterestRate; @@ -1608,19 +1589,10 @@ public class FixedDepositTest { log.info("per day = {}", perDay); double interestPerDay = interestRateInFraction * perDay; - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime())); - Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); - daysInMonth = daysInMonth - currentDate + 1; - Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth); - principal += interestPerMonth; - todaysDate.add(Calendar.DATE, daysInMonth); - log.info("{}", monthDayFormat.format(todaysDate.getTime())); + long daysBetween = DAYS.between(activationDate, closingDate); + Float totalInterest = (float) (interestPerDay * principal * daysBetween); + principal += totalInterest; - interestPerMonth = (float) (interestPerDay * principal * currentDate); - log.info("IPM = {}", interestPerMonth); - principal += interestPerMonth; log.info("principal = {}", principal); this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE); @@ -1650,26 +1622,25 @@ public class FixedDepositTest { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); - DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); - DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); - DateFormat currentDateFormat = new SimpleDateFormat("dd"); + DateTimeFormatter dateFormat = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); - Calendar todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -3); - final String VALID_FROM = dateFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.YEAR, 10); - final String VALID_TO = dateFormat.format(todaysDate.getTime()); + LocalDate todaysDate = Utils.getLocalDateOfTenant().minusDays(32); + todaysDate = todaysDate.minusMonths(3); + final String VALID_FROM = dateFormat.format(todaysDate); + todaysDate = todaysDate.plusYears(10); + final String VALID_TO = dateFormat.format(todaysDate); - todaysDate = Calendar.getInstance(); - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); - final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); - monthDayFormat.format(todaysDate.getTime()); - todaysDate.add(Calendar.MONTH, 1); - todaysDate.add(Calendar.DAY_OF_MONTH, 1); - final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime()); + todaysDate = Utils.getLocalDateOfTenant().minusDays(32); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.minusDays(1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + LocalDate activationDate = todaysDate; + + todaysDate = Utils.getLocalDateOfTenant(); + final String CLOSED_ON_DATE = dateFormat.format(todaysDate); + LocalDate closingDate = todaysDate; Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); Assertions.assertNotNull(clientId); @@ -1705,15 +1676,7 @@ public class FixedDepositTest { ArrayList> interestRateChartData = FixedDepositProductHelper .getInterestRateChartSlabsByProductId(this.requestSpec, this.responseSpec, fixedDepositProductId); - Calendar activationDate = Calendar.getInstance(); - activationDate.add(Calendar.MONTH, -1); - activationDate.add(Calendar.DAY_OF_MONTH, -1); - ZonedDateTime startDate = ZonedDateTime.ofInstant(activationDate.getTime().toInstant(), this.systemTimeZone.toZoneId()); - - Calendar prematureClosureDate = Calendar.getInstance(); - ZonedDateTime endDate = ZonedDateTime.ofInstant(prematureClosureDate.getTime().toInstant(), this.systemTimeZone.toZoneId()); - - Integer depositedPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(startDate.toLocalDate(), endDate.toLocalDate())); + Integer depositedPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(activationDate, closingDate)); Float interestRate = FixedDepositAccountHelper.getInterestRate(interestRateChartData, depositedPeriod); interestRate -= preClosurePenalInterestRate; @@ -1722,19 +1685,10 @@ public class FixedDepositTest { log.info("per day = {}", perDay); double interestPerDay = interestRateInFraction * perDay; - todaysDate.add(Calendar.MONTH, -1); - todaysDate.add(Calendar.DAY_OF_MONTH, -1); - Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime())); - Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); - daysInMonth = daysInMonth - currentDate + 1; - Float interestPerMonth = (float) (interestPerDay * principal * daysInMonth); - principal += interestPerMonth; - todaysDate.add(Calendar.DATE, daysInMonth); - log.info("{}", monthDayFormat.format(todaysDate.getTime())); + long daysBetween = DAYS.between(activationDate, closingDate); + Float totalInterest = (float) (interestPerDay * principal * daysBetween); + principal += totalInterest; - interestPerMonth = (float) (interestPerDay * principal * currentDate); - log.info("IPM = {}", interestPerMonth); - principal += interestPerMonth; log.info("principal = {}", principal); this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, CLOSED_ON_DATE);