diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index ad28f696c2b..39cb4d035d3 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -136,6 +136,9 @@ public class CommandSource extends AbstractPersistableCustom { @Column(name = "loan_external_id", length = 100) private ExternalId loanExternalId; + @Column(name = "term_variation_id", length = 100) + private Long termVariationId; + public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker, String idempotencyKey, Integer status) { @@ -160,7 +163,7 @@ public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final Js .creditBureauId(command.getCreditBureauId()) // .organisationCreditBureauId(command.getOrganisationCreditBureauId()) // .loanExternalId(command.getLoanExternalId()) // - .build(); // + .termVariationId(command.getTermVariationId()).build(); // } public String getPermissionCode() { @@ -200,5 +203,6 @@ public void updateForAudit(final CommandProcessingResult result) { this.subResourceId = result.getSubResourceId(); this.subResourceExternalId = result.getSubResourceExternalId(); this.loanExternalId = result.getLoanExternalId(); + this.termVariationId = result.getTermVariationId(); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java index 5b51f40de3a..72916072077 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java @@ -45,6 +45,7 @@ public class CommandWrapper { private final Long organisationCreditBureauId; private final String jobName; private final ExternalId loanExternalId; + private final Long termVariationId; private final String idempotencyKey; @@ -63,11 +64,11 @@ public static CommandWrapper fromExistingCommand(final Long commandId, final Str public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName, final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, - final Long creditBureauId, final Long organisationCreditBureauId, final String idempotencyKey, - final ExternalId loanExternalId) { + final Long creditBureauId, final Long organisationCreditBureauId, final String idempotencyKey, final ExternalId loanExternalId, + final Long termVariationId) { return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId, officeId, groupId, clientId, loanId, savingsId, transactionId, creditBureauId, organisationCreditBureauId, idempotencyKey, - loanExternalId); + loanExternalId, termVariationId); } private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, @@ -92,12 +93,14 @@ private CommandWrapper(final Long commandId, final String actionName, final Stri this.jobName = null; this.idempotencyKey = null; this.loanExternalId = null; + this.termVariationId = null; } public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href, final String json, final String transactionId, final Long productId, final Long templateId, final Long creditBureauId, - final Long organisationCreditBureauId, final String jobName, final String idempotencyKey, final ExternalId loanExternalId) { + final Long organisationCreditBureauId, final String jobName, final String idempotencyKey, final ExternalId loanExternalId, + final Long termVariationId) { this.commandId = null; this.officeId = officeId; @@ -120,12 +123,14 @@ public CommandWrapper(final Long officeId, final Long groupId, final Long client this.jobName = jobName; this.idempotencyKey = idempotencyKey; this.loanExternalId = loanExternalId; + this.termVariationId = termVariationId; } private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final Long creditBureauId, - final Long organisationCreditBureauId, final String idempotencyKey, final ExternalId loanExternalId) { + final Long organisationCreditBureauId, final String idempotencyKey, final ExternalId loanExternalId, + final Long termVariationId) { this.commandId = commandId; this.officeId = officeId; @@ -147,6 +152,7 @@ private CommandWrapper(final Long commandId, final String actionName, final Stri this.jobName = null; this.idempotencyKey = idempotencyKey; this.loanExternalId = loanExternalId; + this.termVariationId = termVariationId; } public boolean isCreate() { @@ -251,8 +257,16 @@ public boolean isInterestPauseResource() { return this.entityName.equalsIgnoreCase("INTEREST_PAUSE"); } - public boolean isInterestPauseExternalIdResource() { - return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && this.href.contains("/external-id/"); + public boolean isInterestPauseCreateResource() { + return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "CREATE".equalsIgnoreCase(this.actionName); + } + + public boolean isInterestPauseUpdateResource() { + return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "UPDATE".equalsIgnoreCase(this.actionName); + } + + public boolean isInterestPauseDeleteResource() { + return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && "DELETE".equalsIgnoreCase(this.actionName); } public Long commandId() { diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 497862cbffe..4a8ae24d6dd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -49,18 +49,21 @@ public class CommandWrapperBuilder { private String jobName; private String idempotencyKey; private ExternalId loanExternalId; + private Long termVariationId; @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "TODO: fix this!") public CommandWrapper build() { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, - this.creditBureauId, this.organisationCreditBureauId, this.jobName, this.idempotencyKey, this.loanExternalId); + this.creditBureauId, this.organisationCreditBureauId, this.jobName, this.idempotencyKey, this.loanExternalId, + this.termVariationId); } public CommandWrapper build(String idempotencyKey) { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, - this.creditBureauId, this.organisationCreditBureauId, this.jobName, idempotencyKey, this.loanExternalId); + this.creditBureauId, this.organisationCreditBureauId, this.jobName, idempotencyKey, this.loanExternalId, + this.termVariationId); } public CommandWrapperBuilder updateCreditBureau() { @@ -3732,4 +3735,22 @@ public CommandWrapperBuilder createInterestPauseByExternalId(final String loanEx this.href = "/v1/loans/external-id/" + loanExternalId + "/interest-pauses"; return this; } + + public CommandWrapperBuilder deleteInterestPause(final long loanId, final long variationId) { + this.actionName = "DELETE"; + this.entityName = "INTEREST_PAUSE"; + this.loanId = loanId; + this.termVariationId = 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.termVariationId = variationId; + this.href = "/v1/loans/" + loanId + "/interest-pauses/" + variationId; + return this; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index dd2d8c81bff..07ebcabf5da 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -73,7 +73,7 @@ public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), wrapper.getCreditBureauId(), - wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), wrapper.getLoanExternalId()); + wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), wrapper.getLoanExternalId(), wrapper.getTermVariationId()); return this.processAndLogCommandService.executeCommand(wrapper, command, isApprovedByChecker); } @@ -89,15 +89,15 @@ public CommandProcessingResult approveEntry(final Long makerCheckerId) { commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(), commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey(), - commandSourceInput.getLoanExternalId()); + commandSourceInput.getLoanExternalId(), commandSourceInput.getTermVariationId()); final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.getCommandAsJson()); final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.getCommandAsJson(), parsedCommand, this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.getResourceId(), commandSourceInput.getSubResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(), - commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName(), - commandSourceInput.getLoanExternalId()); + commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName(), commandSourceInput.getLoanExternalId(), + commandSourceInput.getTermVariationId()); return this.processAndLogCommandService.executeCommand(wrapper, command, true); } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java index 9b525d5cd9c..f021cb23cae 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java @@ -249,8 +249,16 @@ private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) } else { throw new UnsupportedCommandException(wrapper.commandName()); } - } else if (wrapper.isInterestPauseResource() || wrapper.isInterestPauseExternalIdResource()) { - handler = applicationContext.getBean("interestPauseCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isInterestPauseResource()) { + if (wrapper.isInterestPauseCreateResource()) { + handler = applicationContext.getBean("createInterestPauseCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isInterestPauseUpdateResource()) { + handler = applicationContext.getBean("updateInterestPauseCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isInterestPauseDeleteResource()) { + handler = applicationContext.getBean("deleteInterestPauseCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } } else { handler = commandHandlerProvider.getHandler(wrapper.entityName(), wrapper.actionName()); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java index 72ad7c27d08..92cbf7cba88 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java @@ -72,33 +72,35 @@ public final class JsonCommand { private final Long organisationCreditBureauId; private final String jobName; private final ExternalId loanExternalId; + private final Long termVariationId; public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, final Long productId, - final Long creditBureauId, final Long organisationCreditBureauId, final String jobName, final ExternalId loanExternalId) { + final Long creditBureauId, final Long organisationCreditBureauId, final String jobName, final ExternalId loanExternalId, + final Long termVariationId) { return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId, clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId, jobName, - loanExternalId); + loanExternalId, termVariationId); } public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final String url, final Long productId, final Long creditBureauId, final Long organisationCreditBureauId, final String jobName, - final ExternalId loanExternalId) { + final ExternalId loanExternalId, final Long termVariationId) { return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null, - null, null, null, url, productId, creditBureauId, organisationCreditBureauId, jobName, loanExternalId); + null, null, null, url, productId, creditBureauId, organisationCreditBureauId, jobName, loanExternalId, termVariationId); } public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, final Long productId, Long creditBureauId, final Long organisationCreditBureauId, final String jobName, - final ExternalId loanExternalId) { + final ExternalId loanExternalId, final Long termVariationId) { return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId, clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId, jobName, - loanExternalId); + loanExternalId, termVariationId); } @@ -107,7 +109,7 @@ public static JsonCommand fromExistingCommand(JsonCommand command, final JsonEle return new JsonCommand(command.commandId, jsonCommand, parsedCommand, command.fromApiJsonHelper, command.entityName, command.resourceId, command.subresourceId, command.groupId, command.clientId, command.loanId, command.savingsId, command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId, - command.jobName, command.loanExternalId); + command.jobName, command.loanExternalId, command.termVariationId); } public static JsonCommand fromExistingCommand(JsonCommand command, final JsonElement parsedCommand, final Long clientId) { @@ -115,14 +117,14 @@ public static JsonCommand fromExistingCommand(JsonCommand command, final JsonEle return new JsonCommand(command.commandId, jsonCommand, parsedCommand, command.fromApiJsonHelper, command.entityName, command.resourceId, command.subresourceId, command.groupId, clientId, command.loanId, command.savingsId, command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId, - command.jobName, command.loanExternalId); + command.jobName, command.loanExternalId, command.termVariationId); } public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, final Long productId, final Long creditBureauId, final Long organisationCreditBureauId, final String jobName, - final ExternalId loanExternalId) { + final ExternalId loanExternalId, final Long termVariationId) { this.commandId = commandId; this.jsonCommand = jsonCommand; @@ -142,6 +144,7 @@ public JsonCommand(final Long commandId, final String jsonCommand, final JsonEle this.organisationCreditBureauId = organisationCreditBureauId; this.jobName = jobName; this.loanExternalId = loanExternalId; + this.termVariationId = termVariationId; } public static JsonCommand fromJsonElement(final Long resourceId, final JsonElement parsedCommand) { @@ -172,6 +175,7 @@ public JsonCommand(final Long resourceId, final JsonElement parsedCommand) { this.organisationCreditBureauId = null; this.jobName = null; this.loanExternalId = null; + this.termVariationId = null; } public JsonCommand(final Long resourceId, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper) { @@ -193,11 +197,12 @@ public JsonCommand(final Long resourceId, final JsonElement parsedCommand, final this.organisationCreditBureauId = null; this.jobName = null; this.loanExternalId = null; + this.termVariationId = null; } public static JsonCommand from(final String jsonCommand) { return new JsonCommand(null, jsonCommand, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null); + null, null); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java index 5b07f76bf2a..b55e0df8d70 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java @@ -50,12 +50,14 @@ public class CommandProcessingResult implements Serializable { private final ExternalId resourceExternalId; private final ExternalId subResourceExternalId; private final ExternalId loanExternalId; + private final Long termVariationId; private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, final Map changes, final Long productId, final Long gsimId, final Long glimId, final Map creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId, - final ExternalId resourceExternalId, final ExternalId subResourceExternalId, final ExternalId loanExternalId) { + final ExternalId resourceExternalId, final ExternalId subResourceExternalId, final ExternalId loanExternalId, + final Long termVariationId) { this.commandId = commandId; this.officeId = officeId; this.groupId = groupId; @@ -75,12 +77,13 @@ private CommandProcessingResult(final Long commandId, final Long officeId, final this.resourceExternalId = resourceExternalId; this.subResourceExternalId = subResourceExternalId; this.loanExternalId = loanExternalId; + this.termVariationId = termVariationId; } protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changes, Long clientId) { this(commandId, officeId, null, clientId, null, null, resourceId == null ? null : resourceId.toString(), resourceId, null, changes, - null, null, null, null, null, null, ExternalId.empty(), ExternalId.empty(), ExternalId.empty()); + null, null, null, null, null, null, ExternalId.empty(), ExternalId.empty(), ExternalId.empty(), null); } protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changes) { @@ -96,7 +99,8 @@ public static CommandProcessingResult fromCommandProcessingResult(CommandProcess commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, resourceId, commandResult.transactionId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId, commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId, - commandResult.resourceExternalId, commandResult.subResourceExternalId, commandResult.loanExternalId); + commandResult.resourceExternalId, commandResult.subResourceExternalId, commandResult.loanExternalId, + commandResult.termVariationId); } public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) { @@ -107,10 +111,11 @@ public static CommandProcessingResult fromDetails(final Long commandId, final Lo final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final Long gsimId, final Long glimId, final Map creditBureauReportData, final String transactionId, final Map changes, final Long productId, final Boolean rollbackTransaction, final Long subResourceId, - final ExternalId resourceExternalId, final ExternalId subResourceExternalId, final ExternalId loanExternalId) { + final ExternalId resourceExternalId, final ExternalId subResourceExternalId, final ExternalId loanExternalId, + final Long termVariationId) { return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId, transactionId, changes, productId, gsimId, glimId, creditBureauReportData, rollbackTransaction, subResourceId, - resourceExternalId, subResourceExternalId, loanExternalId); + resourceExternalId, subResourceExternalId, loanExternalId, termVariationId); } public static CommandProcessingResult commandOnlyResult(final Long commandId) { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java index e630ba7076a..797b4bb3200 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java @@ -48,11 +48,13 @@ public class CommandProcessingResultBuilder { private ExternalId loanExternalId = ExternalId.empty(); + private Long termVariationId; + public CommandProcessingResult build() { return CommandProcessingResult.fromDetails(this.commandId, this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.resourceIdentifier, this.entityId, this.gsimId, this.glimId, this.creditBureauReportData, this.transactionId, this.changes, this.productId, this.rollbackTransaction, this.subEntityId, this.entityExternalId, this.subEntityExternalId, - this.loanExternalId); + this.loanExternalId, this.termVariationId); } public CommandProcessingResultBuilder withCommandId(final Long withCommandId) { @@ -149,4 +151,9 @@ public CommandProcessingResultBuilder withLoanExternalId(final ExternalId loanEx this.loanExternalId = loanExternalId; return this; } + + public CommandProcessingResultBuilder withTermVariationId(final Long termVariationId) { + this.termVariationId = termVariationId; + return this; + } } diff --git a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java index d246f833b6d..fb09728a3d8 100644 --- a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java +++ b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/exception/IdempotencyCommandProcessFailedExceptionTest.java @@ -58,7 +58,7 @@ public void tearDown() { public void testInconsistentStatus() { IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper(); CommandWrapper command = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null); + null, null, null, null, null, null); CommandSource source = CommandSource.fullEntryFrom(command, JsonCommand.from("{}"), null, "dummy-key", null); IdempotentCommandProcessFailedException exception = new IdempotentCommandProcessFailedException(command, null, source); Response result = mapper.toResponse(exception); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java index 1b08ad6d8d9..45f0fc28f82 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java @@ -25,12 +25,15 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.domain.CommandWrapper; @@ -114,4 +117,38 @@ public List retrieveInterestPausesByExternalId( 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(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(RESOURCE_NAME_FOR_PERMISSIONS); + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateInterestPause(loanId, variationId) + .withJson(request.toJson()).build(); + + return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java similarity index 85% rename from fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java rename to fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java index 5747625dd22..6246f278c8b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/CreateInterestPauseCommandHandler.java @@ -27,16 +27,14 @@ import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService; import org.springframework.stereotype.Component; -@Component("interestPauseCommandHandler") +@Component("createInterestPauseCommandHandler") @RequiredArgsConstructor -public class InterestPauseCommandHandler implements NewCommandSourceHandler { +public class CreateInterestPauseCommandHandler implements NewCommandSourceHandler { private final InterestPauseWritePlatformService interestPauseService; @Override public CommandProcessingResult processCommand(final JsonCommand command) { - CommandProcessingResult result; - final String startDate = command.stringValueOfParameterNamed("startDate"); final String endDate = command.stringValueOfParameterNamed("endDate"); final String dateFormat = command.stringValueOfParameterNamed("dateFormat"); @@ -44,15 +42,13 @@ public CommandProcessingResult processCommand(final JsonCommand command) { if (command.getLoanId() != null) { 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) { final ExternalId loanExternalId = command.getLoanExternalId(); - result = interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale); + return interestPauseService.createInterestPause(loanExternalId, startDate, endDate, dateFormat, locale); } else { throw new PlatformApiDataValidationException("validation.msg.missing.loan.id.or.external.id", "Either loanId or loanExternalId must be provided.", "loanId"); } - - return result; } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java new file mode 100644 index 00000000000..d4602793e16 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/DeleteInterestPauseCommandHandler.java @@ -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.getTermVariationId(); + + return interestPauseService.deleteInterestPause(loanId, termVariationId); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java new file mode 100644 index 00000000000..3113b9f90a5 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/UpdateInterestPauseCommandHandler.java @@ -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.getTermVariationId(); + final String startDate = command.stringValueOfParameterNamed("startDate"); + final String endDate = command.stringValueOfParameterNamed("endDate"); + final String dateFormat = command.stringValueOfParameterNamed("dateFormat"); + final String locale = command.stringValueOfParameterNamed("locale"); + + return interestPauseService.updateInterestPause(loanId, termVariationId, startDate, endDate, dateFormat, locale); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java index e3b8c397a22..491a45171dc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.java @@ -23,6 +23,21 @@ public interface InterestPauseWritePlatformService { + /** + * Create a new interest pause period for a loan identified by its external ID. + * + * @param loanExternalId + * the external ID of the loan + * @param startDate + * the start date of the interest pause period (inclusive) + * @param endDate + * the end date of the interest pause period (inclusive) + * @param dateFormat + * the format of the provided dates + * @param locale + * the locale used for date parsing + * @return the ID of the created loan term variation representing the interest pause + */ CommandProcessingResult createInterestPause(ExternalId loanExternalId, String startDate, String endDate, String dateFormat, String locale); @@ -35,7 +50,42 @@ CommandProcessingResult createInterestPause(ExternalId loanExternalId, String st * the start date of the interest pause period (inclusive) * @param endDate * the end date of the interest pause period (inclusive) + * @param dateFormat + * the format of the provided dates + * @param locale + * the locale used for date parsing * @return the ID of the created loan term variation representing the interest pause */ CommandProcessingResult createInterestPause(Long loanId, String startDate, String endDate, String dateFormat, String locale); + + /** + * Delete an existing interest pause period for a loan. + * + * @param loanId + * the ID of the loan + * @param variationId + * the ID of the loan term variation representing the interest pause + * @return the result of the delete operation + */ + CommandProcessingResult deleteInterestPause(Long loanId, Long variationId); + + /** + * Update an existing interest pause period for a loan identified by its internal ID. + * + * @param loanId + * the ID of the loan + * @param variationId + * the ID of the loan term variation representing the interest pause to be updated + * @param startDateString + * the new start date of the interest pause period (inclusive) as a string + * @param endDateString + * the new end date of the interest pause period (inclusive) as a string + * @param dateFormat + * the format of the provided dates (e.g., "yyyy-MM-dd") + * @param locale + * the locale used for parsing the provided dates + * @return the updated loan term variation ID along with the updated fields + */ + CommandProcessingResult updateInterestPause(Long loanId, Long variationId, String startDateString, String endDateString, + String dateFormat, String locale); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java index 272ba3f9ff6..ab9134bd103 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import lombok.AllArgsConstructor; @@ -68,6 +69,38 @@ public CommandProcessingResult createInterestPause(Long loanId, String startDate locale); } + @Override + public CommandProcessingResult deleteInterestPause(Long loanId, Long variationId) { + LoanTermVariations variation = loanTermVariationsRepository.findByIdAndLoanId(variationId, loanId).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.findByIdAndLoanId(variationId, loanId).orElseThrow( + () -> new GeneralPlatformDomainRuleException("error.msg.variation.not.found", "Variation not found for the given loan ID")); + + variation.setTermApplicableFrom(startDate); + variation.setDateValue(endDate); + + LoanTermVariations updatedVariation = loanTermVariationsRepository.saveAndFlush(variation); + + return new CommandProcessingResultBuilder().withEntityId(updatedVariation.getId()) + .with(Map.of("startDate", startDate.toString(), "endDate", endDate.toString())).build(); + } + private CommandProcessingResult processInterestPause(Supplier loanSupplier, LocalDate startDate, LocalDate endDate, String dateFormat, String locale) { final Loan loan = loanSupplier.get(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java index d5f41828db0..2e933fc72ae 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java @@ -139,6 +139,10 @@ public LocalDate fetchDateValue() { return this.dateValue; } + public void setDateValue(LocalDate dateValue) { + this.dateValue = dateValue; + } + public void setTermApplicableFrom(LocalDate termApplicableFrom) { this.termApplicableFrom = termApplicableFrom; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java index 0bf7209fff8..0780827e443 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain; import java.util.List; +import java.util.Optional; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; @@ -60,4 +61,11 @@ public interface LoanTermVariationsRepository """) List findLoanTermVariationsByExternalLoanIdAndTermType(@Param("loanExternalId") ExternalId loanExternalId, @Param("termType") int termType); + + @Query(""" + select ltv + from LoanTermVariations ltv + where ltv.id = :variationId and ltv.loan.id = :loanId + """) + Optional findByIdAndLoanId(@Param("variationId") long variationId, @Param("loanId") long loanId); } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/AdvancedPaymentAllocationsJsonParserTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/AdvancedPaymentAllocationsJsonParserTest.java index dba4bc6f5a5..8702242367e 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/AdvancedPaymentAllocationsJsonParserTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/AdvancedPaymentAllocationsJsonParserTest.java @@ -230,7 +230,7 @@ private JsonCommand createJsonCommand(Map jsonMap) throws JsonPr ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonMap); JsonCommand command = JsonCommand.from(json, JsonParser.parseString(json), fromJsonHelper, null, 1L, 2L, 3L, 4L, null, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, null); return command; } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/CreditAllocationsJsonParserTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/CreditAllocationsJsonParserTest.java index 45138580bf6..e0f8c8246aa 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/CreditAllocationsJsonParserTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/domain/CreditAllocationsJsonParserTest.java @@ -118,7 +118,7 @@ private JsonCommand createJsonCommand(Map jsonMap) throws JsonPr ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonMap); JsonCommand command = JsonCommand.from(json, JsonParser.parseString(json), fromJsonHelper, null, 1L, 2L, 3L, 4L, null, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, null); return command; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java index 40bfb22d8a0..d6a49f81a08 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java @@ -268,7 +268,7 @@ private Integer importCenterMeeting(final List meetings, final Com String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null); + result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null, null); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java index 68558fa79df..fb518fddf0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java @@ -246,7 +246,7 @@ private Integer importGroupMeeting(final List meetings, CommandPro String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null); + result.getProductId(), null, null, null, null, idempotencyKeyGenerator.create(), null, null); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java index 3a3a2abcf4f..28cc3f2c9c5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java @@ -429,8 +429,8 @@ public CreditBureauToken createToken(Long bureauID) { JsonCommand apicommand = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), - wrapper.getCreditBureauId(), wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), - wrapper.getLoanExternalId()); + wrapper.getCreditBureauId(), wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), wrapper.getLoanExternalId(), + wrapper.getTermVariationId()); this.fromApiJsonDeserializer.validateForCreate(apicommand.json()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java index 6556b50c24c..dac41655ed2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java @@ -37,7 +37,7 @@ public class InteropWrapperBuilder { public CommandWrapper build() { return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, null, null, - null, null, null, null); + null, null, null, null, null); } public InteropWrapperBuilder withJson(final String json) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 7751d18aa25..21eb2232221 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -784,7 +784,7 @@ public void applyOverdueChargesForLoan(final Long loanId, Collection + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_term_variation_id_to_commands.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_term_variation_id_to_commands.xml new file mode 100644 index 00000000000..195e679358f --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0162_add_term_variation_id_to_commands.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java index 8a81e104f5a..f3e77034261 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java @@ -42,7 +42,7 @@ public CommandHandlerProviderStepDefinitions() { When("The user processes the command with ID {long}", (Long id) -> { this.result = commandHandler.processCommand( - JsonCommand.fromExistingCommand(id, null, null, null, null, null, null, null, null, null, null, null, null)); + JsonCommand.fromExistingCommand(id, null, null, null, null, null, null, null, null, null, null, null, null, null)); }); Then("The command ID matches {long}", (Long id) -> { diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java index 9e494ff6644..b672f4623f5 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/service/CommandServiceStepDefinitions.java @@ -101,7 +101,7 @@ public static class DummyCommand extends CommandWrapper { public DummyCommand() { super(null, null, null, null, null, null, null, null, null, null, "{}", null, null, null, null, null, null, - UUID.randomUUID().toString(), null); + UUID.randomUUID().toString(), null, null); } @Override @@ -124,7 +124,8 @@ public CommandProcessingResult logCommandSource(CommandWrapper wrapper) { JsonCommand command = JsonCommand.from(json, null, null, wrapper.getEntityName(), wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), wrapper.getCreditBureauId(), - wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), wrapper.getLoanExternalId()); + wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(), wrapper.getLoanExternalId(), + wrapper.getTermVariationId()); return this.processAndLogCommandService.executeCommand(wrapper, command, true); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/service/IdempotencyKeyResolverTest.java b/fineract-provider/src/test/java/org/apache/fineract/commands/service/IdempotencyKeyResolverTest.java index 2e3af19d96c..4b13d8a9081 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/service/IdempotencyKeyResolverTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/service/IdempotencyKeyResolverTest.java @@ -77,7 +77,7 @@ public void testIPKResolveFromGenerate() { public void testIPKResolveFromWrapper() { String idk = "idk"; CommandWrapper wrapper = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, idk, null); + null, null, null, idk, null, null); String resolvedIdk = underTest.resolve(wrapper); Assertions.assertEquals(idk, resolvedIdk); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java index b83c3729d8a..d50315c907a 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest.java @@ -421,7 +421,7 @@ private JsonCommand initialJsonCommand() throws JsonProcessingException { command.put("creditBureauID", "1"); // Must match to the mocked config String json = mapper.writeValueAsString(command); return JsonCommand.from(json, JsonParser.parseString(json), fromJsonHelper, null, 1L, 2L, 3L, 4L, null, null, null, null, null, - null, null, null, null); + null, null, null, null, null); } private void mockTokenGeneration() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java index 347b72f2369..47d2bcb12e0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java @@ -63,7 +63,9 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { private static RequestSpecification REQUEST_SPEC; private static ResponseSpecification RESPONSE_SPEC; private static ResponseSpecification RESPONSE_SPEC_403; + private static ResponseSpecification RESPONSE_SPEC_204; private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER; + private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER_204; private static LoanTransactionHelper LOAN_TRANSACTION_HELPER_403; private static AccountHelper accountHelper; private static PostClientsResponse client; @@ -80,8 +82,10 @@ public static void setupTests() { REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); RESPONSE_SPEC_403 = new ResponseSpecBuilder().expectStatusCode(403).build(); + RESPONSE_SPEC_204 = new ResponseSpecBuilder().expectStatusCode(204).build(); LOAN_TRANSACTIONAL_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); LOAN_TRANSACTION_HELPER_403 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_403); + LOAN_TRANSACTIONAL_HELPER_204 = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC_204); accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC); ClientHelper clientHelper = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC); client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); @@ -238,6 +242,116 @@ public void testRetrieveInterestPausesByExternalLoanId_shouldReturnData() { Assertions.assertTrue(response.contains("2023-02-05")); } + @Test + public void testUpdateInterestPauseByLoanId_validRequest_shouldSucceed() { + PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", + "2023-02-01", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(createResponse); + Assertions.assertNotNull(createResponse.getResourceId()); + + Long variationId = createResponse.getResourceId(); + + PostLoansLoanIdTransactionsResponse updateResponse = LOAN_TRANSACTIONAL_HELPER.updateInterestPauseByLoanId(variationId, + "2023-02-01", "2023-02-14", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(updateResponse); + Assertions.assertNotNull(updateResponse.getResourceId()); + Assertions.assertEquals(variationId, updateResponse.getResourceId()); + } + + @Test + public void testUpdateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() { + PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", + "2023-02-01", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(createResponse); + Assertions.assertNotNull(createResponse.getResourceId()); + + Long variationId = createResponse.getResourceId(); + + try { + LOAN_TRANSACTION_HELPER_403.updateInterestPauseByLoanId(variationId, "2023-03-01", "2023-02-01", "yyyy-MM-dd", "en", loanId); + } catch (Exception e) { + String responseBody = e.getMessage(); + Assertions.assertNotNull(responseBody, "Response body should not be null"); + Assertions.assertTrue(responseBody.contains("interest.pause.end.date.before.start.date"), + "Response should contain the validation error message for end date before start date"); + } + } + + @Test + public void testUpdateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() { + PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", + "2023-02-01", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(createResponse); + Assertions.assertNotNull(createResponse.getResourceId()); + + Long variationId = createResponse.getResourceId(); + + try { + LOAN_TRANSACTION_HELPER_403.updateInterestPauseByLoanId(variationId, "2022-12-01", "2023-02-01", "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"); + } + } + + @Test + public void testDeleteInterestPauseByLoanId_validRequest_shouldSucceed() { + PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", + "2023-02-01", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(createResponse, "Create response should not be null"); + Assertions.assertNotNull(createResponse.getResourceId(), "Resource ID should not be null"); + + Long variationId = createResponse.getResourceId(); + + try { + LOAN_TRANSACTIONAL_HELPER_204.deleteInterestPauseByLoanId(variationId, loanId); + } catch (Exception e) { + Assertions.fail("Delete operation failed: " + e.getMessage()); + } + + String response = LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId); + Assertions.assertFalse(response.contains(String.valueOf(variationId)), "Response should not contain the deleted variation ID"); + } + + @Test + public void testDeleteInterestPauseByLoanId_nonExistentVariation_shouldFail() { + try { + LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(99999L, loanId); + } catch (Exception e) { + String responseBody = e.getMessage(); + Assertions.assertNotNull(responseBody, "Response body should not be null"); + Assertions.assertTrue(responseBody.contains("error.msg.variation.not.found"), + "Response should contain the validation error message for variation not found"); + } + } + + @Test + public void testDeleteInterestPauseByLoanId_invalidLoanId_shouldFail() { + PostLoansLoanIdTransactionsResponse createResponse = LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", + "2023-02-01", "yyyy-MM-dd", "en", loanId); + + Assertions.assertNotNull(createResponse); + Assertions.assertNotNull(createResponse.getResourceId()); + + Long variationId = createResponse.getResourceId(); + + try { + LOAN_TRANSACTION_HELPER_403.deleteInterestPauseByLoanId(variationId, nonExistLoanId); + } catch (Exception e) { + String responseBody = e.getMessage(); + Assertions.assertNotNull(responseBody, "Response body should not be null"); + Assertions.assertTrue(responseBody.contains("error.msg.variation.not.found"), + "Response should contain the validation error message for variation not found"); + } + } + private static Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments, boolean downPaymentEnabled, String downPaymentPercentage, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType, LoanScheduleProcessingType loanScheduleProcessingType, final Account... accounts) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 1748863999d..4b63b2bec90 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -624,6 +624,19 @@ public PostLoansLoanIdTransactionsResponse createInterestPauseByExternalId(final return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, externalId), body); } + public PostLoansLoanIdTransactionsResponse updateInterestPauseByLoanId(final Long termVariationId, final String startDate, + final String endDate, final String dateFormat, final String locale, final Long 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 Long loanID) { + log.info("Deleting interest pause for Loan ID {} with Term Variation ID {}", loanID, termVariationId); + deleteLoanTransaction(deleteInterestPause(termVariationId, loanID)); + } + public String retrieveInterestPauseByLoanId(final Long loanID) { log.info("Retrieving interest pauses for Loan ID {}", loanID); String url = retrieveInterestPause(loanID); @@ -1467,6 +1480,14 @@ private String retrieveInterestPause(final String externalId) { return "/fineract-provider/api/v1/loans/external-id/" + externalId + "/interest-pauses?" + Utils.TENANT_IDENTIFIER; } + private String updateInterestPause(final Long termVariationId, final Long loanID) { + return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses/" + termVariationId + "?" + Utils.TENANT_IDENTIFIER; + } + + private String deleteInterestPause(final Long termVariationId, final Long loanID) { + return "/fineract-provider/api/v1/loans/" + loanID + "/interest-pauses/" + termVariationId + "?" + Utils.TENANT_IDENTIFIER; + } + private String createInteroperationLoanTransactionURL(final String accountNo) { return "/fineract-provider/api/v1/interoperation/transactions/" + accountNo + "/loanrepayment"; } @@ -1513,6 +1534,15 @@ private PostLoansLoanIdTransactionsResponse postLoanTransaction(final String pos return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class); } + private PostLoansLoanIdTransactionsResponse putLoanTransaction(final String putURLForLoanTransaction, final String jsonToBeSent) { + final String response = Utils.performServerPut(this.requestSpec, this.responseSpec, putURLForLoanTransaction, jsonToBeSent); + return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class); + } + + private void deleteLoanTransaction(final String deleteURLForLoanTransaction) { + Utils.performServerDelete(this.requestSpec, this.responseSpec, deleteURLForLoanTransaction, null); + } + private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, ResponseSpecification responseValidationError) {