This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch revert-4235-FINERACT-2152/delete_and_update_interest_pause_period in repository https://gitbox.apache.org/repos/asf/fineract.git
commit f0cad9ece8aee77bbf49a83529813bbf9433dc7a Author: Adam Saghy <[email protected]> AuthorDate: Wed Jan 8 16:21:33 2025 +0100 Revert "FINERACT-2152: API update and delete interest pause" This reverts commit 8663c054a076ca52e49b8df3987f8da46c929e51. --- .../fineract/commands/domain/CommandSource.java | 3 +- .../fineract/commands/domain/CommandWrapper.java | 12 +- .../commands/service/CommandWrapperBuilder.java | 18 -- .../SynchronousCommandProcessingService.java | 12 +- .../api/LoanInterestPauseApiResource.java | 42 +-- .../handler/DeleteInterestPauseCommandHandler.java | 41 --- ...ndler.java => InterestPauseCommandHandler.java} | 12 +- .../handler/UpdateInterestPauseCommandHandler.java | 45 --- .../service/InterestPauseWritePlatformService.java | 50 ---- .../InterestPauseWritePlatformServiceImpl.java | 91 +----- .../loanaccount/domain/LoanTermVariations.java | 48 --- .../domain/LoanTermVariationsRepository.java | 9 - .../starter/LoanAccountConfiguration.java | 4 +- .../db/changelog/tenant/changelog-tenant.xml | 1 - ..._additional_audit_fields_to_term_variations.xml | 51 ---- .../integrationtests/LoanInterestPauseApiTest.java | 328 +++++++-------------- .../common/loans/LoanProductTestBuilder.java | 1 - .../common/loans/LoanTransactionHelper.java | 38 +-- 18 files changed, 131 insertions(+), 675 deletions(-) diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index e468e4a17..ad28f696c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -159,7 +159,8 @@ public class CommandSource extends AbstractPersistableCustom<Long> { .transactionId(command.getTransactionId()) // .creditBureauId(command.getCreditBureauId()) // .organisationCreditBureauId(command.getOrganisationCreditBureauId()) // - .loanExternalId(command.getLoanExternalId()).build(); // + .loanExternalId(command.getLoanExternalId()) // + .build(); // } public String getPermissionCode() { diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java index fd73b43b9..5b51f40de 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java @@ -251,16 +251,8 @@ public class CommandWrapper { return this.entityName.equalsIgnoreCase("INTEREST_PAUSE"); } - public boolean isInterestPauseCreateResource() { - return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "CREATE".equalsIgnoreCase(this.actionName); - } - - public boolean isInterestPauseUpdateResource() { - return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "UPDATE".equalsIgnoreCase(this.actionName); - } - - public boolean isInterestPauseDeleteResource() { - return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "DELETE".equalsIgnoreCase(this.actionName); + public boolean isInterestPauseExternalIdResource() { + return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && this.href.contains("/external-id/"); } public Long commandId() { diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 740fbbbc6..497862cbf 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -3732,22 +3732,4 @@ public class CommandWrapperBuilder { this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses"; return this; } - - public CommandWrapperBuilder deleteInterestPause(final long loanId, final long variationId) { - this.actionName = "DELETE"; - this.entityName = "INTEREST_PAUSE"; - this.loanId = loanId; - this.entityId = variationId; - this.href = "/v1/loans/" + loanId + "/interest-pauses/" + variationId; - return this; - } - - public CommandWrapperBuilder updateInterestPause(final long loanId, final long variationId) { - this.actionName = "UPDATE"; - this.entityName = "INTEREST_PAUSE"; - this.loanId = loanId; - this.entityId = variationId; - this.href = "/v1/loans/" + loanId + "/interest-pauses/" + variationId; - return this; - } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java index f021cb23c..9b525d5cd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java @@ -249,16 +249,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } - } else if (wrapper.isInterestPauseResource()) { - if (wrapper.isInterestPauseCreateResource()) { - handler = applicationContext.getBean("createInterestPauseCommandHandler", NewCommandSourceHandler.class); - } else if (wrapper.isInterestPauseUpdateResource()) { - handler = applicationContext.getBean("updateInterestPauseCommandHandler", NewCommandSourceHandler.class); - } else if (wrapper.isInterestPauseDeleteResource()) { - handler = applicationContext.getBean("deleteInterestPauseCommandHandler", NewCommandSourceHandler.class); - } else { - throw new UnsupportedCommandException(wrapper.commandName()); - } + } else if (wrapper.isInterestPauseResource() || wrapper.isInterestPauseExternalIdResource()) { + handler = applicationContext.getBean("interestPauseCommandHandler", NewCommandSourceHandler.class); } else { handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName()); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java index 253727547..1b08ad6d8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java @@ -25,15 +25,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.domain.CommandWrapper; @@ -53,7 +50,6 @@ import org.springframework.stereotype.Component; public class LoanInterestPauseApiResource { private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN"; - private static final String MODIFY_RESOURCE_NAME_FOR_PERMISSIONS = "UPDATE LOAN"; private final PlatformSecurityContext context; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; @@ -68,7 +64,7 @@ public class LoanInterestPauseApiResource { public CommandProcessingResult createInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @RequestBody(required = true) final InterestPauseRequestDto request) { - this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPause(loanId).withJson(request.toJson()).build(); @@ -85,7 +81,7 @@ public class LoanInterestPauseApiResource { @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @RequestBody(required = true) final InterestPauseRequestDto request) { - this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS); + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPauseByExternalId(loanExternalId) .withJson(request.toJson()).build(); @@ -118,38 +114,4 @@ public class LoanInterestPauseApiResource { return this.interestPauseReadPlatformService.retrieveInterestPauses(loanExternalId); } - - @DELETE - @Path("/{loanId}/interest-pauses/{variationId}") - @Operation(summary = "Delete an interest pause period", description = "Deletes a specific interest pause period by its variation ID.") - @ApiResponses({ @ApiResponse(responseCode = "204", description = "No Content") }) - public Response deleteInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, - @PathParam("variationId") @Parameter(description = "variationId") final Long variationId) { - - this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS); - - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteInterestPause(loanId, variationId).build(); - - this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return Response.noContent().build(); - } - - @PUT - @Path("/{loanId}/interest-pauses/{variationId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update an interest pause period", description = "Updates a specific interest pause period by its variation ID.") - @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") }) - public CommandProcessingResult updateInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, - @PathParam("variationId") @Parameter(description = "variationId") final Long variationId, - @RequestBody(required = true) final InterestPauseRequestDto request) { - - this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS); - - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateInterestPause(loanId, variationId) - .withJson(request.toJson()).build(); - - return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java deleted file mode 100644 index 8068c8c5a..000000000 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.interestpauses.handler; - -import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService; -import org.springframework.stereotype.Component; - -@Component("deleteInterestPauseCommandHandler") -@RequiredArgsConstructor -public class DeleteInterestPauseCommandHandler implements NewCommandSourceHandler { - - private final InterestPauseWritePlatformService interestPauseService; - - @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - final Long loanId = command.getLoanId(); - final Long termVariationId = command.getResourceId(); - - return interestPauseService.deleteInterestPause(loanId, termVariationId); - } -} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java similarity index 85% rename from fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java rename to fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java index 6246f278c..5747625dd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java @@ -27,14 +27,16 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService; import org.springframework.stereotype.Component; -@Component("createInterestPauseCommandHandler") +@Component("interestPauseCommandHandler") @RequiredArgsConstructor -public class CreateInterestPauseCommandHandler implements NewCommandSourceHandler { +public class InterestPauseCommandHandler implements NewCommandSourceHandler { private final InterestPauseWritePlatformService interestPauseService; @Override public CommandProcessingResult processCommand(final JsonCommand command) { + CommandProcessingResult result; + final String startDate = command.stringValueOfParameterNamed("startDate"); final String endDate = command.stringValueOfParameterNamed("endDate"); final String dateFormat = command.stringValueOfParameterNamed("dateFormat"); @@ -42,13 +44,15 @@ public class CreateInterestPauseCommandHandler implements NewCommandSourceHandle if (command.getLoanId() != null) { final Long loanId = command.getLoanId(); - return interestPauseService.createInterestPause(loanId, startDate, endDate, dateFormat, locale); + result = interestPauseService.createInterestPause(loanId, startDate, endDate, dateFormat, locale); } else if (command.getLoanExternalId() != null) { final ExternalId loanExternalId = command.getLoanExternalId(); - return interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale); + result = interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale); } else { throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id", "Either loanId or loanExternalId must be provided.", "loanId"); } + + return result; } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java deleted file mode 100644 index 04a9b2f03..000000000 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.interestpauses.handler; - -import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService; -import org.springframework.stereotype.Component; - -@Component("updateInterestPauseCommandHandler") -@RequiredArgsConstructor -public class UpdateInterestPauseCommandHandler implements NewCommandSourceHandler { - - private final InterestPauseWritePlatformService interestPauseService; - - @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - final Long loanId = command.getLoanId(); - final Long termVariationId = command.getResourceId(); - final String startDate = command.stringValueOfParameterNamed("startDate"); - final String endDate = command.stringValueOfParameterNamed("endDate"); - final String dateFormat = command.stringValueOfParameterNamed("dateFormat"); - final String locale = command.stringValueOfParameterNamed("locale"); - - return interestPauseService.updateInterestPause(loanId, termVariationId, startDate, endDate, dateFormat, locale); - } -} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java index 491a45171..e3b8c397a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java @@ -23,21 +23,6 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId; public interface InterestPauseWritePlatformService { - /** - * Create a new interest pause period for a loan identified by its external ID. - * - * @param loanExternalId - * the external ID of the loan - * @param startDate - * the start date of the interest pause period (inclusive) - * @param endDate - * the end date of the interest pause period (inclusive) - * @param dateFormat - * the format of the provided dates - * @param locale - * the locale used for date parsing - * @return the ID of the created loan term variation representing the interest pause - */ CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDate, String endDate, String dateFormat, String locale); @@ -50,42 +35,7 @@ public interface InterestPauseWritePlatformService { * the start date of the interest pause period (inclusive) * @param endDate * the end date of the interest pause period (inclusive) - * @param dateFormat - * the format of the provided dates - * @param locale - * the locale used for date parsing * @return the ID of the created loan term variation representing the interest pause */ CommandProcessingResult createInterestPause(Long loanId, String startDate, String endDate, String dateFormat, String locale); - - /** - * Delete an existing interest pause period for a loan. - * - * @param loanId - * the ID of the loan - * @param variationId - * the ID of the loan term variation representing the interest pause - * @return the result of the delete operation - */ - CommandProcessingResult deleteInterestPause(Long loanId, Long variationId); - - /** - * Update an existing interest pause period for a loan identified by its internal ID. - * - * @param loanId - * the ID of the loan - * @param variationId - * the ID of the loan term variation representing the interest pause to be updated - * @param startDateString - * the new start date of the interest pause period (inclusive) as a string - * @param endDateString - * the new end date of the interest pause period (inclusive) as a string - * @param dateFormat - * the format of the provided dates (e.g., "yyyy-MM-dd") - * @param locale - * the locale used for parsing the provided dates - * @return the updated loan term variation ID along with the updated fields - */ - CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString, - String dateFormat, String locale); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java index 095ec4024..272ba3f9f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java @@ -18,19 +18,12 @@ */ package org.apache.fineract.portfolio.interestpauses.service; -import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.ACTIVE; -import static org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType.INTEREST_PAUSE; -import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE; - import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; import lombok.AllArgsConstructor; @@ -41,13 +34,11 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; -import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.transaction.annotation.Transactional; @AllArgsConstructor @@ -56,7 +47,6 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite private final LoanTermVariationsRepository loanTermVariationsRepository; private final LoanRepositoryWrapper loanRepositoryWrapper; - private final PlatformSecurityContext context; @Override public CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDateString, String endDateString, @@ -78,59 +68,14 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite locale); } - @Override - public CommandProcessingResult deleteInterestPause(Long loanId, Long variationId) { - LoanTermVariations variation = loanTermVariationsRepository - .findByIdAndLoanIdAndTermType(variationId, loanId, INTEREST_PAUSE.getValue()) - .orElseThrow(() -> new GeneralPlatformDomainRuleException("error.msg.variation.not.found", - "Variation not found for the given loan ID")); - - loanTermVariationsRepository.delete(variation); - - return new CommandProcessingResultBuilder().withEntityId(variationId).build(); - } - - @Override - public CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString, - String dateFormat, String locale) { - Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); - - LocalDate startDate = parseDate(startDateString, dateFormat, locale); - LocalDate endDate = parseDate(endDateString, dateFormat, locale); - - validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale); - - LoanTermVariations variation = loanTermVariationsRepository - .findByIdAndLoanIdAndTermType(variationId, loanId, INTEREST_PAUSE.getValue()) - .orElseThrow(() -> new GeneralPlatformDomainRuleException("error.msg.variation.not.found", - "Variation not found for the given loan ID")); - - validateVariations(loan, variation); - - variation.setTermApplicableFrom(startDate); - variation.setDateValue(endDate); - - AppUser currentUser = context.authenticatedUser(); - variation.setUpdatedBy(currentUser != null ? currentUser.getId() : null); - variation.setUpdatedOnDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant())); - - LoanTermVariations updatedVariation = loanTermVariationsRepository.save(variation); - - return new CommandProcessingResultBuilder().withEntityId(updatedVariation.getId()) - .with(Map.of("startDate", startDate.toString(), "endDate", endDate.toString())).build(); - } - private CommandProcessingResult processInterestPause(Supplier<Loan> loanSupplier, LocalDate startDate, LocalDate endDate, String dateFormat, String locale) { final Loan loan = loanSupplier.get(); validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale); - LoanTermVariations variation = new LoanTermVariations(INTEREST_PAUSE.getValue(), startDate, null, endDate, false, loan); - - AppUser currentUser = context.authenticatedUser(); - variation.setCreatedBy(currentUser != null ? currentUser.getId() : null); - variation.setCreatedOnDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant())); + LoanTermVariations variation = new LoanTermVariations(LoanTermVariationType.INTEREST_PAUSE.getValue(), startDate, null, endDate, + false, loan); LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation); @@ -164,36 +109,6 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite .format("Interest pause end date (%s) must not be before the interest pause start date (%s).", endDate, startDate), endDate, startDate); } - - if (!Objects.equals(loan.getLoanStatus(), ACTIVE.getValue())) { - throw new GeneralPlatformDomainRuleException("loan.must.be.active", - "Operations on interest pauses are restricted to active loans."); - } - - if (!PROGRESSIVE.equals(loan.getLoanRepaymentScheduleDetail().getLoanScheduleType())) { - throw new GeneralPlatformDomainRuleException("loan.must.be.progressive", - "Interest pause is only supported for progressive loans."); - } - - if (!loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) { - throw new GeneralPlatformDomainRuleException("loan.must.have.recalculate.interest.enabled", - "Interest pause is only supported for loans with recalculate interest enabled."); - } - } - - private void validateVariations(Loan loan, LoanTermVariations variation) { - if (variation == null) { - throw new GeneralPlatformDomainRuleException("interest.pause.not.found", - "The specified interest pause does not exist for the given loan."); - } - - List<LoanTermVariations> existingVariations = loan.getLoanTermVariations(); - for (LoanTermVariations existingVariation : existingVariations) { - if (!existingVariation.equals(variation) && variation.getTermApplicableFrom().isBefore(existingVariation.getDateValue()) - && variation.getDateValue().isAfter(existingVariation.getTermApplicableFrom())) { - throw new GeneralPlatformDomainRuleException("interest.pause.overlapping", "Overlapping interest pauses are not allowed."); - } - } } private LocalDate parseDate(String date, String dateFormat, String locale) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java index 891dde0e2..d5f41828d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java @@ -27,7 +27,6 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; @@ -62,18 +61,6 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> { @Column(name = "is_active", nullable = false) private Boolean isActive; - @Column(name = "created_by") - private Long createdBy; - - @Column(name = "created_on_date") - private LocalDateTime createdOnDate; - - @Column(name = "updated_by") - private Long updatedBy; - - @Column(name = "updated_on_date") - private LocalDateTime updatedOnDate; - @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private LoanTermVariations parent; @@ -152,10 +139,6 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> { return this.dateValue; } - public void setDateValue(LocalDate dateValue) { - this.dateValue = dateValue; - } - public void setTermApplicableFrom(LocalDate termApplicableFrom) { this.termApplicableFrom = termApplicableFrom; } @@ -184,35 +167,4 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> { this.isActive = false; } - public Long getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(Long createdBy) { - this.createdBy = createdBy; - } - - public LocalDateTime getCreatedOnDate() { - return createdOnDate; - } - - public void setCreatedOnDate(LocalDateTime createdOnDate) { - this.createdOnDate = createdOnDate; - } - - public Long getUpdatedBy() { - return updatedBy; - } - - public void setUpdatedBy(Long updatedBy) { - this.updatedBy = updatedBy; - } - - public LocalDateTime getUpdatedOnDate() { - return updatedOnDate; - } - - public void setUpdatedOnDate(LocalDateTime updatedOnDate) { - this.updatedOnDate = updatedOnDate; - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java index 57993fe05..0bf7209ff 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java @@ -19,7 +19,6 @@ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain; import java.util.List; -import java.util.Optional; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; @@ -61,12 +60,4 @@ public interface LoanTermVariationsRepository """) List<LoanTermVariationsData> findLoanTermVariationsByExternalLoanIdAndTermType(@Param("loanExternalId") ExternalId loanExternalId, @Param("termType") int termType); - - @Query(""" - select ltv - from LoanTermVariations ltv - where ltv.id = :variationId and ltv.loan.id = :loanId and ltv.termType = :termType - """) - Optional<LoanTermVariations> findByIdAndLoanIdAndTermType(@Param("variationId") long variationId, @Param("loanId") long loanId, - @Param("termType") int termType); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index a1a1fddbb..b7b9a955d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -471,7 +471,7 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(InterestPauseWritePlatformService.class) public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository, - LoanRepositoryWrapper loanRepositoryWrapper, PlatformSecurityContext context) { - return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, context); + LoanRepositoryWrapper loanRepositoryWrapper) { + return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper); } } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index b058f2a2a..673a92f1f 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -180,5 +180,4 @@ <include file="parts/0159_trial_balance_summary_with_asset_owner_year_end_summary.xml" relativeToChangelogFile="true" /> <include file="parts/0160_add_acc_product_mapping_product_id_index.xml" relativeToChangelogFile="true" /> <include file="parts/0161_add_loan_external_id_to_commands.xml" relativeToChangelogFile="true" /> - <include file="parts/0162_add_additional_audit_fields_to_term_variations.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_additional_audit_fields_to_term_variations.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_additional_audit_fields_to_term_variations.xml deleted file mode 100644 index fbb1ef2bf..000000000 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_additional_audit_fields_to_term_variations.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - ---> -<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd"> - - <changeSet id="1" author="fineract"> - <addColumn tableName="m_loan_term_variations"> - <column name="created_by" type="BIGINT"> - <constraints nullable="true"/> - </column> - </addColumn> - - <addColumn tableName="m_loan_term_variations"> - <column name="created_on_date" type="TIMESTAMP"> - <constraints nullable="true"/> - </column> - </addColumn> - - <addColumn tableName="m_loan_term_variations"> - <column name="updated_by" type="BIGINT"> - <constraints nullable="true"/> - </column> - </addColumn> - - <addColumn tableName="m_loan_term_variations"> - <column name="updated_on_date" type="TIMESTAMP"> - <constraints nullable="true"/> - </column> - </addColumn> - </changeSet> -</databaseChangeLog> diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java index d4f5d0dc0..347b72f23 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java @@ -24,30 +24,38 @@ import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.AdvancedPaymentData; -import org.apache.fineract.client.models.PostLoansLoanIdRequest; +import org.apache.fineract.client.models.PaymentAllocationOrder; +import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.client.models.PostLoansRequest; +import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; -import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Slf4j -@ExtendWith(LoanTestLifecycleExtension.class) +@ExtendWith({ LoanTestLifecycleExtension.class }) public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(LoanInterestPauseApiTest.class); @@ -55,56 +63,52 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { private static RequestSpecification REQUEST_SPEC; private static ResponseSpecification RESPONSE_SPEC; private static ResponseSpecification RESPONSE_SPEC_403; - private static ResponseSpecification RESPONSE_SPEC_204; private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER; - private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER_204; private static LoanTransactionHelper LOAN_TRANSACTION_HELPER_403; - private static AccountHelper ACCOUNT_HELPER; - private static final Integer nonExistLoanId = 99999; + private static AccountHelper accountHelper; + private static PostClientsResponse client; + private static Integer loanProductId; + private static Long loanId; + private static Long nonExistLoanId = 99999L; private static String externalId; - private static final String nonExistExternalId = "7c4fb86f-a778-4d02-b7a8-ec3ec98941fa"; - private Integer clientId; - private Integer loanProductId; - private Integer loanId; - private final String loanPrincipalAmount = "10000.00"; - private final String numberOfRepayments = "12"; - private final String interestRatePerPeriod = "18"; - private final String dateString = "01 January 2023"; - - @BeforeEach - public void initialize() { + private static String nonExistExternalId = "7c4fb86f-a778-4d02-b7a8-ec3ec98941fa"; + + @BeforeAll + public static void setupTests() { Utils.initializeRESTAssured(); REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); RESPONSE_SPEC_403 = new ResponseSpecBuilder().expectStatusCode(403).build(); - RESPONSE_SPEC_204 = new ResponseSpecBuilder().expectStatusCode(204).build(); LOAN_TRANSACTIONAL_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); LOAN_TRANSACTION_HELPER_403 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_403); - LOAN_TRANSACTIONAL_HELPER_204 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_204); - ACCOUNT_HELPER = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC); + accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC); + ClientHelper clientHelper = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC); + client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); + + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); externalId = UUID.randomUUID().toString(); - createRequiredEntities(); + loanProductId = createLoanProduct("500", "15", "4", true, "25", true, LoanScheduleType.PROGRESSIVE, + LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, BigDecimal.valueOf(500.0), 45, + 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023", externalId); + + loanId = loanResponse.getLoanId(); Assertions.assertNotNull(loanProductId, "Loan Product ID should not be null after creation"); Assertions.assertNotNull(loanId, "Loan ID should not be null after creation"); Assertions.assertNotNull(externalId, "External Loan ID should not be null after creation"); } - /** - * Creates the client, loan product, and loan entities - **/ - private void createRequiredEntities() { - this.createClientEntity(); - this.createLoanProductEntity(); - this.createLoanEntity(); - } - @Test public void testCreateInterestPauseByLoanId_validRequest_shouldSucceed() { - PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-01-12", + PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", loanId); Assertions.assertNotNull(response); @@ -114,7 +118,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { @Test public void testCreateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() { try { - LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-05", "2023-01-12", "yyyy-MM-dd", "en", loanId); + LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-05", "2024-12-01", "yyyy-MM-dd", "en", loanId); } catch (Exception e) { String responseBody = e.getMessage(); Assertions.assertNotNull(responseBody, "Response body should not be null"); @@ -126,7 +130,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { @Test public void testCreateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() { try { - LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2022-12-01", "2023-01-12", "yyyy-MM-dd", "en", loanId); + LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2022-12-01", "2024-12-05", "yyyy-MM-dd", "en", loanId); } catch (Exception e) { String responseBody = e.getMessage(); Assertions.assertNotNull(responseBody, "Response body should not be null"); @@ -159,18 +163,18 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { @Test public void testRetrieveInterestPausesByLoanId_shouldReturnData() { - LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", loanId); + LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", loanId); String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId); Assertions.assertNotNull(response, "Response should not be null"); Assertions.assertTrue(response.contains("2023-01-01")); - Assertions.assertTrue(response.contains("2023-01-12")); + Assertions.assertTrue(response.contains("2023-02-05")); } @Test public void testCreateInterestPauseByExternalLoanId_validRequest_shouldSucceed() { - PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-01-12", + PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", externalId); Assertions.assertNotNull(response); @@ -180,7 +184,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { @Test public void testCreateInterestPauseByExternalLoanId_endDateBeforeStartDate_shouldFail() { try { - LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2023-01-01", "2022-01-12", "yyyy-MM-dd", "en", externalId); + LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2024-12-05", "2024-12-01", "yyyy-MM-dd", "en", externalId); } catch (Exception e) { String responseBody = e.getMessage(); Assertions.assertNotNull(responseBody, "Response body should not be null"); @@ -225,207 +229,87 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { @Test public void testRetrieveInterestPausesByExternalLoanId_shouldReturnData() { - LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", externalId); + LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", externalId); String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(externalId); Assertions.assertNotNull(response, "Response should not be null"); Assertions.assertTrue(response.contains("2023-01-01")); - Assertions.assertTrue(response.contains("2023-01-12")); - } - - @Test - public void testUpdateInterestPauseByLoanId_validRequest_shouldSucceed() { - PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", - "2023-01-12", "yyyy-MM-dd", "en", loanId); - - Assertions.assertNotNull(createResponse); - Assertions.assertNotNull(createResponse.getResourceId()); - - Long variationId = createResponse.getResourceId(); - - PostLoansLoanIdTransactionsResponse updateResponse = LOAN_TRANSACTIONAL_HELPER.updateInterestPauseByLoanId(variationId, - "2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", loanId); - - Assertions.assertNotNull(updateResponse); - Assertions.assertNotNull(updateResponse.getResourceId()); - Assertions.assertEquals(variationId, updateResponse.getResourceId()); - } - - @Test - public void testUpdateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() { - PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", - "2023-01-12", "yyyy-MM-dd", "en", loanId); - - Assertions.assertNotNull(createResponse); - Assertions.assertNotNull(createResponse.getResourceId()); - - Long variationId = createResponse.getResourceId(); - - try { - LOAN_TRANSACTION_HELPER_403.updateInterestPauseByLoanId(variationId, "2023-03-01", "2023-01-12", "yyyy-MM-dd", "en", loanId); - } catch (Exception e) { - String responseBody = e.getMessage(); - Assertions.assertNotNull(responseBody, "Response body should not be null"); - Assertions.assertTrue(responseBody.contains("interest.pause.end.date.before.start.date"), - "Response should contain the validation error message for end date before start date"); - } - } - - @Test - public void testUpdateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() { - PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", - "2023-01-12", "yyyy-MM-dd", "en", loanId); - - Assertions.assertNotNull(createResponse); - Assertions.assertNotNull(createResponse.getResourceId()); - - Long variationId = createResponse.getResourceId(); - - try { - LOAN_TRANSACTION_HELPER_403.updateInterestPauseByLoanId(variationId, "2022-12-01", "2023-01-12", "yyyy-MM-dd", "en", loanId); - } catch (Exception e) { - String responseBody = e.getMessage(); - Assertions.assertNotNull(responseBody, "Response body should not be null"); - Assertions.assertTrue(responseBody.contains("interest.pause.start.date.before.loan.start.date"), - "Response should contain the validation error message for start date before loan start date"); - } + Assertions.assertTrue(response.contains("2023-02-05")); } - @Test - public void testDeleteInterestPauseByLoanId_validRequest_shouldSucceed() { - PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", - "2023-01-12", "yyyy-MM-dd", "en", loanId); - - Assertions.assertNotNull(createResponse, "Create response should not be null"); - Assertions.assertNotNull(createResponse.getResourceId(), "Resource ID should not be null"); - - Long variationId = createResponse.getResourceId(); - - try { - LOAN_TRANSACTIONAL_HELPER_204.deleteInterestPauseByLoanId(variationId, loanId); - } catch (Exception e) { - Assertions.fail("Delete operation failed: " + e.getMessage()); - } - - String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId); - Assertions.assertFalse(response.contains(String.valueOf(variationId)), "Response should not contain the deleted variation ID"); + private static Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments, + boolean downPaymentEnabled, String downPaymentPercentage, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType, + LoanScheduleProcessingType loanScheduleProcessingType, final Account... accounts) { + AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(); + final String loanProductJSON = new LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal) + .withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments) + .withEnableDownPayment(downPaymentEnabled, downPaymentPercentage, autoPayForDownPayment).withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths() + .withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) + .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts) + .addAdvancedPaymentAllocation(defaultAllocation).withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withInterestTypeAsDecliningBalance().withMultiDisburse().withDisallowExpectedDisbursements(true) + .withLoanScheduleType(loanScheduleType).withLoanScheduleProcessingType(loanScheduleProcessingType).withDaysInMonth("30") + .withDaysInYear("365").withMoratorium("0", "0").build(null); + return LOAN_TRANSACTIONAL_HELPER.getLoanProductId(loanProductJSON); } - @Test - public void testDeleteInterestPauseByLoanId_nonExistentVariation_shouldFail() { - try { - LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(99999L, loanId); - } catch (Exception e) { - String responseBody = e.getMessage(); - Assertions.assertNotNull(responseBody, "Response body should not be null"); - Assertions.assertTrue(responseBody.contains("error.msg.variation.not.found"), - "Response should contain the validation error message for variation not found"); - } - } + private static AdvancedPaymentData createDefaultPaymentAllocation() { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType("DEFAULT"); + advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT"); - @Test - public void testDeleteInterestPauseByLoanId_invalidLoanId_shouldFail() { - PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", - "2023-01-12", "yyyy-MM-dd", "en", loanId); + List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY, + PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST, + PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL, + PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE, + PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST); - Assertions.assertNotNull(createResponse); - Assertions.assertNotNull(createResponse.getResourceId()); - - Long variationId = createResponse.getResourceId(); - - try { - LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(variationId, nonExistLoanId); - } catch (Exception e) { - String responseBody = e.getMessage(); - Assertions.assertNotNull(responseBody, "Response body should not be null"); - Assertions.assertTrue(responseBody.contains("error.msg.variation.not.found"), - "Response should contain the validation error message for variation not found"); - } - } - - /** - * create a new client - **/ - private void createClientEntity() { - this.clientId = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC); - - ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientId); + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders); + return advancedPaymentData; } - /** - * create a new loan product - **/ - private void createLoanProductEntity() { - LOG.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------"); - - final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE; - final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_ADJUST_LAST_UNPAID_PERIOD; - final String preCloseInterestCalculationStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE; - - final Account assetAccount = ACCOUNT_HELPER.createAssetAccount(); - final Account incomeAccount = ACCOUNT_HELPER.createIncomeAccount(); - final Account expenseAccount = ACCOUNT_HELPER.createExpenseAccount(); - final Account overpaymentAccount = ACCOUNT_HELPER.createLiabilityAccount(); - - String futureInstallmentAllocationRule = "NEXT_INSTALLMENT"; - AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule); - String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount).withNumberOfRepayments(numberOfRepayments) - .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod(interestRatePerPeriod) - .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() - .withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount }) - .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).addAdvancedPaymentAllocation(defaultAllocation) - .withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL) - .withMultiDisburse().withDisallowExpectedDisbursements(true).withInterestRecalculationDetails( - interestRecalculationCompoundingMethod, rescheduleStrategyMethod, preCloseInterestCalculationStrategy) - .build(); - - loanProductId = LOAN_TRANSACTIONAL_HELPER.getLoanProductId(loanProductJSON); - LOG.info("Successfully created loan product (ID:{}) ", loanProductId); + private static List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) { + AtomicInteger integer = new AtomicInteger(1); + return Arrays.stream(paymentAllocationTypes).map(pat -> { + PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder(); + paymentAllocationOrder.setPaymentAllocationRule(pat.name()); + paymentAllocationOrder.setOrder(integer.getAndIncrement()); + return paymentAllocationOrder; + }).collect(Collectors.toList()); } - /** - * submit a new loan application, approve and disburse the loan - **/ - private void createLoanEntity() { - LOG.info("---------------------------------NEW LOAN APPLICATION------------------------------------------"); - - String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount) - .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments) - .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod) - .withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() - .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(dateString) - .withSubmittedOnDate(dateString).withLoanType("individual").withExternalId(externalId) - .withRepaymentStrategy("advanced-payment-allocation-strategy").build(clientId.toString(), loanProductId.toString(), null); - - loanId = LOAN_TRANSACTIONAL_HELPER.getLoanId(loanApplicationJSON); - - LOG.info("Sucessfully created loan (ID: {} )", loanId); - - approveLoanApplication(); - disburseLoan(); + private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, + final String expectedDisbursementDate, final String submittedOnDate, final String externalId) { + return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments, + interestRate, expectedDisbursementDate, submittedOnDate, LoanScheduleProcessingType.HORIZONTAL, externalId); } - /** - * approve the loan application - **/ - private void approveLoanApplication() { - - if (loanId != null) { - LOAN_TRANSACTIONAL_HELPER.approveLoan(dateString, loanId); - LOG.info("Successfully approved loan (ID: {} )", loanId); - } + private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, + final String expectedDisbursementDate, final String submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType, + final String externalId) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments, + interestRate, expectedDisbursementDate, submittedOnDate, + AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, loanScheduleProcessingType.name(), + externalId); } - /** - * disburse the newly created loan - **/ - private void disburseLoan() { - - if (loanId != null) { - LOAN_TRANSACTIONAL_HELPER.disburseLoan(externalId, new PostLoansLoanIdRequest().actualDisbursementDate(dateString) - .transactionAmount(new BigDecimal(loanPrincipalAmount)).locale("en").dateFormat("dd MMMM yyyy")); - LOG.info("Successfully disbursed loan (ID: {} )", loanId); - } + private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, + final String expectedDisbursementDate, final String submittedOnDate, String transactionProcessorCode, + String loanScheduleProcessingType, final String externalId) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + return LOAN_TRANSACTIONAL_HELPER.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue()) + .expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN) + .transactionProcessingStrategyCode(transactionProcessorCode).locale("en").submittedOnDate(submittedOnDate) + .amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0) + .repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0) + .numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal) + .loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType).externalId(externalId) + .maxOutstandingLoanBalance(BigDecimal.valueOf(35000))); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index 507d789fe..40116b9ae 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -66,7 +66,6 @@ public class LoanProductTestBuilder { public static final String RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS = "1"; public static final String RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS = "2"; public static final String RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN = "3"; - public static final String RECALCULATION_STRATEGY_ADJUST_LAST_UNPAID_PERIOD = "4"; public static final String RECALCULATION_COMPOUNDING_METHOD_NONE = "0"; public static final String RECALCULATION_COMPOUNDING_METHOD_INTEREST = "1"; 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 52e222d65..8785ec626 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 @@ -609,7 +609,7 @@ public class LoanTransactionHelper extends IntegrationTest { } public PostLoansLoanIdTransactionsResponse createInterestPauseByLoanId(final String startDate, final String endDate, - final String dateFormat, final String locale, final Integer loanID) { + final String dateFormat, final String locale, final Long loanID) { log.info("Creating interest pause for Loan {} from {} to {} with dateFormat {} and locale {}", loanID, startDate, endDate, dateFormat, locale); String body = getInterestPauseBodyAsJSON(startDate, endDate, dateFormat, locale); @@ -624,20 +624,7 @@ public class LoanTransactionHelper extends IntegrationTest { return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, externalId), body); } - public PostLoansLoanIdTransactionsResponse updateInterestPauseByLoanId(final Long termVariationId, final String startDate, - final String endDate, final String dateFormat, final String locale, final Integer loanID) { - log.info("Updating interest pause for Loan {} with Term Variation ID {}: startDate={} endDate={} dateFormat={} locale={}", loanID, - termVariationId, startDate, endDate, dateFormat, locale); - String body = getInterestPauseBodyAsJSON(startDate, endDate, dateFormat, locale); - return putLoanTransaction(updateInterestPause(termVariationId, loanID), body); - } - - public void deleteInterestPauseByLoanId(final Long termVariationId, final Integer loanID) { - log.info("Deleting interest pause for Loan ID {} with Term Variation ID {}", loanID, termVariationId); - deleteLoanTransaction(deleteInterestPause(termVariationId, loanID)); - } - - public String retrieveInterestPauseByLoanId(final Integer loanID) { + public String retrieveInterestPauseByLoanId(final Long loanID) { log.info("Retrieving interest pauses for Loan ID {}", loanID); String url = retrieveInterestPause(loanID); return Utils.performServerGet(requestSpec, responseSpec, url); @@ -1463,7 +1450,7 @@ public class LoanTransactionHelper extends IntegrationTest { return "/fineract-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER; } - private String createInterestPause(final String command, final Integer loanID) { + private String createInterestPause(final String command, final Long loanID) { return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?command=" + command + "&" + Utils.TENANT_IDENTIFIER; } @@ -1472,7 +1459,7 @@ public class LoanTransactionHelper extends IntegrationTest { + Utils.TENANT_IDENTIFIER; } - private String retrieveInterestPause(final Integer loanID) { + private String retrieveInterestPause(final Long loanID) { return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?" + Utils.TENANT_IDENTIFIER; } @@ -1480,14 +1467,6 @@ public class LoanTransactionHelper extends IntegrationTest { return "/fineract-provider/api/v1/loans/external-id/" + externalId + "/interest-pauses?" + Utils.TENANT_IDENTIFIER; } - private String updateInterestPause(final Long termVariationId, final Integer loanID) { - return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses/" + termVariationId + "?" + Utils.TENANT_IDENTIFIER; - } - - private String deleteInterestPause(final Long termVariationId, final Integer loanID) { - return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses/" + termVariationId + "?" + Utils.TENANT_IDENTIFIER; - } - private String createInteroperationLoanTransactionURL(final String accountNo) { return "/fineract-provider/api/v1/interoperation/transactions/" + accountNo + "/loanrepayment"; } @@ -1534,15 +1513,6 @@ public class LoanTransactionHelper extends IntegrationTest { return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class); } - private PostLoansLoanIdTransactionsResponse putLoanTransaction(final String putURLForLoanTransaction, final String jsonToBeSent) { - final String response = Utils.performServerPut(this.requestSpec, this.responseSpec, putURLForLoanTransaction, jsonToBeSent); - return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class); - } - - private void deleteLoanTransaction(final String deleteURLForLoanTransaction) { - Utils.performServerDelete(this.requestSpec, this.responseSpec, deleteURLForLoanTransaction, null); - } - private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, ResponseSpecification responseValidationError) {
