FINERACT-1971: Added reamortization foundational work
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 edbb8f8..ac4955d 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
@@ -3672,6 +3672,22 @@
         return this;
     }
 
+    public CommandWrapperBuilder reAmortize(final Long loanId) {
+        this.actionName = "REAMORTIZE";
+        this.entityName = "LOAN";
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions?command=reAmortize";
+        return this;
+    }
+
+    public CommandWrapperBuilder undoReAmortize(final Long loanId) {
+        this.actionName = "UNDO_REAMORTIZE";
+        this.entityName = "LOAN";
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions?command=undoReAmortize";
+        return this;
+    }
+
     public CommandWrapperBuilder createDelinquencyAction(final Long loanId) {
         this.actionName = "CREATE";
         this.entityName = "DELINQUENCY_ACTION";
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java
new file mode 100644
index 0000000..5ef3744
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java
@@ -0,0 +1,26 @@
+/**
+ * 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.loanaccount.api;
+
+public interface LoanReAmortizationApiConstants {
+
+    String localeParameterName = "locale";
+    String dateFormatParameterName = "dateFormat";
+    String externalIdParameterName = "externalId";
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
index c3985e4..603d7b9 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -57,6 +57,7 @@
     private final boolean chargeoff;
     private final boolean downPayment;
     private final boolean reAge;
+    private final boolean reAmortize;
 
     public LoanTransactionEnumData(final Long id, final String code, final String value) {
         this.id = id;
@@ -88,6 +89,7 @@
         this.chargeoff = Long.valueOf(27).equals(this.id);
         this.downPayment = Long.valueOf(28).equals(this.id);
         this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
+        this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
     }
 
     public boolean isRepaymentType() {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 811baee..7df92ed 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -685,6 +685,10 @@
         return getTypeOf().isReAge() && isNotReversed();
     }
 
+    public boolean isReAmortize() {
+        return getTypeOf().isReAmortize() && isNotReversed();
+    }
+
     public boolean isIdentifiedBy(final Long identifier) {
         return getId().equals(identifier);
     }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index a057300..3e33114 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -61,7 +61,7 @@
     CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment"), //
     CHARGE_OFF(27, "loanTransactionType.chargeOff"), //
     DOWN_PAYMENT(28, "loanTransactionType.downPayment"), //
-    REAGE(29, "loanTransactionType.reAge");
+    REAGE(29, "loanTransactionType.reAge"), REAMORTIZE(30, "loanTransactionType.reAmortize");
 
     private final Integer value;
     private final String code;
@@ -106,6 +106,7 @@
             case 27 -> LoanTransactionType.CHARGE_OFF;
             case 28 -> LoanTransactionType.DOWN_PAYMENT;
             case 29 -> LoanTransactionType.REAGE;
+            case 30 -> LoanTransactionType.REAMORTIZE;
             default -> LoanTransactionType.INVALID;
         };
     }
@@ -198,6 +199,10 @@
         return this.equals(LoanTransactionType.REAGE);
     }
 
+    public boolean isReAmortize() {
+        return this.equals(LoanTransactionType.REAMORTIZE);
+    }
+
     public boolean isDownPayment() {
         return this.equals(LoanTransactionType.DOWN_PAYMENT);
     }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
index efcae19..a30de5e 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -315,6 +315,8 @@
                     LoanTransactionType.DOWN_PAYMENT.getCode(), "Down Payment");
             case REAGE -> new LoanTransactionEnumData(LoanTransactionType.REAGE.getValue().longValue(), LoanTransactionType.REAGE.getCode(),
                     "Re-age");
+            case REAMORTIZE -> new LoanTransactionEnumData(LoanTransactionType.REAMORTIZE.getValue().longValue(),
+                    LoanTransactionType.REAMORTIZE.getCode(), "Re-amortize");
         };
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanReAmortizeTransactionBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanReAmortizeTransactionBusinessEvent.java
new file mode 100644
index 0000000..4ad0a2d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanReAmortizeTransactionBusinessEvent.java
@@ -0,0 +1,36 @@
+/**
+ * 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.infrastructure.event.business.domain.loan.transaction.reamortization;
+
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class LoanReAmortizeTransactionBusinessEvent extends LoanTransactionBusinessEvent {
+
+    private static final String TYPE = "LoanReAmortizeTransactionBusinessEvent";
+
+    public LoanReAmortizeTransactionBusinessEvent(LoanTransaction value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanUndoReAmortizeTransactionBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanUndoReAmortizeTransactionBusinessEvent.java
new file mode 100644
index 0000000..30cf19e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reamortization/LoanUndoReAmortizeTransactionBusinessEvent.java
@@ -0,0 +1,36 @@
+/**
+ * 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.infrastructure.event.business.domain.loan.transaction.reamortization;
+
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class LoanUndoReAmortizeTransactionBusinessEvent extends LoanTransactionBusinessEvent {
+
+    private static final String TYPE = "LoanUndoReAmortizeTransactionBusinessEvent";
+
+    public LoanUndoReAmortizeTransactionBusinessEvent(LoanTransaction value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index b479448..98fd503 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -83,6 +83,8 @@
     public static final String DOWN_PAYMENT = "downPayment";
     public static final String UNDO_REAGE = "undoReAge";
     public static final String REAGE = "reAge";
+    public static final String REAMORTIZE = "reAmortize";
+    public static final String UNDO_REAMORTIZE = "undoReAmortize";
     private final Set<String> responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId",
             LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
 
@@ -483,6 +485,10 @@
             commandRequest = builder.reAge(resolvedLoanId).build();
         } else if (CommandParameterUtil.is(commandParam, UNDO_REAGE)) {
             commandRequest = builder.undoReAge(resolvedLoanId).build();
+        } else if (CommandParameterUtil.is(commandParam, REAMORTIZE)) {
+            commandRequest = builder.reAmortize(resolvedLoanId).build();
+        } else if (CommandParameterUtil.is(commandParam, UNDO_REAMORTIZE)) {
+            commandRequest = builder.undoReAmortize(resolvedLoanId).build();
         }
 
         if (commandRequest == null) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanReAmortizationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanReAmortizationCommandHandler.java
new file mode 100644
index 0000000..50dc537
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanReAmortizationCommandHandler.java
@@ -0,0 +1,50 @@
+/**
+ * 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.loanaccount.handler.loan.reamortization;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.reamortization.LoanReAmortizationServiceImpl;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.orm.jpa.JpaSystemException;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "LOAN", action = "REAMORTIZE")
+public class LoanReAmortizationCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanReAmortizationServiceImpl loanReAmortizationService;
+    private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        try {
+            return loanReAmortizationService.reAmortize(command.getLoanId(), command);
+        } catch (final JpaSystemException | DataIntegrityViolationException dve) {
+            dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.reAmortize",
+                    "Error while handling re-amortizing");
+            return CommandProcessingResult.empty();
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanUndoReAmortizationCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanUndoReAmortizationCommandHandler.java
new file mode 100644
index 0000000..02012e1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/loan/reamortization/LoanUndoReAmortizationCommandHandler.java
@@ -0,0 +1,50 @@
+/**
+ * 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.loanaccount.handler.loan.reamortization;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.reamortization.LoanReAmortizationServiceImpl;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.orm.jpa.JpaSystemException;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "LOAN", action = "UNDO_REAMORTIZE")
+public class LoanUndoReAmortizationCommandHandler implements NewCommandSourceHandler {
+
+    private final LoanReAmortizationServiceImpl loanReAmortizationService;
+    private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
+
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        try {
+            return loanReAmortizationService.undoReAmortize(command.getLoanId(), command);
+        } catch (final JpaSystemException | DataIntegrityViolationException dve) {
+            dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.undoReAmortize",
+                    "Error while handling undo re-amortizing");
+            return CommandProcessingResult.empty();
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/listener/LoanTransactionDelinquencyRecalculationListener.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/listener/LoanTransactionDelinquencyRecalculationListener.java
index 9ee55b1..2d0c27a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/listener/LoanTransactionDelinquencyRecalculationListener.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/listener/LoanTransactionDelinquencyRecalculationListener.java
@@ -24,6 +24,8 @@
 import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanReAgeTransactionBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanUndoReAgeTransactionBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reamortization.LoanReAmortizeTransactionBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reamortization.LoanUndoReAmortizeTransactionBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -39,7 +41,9 @@
     // use-cases
     private static final List<Class<? extends LoanTransactionBusinessEvent>> SUPPORTED_EVENT_TYPES = List.of(//
             LoanReAgeTransactionBusinessEvent.class, //
-            LoanUndoReAgeTransactionBusinessEvent.class //
+            LoanUndoReAgeTransactionBusinessEvent.class, //
+            LoanReAmortizeTransactionBusinessEvent.class, //
+            LoanUndoReAmortizeTransactionBusinessEvent.class //
     );//
 
     private final LoanAccountDomainService loanAccountDomainService;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java
new file mode 100644
index 0000000..4909adb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java
@@ -0,0 +1,140 @@
+/**
+ * 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.loanaccount.service.reamortization;
+
+import static java.math.BigDecimal.ZERO;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reamortization.LoanReAmortizeTransactionBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reamortization.LoanUndoReAmortizeTransactionBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.api.LoanReAmortizationApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class LoanReAmortizationServiceImpl {
+
+    private final LoanAssembler loanAssembler;
+    private final LoanReAmortizationValidator reAmortizationValidator;
+    private final ExternalIdFactory externalIdFactory;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanTransactionRepository loanTransactionRepository;
+
+    public CommandProcessingResult reAmortize(Long loanId, JsonCommand command) {
+        Loan loan = loanAssembler.assembleFrom(loanId);
+        reAmortizationValidator.validateReAmortize(loan, command);
+
+        Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put(LoanReAmortizationApiConstants.localeParameterName, command.locale());
+        changes.put(LoanReAmortizationApiConstants.dateFormatParameterName, command.dateFormat());
+
+        LoanTransaction reAmortizeTransaction = createReAmortizeTransaction(loan, command);
+        loanTransactionRepository.saveAndFlush(reAmortizeTransaction);
+
+        // delinquency recalculation will be triggered by the event in a decoupled way via a listener
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanReAmortizeTransactionBusinessEvent(reAmortizeTransaction));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(reAmortizeTransaction.getId()) //
+                .withEntityExternalId(reAmortizeTransaction.getExternalId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(command.getLoanId()) //
+                .with(changes).build();
+    }
+
+    public CommandProcessingResult undoReAmortize(Long loanId, JsonCommand command) {
+        Loan loan = loanAssembler.assembleFrom(loanId);
+        reAmortizationValidator.validateUndoReAmortize(loan, command);
+
+        Map<String, Object> changes = new LinkedHashMap<>();
+        changes.put(LoanReAmortizationApiConstants.localeParameterName, command.locale());
+        changes.put(LoanReAmortizationApiConstants.dateFormatParameterName, command.dateFormat());
+
+        LoanTransaction reAmortizeTransaction = findLatestNonReversedReAmortizeTransaction(loan);
+        if (reAmortizeTransaction == null) {
+            // TODO: when validations implemented; throw exception if there isn't a reamortize transaction available
+        }
+        reverseReAmortizeTransaction(reAmortizeTransaction, command);
+        loanTransactionRepository.saveAndFlush(reAmortizeTransaction);
+
+        // delinquency recalculation will be triggered by the event in a decoupled way via a listener
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoReAmortizeTransactionBusinessEvent(reAmortizeTransaction));
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(reAmortizeTransaction.getId()) //
+                .withEntityExternalId(reAmortizeTransaction.getExternalId()) //
+                .withOfficeId(loan.getOfficeId()) //
+                .withClientId(loan.getClientId()) //
+                .withGroupId(loan.getGroupId()) //
+                .withLoanId(command.getLoanId()) //
+                .with(changes).build();
+    }
+
+    private void reverseReAmortizeTransaction(LoanTransaction reAmortizeTransaction, JsonCommand command) {
+        ExternalId reversalExternalId = externalIdFactory.createFromCommand(command,
+                LoanReAmortizationApiConstants.externalIdParameterName);
+        reAmortizeTransaction.reverse(reversalExternalId);
+        reAmortizeTransaction.manuallyAdjustedOrReversed();
+    }
+
+    private LoanTransaction findLatestNonReversedReAmortizeTransaction(Loan loan) {
+        return loan.getLoanTransactions().stream() //
+                .filter(LoanTransaction::isNotReversed) //
+                .filter(LoanTransaction::isReAmortize) //
+                .max(Comparator.comparing(LoanTransaction::getTransactionDate)) //
+                .orElse(null);
+    }
+
+    private LoanTransaction createReAmortizeTransaction(Loan loan, JsonCommand command) {
+        ExternalId txExternalId = externalIdFactory.createFromCommand(command, LoanReAmortizationApiConstants.externalIdParameterName);
+
+        // reamortize transaction date is always the current business date
+        LocalDate transactionDate = DateUtils.getBusinessLocalDate();
+
+        // in case of a reamortize transaction, only the outstanding principal amount until the business date is
+        // considered
+        Money txPrincipal = loan.getTotalPrincipalOutstandingUntil(transactionDate);
+        BigDecimal txPrincipalAmount = txPrincipal.getAmount();
+
+        return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.REAMORTIZE.getValue(), transactionDate, txPrincipalAmount,
+                txPrincipalAmount, ZERO, ZERO, ZERO, null, false, null, txExternalId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
new file mode 100644
index 0000000..643c260
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
@@ -0,0 +1,35 @@
+/**
+ * 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.loanaccount.service.reamortization;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.stereotype.Component;
+
+@Component
+public class LoanReAmortizationValidator {
+
+    public void validateReAmortize(Loan loan, JsonCommand command) {
+        // TODO: implement
+    }
+
+    public void validateUndoReAmortize(Loan loan, JsonCommand command) {
+        // TODO: implement
+    }
+}
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 40e78b8..5b640f4 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
@@ -156,4 +156,5 @@
     <include file="parts/0134_transaction_summary_with_asset_owner_report_down_payment_amount_fix.xml" relativeToChangelogFile="true" />
     <include file="parts/0135_add_external_event_for_loan_reaging.xml" relativeToChangelogFile="true" />
     <include file="parts/0136_loan_reaging_parameters.xml" relativeToChangelogFile="true" />
+    <include file="parts/0137_add_external_event_for_loan_reamortization.xml" relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0137_add_external_event_for_loan_reamortization.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0137_add_external_event_for_loan_reamortization.xml
new file mode 100644
index 0000000..1579189
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0137_add_external_event_for_loan_reamortization.xml
@@ -0,0 +1,37 @@
+<?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.1.xsd">
+    <changeSet author="fineract" id="1">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanReAmortizeTransactionBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanUndoReAmortizeTransactionBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
index 76e2256..8c30bda 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
@@ -99,7 +99,8 @@
                 "LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
                 "LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
                 "LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent",
-                "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent");
+                "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent",
+                "LoanUndoReAmortizeTransactionBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -180,7 +181,8 @@
                 "LoanRescheduledDueAdjustScheduleBusinessEvent", "LoanOwnershipTransferBusinessEvent", "LoanAccountSnapshotBusinessEvent",
                 "LoanTransactionDownPaymentPostBusinessEvent", "LoanTransactionDownPaymentPreBusinessEvent",
                 "LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent",
-                "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent");
+                "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent",
+                "LoanUndoReAmortizeTransactionBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 375fdc2..a3073f7 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -375,10 +375,21 @@
         loanTransactionHelper.reAge(loanId, request);
     }
 
+    protected void reAmortizeLoan(Long loanId) {
+        PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest();
+        request.setDateFormat(DATETIME_PATTERN);
+        request.setLocale("en");
+        loanTransactionHelper.reAmortize(loanId, request);
+    }
+
     protected void undoReAgeLoan(Long loanId) {
         loanTransactionHelper.undoReAge(loanId, new PostLoansLoanIdTransactionsRequest());
     }
 
+    protected void undoReAmortizeLoan(Long loanId) {
+        loanTransactionHelper.undoReAmortize(loanId, new PostLoansLoanIdTransactionsRequest());
+    }
+
     protected void verifyLastClosedBusinessDate(Long loanId, String lastClosedBusinessDate) {
         GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
         Assertions.assertNotNull(loanDetails.getLastClosedBusinessDate());
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index 2b8b9e1..1196b72 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
@@ -510,6 +510,16 @@
         loanUndoReAgeTransactionBusinessEvent.put("enabled", false);
         defaults.add(loanUndoReAgeTransactionBusinessEvent);
 
+        Map<String, Object> loanReAmortizeTransactionBusinessEvent = new HashMap<>();
+        loanReAmortizeTransactionBusinessEvent.put("type", "LoanReAmortizeTransactionBusinessEvent");
+        loanReAmortizeTransactionBusinessEvent.put("enabled", false);
+        defaults.add(loanReAmortizeTransactionBusinessEvent);
+
+        Map<String, Object> loanUndoReAmortizeTransactionBusinessEvent = new HashMap<>();
+        loanUndoReAmortizeTransactionBusinessEvent.put("type", "LoanUndoReAmortizeTransactionBusinessEvent");
+        loanUndoReAmortizeTransactionBusinessEvent.put("enabled", false);
+        defaults.add(loanUndoReAmortizeTransactionBusinessEvent);
+
         return defaults;
 
     }
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 e61438c..9f06c03 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
@@ -593,10 +593,18 @@
         return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "reAge"));
     }
 
+    public PostLoansLoanIdTransactionsResponse reAmortize(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
+        return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "reAmortize"));
+    }
+
     public PostLoansLoanIdTransactionsResponse undoReAge(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
         return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "undoReAge"));
     }
 
+    public PostLoansLoanIdTransactionsResponse undoReAmortize(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
+        return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "undoReAmortize"));
+    }
+
     public PutChargeTransactionChangesResponse undoWaiveLoanCharge(final Long loanId, final Long transactionId,
             final PutChargeTransactionChangesRequest request) {
         log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------");
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
new file mode 100644
index 0000000..fd39469
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
@@ -0,0 +1,184 @@
+/**
+ * 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.loan.reamortization;
+
+import java.math.BigDecimal;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.jupiter.api.Test;
+
+public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
+
+    @Test
+    public void test_LoanReAmortizeTransaction_Works() {
+        AtomicLong createdLoanId = new AtomicLong();
+
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 1;
+            int repaymentEvery = 1;
+
+            // Create Loan Product
+            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
+                    .numberOfRepayments(numberOfRepayments) //
+                    .repaymentEvery(repaymentEvery) //
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
+
+            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
+            Long loanProductId = loanProductResponse.getResourceId();
+
+            // Apply and Approve Loan
+            double amount = 1250.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
+
+            PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
+
+            PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                    approveLoanRequest(amount, "01 January 2023"));
+
+            Long loanId = approvedLoanResult.getLoanId();
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(1250.0, false, "01 February 2023") //
+            );
+
+            createdLoanId.set(loanId);
+        });
+
+        runAt("02 February 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // create re-amortize transaction
+            reAmortizeLoan(loanId);
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(1250.0, "Re-amortize", "02 February 2023") //
+            );
+
+            // TODO: verify installments when schedule generation is implemented
+        });
+    }
+
+    @Test
+    public void test_LoanUndoReAmortizeTransaction_Works() {
+        AtomicLong createdLoanId = new AtomicLong();
+
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 1;
+            int repaymentEvery = 1;
+
+            // Create Loan Product
+            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
+                    .numberOfRepayments(numberOfRepayments) //
+                    .repaymentEvery(repaymentEvery) //
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
+
+            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
+            Long loanProductId = loanProductResponse.getResourceId();
+
+            // Apply and Approve Loan
+            double amount = 1250.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
+
+            PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
+
+            PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                    approveLoanRequest(amount, "01 January 2023"));
+
+            Long loanId = approvedLoanResult.getLoanId();
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0, null, "01 January 2023"), //
+                    installment(1250.0, false, "01 February 2023") //
+            );
+
+            createdLoanId.set(loanId);
+        });
+
+        runAt("02 February 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // create re-amortize transaction
+            reAmortizeLoan(loanId);
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(1250.0, "Re-amortize", "02 February 2023") //
+            );
+        });
+
+        runAt("03 February 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // create re-amortize transaction
+            undoReAmortizeLoan(loanId);
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    reversedTransaction(1250.0, "Re-amortize", "02 February 2023") //
+            );
+
+            // TODO: verify installments when schedule generation is implemented
+        });
+    }
+}