FINERACT-2152: API update and delete interest pause
This commit is contained in:
committed by
Adam Saghy
parent
d2afd1aac7
commit
8663c054a0
@@ -159,8 +159,7 @@ public class CommandSource extends AbstractPersistableCustom<Long> {
|
|||||||
.transactionId(command.getTransactionId()) //
|
.transactionId(command.getTransactionId()) //
|
||||||
.creditBureauId(command.getCreditBureauId()) //
|
.creditBureauId(command.getCreditBureauId()) //
|
||||||
.organisationCreditBureauId(command.getOrganisationCreditBureauId()) //
|
.organisationCreditBureauId(command.getOrganisationCreditBureauId()) //
|
||||||
.loanExternalId(command.getLoanExternalId()) //
|
.loanExternalId(command.getLoanExternalId()).build(); //
|
||||||
.build(); //
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPermissionCode() {
|
public String getPermissionCode() {
|
||||||
|
|||||||
+10
-2
@@ -251,8 +251,16 @@ public class CommandWrapper {
|
|||||||
return this.entityName.equalsIgnoreCase("INTEREST_PAUSE");
|
return this.entityName.equalsIgnoreCase("INTEREST_PAUSE");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInterestPauseExternalIdResource() {
|
public boolean isInterestPauseCreateResource() {
|
||||||
return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && this.href.contains("/external-id/");
|
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 Long commandId() {
|
public Long commandId() {
|
||||||
|
|||||||
+18
@@ -3732,4 +3732,22 @@ public class CommandWrapperBuilder {
|
|||||||
this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses";
|
this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses";
|
||||||
return this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -249,8 +249,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
} else {
|
} else {
|
||||||
throw new UnsupportedCommandException(wrapper.commandName());
|
throw new UnsupportedCommandException(wrapper.commandName());
|
||||||
}
|
}
|
||||||
} else if (wrapper.isInterestPauseResource() || wrapper.isInterestPauseExternalIdResource()) {
|
} else if (wrapper.isInterestPauseResource()) {
|
||||||
handler = applicationContext.getBean("interestPauseCommandHandler", NewCommandSourceHandler.class);
|
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 {
|
} else {
|
||||||
handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName());
|
handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName());
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-2
@@ -25,12 +25,15 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.DELETE;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.PUT;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
@@ -50,6 +53,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class LoanInterestPauseApiResource {
|
public class LoanInterestPauseApiResource {
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
|
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 PlatformSecurityContext context;
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
@@ -64,7 +68,7 @@ public class LoanInterestPauseApiResource {
|
|||||||
public CommandProcessingResult createInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
|
public CommandProcessingResult createInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
|
||||||
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPause(loanId).withJson(request.toJson()).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPause(loanId).withJson(request.toJson()).build();
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ public class LoanInterestPauseApiResource {
|
|||||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
|
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
|
||||||
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPauseByExternalId(loanExternalId)
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createInterestPauseByExternalId(loanExternalId)
|
||||||
.withJson(request.toJson()).build();
|
.withJson(request.toJson()).build();
|
||||||
@@ -114,4 +118,38 @@ public class LoanInterestPauseApiResource {
|
|||||||
|
|
||||||
return this.interestPauseReadPlatformService.retrieveInterestPauses(loanExternalId);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -27,16 +27,14 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
|
|||||||
import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService;
|
import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component("interestPauseCommandHandler")
|
@Component("createInterestPauseCommandHandler")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class InterestPauseCommandHandler implements NewCommandSourceHandler {
|
public class CreateInterestPauseCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final InterestPauseWritePlatformService interestPauseService;
|
private final InterestPauseWritePlatformService interestPauseService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
CommandProcessingResult result;
|
|
||||||
|
|
||||||
final String startDate = command.stringValueOfParameterNamed("startDate");
|
final String startDate = command.stringValueOfParameterNamed("startDate");
|
||||||
final String endDate = command.stringValueOfParameterNamed("endDate");
|
final String endDate = command.stringValueOfParameterNamed("endDate");
|
||||||
final String dateFormat = command.stringValueOfParameterNamed("dateFormat");
|
final String dateFormat = command.stringValueOfParameterNamed("dateFormat");
|
||||||
@@ -44,15 +42,13 @@ public class InterestPauseCommandHandler implements NewCommandSourceHandler {
|
|||||||
|
|
||||||
if (command.getLoanId() != null) {
|
if (command.getLoanId() != null) {
|
||||||
final Long loanId = command.getLoanId();
|
final Long loanId = command.getLoanId();
|
||||||
result = interestPauseService.createInterestPause(loanId, startDate, endDate, dateFormat, locale);
|
return interestPauseService.createInterestPause(loanId, startDate, endDate, dateFormat, locale);
|
||||||
} else if (command.getLoanExternalId() != null) {
|
} else if (command.getLoanExternalId() != null) {
|
||||||
final ExternalId loanExternalId = command.getLoanExternalId();
|
final ExternalId loanExternalId = command.getLoanExternalId();
|
||||||
result = interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale);
|
return interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale);
|
||||||
} else {
|
} else {
|
||||||
throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id",
|
throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id",
|
||||||
"Either loanId or loanExternalId must be provided.", "loanId");
|
"Either loanId or loanExternalId must be provided.", "loanId");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -23,6 +23,21 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
|||||||
|
|
||||||
public interface InterestPauseWritePlatformService {
|
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,
|
CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDate, String endDate, String dateFormat,
|
||||||
String locale);
|
String locale);
|
||||||
|
|
||||||
@@ -35,7 +50,42 @@ public interface InterestPauseWritePlatformService {
|
|||||||
* the start date of the interest pause period (inclusive)
|
* the start date of the interest pause period (inclusive)
|
||||||
* @param endDate
|
* @param endDate
|
||||||
* the end date of the interest pause period (inclusive)
|
* 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
|
* @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);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-3
@@ -18,12 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.interestpauses.service;
|
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.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -34,11 +41,13 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
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.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -47,6 +56,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
|||||||
|
|
||||||
private final LoanTermVariationsRepository loanTermVariationsRepository;
|
private final LoanTermVariationsRepository loanTermVariationsRepository;
|
||||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDateString, String endDateString,
|
public CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDateString, String endDateString,
|
||||||
@@ -68,14 +78,59 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
|||||||
locale);
|
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,
|
private CommandProcessingResult processInterestPause(Supplier<Loan> loanSupplier, LocalDate startDate, LocalDate endDate,
|
||||||
String dateFormat, String locale) {
|
String dateFormat, String locale) {
|
||||||
final Loan loan = loanSupplier.get();
|
final Loan loan = loanSupplier.get();
|
||||||
|
|
||||||
validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale);
|
validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale);
|
||||||
|
|
||||||
LoanTermVariations variation = new LoanTermVariations(LoanTermVariationType.INTEREST_PAUSE.getValue(), startDate, null, endDate,
|
LoanTermVariations variation = new LoanTermVariations(INTEREST_PAUSE.getValue(), startDate, null, endDate, false, loan);
|
||||||
false, loan);
|
|
||||||
|
AppUser currentUser = context.authenticatedUser();
|
||||||
|
variation.setCreatedBy(currentUser != null ? currentUser.getId() : null);
|
||||||
|
variation.setCreatedOnDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
|
|
||||||
LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation);
|
LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation);
|
||||||
|
|
||||||
@@ -109,6 +164,36 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
|||||||
.format("Interest pause end date (%s) must not be before the interest pause start date (%s).", endDate, startDate),
|
.format("Interest pause end date (%s) must not be before the interest pause start date (%s).", endDate, startDate),
|
||||||
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) {
|
private LocalDate parseDate(String date, String dateFormat, String locale) {
|
||||||
|
|||||||
+48
@@ -27,6 +27,7 @@ import jakarta.persistence.OneToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
@@ -61,6 +62,18 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "is_active", nullable = false)
|
@Column(name = "is_active", nullable = false)
|
||||||
private Boolean isActive;
|
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)
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "parent_id")
|
@JoinColumn(name = "parent_id")
|
||||||
private LoanTermVariations parent;
|
private LoanTermVariations parent;
|
||||||
@@ -139,6 +152,10 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> {
|
|||||||
return this.dateValue;
|
return this.dateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDateValue(LocalDate dateValue) {
|
||||||
|
this.dateValue = dateValue;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTermApplicableFrom(LocalDate termApplicableFrom) {
|
public void setTermApplicableFrom(LocalDate termApplicableFrom) {
|
||||||
this.termApplicableFrom = termApplicableFrom;
|
this.termApplicableFrom = termApplicableFrom;
|
||||||
}
|
}
|
||||||
@@ -167,4 +184,35 @@ public class LoanTermVariations extends AbstractPersistableCustom<Long> {
|
|||||||
this.isActive = false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
|
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
@@ -60,4 +61,12 @@ public interface LoanTermVariationsRepository
|
|||||||
""")
|
""")
|
||||||
List<LoanTermVariationsData> findLoanTermVariationsByExternalLoanIdAndTermType(@Param("loanExternalId") ExternalId loanExternalId,
|
List<LoanTermVariationsData> findLoanTermVariationsByExternalLoanIdAndTermType(@Param("loanExternalId") ExternalId loanExternalId,
|
||||||
@Param("termType") int termType);
|
@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);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -471,7 +471,7 @@ public class LoanAccountConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(InterestPauseWritePlatformService.class)
|
@ConditionalOnMissingBean(InterestPauseWritePlatformService.class)
|
||||||
public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository,
|
public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository,
|
||||||
LoanRepositoryWrapper loanRepositoryWrapper) {
|
LoanRepositoryWrapper loanRepositoryWrapper, PlatformSecurityContext context) {
|
||||||
return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper);
|
return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,4 +180,5 @@
|
|||||||
<include file="parts/0159_trial_balance_summary_with_asset_owner_year_end_summary.xml" relativeToChangelogFile="true" />
|
<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/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/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>
|
</databaseChangeLog>
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<?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>
|
||||||
+221
-105
@@ -24,38 +24,30 @@ import io.restassured.http.ContentType;
|
|||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
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.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.Utils;
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
import org.apache.fineract.integrationtests.common.accounting.Account;
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
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.LoanProductTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
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.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
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.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ExtendWith({ LoanTestLifecycleExtension.class })
|
@ExtendWith(LoanTestLifecycleExtension.class)
|
||||||
public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LoanInterestPauseApiTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LoanInterestPauseApiTest.class);
|
||||||
@@ -63,52 +55,56 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
private static RequestSpecification REQUEST_SPEC;
|
private static RequestSpecification REQUEST_SPEC;
|
||||||
private static ResponseSpecification RESPONSE_SPEC;
|
private static ResponseSpecification RESPONSE_SPEC;
|
||||||
private static ResponseSpecification RESPONSE_SPEC_403;
|
private static ResponseSpecification RESPONSE_SPEC_403;
|
||||||
|
private static ResponseSpecification RESPONSE_SPEC_204;
|
||||||
private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER;
|
private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER;
|
||||||
|
private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER_204;
|
||||||
private static LoanTransactionHelper LOAN_TRANSACTION_HELPER_403;
|
private static LoanTransactionHelper LOAN_TRANSACTION_HELPER_403;
|
||||||
private static AccountHelper accountHelper;
|
private static AccountHelper ACCOUNT_HELPER;
|
||||||
private static PostClientsResponse client;
|
private static final Integer nonExistLoanId = 99999;
|
||||||
private static Integer loanProductId;
|
|
||||||
private static Long loanId;
|
|
||||||
private static Long nonExistLoanId = 99999L;
|
|
||||||
private static String externalId;
|
private static String externalId;
|
||||||
private static String nonExistExternalId = "7c4fb86f-a778-4d02-b7a8-ec3ec98941fa";
|
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";
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeEach
|
||||||
public static void setupTests() {
|
public void initialize() {
|
||||||
Utils.initializeRESTAssured();
|
Utils.initializeRESTAssured();
|
||||||
REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build();
|
RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
RESPONSE_SPEC_403 = new ResponseSpecBuilder().expectStatusCode(403).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_TRANSACTIONAL_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC);
|
||||||
LOAN_TRANSACTION_HELPER_403 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_403);
|
LOAN_TRANSACTION_HELPER_403 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_403);
|
||||||
accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
|
LOAN_TRANSACTIONAL_HELPER_204 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_204);
|
||||||
ClientHelper clientHelper = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC);
|
ACCOUNT_HELPER = new AccountHelper(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();
|
externalId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
loanProductId = createLoanProduct("500", "15", "4", true, "25", true, LoanScheduleType.PROGRESSIVE,
|
createRequiredEntities();
|
||||||
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(loanProductId, "Loan Product ID should not be null after creation");
|
||||||
Assertions.assertNotNull(loanId, "Loan 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");
|
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
|
@Test
|
||||||
public void testCreateInterestPauseByLoanId_validRequest_shouldSucceed() {
|
public void testCreateInterestPauseByLoanId_validRequest_shouldSucceed() {
|
||||||
PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-02-05",
|
PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-01-12",
|
||||||
"yyyy-MM-dd", "en", loanId);
|
"yyyy-MM-dd", "en", loanId);
|
||||||
|
|
||||||
Assertions.assertNotNull(response);
|
Assertions.assertNotNull(response);
|
||||||
@@ -118,7 +114,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() {
|
public void testCreateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() {
|
||||||
try {
|
try {
|
||||||
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-05", "2024-12-01", "yyyy-MM-dd", "en", loanId);
|
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-05", "2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String responseBody = e.getMessage();
|
String responseBody = e.getMessage();
|
||||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||||
@@ -130,7 +126,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() {
|
public void testCreateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() {
|
||||||
try {
|
try {
|
||||||
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2022-12-01", "2024-12-05", "yyyy-MM-dd", "en", loanId);
|
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2022-12-01", "2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String responseBody = e.getMessage();
|
String responseBody = e.getMessage();
|
||||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||||
@@ -163,18 +159,18 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRetrieveInterestPausesByLoanId_shouldReturnData() {
|
public void testRetrieveInterestPausesByLoanId_shouldReturnData() {
|
||||||
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", loanId);
|
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
|
|
||||||
String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId);
|
String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId);
|
||||||
|
|
||||||
Assertions.assertNotNull(response, "Response should not be null");
|
Assertions.assertNotNull(response, "Response should not be null");
|
||||||
Assertions.assertTrue(response.contains("2023-01-01"));
|
Assertions.assertTrue(response.contains("2023-01-01"));
|
||||||
Assertions.assertTrue(response.contains("2023-02-05"));
|
Assertions.assertTrue(response.contains("2023-01-12"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateInterestPauseByExternalLoanId_validRequest_shouldSucceed() {
|
public void testCreateInterestPauseByExternalLoanId_validRequest_shouldSucceed() {
|
||||||
PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-02-05",
|
PostLoansLoanIdTransactionsResponse response = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-01-12",
|
||||||
"yyyy-MM-dd", "en", externalId);
|
"yyyy-MM-dd", "en", externalId);
|
||||||
|
|
||||||
Assertions.assertNotNull(response);
|
Assertions.assertNotNull(response);
|
||||||
@@ -184,7 +180,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateInterestPauseByExternalLoanId_endDateBeforeStartDate_shouldFail() {
|
public void testCreateInterestPauseByExternalLoanId_endDateBeforeStartDate_shouldFail() {
|
||||||
try {
|
try {
|
||||||
LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2024-12-05", "2024-12-01", "yyyy-MM-dd", "en", externalId);
|
LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2023-01-01", "2022-01-12", "yyyy-MM-dd", "en", externalId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String responseBody = e.getMessage();
|
String responseBody = e.getMessage();
|
||||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||||
@@ -229,87 +225,207 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRetrieveInterestPausesByExternalLoanId_shouldReturnData() {
|
public void testRetrieveInterestPausesByExternalLoanId_shouldReturnData() {
|
||||||
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-02-05", "yyyy-MM-dd", "en", externalId);
|
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||||
|
|
||||||
String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(externalId);
|
String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(externalId);
|
||||||
|
|
||||||
Assertions.assertNotNull(response, "Response should not be null");
|
Assertions.assertNotNull(response, "Response should not be null");
|
||||||
Assertions.assertTrue(response.contains("2023-01-01"));
|
Assertions.assertTrue(response.contains("2023-01-01"));
|
||||||
Assertions.assertTrue(response.contains("2023-02-05"));
|
Assertions.assertTrue(response.contains("2023-01-12"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments,
|
@Test
|
||||||
boolean downPaymentEnabled, String downPaymentPercentage, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType,
|
public void testUpdateInterestPauseByLoanId_validRequest_shouldSucceed() {
|
||||||
LoanScheduleProcessingType loanScheduleProcessingType, final Account... accounts) {
|
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||||
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
|
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
final String loanProductJSON = new LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
|
|
||||||
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
|
Assertions.assertNotNull(createResponse);
|
||||||
.withEnableDownPayment(downPaymentEnabled, downPaymentPercentage, autoPayForDownPayment).withinterestRatePerPeriod("0")
|
Assertions.assertNotNull(createResponse.getResourceId());
|
||||||
.withInterestRateFrequencyTypeAsMonths()
|
|
||||||
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
|
Long variationId = createResponse.getResourceId();
|
||||||
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
|
|
||||||
.addAdvancedPaymentAllocation(defaultAllocation).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
|
PostLoansLoanIdTransactionsResponse updateResponse = LOAN_TRANSACTIONAL_HELPER.updateInterestPauseByLoanId(variationId,
|
||||||
.withInterestTypeAsDecliningBalance().withMultiDisburse().withDisallowExpectedDisbursements(true)
|
"2023-01-01", "2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
.withLoanScheduleType(loanScheduleType).withLoanScheduleProcessingType(loanScheduleProcessingType).withDaysInMonth("30")
|
|
||||||
.withDaysInYear("365").withMoratorium("0", "0").build(null);
|
Assertions.assertNotNull(updateResponse);
|
||||||
return LOAN_TRANSACTIONAL_HELPER.getLoanProductId(loanProductJSON);
|
Assertions.assertNotNull(updateResponse.getResourceId());
|
||||||
|
Assertions.assertEquals(variationId, updateResponse.getResourceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AdvancedPaymentData createDefaultPaymentAllocation() {
|
@Test
|
||||||
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
|
public void testUpdateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() {
|
||||||
advancedPaymentData.setTransactionType("DEFAULT");
|
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||||
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
|
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
|
|
||||||
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
|
Assertions.assertNotNull(createResponse);
|
||||||
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
|
Assertions.assertNotNull(createResponse.getResourceId());
|
||||||
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);
|
|
||||||
|
|
||||||
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
|
Long variationId = createResponse.getResourceId();
|
||||||
return advancedPaymentData;
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
|
@Test
|
||||||
AtomicInteger integer = new AtomicInteger(1);
|
public void testUpdateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() {
|
||||||
return Arrays.stream(paymentAllocationTypes).map(pat -> {
|
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||||
PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder();
|
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
paymentAllocationOrder.setPaymentAllocationRule(pat.name());
|
|
||||||
paymentAllocationOrder.setOrder(integer.getAndIncrement());
|
Assertions.assertNotNull(createResponse);
|
||||||
return paymentAllocationOrder;
|
Assertions.assertNotNull(createResponse.getResourceId());
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal,
|
@Test
|
||||||
final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate,
|
public void testDeleteInterestPauseByLoanId_validRequest_shouldSucceed() {
|
||||||
final String expectedDisbursementDate, final String submittedOnDate, final String externalId) {
|
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||||
return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
|
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
interestRate, expectedDisbursementDate, submittedOnDate, LoanScheduleProcessingType.HORIZONTAL, externalId);
|
|
||||||
|
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 PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal,
|
@Test
|
||||||
final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate,
|
public void testDeleteInterestPauseByLoanId_nonExistentVariation_shouldFail() {
|
||||||
final String expectedDisbursementDate, final String submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType,
|
try {
|
||||||
final String externalId) {
|
LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(99999L, loanId);
|
||||||
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
} catch (Exception e) {
|
||||||
return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
|
String responseBody = e.getMessage();
|
||||||
interestRate, expectedDisbursementDate, submittedOnDate,
|
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||||
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, loanScheduleProcessingType.name(),
|
Assertions.assertTrue(responseBody.contains("error.msg.variation.not.found"),
|
||||||
externalId);
|
"Response should contain the validation error message for variation not found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal,
|
@Test
|
||||||
final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate,
|
public void testDeleteInterestPauseByLoanId_invalidLoanId_shouldFail() {
|
||||||
final String expectedDisbursementDate, final String submittedOnDate, String transactionProcessorCode,
|
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||||
String loanScheduleProcessingType, final String externalId) {
|
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||||
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
|
||||||
return LOAN_TRANSACTIONAL_HELPER.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
|
Assertions.assertNotNull(createResponse);
|
||||||
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
|
Assertions.assertNotNull(createResponse.getResourceId());
|
||||||
.transactionProcessingStrategyCode(transactionProcessorCode).locale("en").submittedOnDate(submittedOnDate)
|
|
||||||
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
|
Long variationId = createResponse.getResourceId();
|
||||||
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
|
|
||||||
.numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal)
|
try {
|
||||||
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType).externalId(externalId)
|
LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(variationId, nonExistLoanId);
|
||||||
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* approve the loan application
|
||||||
|
**/
|
||||||
|
private void approveLoanApplication() {
|
||||||
|
|
||||||
|
if (loanId != null) {
|
||||||
|
LOAN_TRANSACTIONAL_HELPER.approveLoan(dateString, loanId);
|
||||||
|
LOG.info("Successfully approved loan (ID: {} )", loanId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -66,6 +66,7 @@ public class LoanProductTestBuilder {
|
|||||||
public static final String RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS = "1";
|
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_NUMBER_OF_INSTALLMENTS = "2";
|
||||||
public static final String RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN = "3";
|
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_NONE = "0";
|
||||||
public static final String RECALCULATION_COMPOUNDING_METHOD_INTEREST = "1";
|
public static final String RECALCULATION_COMPOUNDING_METHOD_INTEREST = "1";
|
||||||
|
|||||||
+34
-4
@@ -609,7 +609,7 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdTransactionsResponse createInterestPauseByLoanId(final String startDate, final String endDate,
|
public PostLoansLoanIdTransactionsResponse createInterestPauseByLoanId(final String startDate, final String endDate,
|
||||||
final String dateFormat, final String locale, final Long loanID) {
|
final String dateFormat, final String locale, final Integer loanID) {
|
||||||
log.info("Creating interest pause for Loan {} from {} to {} with dateFormat {} and locale {}", loanID, startDate, endDate,
|
log.info("Creating interest pause for Loan {} from {} to {} with dateFormat {} and locale {}", loanID, startDate, endDate,
|
||||||
dateFormat, locale);
|
dateFormat, locale);
|
||||||
String body = getInterestPauseBodyAsJSON(startDate, endDate, dateFormat, locale);
|
String body = getInterestPauseBodyAsJSON(startDate, endDate, dateFormat, locale);
|
||||||
@@ -624,7 +624,20 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, externalId), body);
|
return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, externalId), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String retrieveInterestPauseByLoanId(final Long loanID) {
|
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) {
|
||||||
log.info("Retrieving interest pauses for Loan ID {}", loanID);
|
log.info("Retrieving interest pauses for Loan ID {}", loanID);
|
||||||
String url = retrieveInterestPause(loanID);
|
String url = retrieveInterestPause(loanID);
|
||||||
return Utils.performServerGet(requestSpec, responseSpec, url);
|
return Utils.performServerGet(requestSpec, responseSpec, url);
|
||||||
@@ -1450,7 +1463,7 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return "/fineract-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
|
return "/fineract-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createInterestPause(final String command, final Long loanID) {
|
private String createInterestPause(final String command, final Integer loanID) {
|
||||||
return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
|
return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1459,7 +1472,7 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
+ Utils.TENANT_IDENTIFIER;
|
+ Utils.TENANT_IDENTIFIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String retrieveInterestPause(final Long loanID) {
|
private String retrieveInterestPause(final Integer loanID) {
|
||||||
return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?" + Utils.TENANT_IDENTIFIER;
|
return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses?" + Utils.TENANT_IDENTIFIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1467,6 +1480,14 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return "/fineract-provider/api/v1/loans/external-id/" + externalId + "/interest-pauses?" + Utils.TENANT_IDENTIFIER;
|
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) {
|
private String createInteroperationLoanTransactionURL(final String accountNo) {
|
||||||
return "/fineract-provider/api/v1/interoperation/transactions/" + accountNo + "/loanrepayment";
|
return "/fineract-provider/api/v1/interoperation/transactions/" + accountNo + "/loanrepayment";
|
||||||
}
|
}
|
||||||
@@ -1513,6 +1534,15 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class);
|
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,
|
private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent,
|
||||||
ResponseSpecification responseValidationError) {
|
ResponseSpecification responseValidationError) {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user