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

arnold 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 80401586e Loan transaction reversal data extension
80401586e is described below

commit 80401586e683cdc86105edeb76334b4f9c029430
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Mon Aug 8 20:31:36 2022 -0500

    Loan transaction reversal data extension
---
 .../core/service/CommandParameterUtil.java         |  31 +++++
 .../loanaccount/api/LoanApiConstants.java          |   4 +
 .../api/LoanTransactionsApiResource.java           | 137 ++++++++-------------
 .../api/LoanTransactionsApiResourceSwagger.java    |   4 +
 .../loanaccount/data/LoanTransactionData.java      |  18 ++-
 .../portfolio/loanaccount/domain/Loan.java         |   4 +-
 .../loanaccount/domain/LoanTransaction.java        |  12 ++
 ...tLoanRepaymentScheduleTransactionProcessor.java |   2 +-
 .../serialization/LoanEventApiJsonValidator.java   |  19 ++-
 .../service/LoanReadPlatformServiceImpl.java       |   7 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |   6 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../0038_add_reversal_data_to_loan_transaction.xml |  31 +++++
 13 files changed, 167 insertions(+), 109 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
new file mode 100644
index 000000000..69da158c9
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
@@ -0,0 +1,31 @@
+/**
+ * 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.core.service;
+
+import org.apache.commons.lang3.StringUtils;
+
+public final class CommandParameterUtil {
+
+    private CommandParameterUtil() {}
+
+    public static boolean is(final String commandParam, final String 
commandValue) {
+        return StringUtils.isNotBlank(commandParam) && 
commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 60c4b81cb..ca0907f3a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -142,4 +142,8 @@ public interface LoanApiConstants {
     String fixedPrincipalPercentagePerInstallmentParamName = 
"fixedPrincipalPercentagePerInstallment";
 
     String LOAN_ASSOCIATIONS_ALL = "all";
+
+    // Reversal Transation Data
+    String REVERSAL_EXTERNAL_ID_PARAMNAME = "reversalExternalId";
+    String REVERSED_ON_DATE_PARAMNAME = "reversedOnDate";
 }
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 bea194e6c..62bc82943 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
@@ -44,7 +44,7 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
-import org.apache.commons.lang3.StringUtils;
+import lombok.AllArgsConstructor;
 import org.apache.fineract.accounting.journalentry.api.DateParam;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
@@ -54,6 +54,7 @@ import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
@@ -63,18 +64,18 @@ import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPla
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import 
org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 
 @Path("/loans/{loanId}/transactions")
 @Component
+@AllArgsConstructor
 @Scope("singleton")
 @Tag(name = "Loan Transactions", description = "Capabilities include loan 
repayment's, interest waivers and the ability to 'adjust' an existing 
transaction. An 'adjustment' of a transaction is really a 'reversal' of 
existing transaction followed by creation of a new transaction with the 
provided details.")
 public class LoanTransactionsApiResource {
 
-    private final Set<String> responseDataParameters = new HashSet<>(
-            Arrays.asList("id", "type", "date", "currency", "amount", 
"externalId"));
+    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));
 
     private final String resourceNameForPermissions = "LOAN";
 
@@ -86,26 +87,6 @@ public class LoanTransactionsApiResource {
     private final PaymentTypeReadPlatformService 
paymentTypeReadPlatformService;
     private final LoanChargePaidByReadPlatformService 
loanChargePaidByReadPlatformService;
 
-    @Autowired
-    public LoanTransactionsApiResource(final PlatformSecurityContext context, 
final LoanReadPlatformService loanReadPlatformService,
-            final ApiRequestParameterHelper apiRequestParameterHelper,
-            final DefaultToApiJsonSerializer<LoanTransactionData> 
toApiJsonSerializer,
-            final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService,
-            PaymentTypeReadPlatformService paymentTypeReadPlatformService,
-            LoanChargePaidByReadPlatformService 
loanChargePaidByReadPlatformService) {
-        this.context = context;
-        this.loanReadPlatformService = loanReadPlatformService;
-        this.apiRequestParameterHelper = apiRequestParameterHelper;
-        this.toApiJsonSerializer = toApiJsonSerializer;
-        this.commandsSourceWritePlatformService = 
commandsSourceWritePlatformService;
-        this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
-        this.loanChargePaidByReadPlatformService = 
loanChargePaidByReadPlatformService;
-    }
-
-    private boolean is(final String commandParam, final String commandValue) {
-        return StringUtils.isNotBlank(commandParam) && 
commandParam.trim().equalsIgnoreCase(commandValue);
-    }
-
     @GET
     @Path("template")
     @Consumes({ MediaType.APPLICATION_JSON })
@@ -132,39 +113,39 @@ public class LoanTransactionsApiResource {
         
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
 
         LoanTransactionData transactionData = null;
-        if (is(commandParam, "repayment")) {
+        if (CommandParameterUtil.is(commandParam, "repayment")) {
             transactionData = 
this.loanReadPlatformService.retrieveLoanTransactionTemplate(loanId);
-        } else if (is(commandParam, "merchantIssuedRefund")) {
+        } else if (CommandParameterUtil.is(commandParam, 
"merchantIssuedRefund")) {
             LocalDate transactionDate = DateUtils.getBusinessLocalDate();
             transactionData = 
this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.MERCHANT_ISSUED_REFUND,
                     loanId, transactionDate);
-        } else if (is(commandParam, "payoutRefund")) {
+        } else if (CommandParameterUtil.is(commandParam, "payoutRefund")) {
             LocalDate transactionDate = DateUtils.getBusinessLocalDate();
             transactionData = 
this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.PAYOUT_REFUND,
 loanId,
                     transactionDate);
-        } else if (is(commandParam, "goodwillCredit")) {
+        } else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
             LocalDate transactionDate = DateUtils.getBusinessLocalDate();
             transactionData = 
this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.GOODWILL_CREDIT,
 loanId,
                     transactionDate);
-        } else if (is(commandParam, "waiveinterest")) {
+        } else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
             transactionData = 
this.loanReadPlatformService.retrieveWaiveInterestDetails(loanId);
-        } else if (is(commandParam, "writeoff")) {
+        } else if (CommandParameterUtil.is(commandParam, "writeoff")) {
             transactionData = 
this.loanReadPlatformService.retrieveLoanWriteoffTemplate(loanId);
-        } else if (is(commandParam, "close-rescheduled")) {
+        } else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) 
{
             transactionData = 
this.loanReadPlatformService.retrieveNewClosureDetails();
-        } else if (is(commandParam, "close")) {
+        } else if (CommandParameterUtil.is(commandParam, "close")) {
             transactionData = 
this.loanReadPlatformService.retrieveNewClosureDetails();
-        } else if (is(commandParam, "disburse")) {
+        } else if (CommandParameterUtil.is(commandParam, "disburse")) {
             transactionData = 
this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
             
transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(loanId));
             final List<LoanRepaymentScheduleInstallmentData> 
loanRepaymentScheduleInstallmentData = this.loanReadPlatformService
                     .getRepaymentDataResponse(loanId);
             
transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData);
-        } else if (is(commandParam, "disburseToSavings")) {
+        } else if (CommandParameterUtil.is(commandParam, "disburseToSavings")) 
{
             transactionData = 
this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
-        } else if (is(commandParam, "recoverypayment")) {
+        } else if (CommandParameterUtil.is(commandParam, "recoverypayment")) {
             transactionData = 
this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
-        } else if (is(commandParam, "prepayLoan")) {
+        } else if (CommandParameterUtil.is(commandParam, "prepayLoan")) {
             LocalDate transactionDate = null;
             if (transactionDateParam == null) {
                 transactionDate = DateUtils.getBusinessLocalDate();
@@ -173,11 +154,11 @@ public class LoanTransactionsApiResource {
             }
             transactionData = 
this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT,
 loanId,
                     transactionDate);
-        } else if (is(commandParam, "refundbycash")) {
+        } else if (CommandParameterUtil.is(commandParam, "refundbycash")) {
             transactionData = 
this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
-        } else if (is(commandParam, "refundbytransfer")) {
+        } else if (CommandParameterUtil.is(commandParam, "refundbytransfer")) {
             transactionData = 
this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
-        } else if (is(commandParam, "foreclosure")) {
+        } else if (CommandParameterUtil.is(commandParam, "foreclosure")) {
             LocalDate transactionDate = null;
             if (transactionDateParam == null) {
                 transactionDate = DateUtils.getBusinessLocalDate();
@@ -185,7 +166,7 @@ public class LoanTransactionsApiResource {
                 transactionDate = 
transactionDateParam.getDate("transactionDate", dateFormat, locale);
             }
             transactionData = 
this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, 
transactionDate);
-        } else if (is(commandParam, "creditBalanceRefund")) {
+        } else if (CommandParameterUtil.is(commandParam, 
"creditBalanceRefund")) {
             transactionData = 
this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(loanId);
         } else {
             throw new UnrecognizedQueryParamException("command", commandParam);
@@ -245,55 +226,41 @@ public class LoanTransactionsApiResource {
 
         final CommandWrapperBuilder builder = new 
CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
 
-        CommandProcessingResult result = null;
-        if (is(commandParam, "repayment")) {
-            final CommandWrapper commandRequest = 
builder.loanRepaymentTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "merchantIssuedRefund")) {
-            final CommandWrapper commandRequest = 
builder.loanMerchantIssuedRefundTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "payoutRefund")) {
-            final CommandWrapper commandRequest = 
builder.loanPayoutRefundTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "goodwillCredit")) {
-            final CommandWrapper commandRequest = 
builder.loanGoodwillCreditTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "chargeRefund")) {
-            final CommandWrapper commandRequest = 
builder.refundLoanCharge(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "waiveinterest")) {
-            final CommandWrapper commandRequest = 
builder.waiveInterestPortionTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "writeoff")) {
-            final CommandWrapper commandRequest = 
builder.writeOffLoanTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "close-rescheduled")) {
-            final CommandWrapper commandRequest = 
builder.closeLoanAsRescheduledTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "close")) {
-            final CommandWrapper commandRequest = 
builder.closeLoanTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "undowriteoff")) {
-            final CommandWrapper commandRequest = 
builder.undoWriteOffLoanTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "recoverypayment")) {
-            final CommandWrapper commandRequest = 
builder.loanRecoveryPaymentTransaction(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "refundByCash")) {
-            final CommandWrapper commandRequest = 
builder.refundLoanTransactionByCash(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "foreclosure")) {
-            final CommandWrapper commandRequest = 
builder.loanForeclosure(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
-        } else if (is(commandParam, "creditBalanceRefund")) {
-            final CommandWrapper commandRequest = 
builder.creditBalanceRefund(loanId).build();
-            result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        CommandWrapper commandRequest = null;
+        if (CommandParameterUtil.is(commandParam, "repayment")) {
+            commandRequest = builder.loanRepaymentTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, 
"merchantIssuedRefund")) {
+            commandRequest = 
builder.loanMerchantIssuedRefundTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "payoutRefund")) {
+            commandRequest = 
builder.loanPayoutRefundTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
+            commandRequest = 
builder.loanGoodwillCreditTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "chargeRefund")) {
+            commandRequest = builder.refundLoanCharge(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
+            commandRequest = 
builder.waiveInterestPortionTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "writeoff")) {
+            commandRequest = builder.writeOffLoanTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) 
{
+            commandRequest = 
builder.closeLoanAsRescheduledTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "close")) {
+            commandRequest = builder.closeLoanTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "undowriteoff")) {
+            commandRequest = 
builder.undoWriteOffLoanTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "recoverypayment")) {
+            commandRequest = 
builder.loanRecoveryPaymentTransaction(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "refundByCash")) {
+            commandRequest = 
builder.refundLoanTransactionByCash(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "foreclosure")) {
+            commandRequest = builder.loanForeclosure(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, 
"creditBalanceRefund")) {
+            commandRequest = builder.creditBalanceRefund(loanId).build();
         }
 
-        if (result == null) {
+        if (commandRequest == null) {
             throw new UnrecognizedQueryParamException("command", commandParam);
         }
-
+        final CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
         return this.toApiJsonSerializer.serialize(result);
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index cb7327dc6..39ea011ee 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -142,6 +142,10 @@ final class LoanTransactionsApiResourceSwagger {
         public Double amount;
         @Schema(example = "559.88")
         public Double interestPortion;
+        @Schema(example = "20120514")
+        public String reversalExternalId;
+        @Schema(example = "[2012, 5, 18]")
+        public LocalDate reversedOnDate;
     }
 
     @Schema(description = "PostLoansLoanIdTransactionsRequest")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index ce805898d..4c2ec3483 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -86,6 +86,10 @@ public class LoanTransactionData {
     private transient String transactionType;
     private List<LoanRepaymentScheduleInstallmentData> 
loanRepaymentScheduleInstallments;
 
+    // Reverse Data
+    private String reversalExternalId;
+    private LocalDate reversedOnDate;
+
     public static LoanTransactionData importInstance(BigDecimal 
repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
             Integer rowIndex, String locale, String dateFormat) {
         return new LoanTransactionData(repaymentAmount, lastRepaymentDate, 
repaymentTypeId, rowIndex, locale, dateFormat);
@@ -222,7 +226,7 @@ public class LoanTransactionData {
             boolean manuallyReversed) {
         this(id, officeId, officeName, transactionType, paymentDetailData, 
currency, date, amount, netDisbursalAmount, principalPortion,
                 interestPortion, feeChargesPortion, penaltyChargesPortion, 
overpaymentPortion, unrecognizedIncomePortion,
-                paymentTypeOptions, externalId, transfer, fixedEmiAmount, 
outstandingLoanBalance, null, manuallyReversed);
+                paymentTypeOptions, externalId, transfer, fixedEmiAmount, 
outstandingLoanBalance, null, manuallyReversed, null, null);
     }
 
     public LoanTransactionData(final Long id, final Long officeId, final 
String officeName, final LoanTransactionEnumData transactionType,
@@ -230,10 +234,11 @@ public class LoanTransactionData {
             final BigDecimal netDisbursalAmount, final BigDecimal 
principalPortion, final BigDecimal interestPortion,
             final BigDecimal feeChargesPortion, final BigDecimal 
penaltyChargesPortion, final BigDecimal overpaymentPortion,
             final BigDecimal unrecognizedIncomePortion, final String 
externalId, final AccountTransferData transfer,
-            BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, 
LocalDate submittedOnDate, final boolean manuallyReversed) {
+            BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, 
LocalDate submittedOnDate, final boolean manuallyReversed,
+            final String reversalExternalId, final LocalDate reversedOnDate) {
         this(id, officeId, officeName, transactionType, paymentDetailData, 
currency, date, amount, netDisbursalAmount, principalPortion,
                 interestPortion, feeChargesPortion, penaltyChargesPortion, 
overpaymentPortion, unrecognizedIncomePortion, null, externalId,
-                transfer, fixedEmiAmount, outstandingLoanBalance, 
submittedOnDate, manuallyReversed);
+                transfer, fixedEmiAmount, outstandingLoanBalance, 
submittedOnDate, manuallyReversed, reversalExternalId, reversedOnDate);
     }
 
     public LoanTransactionData(final Long id, final Long officeId, final 
String officeName, final LoanTransactionEnumData transactionType,
@@ -242,7 +247,8 @@ public class LoanTransactionData {
             final BigDecimal feeChargesPortion, final BigDecimal 
penaltyChargesPortion, final BigDecimal overpaymentPortion,
             final BigDecimal unrecognizedIncomePortion, final 
Collection<PaymentTypeData> paymentTypeOptions, final String externalId,
             final AccountTransferData transfer, final BigDecimal 
fixedEmiAmount, BigDecimal outstandingLoanBalance,
-            final LocalDate submittedOnDate, final boolean manuallyReversed) {
+            final LocalDate submittedOnDate, final boolean manuallyReversed, 
final String reversalExternalId,
+            final LocalDate reversedOnDate) {
         this.id = id;
         this.officeId = officeId;
         this.officeName = officeName;
@@ -266,6 +272,8 @@ public class LoanTransactionData {
         this.submittedOnDate = submittedOnDate;
         this.manuallyReversed = manuallyReversed;
         this.possibleNextRepaymentDate = null;
+        this.reversalExternalId = reversalExternalId;
+        this.reversedOnDate = reversedOnDate;
     }
 
     public LoanTransactionData(Long id, LoanTransactionEnumData 
transactionType, LocalDate date, BigDecimal totalAmount,
@@ -274,7 +282,7 @@ public class LoanTransactionData {
             BigDecimal outstandingLoanBalance, final boolean manuallyReversed) 
{
         this(id, null, null, transactionType, null, null, date, totalAmount, 
netDisbursalAmount, principalPortion, interestPortion,
                 feeChargesPortion, penaltyChargesPortion, overpaymentPortion, 
unrecognizedIncomePortion, null, null, null, null,
-                outstandingLoanBalance, null, manuallyReversed);
+                outstandingLoanBalance, null, manuallyReversed, null, null);
     }
 
     public static LoanTransactionData 
loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData 
transactionType,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 8909db4ae..e651117c6 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -3606,7 +3606,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
     public ChangedTransactionDetail adjustExistingTransaction(final 
LoanTransaction newTransactionDetail,
             final LoanLifecycleStateMachine loanLifecycleStateMachine, final 
LoanTransaction transactionForAdjustment,
             final List<Long> existingTransactionIds, final List<Long> 
existingReversedTransactionIds,
-            final ScheduleGeneratorDTO scheduleGeneratorDTO) {
+            final ScheduleGeneratorDTO scheduleGeneratorDTO, final String 
reversalExternalId) {
 
         HolidayDetailDTO holidayDetailDTO = 
scheduleGeneratorDTO.getHolidayDetailDTO();
         
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
 transactionForAdjustment.getTransactionDate());
@@ -3630,7 +3630,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                     
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions",
 errorMessage);
         }
 
-        transactionForAdjustment.reverse();
+        transactionForAdjustment.reverse(reversalExternalId);
         transactionForAdjustment.manuallyAdjustedOrReversed();
 
         if (isClosedWrittenOff()) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index b1c0f3d8b..60e83ac2a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -106,6 +106,12 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
     @Column(name = "external_id", length = 100, nullable = true, unique = true)
     private String externalId;
 
+    @Column(name = "reversal_external_id", length = 100, nullable = true, 
unique = true)
+    private String reversalExternalId;
+
+    @Column(name = "reversed_on_date", nullable = true)
+    private LocalDate reversedOnDate;
+
     @OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", 
orphanRemoval = true, fetch = FetchType.EAGER)
     private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<>();
 
@@ -364,9 +370,15 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
     public void reverse() {
         this.loan.validateRepaymentTypeTransactionNotBeforeAChargeRefund(this, 
"reversed");
         this.reversed = true;
+        this.reversedOnDate = DateUtils.getBusinessLocalDate();
         this.loanTransactionToRepaymentScheduleMappings.clear();
     }
 
+    public void reverse(final String reversalExternalId) {
+        this.reverse();
+        this.reversalExternalId = reversalExternalId;
+    }
+
     public void resetDerivedComponents() {
         this.principalPortion = null;
         this.interestPortion = null;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 224cc7ca9..5a10c2298 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -176,7 +176,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                         
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
                                 
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
                     } else {
-                        loanTransaction.reverse();
+                        
loanTransaction.reverse(loanTransaction.getExternalId());
                         loanTransaction.updateExternalId(null);
                         
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(),
 newLoanTransaction);
                     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index 487952f26..1e0ac6c90 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -33,6 +33,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -51,24 +52,16 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
+@AllArgsConstructor
 public final class LoanEventApiJsonValidator {
 
     private final FromJsonHelper fromApiJsonHelper;
     private final LoanApplicationCommandFromApiJsonHelper 
fromApiJsonDeserializer;
     private final LoanRepository loanRepository;
 
-    @Autowired
-    public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper,
-            final LoanApplicationCommandFromApiJsonHelper 
fromApiJsonDeserializer, final LoanRepository loanRepository) {
-        this.fromApiJsonHelper = fromApiJsonHelper;
-        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
-        this.loanRepository = loanRepository;
-    }
-
     private void throwExceptionIfValidationWarningsExist(final 
List<ApiParameterError> dataValidationErrors) {
         if (!dataValidationErrors.isEmpty()) {
             throw new 
PlatformApiDataValidationException("validation.msg.validation.errors.exist", 
"Validation errors exist.",
@@ -195,7 +188,8 @@ public final class LoanEventApiJsonValidator {
         }
 
         final Set<String> transactionParameters = new 
HashSet<>(Arrays.asList("transactionDate", "transactionAmount", "externalId", 
"note",
-                "locale", "dateFormat", "paymentTypeId", "accountNumber", 
"checkNumber", "routingCode", "receiptNumber", "bankNumber"));
+                "locale", "dateFormat", "paymentTypeId", "accountNumber", 
"checkNumber", "routingCode", "receiptNumber", "bankNumber",
+                LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME));
 
         final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
         this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
transactionParameters);
@@ -213,6 +207,11 @@ public final class LoanEventApiJsonValidator {
         final String note = this.fromApiJsonHelper.extractStringNamed("note", 
element);
         
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
 
+        final String reversalExternalId = 
this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME,
+                element);
+        
baseDataValidator.reset().parameter(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME).ignoreIfNull().value(reversalExternalId)
+                .notExceedingLengthOf(100);
+
         validatePaymentDetails(baseDataValidator, element);
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 1c84f5d8b..2b38734a2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1300,7 +1300,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
                     + " tr.fee_charges_portion_derived as fees, 
tr.penalty_charges_portion_derived as penalties, "
                     + " tr.overpayment_portion_derived as overpayment, 
tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
                     + " tr.unrecognized_income_portion as unrecognizedIncome," 
+ " tr.submitted_on_date as submittedOnDate, "
-                    + " tr.manually_adjusted_or_reversed as manuallyReversed, "
+                    + " tr.manually_adjusted_or_reversed as manuallyReversed, 
tr.reversal_external_id as reversalExternalId, tr.reversed_on_date as 
reversedOnDate, "
                     + " pd.payment_type_id as paymentType,pd.account_number as 
accountNumber,pd.check_number as checkNumber, "
                     + " pd.receipt_number as receiptNumber, pd.bank_number as 
bankNumber,pd.routing_code as routingCode, l.net_disbursal_amount as 
netDisbursalAmount,"
                     + " l.currency_code as currencyCode, l.currency_digits as 
currencyDigits, l.currency_multiplesof as inMultiplesOf, rc."
@@ -1366,6 +1366,8 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
             final BigDecimal unrecognizedIncomePortion = 
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome");
             final BigDecimal outstandingLoanBalance = 
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance");
             final String externalId = rs.getString("externalId");
+            final String reversalExternalId = 
rs.getString("reversalExternalId");
+            final LocalDate reversedOnDate = JdbcSupport.getLocalDate(rs, 
"reversedOnDate");
 
             final BigDecimal netDisbursalAmount = 
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "netDisbursalAmount");
 
@@ -1391,7 +1393,8 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
             }
             return new LoanTransactionData(id, officeId, officeName, 
transactionType, paymentDetailData, currencyData, date, totalAmount,
                     netDisbursalAmount, principalPortion, interestPortion, 
feeChargesPortion, penaltyChargesPortion, overPaymentPortion,
-                    unrecognizedIncomePortion, externalId, transfer, null, 
outstandingLoanBalance, submittedOnDate, manuallyReversed);
+                    unrecognizedIncomePortion, externalId, transfer, null, 
outstandingLoanBalance, submittedOnDate, manuallyReversed,
+                    reversalExternalId, reversedOnDate);
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index c92ff7b9a..833c4c3e4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -800,7 +800,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     @Override
     public CommandProcessingResult undoLoanDisbursal(final Long loanId, final 
JsonCommand command) {
 
-        final AppUser currentUser = getAppUserIfPresent();
         final Loan loan = this.loanAssembler.assembleFrom(loanId);
         checkClientOrGroupActive(loan);
         businessEventNotifierService.notifyPreBusinessEvent(new 
LoanUndoDisbursalBusinessEvent(loan));
@@ -1009,8 +1008,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     @Override
     public CommandProcessingResult adjustLoanTransaction(final Long loanId, 
final Long transactionId, final JsonCommand command) {
 
-        AppUser currentUser = getAppUserIfPresent();
-
         this.loanEventApiJsonValidator.validateTransaction(command.json());
 
         final Loan loan = this.loanAssembler.assembleFrom(loanId);
@@ -1036,6 +1033,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         final LocalDate transactionDate = 
command.localDateValueOfParameterNamed("transactionDate");
         final BigDecimal transactionAmount = 
command.bigDecimalValueOfParameterNamed("transactionAmount");
         final String txnExternalId = 
command.stringValueOfParameterNamedAllowingNull("externalId");
+        final String reversalExternalId = 
command.stringValueOfParameterNamedAllowingNull(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME);
 
         final Map<String, Object> changes = new LinkedHashMap<>();
         changes.put("transactionDate", 
command.stringValueOfParameterNamed("transactionDate"));
@@ -1076,7 +1074,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
 
         final ChangedTransactionDetail changedTransactionDetail = 
loan.adjustExistingTransaction(newTransactionDetail,
                 defaultLoanLifecycleStateMachine(), transactionToAdjust, 
existingTransactionIds, existingReversedTransactionIds,
-                scheduleGeneratorDTO);
+                scheduleGeneratorDTO, reversalExternalId);
 
         if 
(newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
             if (paymentDetail != null) {
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 501a7aafc..362169f97 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
@@ -57,4 +57,5 @@
     <include file="parts/0035_add_audit_entries_to_calendar.xml" 
relativeToChangelogFile="true"/>
     <include 
file="parts/0036_add_audit_entries_and_rework_command_source_datetime_fields.xml"
 relativeToChangelogFile="true"/>
     <include file="parts/0037_add_loan_cob_job_data.xml" 
relativeToChangelogFile="true"/>
+    <include file="parts/0038_add_reversal_data_to_loan_transaction.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0038_add_reversal_data_to_loan_transaction.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0038_add_reversal_data_to_loan_transaction.xml
new file mode 100644
index 000000000..3b30d4bf5
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0038_add_reversal_data_to_loan_transaction.xml
@@ -0,0 +1,31 @@
+<?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">
+        <addColumn tableName="m_loan_transaction">
+            <column defaultValueComputed="NULL" name="reversal_external_id" 
type="VARCHAR(100)" />
+        </addColumn>
+        <addColumn tableName="m_loan_transaction">
+            <column defaultValueComputed="NULL" name="reversed_on_date" 
type="date"/>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>

Reply via email to