FINERACT-1806: Charge-off validation
This commit is contained in:
+8
@@ -3614,4 +3614,12 @@ public class CommandWrapperBuilder {
|
|||||||
this.href = "/loans/" + loanId + "/transactions?command=charge-off";
|
this.href = "/loans/" + loanId + "/transactions?command=charge-off";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder undoChargeOff(final Long loanId) {
|
||||||
|
this.actionName = "UNDOCHARGEOFF";
|
||||||
|
this.entityName = "LOAN";
|
||||||
|
this.loanId = loanId;
|
||||||
|
this.href = "/loans/" + loanId + "/transactions?command=undo-charge-off";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
|
||||||
|
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
|
public class LoanUndoChargeOffBusinessEvent extends LoanTransactionBusinessEvent {
|
||||||
|
|
||||||
|
private static final String TYPE = "LoanUndoChargeOffBusinessEvent";
|
||||||
|
|
||||||
|
public LoanUndoChargeOffBusinessEvent(LoanTransaction value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -77,6 +77,7 @@ import org.springframework.stereotype.Component;
|
|||||||
public class LoanTransactionsApiResource {
|
public class LoanTransactionsApiResource {
|
||||||
|
|
||||||
public static final String CHARGE_OFF_COMMAND_VALUE = "charge-off";
|
public static final String CHARGE_OFF_COMMAND_VALUE = "charge-off";
|
||||||
|
public static final String UNDO_CHARGE_OFF_COMMAND_VALUE = "undo-charge-off";
|
||||||
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));
|
LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
|
||||||
|
|
||||||
@@ -463,6 +464,8 @@ public class LoanTransactionsApiResource {
|
|||||||
commandRequest = builder.creditBalanceRefund(resolvedLoanId).build();
|
commandRequest = builder.creditBalanceRefund(resolvedLoanId).build();
|
||||||
} else if (CommandParameterUtil.is(commandParam, CHARGE_OFF_COMMAND_VALUE)) {
|
} else if (CommandParameterUtil.is(commandParam, CHARGE_OFF_COMMAND_VALUE)) {
|
||||||
commandRequest = builder.chargeOff(resolvedLoanId).build();
|
commandRequest = builder.chargeOff(resolvedLoanId).build();
|
||||||
|
} else if (CommandParameterUtil.is(commandParam, UNDO_CHARGE_OFF_COMMAND_VALUE)) {
|
||||||
|
commandRequest = builder.undoChargeOff(resolvedLoanId).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commandRequest == null) {
|
if (commandRequest == null) {
|
||||||
|
|||||||
+19
@@ -7027,10 +7027,29 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return getLoanTransactions().stream().filter(predicate).toList();
|
return getLoanTransactions().stream().filter(predicate).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanTransaction findChargedOffTransaction() {
|
||||||
|
return getLoanTransactions().stream() //
|
||||||
|
.filter(LoanTransaction::isNotReversed) //
|
||||||
|
.filter(LoanTransaction::isChargeOff) //
|
||||||
|
.findFirst() //
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
public void handleMaturityDateActivate() {
|
public void handleMaturityDateActivate() {
|
||||||
if (this.expectedMaturityDate != null && this.actualMaturityDate == null) {
|
if (this.expectedMaturityDate != null && this.actualMaturityDate == null) {
|
||||||
this.actualMaturityDate = this.expectedMaturityDate;
|
this.actualMaturityDate = this.expectedMaturityDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanTransaction getLastUserTransaction() {
|
||||||
|
return getLoanTransactions().stream() //
|
||||||
|
.filter(LoanTransaction::isNotReversed) //
|
||||||
|
.filter(t -> !(t.isAccrualTransaction() || t.isIncomePosting())) //
|
||||||
|
.reduce((first, second) -> second) //
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getChargedOffOnDate() {
|
||||||
|
return chargedOffOnDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-1
@@ -161,7 +161,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final boolean isRecoveryRepayment, final String chargeRefundChargeType, boolean isAccountTransfer,
|
final boolean isRecoveryRepayment, final String chargeRefundChargeType, boolean isAccountTransfer,
|
||||||
HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) {
|
HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) {
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan);
|
LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan);
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(repaymentEvent);
|
businessEventNotifierService.notifyPreBusinessEvent(repaymentEvent);
|
||||||
|
|
||||||
@@ -357,6 +362,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final Integer transactionType, Integer installmentNumber) {
|
final Integer transactionType, Integer installmentNumber) {
|
||||||
boolean isAccountTransfer = true;
|
boolean isAccountTransfer = true;
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargePaymentPreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargePaymentPreBusinessEvent(loan));
|
||||||
|
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
@@ -436,6 +447,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
boolean isAccountTransfer = true;
|
boolean isAccountTransfer = true;
|
||||||
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
|
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
@@ -482,6 +499,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId, final boolean isLoanToLoanTransfer) {
|
final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId, final boolean isLoanToLoanTransfer) {
|
||||||
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
boolean isAccountTransfer = true;
|
boolean isAccountTransfer = true;
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
@@ -508,6 +531,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reverseTransfer(final LoanTransaction loanTransaction) {
|
public void reverseTransfer(final LoanTransaction loanTransaction) {
|
||||||
|
if (loanTransaction.getLoan().isChargedOff()
|
||||||
|
&& loanTransaction.getTransactionDate().isBefore(loanTransaction.getLoan().getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date",
|
||||||
|
"Loan transaction: " + loanTransaction.getId()
|
||||||
|
+ " reversal is not allowed before or on the date when the loan got charged-off",
|
||||||
|
loanTransaction.getId());
|
||||||
|
}
|
||||||
loanTransaction.reverse();
|
loanTransaction.reverse();
|
||||||
saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
|
saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
|
||||||
}
|
}
|
||||||
@@ -633,6 +663,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
@Override
|
@Override
|
||||||
public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate transactionDate, final BigDecimal transactionAmount,
|
public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate transactionDate, final BigDecimal transactionAmount,
|
||||||
final String noteText, final ExternalId externalId, PaymentDetail paymentDetail) {
|
final String noteText, final ExternalId externalId, PaymentDetail paymentDetail) {
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
|
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan));
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
@@ -670,6 +707,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
|
|
||||||
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
|
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail,
|
final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail,
|
||||||
transactionDate, txnExternalId);
|
transactionDate, txnExternalId);
|
||||||
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
|
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
|
||||||
@@ -702,6 +745,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
@Override
|
@Override
|
||||||
public LoanTransaction foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText,
|
public LoanTransaction foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText,
|
||||||
final ExternalId externalId, Map<String, Object> changes) {
|
final ExternalId externalId, Map<String, Object> changes) {
|
||||||
|
if (loan.isChargedOff() && foreClosureDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loan.getId()
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loan.getId());
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan));
|
||||||
MonetaryCurrency currency = loan.getCurrency();
|
MonetaryCurrency currency = loan.getCurrency();
|
||||||
List<LoanTransaction> newTransactions = new ArrayList<>();
|
List<LoanTransaction> newTransactions = new ArrayList<>();
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.loanaccount.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CommandType(entity = "LOAN", action = "UNDOCHARGEOFF")
|
||||||
|
public class UndoChargeOffLoanCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LoanWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
return writePlatformService.undoChargeOff(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
@@ -131,6 +132,11 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
// use the loan id to get a Loan entity object
|
// use the loan id to get a Loan entity object
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Loan: " + loanId + " reschedule installment is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
|
|
||||||
// validate the request in the JsonCommand object passed as
|
// validate the request in the JsonCommand object passed as
|
||||||
// parameter
|
// parameter
|
||||||
this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
|
this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
|
||||||
|
|||||||
+18
-2
@@ -33,6 +33,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
@@ -41,6 +42,7 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
@@ -131,6 +133,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatformService {
|
public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatformService {
|
||||||
@@ -173,7 +176,10 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
|
|
||||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Adding charge to Loan: " + loanId + " is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
List<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
|
List<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
|
||||||
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
|
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
|
||||||
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
|
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
|
||||||
@@ -360,7 +366,13 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
throw new LoanChargeWaiveCannotBeReversedException(
|
throw new LoanChargeWaiveCannotBeReversedException(
|
||||||
LoanChargeWaiveCannotBeReversedException.LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE, loanCharge.getId());
|
LoanChargeWaiveCannotBeReversedException.LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE, loanCharge.getId());
|
||||||
}
|
}
|
||||||
|
if (loan.isChargedOff() && (loanTransaction.getTransactionDate().isBefore(loan.getChargedOffOnDate())
|
||||||
|
|| loanTransaction.getTransactionDate().isEqual(loan.getChargedOffOnDate()))) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date",
|
||||||
|
"Undo Loan transaction: " + loanTransaction.getId()
|
||||||
|
+ " is not allowed before or on the date when the loan got charged-off",
|
||||||
|
loanTransaction.getId());
|
||||||
|
}
|
||||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||||
|
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveChargeUndoBusinessEvent(loanCharge));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveChargeUndoBusinessEvent(loanCharge));
|
||||||
@@ -713,6 +725,10 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) {
|
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) {
|
||||||
|
|
||||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
log.warn("Adding charge to Loan: {} is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||||
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
|
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
|
||||||
boolean runInterestRecalculation = false;
|
boolean runInterestRecalculation = false;
|
||||||
|
|||||||
+3
@@ -112,4 +112,7 @@ public interface LoanWritePlatformService {
|
|||||||
CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
|
CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult chargeOff(JsonCommand command);
|
CommandProcessingResult chargeOff(JsonCommand command);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
CommandProcessingResult undoChargeOff(JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-8
@@ -80,6 +80,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdra
|
|||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent;
|
||||||
@@ -286,6 +287,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
// Fail fast if client/group is not active or actual loan status disallows disbursal
|
// Fail fast if client/group is not active or actual loan status disallows disbursal
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
|
||||||
|
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||||
|
|
||||||
|
if (loan.isChargedOff() && actualDisbursementDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loanId
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
|
|
||||||
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
||||||
List<LoanDisbursementDetails> filteredList = loan.getDisbursementDetails().stream()
|
List<LoanDisbursementDetails> filteredList = loan.getDisbursementDetails().stream()
|
||||||
.filter(disbursementDetails -> disbursementDetails.actualDisbursementDate() == null).toList();
|
.filter(disbursementDetails -> disbursementDetails.actualDisbursementDate() == null).toList();
|
||||||
@@ -326,8 +337,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
|
||||||
|
|
||||||
// validate ActualDisbursement Date Against Expected Disbursement Date
|
// validate ActualDisbursement Date Against Expected Disbursement Date
|
||||||
LoanProduct loanProduct = loan.loanProduct();
|
LoanProduct loanProduct = loan.loanProduct();
|
||||||
if (loanProduct.syncExpectedWithDisbursementDate()) {
|
if (loanProduct.syncExpectedWithDisbursementDate()) {
|
||||||
@@ -805,6 +814,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Undo Loan: " + loanId + " disbursement is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan));
|
||||||
removeLoanCycle(loan);
|
removeLoanCycle(loan);
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
@@ -1057,6 +1070,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed",
|
throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed",
|
||||||
"Loan transaction:" + transactionId + " update not allowed as loan status is written off", transactionId);
|
"Loan transaction:" + transactionId + " update not allowed as loan status is written off", transactionId);
|
||||||
}
|
}
|
||||||
|
if (loan.isChargedOff() && (transactionToAdjust.getTransactionDate().isBefore(loan.getChargedOffOnDate())
|
||||||
|
|| transactionToAdjust.getTransactionDate().isEqual(loan.getChargedOffOnDate()))) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.adjusted.transaction.date.cannot.be.earlier.than.charge.off.date",
|
||||||
|
"Loan transaction: " + transactionId + " adjustment is not allowed before or on the date when the loan got charged-off",
|
||||||
|
transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
if (transactionToAdjust.hasChargebackLoanTransactionRelations()) {
|
if (transactionToAdjust.hasChargebackLoanTransactionRelations()) {
|
||||||
throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed",
|
throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed",
|
||||||
@@ -1411,6 +1430,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||||
changes.put("locale", command.locale());
|
changes.put("locale", command.locale());
|
||||||
changes.put("dateFormat", command.dateFormat());
|
changes.put("dateFormat", command.dateFormat());
|
||||||
|
LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
if (command.hasParameter("writeoffReasonId")) {
|
if (command.hasParameter("writeoffReasonId")) {
|
||||||
Long writeoffReasonId = command.longValueOfParameterNamed("writeoffReasonId");
|
Long writeoffReasonId = command.longValueOfParameterNamed("writeoffReasonId");
|
||||||
@@ -1421,6 +1441,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loanId
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanWrittenOffPreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanWrittenOffPreBusinessEvent(loan));
|
||||||
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(),
|
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(),
|
||||||
StatusEnum.WRITE_OFF.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
|
StatusEnum.WRITE_OFF.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
|
||||||
@@ -1479,6 +1505,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||||
|
if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
|
+ loanId
|
||||||
|
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
|
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanCloseBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanCloseBusinessEvent(loan));
|
||||||
|
|
||||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||||
@@ -1579,6 +1613,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Loan: " + loanId + " Close as rescheduled is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
removeLoanCycle(loan);
|
removeLoanCycle(loan);
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanCloseAsRescheduleBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanCloseAsRescheduleBusinessEvent(loan));
|
||||||
|
|
||||||
@@ -1929,6 +1967,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.getExpectedFirstRepaymentOnDate(), presentMeetingDate);
|
loan.getExpectedFirstRepaymentOnDate(), presentMeetingDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Loan: " + loan.getId() + " reschedule is not allowed. Loan Account is Charged-off", loan.getId());
|
||||||
|
}
|
||||||
|
|
||||||
Boolean isSkipRepaymentOnFirstMonth = false;
|
Boolean isSkipRepaymentOnFirstMonth = false;
|
||||||
int numberOfDays = 0;
|
int numberOfDays = 0;
|
||||||
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
|
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
|
||||||
@@ -2219,6 +2262,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Update Loan: " + loanId + " disbursement details is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
||||||
LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
|
LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
|
||||||
if (!loan.loanProduct().isMultiDisburseLoan()) {
|
if (!loan.loanProduct().isMultiDisburseLoan()) {
|
||||||
@@ -2314,6 +2361,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Update Loan: " + loanId + " disbursement details is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
|
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
|
||||||
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
|
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
|
||||||
|
|
||||||
@@ -2565,6 +2616,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
|
final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
|
||||||
validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan);
|
validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off",
|
||||||
|
"Undo Loan: " + loanId + " last disbursement is not allowed. Loan Account is Charged-off", loanId);
|
||||||
|
}
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoLastDisbursalBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoLastDisbursalBusinessEvent(loan));
|
||||||
|
|
||||||
final MonetaryCurrency currency = loan.getCurrency();
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
@@ -2655,16 +2710,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
command.stringValueOfParameterNamed(LoanApiConstants.transactionDateParamName));
|
command.stringValueOfParameterNamed(LoanApiConstants.transactionDateParamName));
|
||||||
changes.put(LoanApiConstants.localeParameterName, command.locale());
|
changes.put(LoanApiConstants.localeParameterName, command.locale());
|
||||||
changes.put(LoanApiConstants.dateFormatParameterName, command.dateFormat());
|
changes.put(LoanApiConstants.dateFormatParameterName, command.dateFormat());
|
||||||
|
|
||||||
// TODO: add business logic validation (transaction date cannot be future, cannot be earlier than latest
|
|
||||||
// transactions, etc)
|
|
||||||
Loan loan = loanAssembler.assembleFrom(command.getLoanId());
|
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeOffPreBusinessEvent(loan));
|
|
||||||
|
|
||||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed(LoanApiConstants.transactionDateParamName);
|
final LocalDate transactionDate = command.localDateValueOfParameterNamed(LoanApiConstants.transactionDateParamName);
|
||||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||||
final AppUser currentUser = getAppUserIfPresent();
|
final AppUser currentUser = getAppUserIfPresent();
|
||||||
|
|
||||||
|
Loan loan = loanAssembler.assembleFrom(command.getLoanId());
|
||||||
|
final Long loanId = loan.getId();
|
||||||
|
if (!loan.isOpen()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.active",
|
||||||
|
"Loan: " + loanId + " Charge-off is not allowed. Loan Account is not Active", loanId);
|
||||||
|
}
|
||||||
|
if (loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.already.charged.off",
|
||||||
|
"Loan: " + loanId + " is already charged-off", loanId);
|
||||||
|
}
|
||||||
|
if (transactionDate.isBefore(loan.getLastUserTransactionDate())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.charge.off.is.before.than.the.last.user.transaction",
|
||||||
|
"Loan: " + loanId + " charge-off cannot be executed. User transaction was found after the charge-off transaction date!",
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
|
if (loan.isInterestBearing()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.interest.bearing",
|
||||||
|
"Loan: " + loanId + " Charge-off is not allowed. Loan Account is interest bearing", loanId);
|
||||||
|
}
|
||||||
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeOffPreBusinessEvent(loan));
|
||||||
|
|
||||||
if (command.hasParameter(LoanApiConstants.chargeOffReasonIdParamName)) {
|
if (command.hasParameter(LoanApiConstants.chargeOffReasonIdParamName)) {
|
||||||
Long chargeOffReasonId = command.longValueOfParameterNamed(LoanApiConstants.chargeOffReasonIdParamName);
|
Long chargeOffReasonId = command.longValueOfParameterNamed(LoanApiConstants.chargeOffReasonIdParamName);
|
||||||
CodeValue chargeOffReason = this.codeValueRepository
|
CodeValue chargeOffReason = this.codeValueRepository
|
||||||
@@ -2703,6 +2773,49 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.with(changes).build();
|
.with(changes).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CommandProcessingResult undoChargeOff(JsonCommand command) {
|
||||||
|
final Long loanId = command.getLoanId();
|
||||||
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||||
|
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
|
||||||
|
checkClientOrGroupActive(loan);
|
||||||
|
if (!loan.isOpen()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.active",
|
||||||
|
"Loan: " + loanId + " Undo Charge-off is not allowed. Loan Account is not Active", loanId);
|
||||||
|
}
|
||||||
|
if (!loan.isChargedOff()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.charged.off", "Loan: " + loanId + " is not charged-off",
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
|
LoanTransaction chargedOffTransaction = loan.findChargedOffTransaction();
|
||||||
|
if (chargedOffTransaction == null) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.charge.off.transaction.not.found",
|
||||||
|
"Loan: " + loanId + " charge-off transaction was not found", loanId);
|
||||||
|
}
|
||||||
|
if (!chargedOffTransaction.equals(loan.getLastUserTransaction())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.charge.off.is.not.the.last.user.transaction",
|
||||||
|
"Loan: " + loanId + " charge-off cannot be undone. User transaction was found after charge-off!", loanId);
|
||||||
|
}
|
||||||
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoChargeOffBusinessEvent(chargedOffTransaction));
|
||||||
|
|
||||||
|
chargedOffTransaction.reverse();
|
||||||
|
loan.liftChargeOff();
|
||||||
|
loanTransactionRepository.saveAndFlush(chargedOffTransaction);
|
||||||
|
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoChargeOffBusinessEvent(chargedOffTransaction));
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withOfficeId(loan.getOfficeId()) //
|
||||||
|
.withClientId(loan.getClientId()) //
|
||||||
|
.withGroupId(loan.getGroupId()) //
|
||||||
|
.withLoanId(loanId) //
|
||||||
|
.withEntityId(chargedOffTransaction.getId()) //
|
||||||
|
.withEntityExternalId(chargedOffTransaction.getExternalId()) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
|
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
|
||||||
if (!loan.isMultiDisburmentLoan()) {
|
if (!loan.isMultiDisburmentLoan()) {
|
||||||
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
||||||
|
|||||||
@@ -113,4 +113,5 @@
|
|||||||
<include file="parts/0091_modify_parameter_value_type_in_job_parameters_table.xml" relativeToChangelogFile="true" />
|
<include file="parts/0091_modify_parameter_value_type_in_job_parameters_table.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0092_add_periodic_accrual_entries_business_step.xml" relativeToChangelogFile="true" />
|
<include file="parts/0092_add_periodic_accrual_entries_business_step.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0093_update_general_accounting_table_reports.xml" relativeToChangelogFile="true" />
|
<include file="parts/0093_update_general_accounting_table_reports.xml" relativeToChangelogFile="true" />
|
||||||
|
<include file="parts/0094_add_external_event_default_configuration.xml" relativeToChangelogFile="true" />
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<insert tableName="m_external_event_configuration">
|
||||||
|
<column name="type" value="LoanUndoChargeOffBusinessEvent"/>
|
||||||
|
<column name="enabled" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet id="2" author="fineract">
|
||||||
|
<insert tableName="m_permission">
|
||||||
|
<column name="grouping" value="transaction_loan"/>
|
||||||
|
<column name="code" value="UNDOCHARGEOFF_LOAN"/>
|
||||||
|
<column name="entity_name" value="LOAN"/>
|
||||||
|
<column name="action_name" value="UNDOCHARGEOFF"/>
|
||||||
|
<column name="can_maker_checker" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+3
-2
@@ -95,7 +95,7 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
|
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
|
||||||
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
|
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
|
||||||
"LoanAccountsStayedLockedBusinessEvent", "MockBusinessEvent", "LoanChargeOffPreBusinessEvent",
|
"LoanAccountsStayedLockedBusinessEvent", "MockBusinessEvent", "LoanChargeOffPreBusinessEvent",
|
||||||
"LoanChargeOffPostBusinessEvent");
|
"LoanChargeOffPostBusinessEvent", "LoanUndoChargeOffBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
@@ -171,7 +171,8 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
|
"SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
|
||||||
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
|
"ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
|
||||||
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
|
"LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
|
||||||
"LoanAccountsStayedLockedBusinessEvent", "LoanChargeOffPreBusinessEvent", "LoanChargeOffPostBusinessEvent");
|
"LoanAccountsStayedLockedBusinessEvent", "LoanChargeOffPreBusinessEvent", "LoanChargeOffPostBusinessEvent",
|
||||||
|
"LoanUndoChargeOffBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
|
|||||||
+94
@@ -19,18 +19,23 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +81,95 @@ public class LoanTest {
|
|||||||
assertEquals(0, chargeIds.size());
|
assertEquals(0, chargeIds.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#findChargedOffTransaction()} with empty list
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindChargedOffTransactionEmptyList() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
final LoanTransaction chargedOffTransaction = loan.findChargedOffTransaction();
|
||||||
|
assertNull(chargedOffTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#findChargedOffTransaction()} where there is no charge-off transaction
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindChargedOffTransactionNoChargeOffTransaction() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
final LoanTransaction loanTransaction = new LoanTransaction();
|
||||||
|
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction));
|
||||||
|
final LoanTransaction chargedOffTransaction = loan.findChargedOffTransaction();
|
||||||
|
assertNull(chargedOffTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#findChargedOffTransaction()} where there is charge-off transaction
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindChargedOffTransactionWithChargeOffTransaction() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
|
||||||
|
final LoanTransaction loanTransaction = new LoanTransaction();
|
||||||
|
final LoanTransaction loanTransaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
|
final LoanTransaction loanTransaction3 = new LoanTransaction();
|
||||||
|
when(loanTransaction2.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction2.isChargeOff()).thenReturn(Boolean.TRUE);
|
||||||
|
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction, loanTransaction2, loanTransaction3));
|
||||||
|
final LoanTransaction chargedOffTransaction = loan.findChargedOffTransaction();
|
||||||
|
assertNotNull(chargedOffTransaction);
|
||||||
|
assertEquals(loanTransaction2, chargedOffTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#findChargedOffTransaction()} where there is charge-off transaction but reversed
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindChargedOffTransactionWithReversedChargeOffTransaction() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
|
||||||
|
final LoanTransaction loanTransaction = new LoanTransaction();
|
||||||
|
final LoanTransaction loanTransaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
|
final LoanTransaction loanTransaction3 = new LoanTransaction();
|
||||||
|
when(loanTransaction2.isReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction2.isChargeOff()).thenReturn(Boolean.TRUE);
|
||||||
|
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction, loanTransaction2, loanTransaction3));
|
||||||
|
final LoanTransaction chargedOffTransaction = loan.findChargedOffTransaction();
|
||||||
|
assertNull(chargedOffTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#getLastUserTransaction()} with empty list
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetLastUserTransactionEmptyList() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
final LoanTransaction userTransaction = loan.getLastUserTransaction();
|
||||||
|
assertNull(userTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Loan#getLastUserTransaction()} where there are user transactions
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetLastUserTransaction() {
|
||||||
|
final Loan loan = new Loan();
|
||||||
|
|
||||||
|
final LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
when(loanTransaction.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction.isAccrualTransaction()).thenReturn(Boolean.FALSE);
|
||||||
|
final LoanTransaction loanTransaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
|
when(loanTransaction2.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction2.isAccrualTransaction()).thenReturn(Boolean.FALSE);
|
||||||
|
final LoanTransaction loanTransaction3 = Mockito.mock(LoanTransaction.class);
|
||||||
|
when(loanTransaction3.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction3.isAccrualTransaction()).thenReturn(Boolean.TRUE);
|
||||||
|
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction, loanTransaction2, loanTransaction3));
|
||||||
|
final LoanTransaction userTransaction = loan.getLastUserTransaction();
|
||||||
|
assertNotNull(userTransaction);
|
||||||
|
assertEquals(loanTransaction2, userTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a new loan charge.
|
* Builds a new loan charge.
|
||||||
*
|
*
|
||||||
|
|||||||
+261
-40
@@ -69,9 +69,11 @@ import org.apache.fineract.client.models.PostGLAccountsRequest;
|
|||||||
import org.apache.fineract.client.models.PostGLAccountsResponse;
|
import org.apache.fineract.client.models.PostGLAccountsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
||||||
|
import org.apache.fineract.client.models.PutChargeTransactionChangesRequest;
|
||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||||
@@ -6954,54 +6956,263 @@ public class ClientLoanIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void chargeOff() {
|
public void chargeOff() {
|
||||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
try {
|
||||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, true);
|
||||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, this.responseSpec, true);
|
||||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5);
|
.date("2022.09.30").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
|
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
|
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
overpaymentAccount);
|
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6)
|
||||||
|
+ Utils.randomStringGenerator("is", 5);
|
||||||
|
Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
|
||||||
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterestMultiDisbursement(assetAccount,
|
||||||
|
incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
|
||||||
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
|
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
|
||||||
|
|
||||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
|
||||||
|
|
||||||
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
|
ResponseSpecification errorResponseSpec = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
LoanTransactionHelper errorLoanTransactionHelper = new LoanTransactionHelper(this.requestSpec, errorResponseSpec);
|
||||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
|
||||||
|
|
||||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
errorLoanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("4 September 2022").locale("en").dateFormat("dd MMMM yyyy")
|
||||||
|
.externalId(UUID.randomUUID().toString()).chargeOffReasonId((long) chargeOffReasonId));
|
||||||
|
});
|
||||||
|
|
||||||
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
assertEquals(403, exception.getResponse().code());
|
||||||
assertTrue(loanDetails.getStatus().getActive());
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.not.active"));
|
||||||
assertEquals(1000.0, loanDetails.getSummary().getTotalOutstanding());
|
|
||||||
assertFalse(loanDetails.getChargedOff());
|
|
||||||
assertNull(loanDetails.getSummary().getChargeOffReasonId());
|
|
||||||
assertNull(loanDetails.getSummary().getChargeOffReason());
|
|
||||||
assertNull(loanDetails.getTimeline().getChargedOffOnDate());
|
|
||||||
assertNull(loanDetails.getTimeline().getChargedOffByUsername());
|
|
||||||
assertNull(loanDetails.getTimeline().getChargedOffByFirstname());
|
|
||||||
assertNull(loanDetails.getTimeline().getChargedOffByLastname());
|
|
||||||
|
|
||||||
String transactionExternalId = UUID.randomUUID().toString();
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
this.loanTransactionHelper.chargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest().transactionDate("4 September 2022")
|
this.loanTransactionHelper.undoChargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest());
|
||||||
.locale("en").dateFormat("dd MMMM yyyy").externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.not.active"));
|
||||||
|
|
||||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
|
||||||
assertTrue(loanDetails.getStatus().getActive());
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
assertEquals(1000.0, loanDetails.getSummary().getTotalOutstanding());
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
assertTrue(loanDetails.getChargedOff());
|
|
||||||
assertEquals((long) chargeOffReasonId, loanDetails.getSummary().getChargeOffReasonId());
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithTransactionAmount("02 September 2022", loanID, "1000");
|
||||||
assertEquals(randomText, loanDetails.getSummary().getChargeOffReason());
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithTransactionAmount("03 September 2022", loanID, "1000");
|
||||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTimeline().getChargedOffOnDate());
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
assertEquals("mifos", loanDetails.getTimeline().getChargedOffByUsername());
|
|
||||||
assertEquals("App", loanDetails.getTimeline().getChargedOffByFirstname());
|
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||||
assertEquals("Administrator", loanDetails.getTimeline().getChargedOffByLastname());
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
assertEquals(2000.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
|
assertFalse(loanDetails.getChargedOff());
|
||||||
|
assertNull(loanDetails.getSummary().getChargeOffReasonId());
|
||||||
|
assertNull(loanDetails.getSummary().getChargeOffReason());
|
||||||
|
assertNull(loanDetails.getTimeline().getChargedOffOnDate());
|
||||||
|
assertNull(loanDetails.getTimeline().getChargedOffByUsername());
|
||||||
|
assertNull(loanDetails.getTimeline().getChargedOffByFirstname());
|
||||||
|
assertNull(loanDetails.getTimeline().getChargedOffByLastname());
|
||||||
|
|
||||||
|
Integer flatPenaltySpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "3", true));
|
||||||
|
this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
|
||||||
|
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatPenaltySpecifiedDueDate), "04 September 2022", "3"));
|
||||||
|
Integer chargeId = this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
|
||||||
|
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatPenaltySpecifiedDueDate), "04 September 2022", "5"));
|
||||||
|
|
||||||
|
PostLoansLoanIdChargesChargeIdResponse waiveChargeResponse = this.loanTransactionHelper.waiveLoanCharge((long) loanID,
|
||||||
|
(long) chargeId, new PostLoansLoanIdChargesChargeIdRequest());
|
||||||
|
|
||||||
|
String transactionExternalId = UUID.randomUUID().toString();
|
||||||
|
this.loanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("4 September 2022").locale("en").dateFormat("dd MMMM yyyy")
|
||||||
|
.externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
|
||||||
|
|
||||||
|
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
assertEquals(2003.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
|
assertTrue(loanDetails.getChargedOff());
|
||||||
|
assertEquals((long) chargeOffReasonId, loanDetails.getSummary().getChargeOffReasonId());
|
||||||
|
assertEquals(randomText, loanDetails.getSummary().getChargeOffReason());
|
||||||
|
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTimeline().getChargedOffOnDate());
|
||||||
|
assertEquals("mifos", loanDetails.getTimeline().getChargedOffByUsername());
|
||||||
|
assertEquals("App", loanDetails.getTimeline().getChargedOffByFirstname());
|
||||||
|
assertEquals("Administrator", loanDetails.getTimeline().getChargedOffByLastname());
|
||||||
|
|
||||||
|
GetLoansLoanIdTransactions chargeOffTransaction = loanDetails.getTransactions().get(loanDetails.getTransactions().size() - 1);
|
||||||
|
|
||||||
|
assertEquals(2003.0, chargeOffTransaction.getAmount());
|
||||||
|
assertEquals(2000.0, chargeOffTransaction.getPrincipalPortion());
|
||||||
|
assertEquals(3.0, chargeOffTransaction.getPenaltyChargesPortion());
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("4 September 2022").locale("en").dateFormat("dd MMMM yyyy")
|
||||||
|
.externalId(UUID.randomUUID().toString()).chargeOffReasonId((long) chargeOffReasonId));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.already.charged.off"));
|
||||||
|
|
||||||
|
HashMap chargeAddingError = errorLoanTransactionHelper.addChargesForLoanGetFullResponse(loanID, LoanTransactionHelper
|
||||||
|
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatPenaltySpecifiedDueDate), "04 September 2022", "3"));
|
||||||
|
|
||||||
|
assertEquals("error.msg.loan.is.charged.off",
|
||||||
|
((Map) ((List) chargeAddingError.get("errors")).get(0)).get("userMessageGlobalisationCode"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.undoWaiveLoanCharge((long) loanID, waiveChargeResponse.getSubResourceId(),
|
||||||
|
new PutChargeTransactionChangesRequest());
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.undoChargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest());
|
||||||
|
|
||||||
|
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||||
|
assertFalse(loanDetails.getChargedOff());
|
||||||
|
assertNull(loanDetails.getSummary().getChargeOffReasonId());
|
||||||
|
assertNull(loanDetails.getSummary().getChargeOffReason());
|
||||||
|
assertNull(loanDetails.getTimeline().getChargedOffOnDate());
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.undoChargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest());
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.not.charged.off"));
|
||||||
|
|
||||||
|
PostLoansLoanIdTransactionsResponse loanRepaymentResponse = this.loanTransactionHelper.makeLoanRepayment((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en")
|
||||||
|
.transactionAmount(5.0));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("04 September 2022").locale("en")
|
||||||
|
.dateFormat("dd MMMM yyyy").externalId(UUID.randomUUID().toString())
|
||||||
|
.chargeOffReasonId((long) chargeOffReasonId));
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.charge.off.is.before.than.the.last.user.transaction"));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("06 September 2022").locale("en").dateFormat("dd MMMM yyyy")
|
||||||
|
.externalId(UUID.randomUUID().toString()).chargeOffReasonId((long) chargeOffReasonId));
|
||||||
|
|
||||||
|
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||||
|
chargeOffTransaction = loanDetails.getTransactions().get(loanDetails.getTransactions().size() - 1);
|
||||||
|
|
||||||
|
assertEquals(1998.0, chargeOffTransaction.getAmount());
|
||||||
|
assertEquals(1998.0, chargeOffTransaction.getPrincipalPortion());
|
||||||
|
|
||||||
|
this.loanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate("07 September 2022").locale("en").transactionAmount(5.0));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.undoChargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest());
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.charge.off.is.not.the.last.user.transaction"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.adjustLoanTransaction((long) loanID, loanRepaymentResponse.getResourceId(),
|
||||||
|
new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("06 September 2022").locale("en")
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionAmount(0.0));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.adjusted.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.makeWriteoff((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate("05 September 2022").locale("en"));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.closeLoan((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate("05 September 2022").locale("en"));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.forecloseLoan((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate("05 September 2022").locale("en"));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.closeRescheduledLoan((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en"));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.charged.off"));
|
||||||
|
|
||||||
|
HashMap disbursementDetailREsponse = (HashMap) errorLoanTransactionHelper.addAndDeleteDisbursementDetail(loanID, "1000",
|
||||||
|
"03 September 2022", List.of(this.loanTransactionHelper.createTrancheDetail(null, "05 September 2022", "200")), "");
|
||||||
|
|
||||||
|
assertEquals("error.msg.loan.is.charged.off",
|
||||||
|
((Map) ((List) disbursementDetailREsponse.get("errors")).get(0)).get("userMessageGlobalisationCode"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.undoLastDisbursalLoan((long) loanID, new PostLoansLoanIdRequest());
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.charged.off"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.undoDisbursalLoan((long) loanID, new PostLoansLoanIdRequest());
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.loan.is.charged.off"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.disburseLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("4 September 2022").transactionAmount(new BigDecimal("10"))
|
||||||
|
.locale("en").dateFormat("dd MMMM yyyy"));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate("07 September 2022").locale("en").transactionAmount(5000.0));
|
||||||
|
|
||||||
|
exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
|
errorLoanTransactionHelper.makeRefundByCash((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0));
|
||||||
|
});
|
||||||
|
assertEquals(403, exception.getResponse().code());
|
||||||
|
assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date"));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("08 September 2022").locale("en").transactionAmount(3007.0));
|
||||||
|
} finally {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -7480,6 +7691,16 @@ public class ClientLoanIntegrationTest {
|
|||||||
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer createLoanProductWithPeriodicAccrualAccountingNoInterestMultiDisbursement(final Account... accounts) {
|
||||||
|
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
|
||||||
|
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
|
||||||
|
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||||
|
.withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30")
|
||||||
|
.withDaysInYear("365").withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true).build(null);
|
||||||
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
|
}
|
||||||
|
|
||||||
private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
|
private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
|
||||||
String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
|
String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
|
||||||
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
|||||||
+31
@@ -32,6 +32,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
|
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
|
||||||
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||||
@@ -246,6 +247,36 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateLoanRescheduleRequestFailIfLoanIsChargedOff() {
|
||||||
|
// create all required entities
|
||||||
|
this.createRequiredEntities();
|
||||||
|
this.createLoanRescheduleRequestWhichFailsAsLoanIdChargedOff();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create new loan reschedule request
|
||||||
|
**/
|
||||||
|
private void createLoanRescheduleRequestWhichFailsAsLoanIdChargedOff() {
|
||||||
|
|
||||||
|
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
|
||||||
|
.updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 October 2015")
|
||||||
|
.updateRecalculateInterest(true).build(this.loanId.toString());
|
||||||
|
|
||||||
|
this.loanTransactionHelper.chargeOffLoan((long) this.loanId,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("04 January 2015").locale("en").dateFormat("dd MMMM yyyy"));
|
||||||
|
|
||||||
|
ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||||
|
LoanRescheduleRequestHelper errorLoanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, responseSpec);
|
||||||
|
HashMap response = errorLoanRescheduleRequestHelper.createLoanRescheduleRequestWithFullResponse(requestJSON);
|
||||||
|
assertEquals("error.msg.loan.is.charged.off", ((Map) ((List) response.get("errors")).get(0)).get("userMessageGlobalisationCode"));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.undoChargeOffLoan((long) this.loanId, new PostLoansLoanIdTransactionsRequest());
|
||||||
|
this.loanTransactionHelper.closeRescheduledLoan((long) this.loanId,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("04 January 2015").locale("en"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create new loan reschedule request
|
* create new loan reschedule request
|
||||||
**/
|
**/
|
||||||
|
|||||||
+46
@@ -50,6 +50,7 @@ import java.util.TimeZone;
|
|||||||
import org.apache.fineract.client.models.BusinessDateRequest;
|
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.client.models.PutJobsJobIDRequest;
|
import org.apache.fineract.client.models.PutJobsJobIDRequest;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||||
@@ -603,6 +604,51 @@ public class SchedulerJobsTestResults {
|
|||||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApplyPenaltyForOverdueLoansJobOutcomeIfLoanChargedOff() throws InterruptedException {
|
||||||
|
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
|
||||||
|
Assertions.assertNotNull(clientID);
|
||||||
|
|
||||||
|
Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanOverdueFeeJSON());
|
||||||
|
Assertions.assertNotNull(overdueFeeChargeId);
|
||||||
|
|
||||||
|
final Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString());
|
||||||
|
Assertions.assertNotNull(loanProductID);
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "10 January 2020");
|
||||||
|
Assertions.assertNotNull(loanID);
|
||||||
|
|
||||||
|
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan("01 March 2020", loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
|
||||||
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", loanID,
|
||||||
|
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
this.loanTransactionHelper.chargeOffLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().transactionDate("03 March 2020").locale("en").dateFormat("dd MMMM yyyy"));
|
||||||
|
|
||||||
|
String JobName = "Apply penalty to overdue loans";
|
||||||
|
this.schedulerJobHelper.executeAndAwaitJob(JobName);
|
||||||
|
|
||||||
|
ArrayList<HashMap> repaymentScheduleDataAfter = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec,
|
||||||
|
loanID);
|
||||||
|
|
||||||
|
Assertions.assertEquals(0, (Integer) repaymentScheduleDataAfter.get(1).get("penaltyChargesDue"),
|
||||||
|
"Verifying From Penalty Charges due fot first Repayment after Successful completion of Scheduler Job");
|
||||||
|
|
||||||
|
this.loanTransactionHelper.undoChargeOffLoan((long) loanID, new PostLoansLoanIdTransactionsRequest());
|
||||||
|
this.loanTransactionHelper.closeRescheduledLoan((long) loanID,
|
||||||
|
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("03 March 2020").locale("en"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoanCOBJobOutcome() {
|
public void testLoanCOBJobOutcome() {
|
||||||
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
|
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
|
||||||
|
|||||||
+5
@@ -455,6 +455,11 @@ public class ExternalEventConfigurationHelper {
|
|||||||
loanChargeOffPostBusinessEvent.put("enabled", false);
|
loanChargeOffPostBusinessEvent.put("enabled", false);
|
||||||
defaults.add(loanChargeOffPostBusinessEvent);
|
defaults.add(loanChargeOffPostBusinessEvent);
|
||||||
|
|
||||||
|
Map<String, Object> loanUndoChargeOffPostBusinessEvent = new HashMap<>();
|
||||||
|
loanUndoChargeOffPostBusinessEvent.put("type", "LoanUndoChargeOffBusinessEvent");
|
||||||
|
loanUndoChargeOffPostBusinessEvent.put("enabled", false);
|
||||||
|
defaults.add(loanUndoChargeOffPostBusinessEvent);
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
|
|
||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class LoanRescheduleRequestHelper {
|
public class LoanRescheduleRequestHelper {
|
||||||
|
|
||||||
@@ -40,6 +41,11 @@ public class LoanRescheduleRequestHelper {
|
|||||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
|
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap createLoanRescheduleRequestWithFullResponse(final String requestJSON) {
|
||||||
|
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "");
|
||||||
|
}
|
||||||
|
|
||||||
public Integer rejectLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
|
public Integer rejectLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
|
||||||
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=reject";
|
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=reject";
|
||||||
|
|
||||||
|
|||||||
+43
@@ -710,6 +710,11 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "adjust"));
|
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "adjust"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Long loanId, final Long transactionId,
|
||||||
|
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "adjust"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Integer loanId, final Long transactionId, String date,
|
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Integer loanId, final Long transactionId, String date,
|
||||||
ResponseSpecification responseSpec) {
|
ResponseSpecification responseSpec) {
|
||||||
return postLoanTransaction(createLoanTransactionURL(null, loanId, transactionId.intValue()),
|
return postLoanTransaction(createLoanTransactionURL(null, loanId, transactionId.intValue()),
|
||||||
@@ -757,6 +762,12 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return (Integer) response.get("resourceId");
|
return (Integer) response.get("resourceId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap addChargesForLoanGetFullResponse(final Integer loanId, final String request) {
|
||||||
|
log.info("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
|
||||||
|
final String ADD_CHARGES_URL = LOAN_ACCOUNT_URL + "/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
return Utils.performServerPost(requestSpec, responseSpec, ADD_CHARGES_URL, request, "");
|
||||||
|
}
|
||||||
|
|
||||||
public Integer addChargesForLoan(final Integer loanId, final String request, final ResponseSpecification responseSpecParam) {
|
public Integer addChargesForLoan(final Integer loanId, final String request, final ResponseSpecification responseSpecParam) {
|
||||||
log.info("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
|
log.info("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
|
||||||
final String ADD_CHARGES_URL = LOAN_ACCOUNT_URL + "/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
|
final String ADD_CHARGES_URL = LOAN_ACCOUNT_URL + "/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
|
||||||
@@ -1738,6 +1749,10 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "disburse"));
|
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "disburse"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdResponse disburseLoan(Long loanId, PostLoansLoanIdRequest request) {
|
||||||
|
return ok(fineract().loans.stateTransitions(loanId, request, "disburse"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdResponse disburseToSavingsLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
public PostLoansLoanIdResponse disburseToSavingsLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
||||||
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "disburseToSavings"));
|
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "disburseToSavings"));
|
||||||
}
|
}
|
||||||
@@ -1750,10 +1765,18 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "undodisbursal"));
|
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "undodisbursal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdResponse undoDisbursalLoan(Long loanId, PostLoansLoanIdRequest request) {
|
||||||
|
return ok(fineract().loans.stateTransitions(loanId, request, "undodisbursal"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdResponse undoLastDisbursalLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
public PostLoansLoanIdResponse undoLastDisbursalLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
||||||
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "undolastdisbursal"));
|
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "undolastdisbursal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdResponse undoLastDisbursalLoan(Long loanId, PostLoansLoanIdRequest request) {
|
||||||
|
return ok(fineract().loans.stateTransitions(loanId, request, "undolastdisbursal"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdResponse assignLoanOfficerLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
public PostLoansLoanIdResponse assignLoanOfficerLoan(String loanExternalId, PostLoansLoanIdRequest request) {
|
||||||
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "assignloanofficer"));
|
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "assignloanofficer"));
|
||||||
}
|
}
|
||||||
@@ -1774,14 +1797,26 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "close-rescheduled"));
|
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "close-rescheduled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse closeRescheduledLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "close-rescheduled"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdTransactionsResponse closeLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
public PostLoansLoanIdTransactionsResponse closeLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "close"));
|
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "close"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse closeLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "close"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdTransactionsResponse forecloseLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
public PostLoansLoanIdTransactionsResponse forecloseLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "foreclosure"));
|
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "foreclosure"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse forecloseLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "foreclosure"));
|
||||||
|
}
|
||||||
|
|
||||||
public PostLoansLoanIdTransactionsResponse chargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
public PostLoansLoanIdTransactionsResponse chargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "charge-off"));
|
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "charge-off"));
|
||||||
}
|
}
|
||||||
@@ -1789,4 +1824,12 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
public PostLoansLoanIdTransactionsResponse chargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
public PostLoansLoanIdTransactionsResponse chargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "charge-off"));
|
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "charge-off"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse undoChargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "undo-charge-off"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostLoansLoanIdTransactionsResponse undoChargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) {
|
||||||
|
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "undo-charge-off"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user