This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new c2ec015e6 FINERACT-2152: API Create and retrieve interest pause
c2ec015e6 is described below

commit c2ec015e64876858e12f2e142522c78cb7252e8a
Author: Andrii Kulminskyi <[email protected]>
AuthorDate: Thu Dec 12 14:41:20 2024 +0200

    FINERACT-2152: API Create and retrieve interest pause
---
 .../fineract/commands/domain/CommandSource.java    |   5 +
 .../fineract/commands/domain/CommandWrapper.java   |  23 +-
 .../commands/service/CommandWrapperBuilder.java    |  22 +-
 ...folioCommandSourceWritePlatformServiceImpl.java |   8 +-
 .../SynchronousCommandProcessingService.java       |   2 +
 .../infrastructure/core/api/JsonCommand.java       |  31 +-
 .../core/data/CommandProcessingResult.java         |  12 +-
 .../core/data/CommandProcessingResultBuilder.java  |   9 +-
 ...empotencyCommandProcessFailedExceptionTest.java |   2 +-
 .../api/LoanInterestPauseApiResource.java          | 117 ++++++++
 .../data/InterestPauseRequestDto.java              |  53 ++++
 .../data/InterestPauseResponseDto.java             |  45 +++
 .../handler/InterestPauseCommandHandler.java       |  58 ++++
 .../service/InterestPauseReadPlatformService.java  |  41 +++
 .../InterestPauseReadPlatformServiceImpl.java      |  60 ++++
 .../service/InterestPauseWritePlatformService.java |  41 +++
 .../InterestPauseWritePlatformServiceImpl.java     | 136 +++++++++
 .../loanaccount/domain/LoanRepository.java         |   5 +
 .../loanaccount/domain/LoanRepositoryWrapper.java  |  14 +
 .../loanaccount/domain/LoanTermVariationType.java  |   6 +-
 .../domain/LoanTermVariationsRepository.java       |  11 +
 .../AdvancedPaymentAllocationsJsonParserTest.java  |   2 +-
 .../domain/CreditAllocationsJsonParserTest.java    |   2 +-
 .../importhandler/center/CenterImportHandler.java  |   2 +-
 .../importhandler/group/GroupImportHandler.java    |   2 +-
 ...tBureauIntegrationWritePlatformServiceImpl.java |   3 +-
 .../interoperation/api/InteropWrapperBuilder.java  |   2 +-
 .../LoanChargeWritePlatformServiceImpl.java        |   2 +-
 .../starter/LoanAccountConfiguration.java          |  18 ++
 .../service/ShareAccountCommandsServiceImpl.java   |   2 +-
 .../service/ShareProductCommandsServiceImpl.java   |   2 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../0161_add_loan_external_id_to_commands.xml      |  33 +++
 .../CommandHandlerProviderStepDefinitions.java     |   4 +-
 .../service/CommandServiceStepDefinitions.java     |   4 +-
 .../service/IdempotencyKeyResolverTest.java        |   2 +-
 ...eauIntegrationWritePlatformServiceImplTest.java |   2 +-
 .../integrationtests/LoanInterestPauseApiTest.java | 315 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |  56 ++++
 39 files changed, 1112 insertions(+), 43 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
 
b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
index 97b4cbbe1..ad28f696c 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
@@ -133,6 +133,9 @@ public class CommandSource extends 
AbstractPersistableCustom<Long> {
     @Column(name = "result_status_code")
     private Integer resultStatusCode;
 
+    @Column(name = "loan_external_id", length = 100)
+    private ExternalId loanExternalId;
+
     public static CommandSource fullEntryFrom(final CommandWrapper wrapper, 
final JsonCommand command, final AppUser maker,
             String idempotencyKey, Integer status) {
 
@@ -156,6 +159,7 @@ public class CommandSource extends 
AbstractPersistableCustom<Long> {
                 .transactionId(command.getTransactionId()) //
                 .creditBureauId(command.getCreditBureauId()) //
                 
.organisationCreditBureauId(command.getOrganisationCreditBureauId()) //
+                .loanExternalId(command.getLoanExternalId()) //
                 .build(); //
     }
 
@@ -195,5 +199,6 @@ public class CommandSource extends 
AbstractPersistableCustom<Long> {
         this.resourceExternalId = result.getResourceExternalId();
         this.subResourceId = result.getSubResourceId();
         this.subResourceExternalId = result.getSubResourceExternalId();
+        this.loanExternalId = result.getLoanExternalId();
     }
 }
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 135c1bce6..5b51f40de 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.commands.domain;
 
 import lombok.Getter;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import 
org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
 
 @Getter
@@ -43,6 +44,7 @@ public class CommandWrapper {
     private final Long creditBureauId;
     private final Long organisationCreditBureauId;
     private final String jobName;
+    private final ExternalId loanExternalId;
 
     private final String idempotencyKey;
 
@@ -61,9 +63,11 @@ public class CommandWrapper {
     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 Long creditBureauId, final Long organisationCreditBureauId, 
final String idempotencyKey,
+            final ExternalId loanExternalId) {
         return new CommandWrapper(commandId, actionName, entityName, 
resourceId, subresourceId, resourceGetUrl, productId, officeId,
-                groupId, clientId, loanId, savingsId, transactionId, 
creditBureauId, organisationCreditBureauId, idempotencyKey);
+                groupId, clientId, loanId, savingsId, transactionId, 
creditBureauId, organisationCreditBureauId, idempotencyKey,
+                loanExternalId);
     }
 
     private CommandWrapper(final Long commandId, final String actionName, 
final String entityName, final Long resourceId,
@@ -87,12 +91,13 @@ public class CommandWrapper {
         this.organisationCreditBureauId = null;
         this.jobName = null;
         this.idempotencyKey = null;
+        this.loanExternalId = 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 Long organisationCreditBureauId, final String jobName, final 
String idempotencyKey, final ExternalId loanExternalId) {
 
         this.commandId = null;
         this.officeId = officeId;
@@ -114,12 +119,13 @@ public class CommandWrapper {
         this.organisationCreditBureauId = organisationCreditBureauId;
         this.jobName = jobName;
         this.idempotencyKey = idempotencyKey;
+        this.loanExternalId = loanExternalId;
     }
 
     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 Long organisationCreditBureauId, final String 
idempotencyKey, final ExternalId loanExternalId) {
 
         this.commandId = commandId;
         this.officeId = officeId;
@@ -140,6 +146,7 @@ public class CommandWrapper {
         this.organisationCreditBureauId = organisationCreditBureauId;
         this.jobName = null;
         this.idempotencyKey = idempotencyKey;
+        this.loanExternalId = loanExternalId;
     }
 
     public boolean isCreate() {
@@ -240,6 +247,14 @@ public class CommandWrapper {
         return 
this.entityName.equalsIgnoreCase(PasswordPreferencesApiConstants.ENTITY_NAME);
     }
 
+    public boolean isInterestPauseResource() {
+        return this.entityName.equalsIgnoreCase("INTEREST_PAUSE");
+    }
+
+    public boolean isInterestPauseExternalIdResource() {
+        return this.entityName.equalsIgnoreCase("INTEREST_PAUSE") && 
this.href.contains("/external-id/");
+    }
+
     public Long commandId() {
         return this.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 dad66a861..497862cbf 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -21,6 +21,7 @@ package org.apache.fineract.commands.service;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import 
org.apache.fineract.infrastructure.accountnumberformat.service.AccountNumberFormatConstants;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.client.api.ClientApiConstants;
 import 
org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
 import org.apache.fineract.portfolio.savings.DepositsApiConstants;
@@ -47,18 +48,19 @@ public class CommandWrapperBuilder {
     private Long organisationCreditBureauId;
     private String jobName;
     private String idempotencyKey;
+    private ExternalId loanExternalId;
 
     @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.creditBureauId, this.organisationCreditBureauId, 
this.jobName, this.idempotencyKey, this.loanExternalId);
     }
 
     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.creditBureauId, this.organisationCreditBureauId, 
this.jobName, idempotencyKey, this.loanExternalId);
     }
 
     public CommandWrapperBuilder updateCreditBureau() {
@@ -3714,4 +3716,20 @@ public class CommandWrapperBuilder {
         this.href = "/loans/" + loanId + "/delinquency-action";
         return this;
     }
+
+    public CommandWrapperBuilder createInterestPause(final long loanId) {
+        this.actionName = "CREATE";
+        this.entityName = "INTEREST_PAUSE";
+        this.loanId = loanId;
+        this.href = "/v1/loans/" + loanId + "/interest-pauses";
+        return this;
+    }
+
+    public CommandWrapperBuilder createInterestPauseByExternalId(final String 
loanExternalId) {
+        this.actionName = "CREATE";
+        this.entityName = "INTEREST_PAUSE";
+        this.loanExternalId = new ExternalId(loanExternalId);
+        this.href = "/v1/loans/external-id/" + loanExternalId + 
"/interest-pauses";
+        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 1de67b6e8..dd2d8c81b 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 class PortfolioCommandSourceWritePlatformServiceImpl 
implements Portfolio
         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.getOrganisationCreditBureauId(), wrapper.getJobName(), 
wrapper.getLoanExternalId());
 
         return this.processAndLogCommandService.executeCommand(wrapper, 
command, isApprovedByChecker);
     }
@@ -88,14 +88,16 @@ public class PortfolioCommandSourceWritePlatformServiceImpl 
implements Portfolio
                 commandSourceInput.getResourceGetUrl(), 
commandSourceInput.getProductId(), commandSourceInput.getOfficeId(),
                 commandSourceInput.getGroupId(), 
commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
                 commandSourceInput.getSavingsId(), 
commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(),
-                commandSourceInput.getOrganisationCreditBureauId(), 
commandSourceInput.getIdempotencyKey());
+                commandSourceInput.getOrganisationCreditBureauId(), 
commandSourceInput.getIdempotencyKey(),
+                commandSourceInput.getLoanExternalId());
         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.getOrganisationCreditBureauId(), 
commandSourceInput.getJobName(),
+                commandSourceInput.getLoanExternalId());
 
         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 3d5917d9d..9b525d5cd 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
@@ -249,6 +249,8 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
             } else {
                 throw new UnsupportedCommandException(wrapper.commandName());
             }
+        } else if (wrapper.isInterestPauseResource() || 
wrapper.isInterestPauseExternalIdResource()) {
+            handler = 
applicationContext.getBean("interestPauseCommandHandler", 
NewCommandSourceHandler.class);
         } else {
             handler = commandHandlerProvider.getHandler(wrapper.entityName(), 
wrapper.actionName());
         }
diff --git 
a/fineract-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 46f9f2ddb..72ad7c27d 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
@@ -71,30 +71,34 @@ public final class JsonCommand {
     private final Long creditBureauId;
     private final Long organisationCreditBureauId;
     private final String jobName;
+    private final ExternalId loanExternalId;
 
     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 Long creditBureauId, final Long organisationCreditBureauId, 
final String jobName, final ExternalId loanExternalId) {
         return new JsonCommand(null, jsonCommand, parsedCommand, 
fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
-                clientId, loanId, savingsId, transactionId, url, productId, 
creditBureauId, organisationCreditBureauId, jobName);
+                clientId, loanId, savingsId, transactionId, url, productId, 
creditBureauId, organisationCreditBureauId, jobName,
+                loanExternalId);
 
     }
 
     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 String url, final Long productId, final Long creditBureauId, 
final Long organisationCreditBureauId, final String jobName,
+            final ExternalId loanExternalId) {
         return new JsonCommand(commandId, jsonCommand, parsedCommand, 
fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
-                null, null, null, url, productId, creditBureauId, 
organisationCreditBureauId, jobName);
+                null, null, null, url, productId, creditBureauId, 
organisationCreditBureauId, jobName, loanExternalId);
     }
 
     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 Long productId, Long creditBureauId, final Long 
organisationCreditBureauId, final String jobName,
+            final ExternalId loanExternalId) {
         return new JsonCommand(commandId, jsonCommand, parsedCommand, 
fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
-                clientId, loanId, savingsId, transactionId, url, productId, 
creditBureauId, organisationCreditBureauId, jobName);
+                clientId, loanId, savingsId, transactionId, url, productId, 
creditBureauId, organisationCreditBureauId, jobName,
+                loanExternalId);
 
     }
 
@@ -103,7 +107,7 @@ public final class JsonCommand {
         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.jobName, command.loanExternalId);
     }
 
     public static JsonCommand fromExistingCommand(JsonCommand command, final 
JsonElement parsedCommand, final Long clientId) {
@@ -111,13 +115,14 @@ public final class JsonCommand {
         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.jobName, command.loanExternalId);
     }
 
     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 Long productId, final Long creditBureauId, final Long 
organisationCreditBureauId, final String jobName,
+            final ExternalId loanExternalId) {
 
         this.commandId = commandId;
         this.jsonCommand = jsonCommand;
@@ -136,6 +141,7 @@ public final class JsonCommand {
         this.creditBureauId = creditBureauId;
         this.organisationCreditBureauId = organisationCreditBureauId;
         this.jobName = jobName;
+        this.loanExternalId = loanExternalId;
     }
 
     public static JsonCommand fromJsonElement(final Long resourceId, final 
JsonElement parsedCommand) {
@@ -165,6 +171,7 @@ public final class JsonCommand {
         this.creditBureauId = null;
         this.organisationCreditBureauId = null;
         this.jobName = null;
+        this.loanExternalId = null;
     }
 
     public JsonCommand(final Long resourceId, final JsonElement parsedCommand, 
final FromJsonHelper fromApiJsonHelper) {
@@ -185,10 +192,12 @@ public final class JsonCommand {
         this.creditBureauId = null;
         this.organisationCreditBureauId = null;
         this.jobName = null;
+        this.loanExternalId = 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);
+        return new JsonCommand(null, jsonCommand, 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 8be8de388..5b07f76bf 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
@@ -49,12 +49,13 @@ public class CommandProcessingResult implements 
Serializable {
     private Boolean rollbackTransaction;
     private final ExternalId resourceExternalId;
     private final ExternalId subResourceExternalId;
+    private final ExternalId loanExternalId;
 
     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<String, Object> changes, final Long productId, final 
Long gsimId, final Long glimId,
             final Map<String, Object> creditBureauReportData, Boolean 
rollbackTransaction, final Long subResourceId,
-            final ExternalId resourceExternalId, final ExternalId 
subResourceExternalId) {
+            final ExternalId resourceExternalId, final ExternalId 
subResourceExternalId, final ExternalId loanExternalId) {
         this.commandId = commandId;
         this.officeId = officeId;
         this.groupId = groupId;
@@ -73,12 +74,13 @@ public class CommandProcessingResult implements 
Serializable {
         this.subResourceId = subResourceId;
         this.resourceExternalId = resourceExternalId;
         this.subResourceExternalId = subResourceExternalId;
+        this.loanExternalId = loanExternalId;
     }
 
     protected CommandProcessingResult(final Long resourceId, final Long 
officeId, final Long commandId, final Map<String, Object> 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());
+                null, null, null, null, null, null, ExternalId.empty(), 
ExternalId.empty(), ExternalId.empty());
     }
 
     protected CommandProcessingResult(final Long resourceId, final Long 
officeId, final Long commandId, final Map<String, Object> changes) {
@@ -94,7 +96,7 @@ public class CommandProcessingResult implements Serializable {
                 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.resourceExternalId, 
commandResult.subResourceExternalId, commandResult.loanExternalId);
     }
 
     public static CommandProcessingResult 
fromCommandProcessingResult(CommandProcessingResult commandResult) {
@@ -105,10 +107,10 @@ public class CommandProcessingResult implements 
Serializable {
             final Long loanId, final Long savingsId, final String 
resourceIdentifier, final Long entityId, final Long gsimId,
             final Long glimId, final Map<String, Object> 
creditBureauReportData, final String transactionId,
             final Map<String, Object> changes, final Long productId, final 
Boolean rollbackTransaction, final Long subResourceId,
-            final ExternalId resourceExternalId, final ExternalId 
subResourceExternalId) {
+            final ExternalId resourceExternalId, final ExternalId 
subResourceExternalId, final ExternalId loanExternalId) {
         return new CommandProcessingResult(commandId, officeId, groupId, 
clientId, loanId, savingsId, resourceIdentifier, entityId,
                 transactionId, changes, productId, gsimId, glimId, 
creditBureauReportData, rollbackTransaction, subResourceId,
-                resourceExternalId, subResourceExternalId);
+                resourceExternalId, subResourceExternalId, loanExternalId);
     }
 
     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 1536f08be..e630ba707 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
@@ -46,10 +46,13 @@ public class CommandProcessingResultBuilder {
 
     private ExternalId subEntityExternalId = ExternalId.empty();
 
+    private ExternalId loanExternalId = ExternalId.empty();
+
     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.changes, this.productId, this.rollbackTransaction, 
this.subEntityId, this.entityExternalId, this.subEntityExternalId,
+                this.loanExternalId);
     }
 
     public CommandProcessingResultBuilder withCommandId(final Long 
withCommandId) {
@@ -142,4 +145,8 @@ public class CommandProcessingResultBuilder {
         return this;
     }
 
+    public CommandProcessingResultBuilder withLoanExternalId(final ExternalId 
loanExternalId) {
+        this.loanExternalId = loanExternalId;
+        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 04ba9aa54..d246f833b 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 class IdempotencyCommandProcessFailedExceptionTest {
     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);
         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
new file mode 100644
index 000000000..1b08ad6d8
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/api/LoanInterestPauseApiResource.java
@@ -0,0 +1,117 @@
+/**
+ * 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.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import 
org.apache.fineract.portfolio.interestpauses.data.InterestPauseRequestDto;
+import 
org.apache.fineract.portfolio.interestpauses.data.InterestPauseResponseDto;
+import 
org.apache.fineract.portfolio.interestpauses.service.InterestPauseReadPlatformService;
+import org.springframework.stereotype.Component;
+
+@Path("/v1/loans")
+@Component
+@Tag(name = "Loan Interest Pause", description = "APIs for managing interest 
pause periods on loans.")
+@RequiredArgsConstructor
+public class LoanInterestPauseApiResource {
+
+    private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
+
+    private final PlatformSecurityContext context;
+    private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
+    private final InterestPauseReadPlatformService 
interestPauseReadPlatformService;
+
+    @POST
+    @Path("/{loanId}/interest-pauses")
+    @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") })
+    public CommandProcessingResult createInterestPause(@PathParam("loanId") 
@Parameter(description = "loanId") final Long loanId,
+            @RequestBody(required = true) final InterestPauseRequestDto 
request) {
+
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+
+        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().createInterestPause(loanId).withJson(request.toJson()).build();
+
+        return 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+    }
+
+    @POST
+    @Path("/external-id/{loanExternalId}/interest-pauses")
+    @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") })
+    public CommandProcessingResult createInterestPauseByExternalId(
+            @PathParam("loanExternalId") @Parameter(description = 
"loanExternalId") final String loanExternalId,
+            @RequestBody(required = true) final InterestPauseRequestDto 
request) {
+
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+
+        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().createInterestPauseByExternalId(loanExternalId)
+                .withJson(request.toJson()).build();
+
+        return 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+    }
+
+    @GET
+    @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") })
+    public List<InterestPauseResponseDto> retrieveInterestPauses(
+            @PathParam("loanId") @Parameter(description = "loanId") final Long 
loanId) {
+
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+
+        return 
this.interestPauseReadPlatformService.retrieveInterestPauses(loanId);
+    }
+
+    @GET
+    @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") })
+    public List<InterestPauseResponseDto> retrieveInterestPausesByExternalId(
+            @PathParam("loanExternalId") @Parameter(description = 
"loanExternalId") final String loanExternalId) {
+
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+
+        return 
this.interestPauseReadPlatformService.retrieveInterestPauses(loanExternalId);
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java
new file mode 100644
index 000000000..77efbbcb9
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java
@@ -0,0 +1,53 @@
+/**
+ * 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.data;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "Request DTO for creating an interest pause")
+public class InterestPauseRequestDto {
+
+    @Schema(example = "2024-01-01", description = "Start date of the interest 
pause period")
+    private String startDate;
+
+    @Schema(example = "2024-01-11", description = "End date of the interest 
pause period")
+    private String endDate;
+
+    @Schema(example = "yyyy-MM-dd", description = "Format of the dates 
provided")
+    private String dateFormat;
+
+    @Schema(example = "en", description = "Locale to interpret the date 
format")
+    private String locale;
+
+    public String toJson() {
+        try {
+            return new ObjectMapper().writeValueAsString(this);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException("Error serializing request to 
JSON", e);
+        }
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseResponseDto.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseResponseDto.java
new file mode 100644
index 000000000..63578cf9d
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseResponseDto.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.data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+@Schema(description = "Response DTO for Interest Pause periods")
+public class InterestPauseResponseDto {
+
+    @Schema(example = "1", description = "ID of the loan term variation")
+    private Long id;
+
+    @Schema(example = "2024-01-01", description = "Start date of the interest 
pause period")
+    private LocalDate startDate;
+
+    @Schema(example = "2024-01-11", description = "End date of the interest 
pause period")
+    private LocalDate endDate;
+
+    @Schema(example = "yyyy-MM-dd", description = "Date format used to 
interpret start and end dates")
+    private String dateFormat;
+
+    @Schema(example = "en", description = "Locale used for date formatting")
+    private String locale;
+}
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/InterestPauseCommandHandler.java
new file mode 100644
index 000000000..5747625dd
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/handler/InterestPauseCommandHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.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;
+
+@Component("interestPauseCommandHandler")
+@RequiredArgsConstructor
+public class InterestPauseCommandHandler implements NewCommandSourceHandler {
+
+    private final InterestPauseWritePlatformService interestPauseService;
+
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        CommandProcessingResult result;
+
+        final String startDate = 
command.stringValueOfParameterNamed("startDate");
+        final String endDate = command.stringValueOfParameterNamed("endDate");
+        final String dateFormat = 
command.stringValueOfParameterNamed("dateFormat");
+        final String locale = command.stringValueOfParameterNamed("locale");
+
+        if (command.getLoanId() != null) {
+            final Long loanId = command.getLoanId();
+            result = 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);
+        } 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/service/InterestPauseReadPlatformService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseReadPlatformService.java
new file mode 100644
index 000000000..a55326b3e
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseReadPlatformService.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.service;
+
+import java.util.List;
+import 
org.apache.fineract.portfolio.interestpauses.data.InterestPauseResponseDto;
+
+public interface InterestPauseReadPlatformService {
+
+    /**
+     * Retrieve all interest pause periods for a valid loan.
+     *
+     * @param loanId
+     * @return List of InterestPauseData
+     */
+    List<InterestPauseResponseDto> retrieveInterestPauses(Long loanId);
+
+    /**
+     * Retrieve all interest pause periods for a loan using external ID.
+     *
+     * @param loanExternalId
+     * @return List of InterestPauseData
+     */
+    List<InterestPauseResponseDto> retrieveInterestPauses(String 
loanExternalId);
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseReadPlatformServiceImpl.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseReadPlatformServiceImpl.java
new file mode 100644
index 000000000..e8ae87d75
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseReadPlatformServiceImpl.java
@@ -0,0 +1,60 @@
+/**
+ * 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.service;
+
+import java.util.List;
+import java.util.Locale;
+import lombok.AllArgsConstructor;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import 
org.apache.fineract.portfolio.interestpauses.data.InterestPauseResponseDto;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+@AllArgsConstructor
+@Transactional(readOnly = true)
+public class InterestPauseReadPlatformServiceImpl implements 
InterestPauseReadPlatformService {
+
+    private final LoanTermVariationsRepository loanTermVariationsRepository;
+
+    @Override
+    public List<InterestPauseResponseDto> retrieveInterestPauses(Long loanId) {
+        List<LoanTermVariationsData> variations = 
this.loanTermVariationsRepository.findLoanTermVariationsByLoanIdAndTermType(loanId,
+                LoanTermVariationType.INTEREST_PAUSE.getValue());
+
+        return mapToInterestPauseResponse(variations);
+    }
+
+    @Override
+    public List<InterestPauseResponseDto> retrieveInterestPauses(String 
loanExternalId) {
+        List<LoanTermVariationsData> variations = 
this.loanTermVariationsRepository.findLoanTermVariationsByExternalLoanIdAndTermType(
+                new ExternalId(loanExternalId), 
LoanTermVariationType.INTEREST_PAUSE.getValue());
+
+        return mapToInterestPauseResponse(variations);
+    }
+
+    private List<InterestPauseResponseDto> 
mapToInterestPauseResponse(List<LoanTermVariationsData> variations) {
+        return variations.stream()
+                .map(variation -> new 
InterestPauseResponseDto(variation.getId(), 
variation.getTermVariationApplicableFrom(),
+                        variation.getDateValue(), 
DateUtils.DEFAULT_DATE_FORMAT, Locale.getDefault().toString()))
+                .toList();
+    }
+}
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
new file mode 100644
index 000000000..e3b8c397a
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformService.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.service;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+
+public interface InterestPauseWritePlatformService {
+
+    CommandProcessingResult createInterestPause(ExternalId loanExternalId, 
String startDate, String endDate, String dateFormat,
+            String locale);
+
+    /**
+     * Create a new interest pause period for a loan identified by its 
internal ID.
+     *
+     * @param loanId
+     *            the 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)
+     * @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);
+}
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
new file mode 100644
index 000000000..272ba3f9f
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java
@@ -0,0 +1,136 @@
+/**
+ * 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.service;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.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;
+import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+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.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+@AllArgsConstructor
+@Transactional
+public class InterestPauseWritePlatformServiceImpl implements 
InterestPauseWritePlatformService {
+
+    private final LoanTermVariationsRepository loanTermVariationsRepository;
+    private final LoanRepositoryWrapper loanRepositoryWrapper;
+
+    @Override
+    public CommandProcessingResult createInterestPause(ExternalId 
loanExternalId, String startDateString, String endDateString,
+            String dateFormat, String locale) {
+        final LocalDate startDate = parseDate(startDateString, dateFormat, 
locale);
+        final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
+
+        return processInterestPause(() -> 
loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId), startDate, 
endDate,
+                dateFormat, locale);
+    }
+
+    @Override
+    public CommandProcessingResult createInterestPause(Long loanId, String 
startDateString, String endDateString, String dateFormat,
+            String locale) {
+        final LocalDate startDate = parseDate(startDateString, dateFormat, 
locale);
+        final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
+
+        return processInterestPause(() -> 
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId), startDate, endDate, 
dateFormat,
+                locale);
+    }
+
+    private CommandProcessingResult processInterestPause(Supplier<Loan> 
loanSupplier, LocalDate startDate, LocalDate endDate,
+            String dateFormat, String locale) {
+        final Loan loan = loanSupplier.get();
+
+        validateInterestPauseDates(loan, startDate, endDate, dateFormat, 
locale);
+
+        LoanTermVariations variation = new 
LoanTermVariations(LoanTermVariationType.INTEREST_PAUSE.getValue(), startDate, 
null, endDate,
+                false, loan);
+
+        LoanTermVariations savedVariation = 
loanTermVariationsRepository.saveAndFlush(variation);
+
+        return new 
CommandProcessingResultBuilder().withEntityId(savedVariation.getId()).build();
+    }
+
+    private void validateInterestPauseDates(Loan loan, LocalDate startDate, 
LocalDate endDate, String dateFormat, String locale) {
+
+        validateOrThrow(baseDataValidator -> {
+            
baseDataValidator.reset().parameter("startDate").value(startDate).notBlank();
+            
baseDataValidator.reset().parameter("endDate").value(endDate).notBlank();
+            
baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
+            
baseDataValidator.reset().parameter("locale").value(locale).notBlank();
+        });
+
+        if (startDate.isBefore(loan.getSubmittedOnDate())) {
+            throw new 
GeneralPlatformDomainRuleException("interest.pause.start.date.before.loan.start.date",
+                    String.format("Interest pause start date (%s) cannot be 
earlier than loan start date (%s).", startDate,
+                            loan.getSubmittedOnDate()),
+                    startDate, loan.getSubmittedOnDate());
+        }
+
+        if (endDate.isAfter(loan.getMaturityDate())) {
+            throw new 
GeneralPlatformDomainRuleException("interest.pause.end.date.after.loan.maturity.date",
 String
+                    .format("Interest pause end date (%s) cannot be later than 
loan maturity date (%s).", endDate, loan.getMaturityDate()),
+                    endDate, loan.getMaturityDate());
+        }
+
+        if (endDate.isBefore(startDate)) {
+            throw new 
GeneralPlatformDomainRuleException("interest.pause.end.date.before.start.date", 
String
+                    .format("Interest pause end date (%s) must not be before 
the interest pause start date (%s).", endDate, startDate),
+                    endDate, startDate);
+        }
+    }
+
+    private LocalDate parseDate(String date, String dateFormat, String locale) 
{
+        try {
+            DateTimeFormatter formatter = 
DateTimeFormatter.ofPattern(dateFormat, Locale.forLanguageTag(locale));
+            return LocalDate.parse(date, formatter);
+        } catch (DateTimeParseException e) {
+            throw new 
PlatformApiDataValidationException("validation.msg.invalid.date.format",
+                    String.format("Invalid date format. Provided: %s, Expected 
format: %s, Locale: %s", date, dateFormat, locale),
+                    e.getMessage(), e);
+        }
+    }
+
+    private void validateOrThrow(Consumer<DataValidatorBuilder> 
baseDataValidator) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors).resource("InterestPause");
+
+        baseDataValidator.accept(dataValidatorBuilder);
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new 
PlatformApiDataValidationException("validation.msg.validation.errors.exist", 
"Validation errors exist.",
+                    dataValidationErrors);
+        }
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 0ddebb1a6..4db8dedf8 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -113,6 +113,8 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
             + "and (:futureCharges = true or ls.fromDate < :tillDate or 
(ls.installmentNumber = (select min(lsi.installmentNumber) from 
LoanRepaymentScheduleInstallment lsi where lsi.loan.id = l.id and 
lsi.isDownPayment = false) and ls.fromDate = :tillDate))))";
     String FIND_LOANS_FOR_ADD_ACCRUAL = LOANS_FOR_ACCRUAL + "and 
(:futureCharges = true or ls.dueDate <= :tillDate)))";
 
+    String FIND_LOAN_BY_EXTERNAL_ID = "SELECT loan FROM Loan loan WHERE 
loan.externalId = :externalId";
+
     @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
     List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") 
LocalDate disbursementDate, @Param("groupId") Long groupId,
             @Param("loanType") Integer loanType);
@@ -247,4 +249,7 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
     @Query(FIND_LOANS_FOR_ADD_ACCRUAL)
     List<Loan> findLoansForAddAccrual(@Param("accountingType") Integer 
accountingType, @Param("tillDate") LocalDate tillDate,
             @Param("futureCharges") boolean futureCharges);
+
+    @Query(FIND_LOAN_BY_EXTERNAL_ID)
+    Optional<Loan> findByExternalId(@Param("externalId") ExternalId 
externalId);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
index 3c0f7526e..bac7cb5b4 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
@@ -49,6 +49,20 @@ public class LoanRepositoryWrapper {
         return this.findOneWithNotFoundDetection(id, false);
     }
 
+    @Transactional(readOnly = true)
+    public Loan findOneWithNotFoundDetection(final ExternalId externalId) {
+        return this.findOneWithNotFoundDetection(externalId, false);
+    }
+
+    @Transactional(readOnly = true)
+    public Loan findOneWithNotFoundDetection(final ExternalId externalId, 
boolean loadLazyCollections) {
+        final Loan loan = 
this.repository.findByExternalId(externalId).orElseThrow(() -> new 
LoanNotFoundException(externalId));
+        if (loadLazyCollections) {
+            loan.initializeLazyCollections();
+        }
+        return loan;
+    }
+
     @Transactional(readOnly = true)
     public Loan findOneWithNotFoundDetection(final Long id, boolean 
loadLazyCollections) {
         final Loan loan = this.repository.findById(id).orElseThrow(() -> new 
LoanNotFoundException(id));
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
index 7a462c004..21e4655d3 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
@@ -30,7 +30,8 @@ public enum LoanTermVariationType {
     GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"), //
     GRACE_ON_PRINCIPAL(8, "loanTermType.graceOnPrincipal"), //
     EXTEND_REPAYMENT_PERIOD(9, "loanTermType.extendRepaymentPeriod"), //
-    INTEREST_RATE_FROM_INSTALLMENT(10, 
"loanTermType.interestRateFromInstallment"); //
+    INTEREST_RATE_FROM_INSTALLMENT(10, 
"loanTermType.interestRateFromInstallment"), //
+    INTEREST_PAUSE(11, "loanTermType.interestPause"); //
 
     private final Integer value;
     private final String code;
@@ -82,6 +83,9 @@ public enum LoanTermVariationType {
             case 10:
                 enumeration = 
LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT;
             break;
+            case 11:
+                enumeration = LoanTermVariationType.INTEREST_PAUSE;
+            break;
         }
         return enumeration;
     }
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 d667825e9..0bf7209ff 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
 
 import java.util.List;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -49,4 +50,14 @@ public interface LoanTermVariationsRepository
             """)
     List<LoanTermVariationsData> 
findLoanTermVariationsByLoanIdAndTermType(@Param("loanId") long loanId, 
@Param("termType") int termType);
 
+    @Query("""
+            select new 
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData(
+                ltv.id, ltv.termType, ltv.termApplicableFrom, 
ltv.decimalValue, ltv.dateValue, ltv.isSpecificToInstallment
+            )
+            from LoanTermVariations ltv
+            where ltv.loan.externalId = :loanExternalId and ltv.termType = 
:termType
+            order by ltv.termApplicableFrom
+            """)
+    List<LoanTermVariationsData> 
findLoanTermVariationsByExternalLoanIdAndTermType(@Param("loanExternalId") 
ExternalId loanExternalId,
+            @Param("termType") int termType);
 }
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 85b30ee95..dba4bc6f5 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 @@ class AdvancedPaymentAllocationsJsonParserTest {
         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);
         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 d33aa1fe8..45138580b 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 @@ public class CreditAllocationsJsonParserTest {
         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);
         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 42878e296..40bfb22d8 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 @@ public class CenterImportHandler implements ImportHandler {
         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());
+                result.getProductId(), null, null, null, null, 
idempotencyKeyGenerator.create(), 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 2fa48085a..68558fa79 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 @@ public class GroupImportHandler implements ImportHandler {
         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());
+                result.getProductId(), null, null, null, null, 
idempotencyKeyGenerator.create(), 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 8316d71bd..3a3a2abcf 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,7 +429,8 @@ public class 
ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl implemen
             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.getCreditBureauId(), 
wrapper.getOrganisationCreditBureauId(), wrapper.getJobName(),
+                    wrapper.getLoanExternalId());
 
             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 20a7c2bc5..6556b50c2 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);
     }
 
     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 c8525e4de..7751d18aa 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 class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
 
             final JsonElement parsedCommand = 
this.fromApiJsonHelper.parse(overdueInstallment.toString());
             final JsonCommand command = 
JsonCommand.from(overdueInstallment.toString(), parsedCommand, 
this.fromApiJsonHelper, null, null,
-                    null, null, null, loanId, null, null, null, null, null, 
null, null);
+                    null, null, null, loanId, null, null, null, null, null, 
null, null, null);
             LoanOverdueDTO overdueDTO = 
applyChargeToOverdueLoanInstallment(loan, overdueInstallment.getChargeId(),
                     overdueInstallment.getPeriodNumber(), command);
             loan = overdueDTO.getLoan();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 48d5393f3..b7b9a955d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -64,6 +64,10 @@ import 
org.apache.fineract.portfolio.fund.domain.FundRepository;
 import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
 import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
 import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import 
org.apache.fineract.portfolio.interestpauses.service.InterestPauseReadPlatformService;
+import 
org.apache.fineract.portfolio.interestpauses.service.InterestPauseReadPlatformServiceImpl;
+import 
org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService;
+import 
org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformServiceImpl;
 import 
org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
@@ -81,6 +85,7 @@ import 
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanSchedu
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
 import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
 import 
org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
 import 
org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator;
 import 
org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
 import 
org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
@@ -456,4 +461,17 @@ public class LoanAccountConfiguration {
     public LoanRefundService loanRefundService(LoanRefundValidator 
loanRefundValidator) {
         return new LoanRefundService(loanRefundValidator);
     }
+
+    @Bean
+    @ConditionalOnMissingBean(InterestPauseReadPlatformService.class)
+    public InterestPauseReadPlatformService 
interestPauseReadPlatformService(LoanTermVariationsRepository 
loanTermVariationsRepository) {
+        return new 
InterestPauseReadPlatformServiceImpl(loanTermVariationsRepository);
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(InterestPauseWritePlatformService.class)
+    public InterestPauseWritePlatformService 
interestPauseWritePlatformService(LoanTermVariationsRepository 
loanTermVariationsRepository,
+            LoanRepositoryWrapper loanRepositoryWrapper) {
+        return new 
InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, 
loanRepositoryWrapper);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java
index 24b52b410..708425f92 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java
@@ -38,7 +38,7 @@ public class ShareAccountCommandsServiceImpl implements 
AccountsCommandsService
     public Object handleCommand(Long accountId, String command, String 
jsonBody) {
         final JsonElement parsedCommand = 
this.fromApiJsonHelper.parse(jsonBody);
         final JsonCommand jsonCommand = JsonCommand.from(jsonBody, 
parsedCommand, this.fromApiJsonHelper, null, null, null, null, null,
-                null, null, null, null, null, null, null, null);
+                null, null, null, null, null, null, null, null, null);
         if (ShareAccountApiConstants.APPROVE_COMMAND.equals(command)) {
             return approveShareAccount(accountId, jsonCommand);
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java
index fcc54c495..b9251e5f1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java
@@ -39,7 +39,7 @@ public class ShareProductCommandsServiceImpl implements 
ProductCommandsService {
     public Object handleCommand(Long productId, String command, String 
jsonBody) {
         final JsonElement parsedCommand = 
this.fromApiJsonHelper.parse(jsonBody);
         final JsonCommand jsonCommand = JsonCommand.from(jsonBody, 
parsedCommand, this.fromApiJsonHelper, null, null, null, null, null,
-                null, null, null, null, null, null, null, null);
+                null, null, null, null, null, null, null, null, null);
         if 
(ShareProductApiConstants.PREIEW_DIVIDENDS_COMMAND_STRING.equals(command)) {
             return null;
         } else if 
(ShareProductApiConstants.POST_DIVIDENdS_COMMAND_STRING.equals(command)) {
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index da814c8ad..673a92f1f 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -179,4 +179,5 @@
     <include file="parts/0158_create_acc_gl_journal_entry_annual_summary.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0159_trial_balance_summary_with_asset_owner_year_end_summary.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0160_add_acc_product_mapping_product_id_index.xml" 
relativeToChangelogFile="true" />
+    <include file="parts/0161_add_loan_external_id_to_commands.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0161_add_loan_external_id_to_commands.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0161_add_loan_external_id_to_commands.xml
new file mode 100644
index 000000000..bc3821212
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0161_add_loan_external_id_to_commands.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet id="1" author="fineract">
+        <addColumn tableName="m_portfolio_command_source">
+            <column name="loan_external_id" type="VARCHAR(100)">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
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 a422ef191..8a81e104f 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
@@ -41,8 +41,8 @@ public class CommandHandlerProviderStepDefinitions implements 
En {
         });
 
         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));
+            this.result = commandHandler.processCommand(
+                    JsonCommand.fromExistingCommand(id, 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 c4d694e9d..9e494ff66 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 class CommandServiceStepDefinitions implements En {
 
         public DummyCommand() {
             super(null, null, null, null, null, null, null, null, null, null, 
"{}", null, null, null, null, null, null,
-                    UUID.randomUUID().toString());
+                    UUID.randomUUID().toString(), null);
         }
 
         @Override
@@ -124,7 +124,7 @@ public class CommandServiceStepDefinitions implements En {
             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.getOrganisationCreditBureauId(), 
wrapper.getJobName(), wrapper.getLoanExternalId());
 
             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 6e6b0af85..2e3af19d9 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 class IdempotencyKeyResolverTest {
     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, idk, 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 26e545af6..b83c3729d 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 @@ public class 
ThitsaWorksCreditBureauIntegrationWritePlatformServiceImplTest {
         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);
     }
 
     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
new file mode 100644
index 000000000..347b72f23
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java
@@ -0,0 +1,315 @@
+/**
+ * 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.integrationtests;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.PaymentAllocationOrder;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Slf4j
+@ExtendWith({ LoanTestLifecycleExtension.class })
+public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(LoanInterestPauseApiTest.class);
+
+    private static RequestSpecification REQUEST_SPEC;
+    private static ResponseSpecification RESPONSE_SPEC;
+    private static ResponseSpecification RESPONSE_SPEC_403;
+    private static LoanTransactionHelper LOAN_TRANSACTIONAL_HELPER;
+    private static LoanTransactionHelper LOAN_TRANSACTION_HELPER_403;
+    private static AccountHelper accountHelper;
+    private static PostClientsResponse client;
+    private static Integer loanProductId;
+    private static Long loanId;
+    private static Long nonExistLoanId = 99999L;
+    private static String externalId;
+    private static String nonExistExternalId = 
"7c4fb86f-a778-4d02-b7a8-ec3ec98941fa";
+
+    @BeforeAll
+    public static void setupTests() {
+        Utils.initializeRESTAssured();
+        REQUEST_SPEC = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        REQUEST_SPEC.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        RESPONSE_SPEC = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        RESPONSE_SPEC_403 = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        LOAN_TRANSACTIONAL_HELPER = new LoanTransactionHelper(REQUEST_SPEC, 
RESPONSE_SPEC);
+        LOAN_TRANSACTION_HELPER_403 = new LoanTransactionHelper(REQUEST_SPEC, 
RESPONSE_SPEC_403);
+        accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC);
+        ClientHelper clientHelper = new ClientHelper(REQUEST_SPEC, 
RESPONSE_SPEC);
+        client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+        final Account assetAccount = accountHelper.createAssetAccount();
+        final Account incomeAccount = accountHelper.createIncomeAccount();
+        final Account expenseAccount = accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = 
accountHelper.createLiabilityAccount();
+
+        externalId = UUID.randomUUID().toString();
+
+        loanProductId = createLoanProduct("500", "15", "4", true, "25", true, 
LoanScheduleType.PROGRESSIVE,
+                LoanScheduleProcessingType.HORIZONTAL, assetAccount, 
incomeAccount, expenseAccount, overpaymentAccount);
+
+        final PostLoansResponse loanResponse = 
applyForLoanApplication(client.getClientId(), loanProductId, 
BigDecimal.valueOf(500.0), 45,
+                15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023", 
externalId);
+
+        loanId = loanResponse.getLoanId();
+
+        Assertions.assertNotNull(loanProductId, "Loan Product ID should not be 
null after creation");
+        Assertions.assertNotNull(loanId, "Loan ID should not be null after 
creation");
+        Assertions.assertNotNull(externalId, "External Loan ID should not be 
null after creation");
+    }
+
+    @Test
+    public void testCreateInterestPauseByLoanId_validRequest_shouldSucceed() {
+        PostLoansLoanIdTransactionsResponse response = 
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", 
"2023-02-05",
+                "yyyy-MM-dd", "en", loanId);
+
+        Assertions.assertNotNull(response);
+        Assertions.assertNotNull(response.getResourceId());
+    }
+
+    @Test
+    public void 
testCreateInterestPauseByLoanId_endDateBeforeStartDate_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-05", 
"2024-12-01", "yyyy-MM-dd", "en", loanId);
+        } catch (Exception e) {
+            String responseBody = e.getMessage();
+            Assertions.assertNotNull(responseBody, "Response body should not 
be null");
+            
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 
testCreateInterestPauseByLoanId_startDateBeforeLoanStart_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2022-12-01", 
"2024-12-05", "yyyy-MM-dd", "en", loanId);
+        } catch (Exception e) {
+            String responseBody = e.getMessage();
+            Assertions.assertNotNull(responseBody, "Response body should not 
be null");
+            
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 
testCreateInterestPauseByLoanId_endDateAfterLoanMaturity_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByLoanId("2024-12-01", 
"2025-12-05", "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.after.loan.maturity.date"),
+                    "Response should contain the validation error message for 
end date after loan maturity date");
+        }
+    }
+
+    @Test
+    public void 
testRetrieveInterestPausesByLoanId_noPauses_shouldReturnEmpty() {
+        String response = 
LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(nonExistLoanId);
+
+        Assertions.assertNotNull(response, "Response should not be null");
+        Assertions.assertFalse(response.contains("id"));
+        Assertions.assertFalse(response.contains("startDate"));
+        Assertions.assertFalse(response.contains("endDate"));
+    }
+
+    @Test
+    public void testRetrieveInterestPausesByLoanId_shouldReturnData() {
+        LOAN_TRANSACTIONAL_HELPER.createInterestPauseByLoanId("2023-01-01", 
"2023-02-05", "yyyy-MM-dd", "en", loanId);
+
+        String response = 
LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByLoanId(loanId);
+
+        Assertions.assertNotNull(response, "Response should not be null");
+        Assertions.assertTrue(response.contains("2023-01-01"));
+        Assertions.assertTrue(response.contains("2023-02-05"));
+    }
+
+    @Test
+    public void 
testCreateInterestPauseByExternalLoanId_validRequest_shouldSucceed() {
+        PostLoansLoanIdTransactionsResponse response = 
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", 
"2023-02-05",
+                "yyyy-MM-dd", "en", externalId);
+
+        Assertions.assertNotNull(response);
+        Assertions.assertNotNull(response.getResourceId());
+    }
+
+    @Test
+    public void 
testCreateInterestPauseByExternalLoanId_endDateBeforeStartDate_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2024-12-05", 
"2024-12-01", "yyyy-MM-dd", "en", externalId);
+        } catch (Exception e) {
+            String responseBody = e.getMessage();
+            Assertions.assertNotNull(responseBody, "Response body should not 
be null");
+            
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 
testCreateInterestPauseByExternalLoanId_startDateBeforeLoanStart_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2022-12-01", 
"2024-12-05", "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 
testCreateInterestPauseByExternalLoanId_endDateAfterLoanMaturity_shouldFail() {
+        try {
+            
LOAN_TRANSACTION_HELPER_403.createInterestPauseByExternalId("2024-12-01", 
"2025-12-05", "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.after.loan.maturity.date"),
+                    "Response should contain the validation error message for 
end date after loan maturity date");
+        }
+    }
+
+    @Test
+    public void 
testRetrieveInterestPausesByExternalLoanId_noPauses_shouldReturnEmpty() {
+        String response = 
LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(nonExistExternalId);
+
+        Assertions.assertNotNull(response, "Response should not be null");
+        Assertions.assertFalse(response.contains("id"));
+        Assertions.assertFalse(response.contains("startDate"));
+        Assertions.assertFalse(response.contains("endDate"));
+    }
+
+    @Test
+    public void testRetrieveInterestPausesByExternalLoanId_shouldReturnData() {
+        
LOAN_TRANSACTIONAL_HELPER.createInterestPauseByExternalId("2023-01-01", 
"2023-02-05", "yyyy-MM-dd", "en", externalId);
+
+        String response = 
LOAN_TRANSACTIONAL_HELPER.retrieveInterestPauseByExternalId(externalId);
+
+        Assertions.assertNotNull(response, "Response should not be null");
+        Assertions.assertTrue(response.contains("2023-01-01"));
+        Assertions.assertTrue(response.contains("2023-02-05"));
+    }
+
+    private static Integer createLoanProduct(final String principal, final 
String repaymentAfterEvery, final String numberOfRepayments,
+            boolean downPaymentEnabled, String downPaymentPercentage, boolean 
autoPayForDownPayment, LoanScheduleType loanScheduleType,
+            LoanScheduleProcessingType loanScheduleProcessingType, final 
Account... accounts) {
+        AdvancedPaymentData defaultAllocation = 
createDefaultPaymentAllocation();
+        final String loanProductJSON = new 
LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
+                
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
+                .withEnableDownPayment(downPaymentEnabled, 
downPaymentPercentage, autoPayForDownPayment).withinterestRatePerPeriod("0")
+                .withInterestRateFrequencyTypeAsMonths()
+                
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+                
.addAdvancedPaymentAllocation(defaultAllocation).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                
.withInterestTypeAsDecliningBalance().withMultiDisburse().withDisallowExpectedDisbursements(true)
+                
.withLoanScheduleType(loanScheduleType).withLoanScheduleProcessingType(loanScheduleProcessingType).withDaysInMonth("30")
+                .withDaysInYear("365").withMoratorium("0", "0").build(null);
+        return LOAN_TRANSACTIONAL_HELPER.getLoanProductId(loanProductJSON);
+    }
+
+    private static AdvancedPaymentData createDefaultPaymentAllocation() {
+        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+        advancedPaymentData.setTransactionType("DEFAULT");
+        
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
+
+        List<PaymentAllocationOrder> paymentAllocationOrders = 
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+                PaymentAllocationType.PAST_DUE_FEE, 
PaymentAllocationType.PAST_DUE_PRINCIPAL, 
PaymentAllocationType.PAST_DUE_INTEREST,
+                PaymentAllocationType.DUE_PENALTY, 
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+                PaymentAllocationType.DUE_INTEREST, 
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+                PaymentAllocationType.IN_ADVANCE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+        return advancedPaymentData;
+    }
+
+    private static List<PaymentAllocationOrder> 
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
+        AtomicInteger integer = new AtomicInteger(1);
+        return Arrays.stream(paymentAllocationTypes).map(pat -> {
+            PaymentAllocationOrder paymentAllocationOrder = new 
PaymentAllocationOrder();
+            paymentAllocationOrder.setPaymentAllocationRule(pat.name());
+            paymentAllocationOrder.setOrder(integer.getAndIncrement());
+            return paymentAllocationOrder;
+        }).collect(Collectors.toList());
+    }
+
+    private static PostLoansResponse applyForLoanApplication(final Long 
clientId, final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate, final String externalId) {
+        return applyForLoanApplication(clientId, loanProductId, principal, 
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+                interestRate, expectedDisbursementDate, submittedOnDate, 
LoanScheduleProcessingType.HORIZONTAL, externalId);
+    }
+
+    private static PostLoansResponse applyForLoanApplication(final Long 
clientId, final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType,
+            final String externalId) {
+        LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        return applyForLoanApplication(clientId, loanProductId, principal, 
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+                interestRate, expectedDisbursementDate, submittedOnDate,
+                
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY,
 loanScheduleProcessingType.name(),
+                externalId);
+    }
+
+    private static PostLoansResponse applyForLoanApplication(final Long 
clientId, final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate, String transactionProcessorCode,
+            String loanScheduleProcessingType, final String externalId) {
+        LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        return LOAN_TRANSACTIONAL_HELPER.applyLoan(new 
PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
+                
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
+                
.transactionProcessingStrategyCode(transactionProcessorCode).locale("en").submittedOnDate(submittedOnDate)
+                
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
+                
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
+                
.numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal)
+                
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType).externalId(externalId)
+                .maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index c00b13759..174886399 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
@@ -116,6 +116,7 @@ public class LoanTransactionHelper extends IntegrationTest {
     private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
     private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
     private static final String MAKE_REPAYMENT_COMMAND = "repayment";
+    private static final String INTEREST_PAUSE_COMMAND = "interestpause";
     private static final String UNDO = "undo";
     private static final String LOANCHARGE_REFUND_REPAYMENT_COMMAND = 
"chargeRefund";
     private static final String CREDIT_BALANCE_REFUND_COMMAND = 
"creditBalanceRefund";
@@ -607,6 +608,34 @@ public class LoanTransactionHelper extends IntegrationTest 
{
         return ok(newFineract(user, 
pass).loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
     }
 
+    public PostLoansLoanIdTransactionsResponse 
createInterestPauseByLoanId(final String startDate, final String endDate,
+            final String dateFormat, final String locale, final Long loanID) {
+        log.info("Creating interest pause for Loan {} from {} to {} with 
dateFormat {} and locale {}", loanID, startDate, endDate,
+                dateFormat, locale);
+        String body = getInterestPauseBodyAsJSON(startDate, endDate, 
dateFormat, locale);
+        return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, 
loanID), body);
+    }
+
+    public PostLoansLoanIdTransactionsResponse 
createInterestPauseByExternalId(final String startDate, final String endDate,
+            final String dateFormat, final String locale, final String 
externalId) {
+        log.info("Creating interest pause for Loan {} from {} to {} with 
dateFormat {} and locale {}", externalId, startDate, endDate,
+                dateFormat, locale);
+        String body = getInterestPauseBodyAsJSON(startDate, endDate, 
dateFormat, locale);
+        return postLoanTransaction(createInterestPause(INTEREST_PAUSE_COMMAND, 
externalId), body);
+    }
+
+    public String retrieveInterestPauseByLoanId(final Long loanID) {
+        log.info("Retrieving interest pauses for Loan ID {}", loanID);
+        String url = retrieveInterestPause(loanID);
+        return Utils.performServerGet(requestSpec, responseSpec, url);
+    }
+
+    public String retrieveInterestPauseByExternalId(final String externalId) {
+        log.info("Retrieving interest pauses for External ID {}", externalId);
+        String url = retrieveInterestPause(externalId);
+        return Utils.performServerGet(requestSpec, responseSpec, url);
+    }
+
     public PostLoansLoanIdTransactionsResponse makeInterestPaymentWaiver(final 
Long loanId,
             final PostLoansLoanIdTransactionsRequest request) {
         return ok(fineract().loanTransactions.executeLoanTransaction(loanId, 
request, "interestPaymentWaiver"));
@@ -1218,6 +1247,16 @@ public class LoanTransactionHelper extends 
IntegrationTest {
         return new Gson().toJson(map);
     }
 
+    private String getInterestPauseBodyAsJSON(final String startDate, final 
String endDate, final String dateFormat, final String locale) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("startDate", startDate);
+        map.put("endDate", endDate);
+        map.put("dateFormat", dateFormat);
+        map.put("locale", locale);
+
+        return new Gson().toJson(map);
+    }
+
     private String getAdjustTransactionJsonBody(String date, String amount) {
         final HashMap<String, String> map = new HashMap<>();
         map.put("transactionDate", date);
@@ -1411,6 +1450,23 @@ public class LoanTransactionHelper extends 
IntegrationTest {
         return "/fineract-provider/api/v1/loans/" + loanID + 
"/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
     }
 
+    private String createInterestPause(final String command, final Long 
loanID) {
+        return "/fineract-provider/api/v1/loans/" + loanID + 
"/interest-pauses?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String createInterestPause(final String command, final String 
externalId) {
+        return "/fineract-provider/api/v1/loans/external-id/" + externalId + 
"/interest-pauses?command=" + command + "&"
+                + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String retrieveInterestPause(final Long loanID) {
+        return "/fineract-provider/api/v1/loans/" + loanID + 
"/interest-pauses?" + Utils.TENANT_IDENTIFIER;
+    }
+
+    private String retrieveInterestPause(final String externalId) {
+        return "/fineract-provider/api/v1/loans/external-id/" + externalId + 
"/interest-pauses?" + Utils.TENANT_IDENTIFIER;
+    }
+
     private String createInteroperationLoanTransactionURL(final String 
accountNo) {
         return "/fineract-provider/api/v1/interoperation/transactions/" + 
accountNo + "/loanrepayment";
     }

Reply via email to