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 responseDataParameters = new HashSet<>( - Arrays.asList("id", "type", "date", "currency", "amount", "externalId")); + private final Set 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 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 = 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 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 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 existingTransactionIds, final List 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 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 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 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>() {}.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 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 @@ + 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 @@ + + + + + + + + + + + +