FINERACT-1971: update and delete interest pause by external id
This commit is contained in:
committed by
Adam Saghy
parent
001797b97a
commit
9258be777c
+18
@@ -3759,6 +3759,15 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deleteInterestPause(final String loanExternalId, final long variationId) {
|
||||
this.actionName = "DELETE";
|
||||
this.entityName = "INTEREST_PAUSE";
|
||||
this.loanExternalId = new ExternalId(loanExternalId);
|
||||
this.entityId = variationId;
|
||||
this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses/" + variationId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateInterestPause(final long loanId, final long variationId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "INTEREST_PAUSE";
|
||||
@@ -3767,4 +3776,13 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/v1/loans/" + loanId + "/interest-pauses/" + variationId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateInterestPause(final String loanExternalId, final long variationId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "INTEREST_PAUSE";
|
||||
this.loanExternalId = new ExternalId(loanExternalId);
|
||||
this.entityId = variationId;
|
||||
this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses/" + variationId;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+50
-5
@@ -20,6 +20,9 @@ package org.apache.fineract.portfolio.interestpauses.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
@@ -64,7 +67,8 @@ public class LoanInterestPauseApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a new interest pause period for a loan", description = "Allows users to define a period during which no interest will be accrued for a specific loan.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Command successfully processed", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class))) })
|
||||
public CommandProcessingResult createInterestPause(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
|
||||
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
||||
|
||||
@@ -80,7 +84,8 @@ public class LoanInterestPauseApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a new interest pause for a loan using external ID", description = "Allows users to define a period during which no interest will be accrued for a specific loan using the external loan ID.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Command successfully processed", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class))) })
|
||||
public CommandProcessingResult createInterestPauseByExternalId(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
|
||||
@RequestBody(required = true) final InterestPauseRequestDto request) {
|
||||
@@ -97,7 +102,8 @@ public class LoanInterestPauseApiResource {
|
||||
@Path("/{loanId}/interest-pauses")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve all interest pause periods for a loan", description = "Fetches a list of all active interest pause periods for a specific loan.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "List of interest pause periods", content = @Content(array = @ArraySchema(schema = @Schema(implementation = InterestPauseResponseDto.class)))) })
|
||||
public List<InterestPauseResponseDto> retrieveInterestPauses(
|
||||
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
|
||||
|
||||
@@ -110,7 +116,8 @@ public class LoanInterestPauseApiResource {
|
||||
@Path("/external-id/{loanExternalId}/interest-pauses")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve all interest pause periods for a loan using external ID", description = "Fetches a list of all active interest pause periods for a specific loan using the external loan ID.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "List of interest pause periods", content = @Content(array = @ArraySchema(schema = @Schema(implementation = InterestPauseResponseDto.class)))) })
|
||||
public List<InterestPauseResponseDto> retrieveInterestPausesByExternalId(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId) {
|
||||
|
||||
@@ -135,12 +142,30 @@ public class LoanInterestPauseApiResource {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/external-id/{loanExternalId}/interest-pauses/{variationId}")
|
||||
@Operation(summary = "Delete an interest pause period by external id", description = "Deletes a specific interest pause period by its variation ID.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "204", description = "No Content") })
|
||||
public Response deleteInterestPauseByExternalId(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
|
||||
@PathParam("variationId") @Parameter(description = "variationId") final Long variationId) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(MODIFY_RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteInterestPause(loanExternalId, 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") })
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Command successfully processed", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class))) })
|
||||
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) {
|
||||
@@ -152,4 +177,24 @@ public class LoanInterestPauseApiResource {
|
||||
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/external-id/{loanExternalId}/interest-pauses/{variationId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update an interest pause period by external id", description = "Updates a specific interest pause period by its variation ID.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Command successfully processed", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class))) })
|
||||
public CommandProcessingResult updateInterestPauseByExternalId(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
|
||||
@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(loanExternalId, variationId)
|
||||
.withJson(request.toJson()).build();
|
||||
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -22,6 +22,8 @@ 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.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -34,8 +36,16 @@ public class DeleteInterestPauseCommandHandler implements NewCommandSourceHandle
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
final Long loanId = command.getLoanId();
|
||||
final ExternalId loanExternalId = command.getLoanExternalId();
|
||||
final Long termVariationId = command.getResourceId();
|
||||
|
||||
return interestPauseService.deleteInterestPause(loanId, termVariationId);
|
||||
if (loanId != null) {
|
||||
return interestPauseService.deleteInterestPause(loanId, termVariationId);
|
||||
} else if (loanExternalId != null) {
|
||||
return interestPauseService.deleteInterestPause(loanExternalId, termVariationId);
|
||||
} else {
|
||||
throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id",
|
||||
"Either loanId or loanExternalId must be provided.", "loanId");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -22,6 +22,8 @@ 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.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -34,12 +36,20 @@ public class UpdateInterestPauseCommandHandler implements NewCommandSourceHandle
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
final Long loanId = command.getLoanId();
|
||||
final ExternalId loanExternalId = command.getLoanExternalId();
|
||||
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);
|
||||
if (loanId != null) {
|
||||
return interestPauseService.updateInterestPause(loanId, termVariationId, startDate, endDate, dateFormat, locale);
|
||||
} else if (loanExternalId != null) {
|
||||
return interestPauseService.updateInterestPause(loanExternalId, termVariationId, startDate, endDate, dateFormat, locale);
|
||||
} else {
|
||||
throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id",
|
||||
"Either loanId or loanExternalId must be provided.", "loanId");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -69,6 +69,17 @@ public interface InterestPauseWritePlatformService {
|
||||
*/
|
||||
CommandProcessingResult deleteInterestPause(Long loanId, Long variationId);
|
||||
|
||||
/**
|
||||
* Delete an existing interest pause period for a loan.
|
||||
*
|
||||
* @param loanExternalId
|
||||
* the external 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(ExternalId loanExternalId, Long variationId);
|
||||
|
||||
/**
|
||||
* Update an existing interest pause period for a loan identified by its internal ID.
|
||||
*
|
||||
@@ -88,4 +99,24 @@ public interface InterestPauseWritePlatformService {
|
||||
*/
|
||||
CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString,
|
||||
String dateFormat, String locale);
|
||||
|
||||
/**
|
||||
* Update an existing interest pause period for a loan identified by its internal ID.
|
||||
*
|
||||
* @param loanExternalId
|
||||
* the external 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(ExternalId loanExternalId, Long variationId, String startDateString, String endDateString,
|
||||
String dateFormat, String locale);
|
||||
}
|
||||
|
||||
+58
-25
@@ -31,7 +31,6 @@ 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;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
@@ -40,7 +39,6 @@ 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.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.LoanTermVariations;
|
||||
@@ -53,7 +51,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,
|
||||
@@ -61,8 +58,8 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
final LocalDate startDate = parseDate(startDateString, dateFormat, locale);
|
||||
final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
|
||||
|
||||
return processInterestPause(() -> loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId), startDate, endDate,
|
||||
dateFormat, locale);
|
||||
return processInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId), startDate, endDate, dateFormat,
|
||||
locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,14 +68,38 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
final LocalDate startDate = parseDate(startDateString, dateFormat, locale);
|
||||
final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
|
||||
|
||||
return processInterestPause(() -> loanRepositoryWrapper.findOneWithNotFoundDetection(loanId), startDate, endDate, dateFormat,
|
||||
locale);
|
||||
return processInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId), startDate, endDate, dateFormat, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult deleteInterestPause(ExternalId loanExternalId, Long variationId) {
|
||||
return processDeleteInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId), variationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult deleteInterestPause(Long loanId, Long variationId) {
|
||||
return processDeleteInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId), variationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult updateInterestPause(ExternalId loanExternalId, Long variationId, String startDateString,
|
||||
String endDateString, String dateFormat, String locale) {
|
||||
return processUpdateInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId), variationId, startDateString,
|
||||
endDateString, dateFormat, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString,
|
||||
String dateFormat, String locale) {
|
||||
return processUpdateInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId), variationId, startDateString,
|
||||
endDateString, dateFormat, locale);
|
||||
}
|
||||
|
||||
private CommandProcessingResult processDeleteInterestPause(Loan loan, Long variationId) {
|
||||
validateActiveLoan(loan);
|
||||
|
||||
LoanTermVariations variation = loanTermVariationsRepository
|
||||
.findByIdAndLoanIdAndTermType(variationId, loanId, INTEREST_PAUSE.getValue())
|
||||
.findByIdAndLoanIdAndTermType(variationId, loan.getId(), INTEREST_PAUSE.getValue())
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException("error.msg.variation.not.found",
|
||||
"Variation not found for the given loan ID"));
|
||||
|
||||
@@ -87,18 +108,16 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
return new CommandProcessingResultBuilder().withEntityId(variationId).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString,
|
||||
private CommandProcessingResult processUpdateInterestPause(Loan loan, 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);
|
||||
|
||||
validateActiveLoan(loan);
|
||||
validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale);
|
||||
|
||||
LoanTermVariations variation = loanTermVariationsRepository
|
||||
.findByIdAndLoanIdAndTermType(variationId, loanId, INTEREST_PAUSE.getValue())
|
||||
.findByIdAndLoanIdAndTermType(variationId, loan.getId(), INTEREST_PAUSE.getValue())
|
||||
.orElseThrow(() -> new GeneralPlatformDomainRuleException("error.msg.variation.not.found",
|
||||
"Variation not found for the given loan ID"));
|
||||
|
||||
@@ -113,10 +132,9 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
.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();
|
||||
|
||||
private CommandProcessingResult processInterestPause(Loan loan, LocalDate startDate, LocalDate endDate, String dateFormat,
|
||||
String locale) {
|
||||
validateActiveLoan(loan);
|
||||
validateInterestPauseDates(loan, startDate, endDate, dateFormat, locale);
|
||||
|
||||
LoanTermVariations variation = new LoanTermVariations(INTEREST_PAUSE.getValue(), startDate, null, endDate, false, loan);
|
||||
@@ -154,11 +172,6 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
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.");
|
||||
@@ -168,6 +181,22 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
throw new GeneralPlatformDomainRuleException("loan.must.have.recalculate.interest.enabled",
|
||||
"Interest pause is only supported for loans with recalculate interest enabled.");
|
||||
}
|
||||
|
||||
for (LoanTermVariations existingVariation : loan.getLoanTermVariations()) {
|
||||
if (Objects.equals(existingVariation.getTermType().getValue(), INTEREST_PAUSE.getValue())) {
|
||||
if (!(endDate.isBefore(existingVariation.getTermApplicableFrom()) || startDate.isAfter(existingVariation.getDateValue()))) {
|
||||
throw new GeneralPlatformDomainRuleException("interest.pause.overlapping",
|
||||
"Overlapping interest pauses are not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateActiveLoan(Loan loan) {
|
||||
if (!Objects.equals(loan.getLoanStatus(), ACTIVE.getValue())) {
|
||||
throw new GeneralPlatformDomainRuleException("loan.must.be.active",
|
||||
"Operations on interest pauses are restricted to active loans.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateVariations(Loan loan, LoanTermVariations variation) {
|
||||
@@ -178,9 +207,13 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
|
||||
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.");
|
||||
if (!existingVariation.equals(variation)
|
||||
&& Objects.equals(existingVariation.getTermType().getValue(), INTEREST_PAUSE.getValue())) {
|
||||
if (!(variation.getDateValue().isBefore(existingVariation.getTermApplicableFrom())
|
||||
|| variation.getTermApplicableFrom().isAfter(existingVariation.getDateValue()))) {
|
||||
throw new GeneralPlatformDomainRuleException("interest.pause.overlapping",
|
||||
"Overlapping interest pauses are not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+239
-3
@@ -55,10 +55,12 @@ 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_404;
|
||||
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 LoanTransactionHelper LOAN_TRANSACTION_HELPER_404;
|
||||
private static AccountHelper ACCOUNT_HELPER;
|
||||
private static final Integer nonExistLoanId = 99999;
|
||||
private static String externalId;
|
||||
@@ -78,9 +80,11 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||
REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
RESPONSE_SPEC_403 = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||
RESPONSE_SPEC_404 = new ResponseSpecBuilder().expectStatusCode(404).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_TRANSACTION_HELPER_404 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_404);
|
||||
LOAN_TRANSACTIONAL_HELPER_204 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_204);
|
||||
ACCOUNT_HELPER = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
|
||||
|
||||
@@ -235,7 +239,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByLoanId_validRequest_shouldSucceed() {
|
||||
public void testUpdateInterestPauseByLoanId_overlapping_shouldFail() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", loanId);
|
||||
|
||||
@@ -244,8 +248,68 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByLoanId(variationId, "2023-01-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.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByLoanId_overlapping_shouldFail2() {
|
||||
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-01-02", "2023-01-13", "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.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByLoanId_overlapping_shouldFail3() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-02",
|
||||
"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-01-01", "2023-01-11", "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.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByLoanId_validRequest_shouldSucceed() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01",
|
||||
"2023-01-02", "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);
|
||||
"2023-01-03", "2023-01-04", "yyyy-MM-dd", "en", loanId);
|
||||
|
||||
Assertions.assertNotNull(updateResponse);
|
||||
Assertions.assertNotNull(updateResponse.getResourceId());
|
||||
@@ -335,7 +399,159 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(variationId, nonExistLoanId);
|
||||
LOAN_TRANSACTION_HELPER_404.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.loan.id.invalid"),
|
||||
"Response should contain the validation error message for variation not found");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByExternalId_validRequest_shouldSucceed() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-02", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
PostLoansLoanIdTransactionsResponse updateResponse = LOAN_TRANSACTIONAL_HELPER.updateInterestPauseByExternalId(variationId,
|
||||
"2023-01-03", "2023-01-04", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(updateResponse);
|
||||
Assertions.assertNotNull(updateResponse.getResourceId());
|
||||
Assertions.assertEquals(variationId, updateResponse.getResourceId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByExternalId_overlapping_shouldFail() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByExternalId(variationId, "2023-01-01", "2023-01-12", "yyyy-MM-dd", "en",
|
||||
externalId);
|
||||
} catch (Exception e) {
|
||||
String responseBody = e.getMessage();
|
||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||
Assertions.assertTrue(responseBody.contains("interest.pause.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByExternalId_overlapping_shouldFail2() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByExternalId(variationId, "2023-01-02", "2023-01-13", "yyyy-MM-dd", "en",
|
||||
externalId);
|
||||
} catch (Exception e) {
|
||||
String responseBody = e.getMessage();
|
||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||
Assertions.assertTrue(responseBody.contains("interest.pause.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByExternalId_overlapping_shouldFail3() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-02",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByExternalId(variationId, "2023-01-01", "2023-01-11", "yyyy-MM-dd", "en",
|
||||
externalId);
|
||||
} catch (Exception e) {
|
||||
String responseBody = e.getMessage();
|
||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||
Assertions.assertTrue(responseBody.contains("interest.pause.overlapping"),
|
||||
"Response should contain the validation error message for end date after loan maturity date");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterestPauseByExternalId_endDateBeforeStartDate_shouldFail() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByExternalId(variationId, "2023-03-01", "2023-01-12", "yyyy-MM-dd", "en",
|
||||
externalId);
|
||||
} 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 testUpdateInterestPauseByExternalId_startDateBeforeLoanStart_shouldFail() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.updateInterestPauseByExternalId(variationId, "2022-12-01", "2023-01-12", "yyyy-MM-dd", "en",
|
||||
externalId);
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInterestPauseByExternalId_validRequest_shouldSucceed() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", 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.deleteInterestPauseByExternalId(variationId, externalId);
|
||||
} catch (Exception e) {
|
||||
Assertions.fail("Delete operation failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(externalId);
|
||||
Assertions.assertFalse(response.contains(String.valueOf(variationId)), "Response should not contain the deleted variation ID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInterestPauseByExternalId_nonExistentVariation_shouldFail() {
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByExternalId(99999L, externalId);
|
||||
} catch (Exception e) {
|
||||
String responseBody = e.getMessage();
|
||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||
@@ -344,6 +560,26 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInterestPauseByExternalId_invalidExternalId_shouldFail() {
|
||||
PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01",
|
||||
"2023-01-12", "yyyy-MM-dd", "en", externalId);
|
||||
|
||||
Assertions.assertNotNull(createResponse);
|
||||
Assertions.assertNotNull(createResponse.getResourceId());
|
||||
|
||||
Long variationId = createResponse.getResourceId();
|
||||
|
||||
try {
|
||||
LOAN_TRANSACTION_HELPER_404.deleteInterestPauseByExternalId(variationId, nonExistExternalId);
|
||||
} catch (Exception e) {
|
||||
String responseBody = e.getMessage();
|
||||
Assertions.assertNotNull(responseBody, "Response body should not be null");
|
||||
Assertions.assertTrue(responseBody.contains("error.msg.loan.external.id.invalid"),
|
||||
"Response should contain the validation error message for variation not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new client
|
||||
**/
|
||||
|
||||
+23
@@ -632,11 +632,24 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return putLoanTransaction(updateInterestPause(termVariationId, loanID), body);
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse updateInterestPauseByExternalId(final Long termVariationId, final String startDate,
|
||||
final String endDate, final String dateFormat, final String locale, final String externalID) {
|
||||
log.info("Updating interest pause for Loan {} with Term Variation ID {}: startDate={} endDate={} dateFormat={} locale={}",
|
||||
externalID, termVariationId, startDate, endDate, dateFormat, locale);
|
||||
String body = getInterestPauseBodyAsJSON(startDate, endDate, dateFormat, locale);
|
||||
return putLoanTransaction(updateInterestPause(termVariationId, externalID), 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 void deleteInterestPauseByExternalId(final Long termVariationId, final String externalID) {
|
||||
log.info("Deleting interest pause for Loan ID {} with Term Variation ID {}", externalID, termVariationId);
|
||||
deleteLoanTransaction(deleteInterestPause(termVariationId, externalID));
|
||||
}
|
||||
|
||||
public String retrieveInterestPauseByLoanId(final Integer loanID) {
|
||||
log.info("Retrieving interest pauses for Loan ID {}", loanID);
|
||||
String url = retrieveInterestPause(loanID);
|
||||
@@ -1484,10 +1497,20 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses/" + termVariationId + "?" + Utils.TENANT_IDENTIFIER;
|
||||
}
|
||||
|
||||
private String updateInterestPause(final Long termVariationId, final String externalID) {
|
||||
return "/fineract-provider/api/v1/loans/external-id/" + externalID + "/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 deleteInterestPause(final Long termVariationId, final String externalID) {
|
||||
return "/fineract-provider/api/v1/loans/external-id/" + externalID + "/interest-pauses/" + termVariationId + "?"
|
||||
+ Utils.TENANT_IDENTIFIER;
|
||||
}
|
||||
|
||||
private String createInteroperationLoanTransactionURL(final String accountNo) {
|
||||
return "/fineract-provider/api/v1/interoperation/transactions/" + accountNo + "/loanrepayment";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user