Loan transaction reversal data extension

This commit is contained in:
Jose Alberto Hernandez
2022-08-17 17:02:37 +02:00
committed by Arnold Gálovics
parent 163b9b1b8a
commit 80401586e6
13 changed files with 167 additions and 109 deletions
@@ -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);
}
}
@@ -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";
}
@@ -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);
}
@@ -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")
@@ -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,
@@ -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()) {
@@ -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;
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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) {
@@ -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>
@@ -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>