FINERACT-2082: New Transaction Type - Interest Payment Waiver
This commit is contained in:
+100
@@ -110,6 +110,106 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
else if (loanTransactionDTO.getTransactionType().isChargeoff()) {
|
||||
createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
// Logic for Interest Payment Waiver
|
||||
else if (loanTransactionDTO.getTransactionType().isInterestPaymentWaiver()) {
|
||||
createJournalEntriesForInterestPaymentWaiver(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForInterestPaymentWaiver(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
|
||||
|
||||
// transaction properties
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPayment = loanTransactionDTO.getOverPayment();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
|
||||
Map<GLAccount, BigDecimal> accountMapForCredit = new LinkedHashMap<>();
|
||||
|
||||
Map<Integer, BigDecimal> accountMapForDebit = new LinkedHashMap<>();
|
||||
|
||||
if (isMarkedAsChargeOff) {
|
||||
// ChargeOFF
|
||||
// principal payment
|
||||
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// interest payment
|
||||
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// handle fees payment
|
||||
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
|
||||
// handle penalty payment
|
||||
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// handle overpayment
|
||||
if (overPayment != null && overPayment.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, overPayment, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
} else {
|
||||
// principal payment
|
||||
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// interest payment
|
||||
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// handle fees payment
|
||||
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
|
||||
// handle penalty payment
|
||||
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(),
|
||||
accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
// handle overpayment
|
||||
if (overPayment != null && overPayment.compareTo(BigDecimal.ZERO) > 0) {
|
||||
populateCreditDebitMaps(loanProductId, overPayment, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), accountMapForCredit, accountMapForDebit);
|
||||
}
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<GLAccount, BigDecimal> creditEntry : accountMapForCredit.entrySet()) {
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), isReversal, creditEntry.getKey());
|
||||
}
|
||||
|
||||
// create debit entries
|
||||
for (Map.Entry<Integer, BigDecimal> debitEntry : accountMapForDebit.entrySet()) {
|
||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, debitEntry.getKey().intValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue(), isReversal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-6
@@ -111,9 +111,9 @@ public class LoanTransactionsApiResource {
|
||||
+ "loans/1/transactions/template?command=disburse" + "\n" + "loans/1/transactions/template?command=disburseToSavings" + "\n"
|
||||
+ "loans/1/transactions/template?command=recoverypayment" + "\n" + "loans/1/transactions/template?command=prepayLoan" + "\n"
|
||||
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n"
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" + "\n"
|
||||
+ "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
|
||||
+ "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)"
|
||||
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
||||
public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
|
||||
@@ -140,9 +140,9 @@ public class LoanTransactionsApiResource {
|
||||
+ "loans/1/transactions/template?command=disburse" + "\n" + "loans/1/transactions/template?command=disburseToSavings" + "\n"
|
||||
+ "loans/1/transactions/template?command=recoverypayment" + "\n" + "loans/1/transactions/template?command=prepayLoan" + "\n"
|
||||
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n"
|
||||
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" + "\n"
|
||||
+ "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
|
||||
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
|
||||
+ "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)"
|
||||
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
||||
public String retrieveTransactionTemplate(
|
||||
@@ -455,6 +455,8 @@ public class LoanTransactionsApiResource {
|
||||
commandRequest = builder.loanPayoutRefundTransaction(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
|
||||
commandRequest = builder.loanGoodwillCreditTransaction(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "interestPaymentWaiver")) {
|
||||
commandRequest = builder.loanInterestPaymentWaiverTransaction(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "chargeRefund")) {
|
||||
commandRequest = builder.refundLoanCharge(resolvedLoanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
|
||||
@@ -521,6 +523,10 @@ public class LoanTransactionsApiResource {
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.GOODWILL_CREDIT,
|
||||
resolvedLoanId, transactionDate);
|
||||
} else if (CommandParameterUtil.is(commandParam, "interestPaymentWaiver")) {
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.INTEREST_PAYMENT_WAIVER,
|
||||
resolvedLoanId, transactionDate);
|
||||
} else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveWaiveInterestDetails(resolvedLoanId);
|
||||
} else if (CommandParameterUtil.is(commandParam, "writeoff")) {
|
||||
|
||||
+6
@@ -57,6 +57,8 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionGoodwillCreditPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionGoodwillCreditPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMerchantIssuedRefundPostBusinessEvent;
|
||||
@@ -292,6 +294,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
repaymentEvent = new LoanTransactionPayoutRefundPreBusinessEvent(loan);
|
||||
} else if (repaymentTransactionType.isGoodwillCredit()) {
|
||||
repaymentEvent = new LoanTransactionGoodwillCreditPreBusinessEvent(loan);
|
||||
} else if (repaymentTransactionType.isInterestPaymentWaiver()) {
|
||||
repaymentEvent = new LoanTransactionInterestPaymentWaiverPreBusinessEvent(loan);
|
||||
} else if (repaymentTransactionType.isChargeRefund()) {
|
||||
repaymentEvent = new LoanChargePaymentPreBusinessEvent(loan);
|
||||
} else if (isRecoveryRepayment) {
|
||||
@@ -313,6 +317,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
repaymentEvent = new LoanTransactionPayoutRefundPostBusinessEvent(transaction);
|
||||
} else if (repaymentTransactionType.isGoodwillCredit()) {
|
||||
repaymentEvent = new LoanTransactionGoodwillCreditPostBusinessEvent(transaction);
|
||||
} else if (repaymentTransactionType.isInterestPaymentWaiver()) {
|
||||
repaymentEvent = new LoanTransactionInterestPaymentWaiverPostBusinessEvent(transaction);
|
||||
} else if (repaymentTransactionType.isChargeRefund()) {
|
||||
repaymentEvent = new LoanChargePaymentPostBusinessEvent(transaction);
|
||||
} else if (isRecoveryRepayment) {
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.DataIntegrityErrorHandler;
|
||||
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 = "INTERESTPAYMENTWAIVER")
|
||||
public class LoanInterestPaymentWaiverCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.makeInterestPaymentWaiver(command);
|
||||
}
|
||||
}
|
||||
+173
-1
@@ -35,32 +35,54 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.exception.CurrencyNotFoundException;
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public final class LoanEventApiJsonValidator {
|
||||
public final class LoanTransactionValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanApplicationValidator fromApiJsonDeserializer;
|
||||
private final LoanRepository loanRepository;
|
||||
private final ApplicationCurrencyRepository applicationCurrencyRepository;
|
||||
private final LoanUtilService loanUtilService;
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
@@ -562,4 +584,154 @@ public final class LoanEventApiJsonValidator {
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateLoanClientIsActive(final Loan loan) {
|
||||
final Client client = loan.client();
|
||||
if (client != null && client.isNotActive()) {
|
||||
throw new ClientNotActiveException(client.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanGroupIsActive(final Loan loan) {
|
||||
final Group group = loan.group();
|
||||
if (group != null && group.isNotActive()) {
|
||||
throw new GroupNotActiveException(group.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanStatusIsActiveOrFullyPaidOrOverpaid(final Loan loan) {
|
||||
if (!(loan.isOpen() || loan.isClosedObligationsMet() || loan.isOverPaid())) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final String defaultUserMessage = "Loan must be Active, Fully Paid or Overpaid";
|
||||
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.must.be.active.fully.paid.or.overpaid",
|
||||
defaultUserMessage);
|
||||
dataValidationErrors.add(error);
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
|
||||
String reversedOrCreated) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
if (txn.isChargeRefund() && DateUtils.isBefore(transactionDate, txn.getTransactionDate())) {
|
||||
final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists";
|
||||
final String details = "Loan Transaction: " + loan.getId() + " Can't be " + reversedOrCreated
|
||||
+ " because a Later Charge Refund Exists.";
|
||||
throw new LoanChargeRefundException(errorMessage, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanDisbursementIsBeforeTransactionDate(final Loan loan, final LocalDate transactionDate) {
|
||||
if (DateUtils.isBefore(transactionDate, loan.getDisbursementDate())) {
|
||||
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
||||
+ loan.getDisbursementDate().toString();
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage,
|
||||
transactionDate, loan.getDisbursementDate());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionShouldNotBeInTheFuture(final LocalDate transactionDate) {
|
||||
if (DateUtils.isDateInTheFuture(transactionDate)) {
|
||||
final String errorMessage = "The transaction date cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, transactionDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasCurrency(final Loan loan) {
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency defaultApplicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
|
||||
if (defaultApplicationCurrency == null) {
|
||||
throw new CurrencyNotFoundException(currency.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, LocalDate transactionDate) {
|
||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||
if (DateUtils.isBefore(transactionDate, clientOfficeJoiningDate)) {
|
||||
String errorMessage = "The date on which a repayment or waiver is made cannot be earlier than client's transfer date to this office";
|
||||
String action = "repayment.or.waiver";
|
||||
String postfix = "cannot.be.made.before.client.transfer.date";
|
||||
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate) {
|
||||
if (!(loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())) {
|
||||
return;
|
||||
}
|
||||
LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
|
||||
if (DateUtils.isAfter(lastTransactionDate, activityDate)) {
|
||||
String errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date";
|
||||
String action = "repayment.or.waiver";
|
||||
String postfix = "cannot.be.made.before.last.transaction.date";
|
||||
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, lastTransactionDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||
final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||
final String errorMessage = "Repayment date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("repayment.date.on.non.working.day", errorMessage, repaymentDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
|
||||
final List<Holiday> holidays) {
|
||||
if (!allowTransactionsOnHoliday && HolidayUtil.isHoliday(repaymentDate, holidays)) {
|
||||
final String errorMessage = "Repayment date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("repayment.date.on.holiday", errorMessage, repaymentDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(Loan loan) {
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted);
|
||||
if (totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) < 0) {
|
||||
final String errorMessage = "The transaction amount cannot exceed threshold.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "amount.exceeds.threshold", errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
||||
final Long loanId = command.getLoanId();
|
||||
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
validateNewRepaymentTransaction(command.json());
|
||||
validateTransactionShouldNotBeInTheFuture(transactionDate);
|
||||
validateLoanClientIsActive(loan);
|
||||
validateLoanHasCurrency(loan);
|
||||
validateLoanGroupIsActive(loan);
|
||||
validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan);
|
||||
validateLoanDisbursementIsBeforeTransactionDate(loan, transactionDate);
|
||||
validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(loan, transactionDate, "created");
|
||||
|
||||
validateClientOfficeJoiningDateIsBeforeTransactionDate(loan, transactionDate);
|
||||
validateActivityNotBeforeLastTransactionDate(loan, transactionDate);
|
||||
HolidayDetailDTO holidayDetailDTO = loanUtilService.constructHolidayDTO(loan.getOfficeId(), loan.getDisbursementDate());
|
||||
validateRepaymentDateIsOnHoliday(transactionDate, holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
||||
validateRepaymentDateIsOnNonWorkingDay(transactionDate, holidayDetailDTO.getWorkingDays(),
|
||||
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
|
||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||
}
|
||||
|
||||
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
||||
// Payment allocation calculates the new total principal repaid, and it should be validated after recalculation
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted);
|
||||
if (totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) < 0
|
||||
&& loan.getLoanRepaymentScheduleDetail().getPrincipal().minus(totalDisbursed).isGreaterThanZero()) {
|
||||
final String errorMessage = "The transaction amount cannot exceed threshold.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "amount.exceeds.threshold", errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LoanTransactionAssembler {
|
||||
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
|
||||
LoanTransaction assembleTransactionAndCalculateChanges(Loan loan, JsonCommand command, Map<String, Object> changes) {
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
|
||||
changes.put("locale", command.locale());
|
||||
changes.put("dateFormat", command.dateFormat());
|
||||
changes.put("paymentTypeId", command.longValueOfParameterNamed("paymentTypeId"));
|
||||
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
}
|
||||
if (!txnExternalId.isEmpty()) {
|
||||
changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
|
||||
}
|
||||
|
||||
final PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
final LoanTransactionType repaymentTransactionType = LoanTransactionType.INTEREST_PAYMENT_WAIVER;
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
return LoanTransaction.repaymentType(repaymentTransactionType, loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
|
||||
txnExternalId, null);
|
||||
}
|
||||
}
|
||||
+11
@@ -175,6 +175,17 @@ public class LoanUtilService {
|
||||
return holidayDetailDTO;
|
||||
}
|
||||
|
||||
public HolidayDetailDTO constructHolidayDTO(final Long officeId, LocalDate localDate) {
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, localDate,
|
||||
HolidayStatusType.ACTIVE.getValue());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
|
||||
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
|
||||
|
||||
return new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
|
||||
}
|
||||
|
||||
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
|
||||
FloatingRateDTO floatingRateDTO = null;
|
||||
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
|
||||
|
||||
+326
-21
@@ -84,6 +84,9 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
|
||||
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.LoanDisbursalTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
||||
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.LoanWaiveInterestBusinessEvent;
|
||||
@@ -109,7 +112,9 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepositor
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
@@ -162,7 +167,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
|
||||
@@ -180,7 +188,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
@@ -188,6 +196,7 @@ import org.apache.fineract.portfolio.note.domain.Note;
|
||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus;
|
||||
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
|
||||
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
|
||||
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
|
||||
@@ -203,7 +212,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanEventApiJsonValidator loanEventApiJsonValidator;
|
||||
private final LoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanAccountDomainService loanAccountDomainService;
|
||||
@@ -247,6 +256,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
private final ErrorHandler errorHandler;
|
||||
private final LoanDownPaymentHandlerService loanDownPaymentHandlerService;
|
||||
private final AccountTransferRepository accountTransferRepository;
|
||||
private final LoanTransactionAssembler loanTransactionAssembler;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -283,11 +294,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer);
|
||||
this.loanTransactionValidator.validateDisbursement(command.json(), isAccountTransfer);
|
||||
|
||||
if (command.parameterExists("postDatedChecks")) {
|
||||
// validate with post dated checks for the disbursement
|
||||
this.loanEventApiJsonValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId);
|
||||
this.loanTransactionValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId);
|
||||
}
|
||||
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
@@ -364,7 +375,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
if (loan.isSyncDisbursementWithMeeting()) {
|
||||
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
|
||||
this.loanTransactionValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
|
||||
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays());
|
||||
}
|
||||
|
||||
@@ -956,13 +967,307 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
chargeRefundChargeType);
|
||||
}
|
||||
|
||||
private void handleLoanOverpayment(Loan loan, LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
loan.setOverpaidOnDate(transactionDate);
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, loan);
|
||||
loan.setClosedOnDate(null);
|
||||
loan.setActualMaturityDate(null);
|
||||
}
|
||||
|
||||
private void handleLoanRepaymentInFull(final Loan loan, final LocalDate transactionDate,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
|
||||
boolean isAllChargesPaid = true;
|
||||
for (final LoanCharge loanCharge : loan.getCharges()) {
|
||||
if (loanCharge.isActive() && loanCharge.amount().compareTo(BigDecimal.ZERO) > 0
|
||||
&& !(loanCharge.isPaid() || loanCharge.isWaived())) {
|
||||
isAllChargesPaid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAllChargesPaid) {
|
||||
loan.setClosedOnDate(transactionDate);
|
||||
loan.setActualMaturityDate(transactionDate);
|
||||
loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, loan);
|
||||
} else if (LoanStatus.fromInt(loan.getLoanStatus()).isOverpaid()) {
|
||||
if (loan.getTotalOverpaid() == null || BigDecimal.ZERO.compareTo(loan.getTotalOverpaid()) == 0) {
|
||||
loan.setOverpaidOnDate(null);
|
||||
}
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
|
||||
}
|
||||
loan.processIncomeAccrualTransactionOnLoanClosure();
|
||||
}
|
||||
|
||||
private boolean doPostLoanTransactionChecks(final Loan loan, final LocalDate transactionDate,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
boolean statusChanged = false;
|
||||
boolean isOverpaid = loan.getTotalOverpaid() != null && loan.getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0;
|
||||
if (isOverpaid) {
|
||||
// FIXME - kw - update account balance to negative amount.
|
||||
handleLoanOverpayment(loan, transactionDate, loanLifecycleStateMachine);
|
||||
statusChanged = true;
|
||||
} else if (loan.getSummary().isRepaidInFull(loan.loanCurrency())) {
|
||||
handleLoanRepaymentInFull(loan, transactionDate, loanLifecycleStateMachine);
|
||||
statusChanged = true;
|
||||
} else {
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
|
||||
}
|
||||
if (loan.getTotalOverpaid() == null || BigDecimal.ZERO.compareTo(loan.getTotalOverpaid()) == 0) {
|
||||
loan.setOverpaidOnDate(null);
|
||||
}
|
||||
return statusChanged;
|
||||
}
|
||||
|
||||
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
|
||||
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
|
||||
if (transferTransaction != null) {
|
||||
transferTransaction.updateToLoanTransaction(newLoanTransaction);
|
||||
this.accountTransferRepository.save(transferTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check
|
||||
public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction,
|
||||
final List<LoanTransaction> loanTransactions) {
|
||||
boolean isChronologicallyLatestRepaymentOrWaiver = true;
|
||||
|
||||
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
|
||||
for (final LoanTransaction previousTransaction : loanTransactions) {
|
||||
if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()
|
||||
&& (DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())
|
||||
|| (DateUtils.isEqual(currentTransactionDate, previousTransaction.getTransactionDate())
|
||||
&& ((loanTransaction.getId() == null && previousTransaction.getId() == null)
|
||||
|| (loanTransaction.getId() != null && (previousTransaction.getId() == null
|
||||
|| loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) {
|
||||
isChronologicallyLatestRepaymentOrWaiver = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isChronologicallyLatestRepaymentOrWaiver;
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loan loan,
|
||||
LoanTransaction newInterestPaymentWaiverTransaction) {
|
||||
LocalDate recalculateFrom = null;
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
recalculateFrom = newInterestPaymentWaiverTransaction.getTransactionDate();
|
||||
}
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
newInterestPaymentWaiverTransaction.updateLoan(loan);
|
||||
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction,
|
||||
loan.getLoanTransactions());
|
||||
|
||||
if (newInterestPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) {
|
||||
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
||||
}
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessorFactory()
|
||||
.determineProcessor(loan.getTransactionProcessingStrategyCode());
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan
|
||||
.fetchLoanRepaymentScheduleInstallment(newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
boolean reprocess = true;
|
||||
|
||||
if (!loan.isForeclosure() && isTransactionChronologicallyLatest
|
||||
&& DateUtils.isEqualBusinessDate(newInterestPaymentWaiverTransaction.getTransactionDate()) && currentInstallment != null
|
||||
&& currentInstallment.getTotalOutstanding(loan.getCurrency())
|
||||
.isEqualTo(newInterestPaymentWaiverTransaction.getAmount(loan.getCurrency()))) {
|
||||
reprocess = false;
|
||||
}
|
||||
|
||||
if (isTransactionChronologicallyLatest && (!reprocess || !loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled())
|
||||
&& !loan.isForeclosure()) {
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(newInterestPaymentWaiverTransaction,
|
||||
new LoanRepaymentScheduleTransactionProcessor.TransactionCtx(loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney())));
|
||||
reprocess = false;
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
|
||||
reprocess = true;
|
||||
} else {
|
||||
final LoanRepaymentScheduleInstallment nextInstallment = loan
|
||||
.fetchRepaymentScheduleInstallment(currentInstallment.getInstallmentNumber() + 1);
|
||||
if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(loan.getCurrency()).isGreaterThanZero()) {
|
||||
reprocess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reprocess) {
|
||||
changedTransactionDetail = reprocessChangedLoanTransactions(loan, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
|
||||
doPostLoanTransactionChecks(loan, newInterestPaymentWaiverTransaction.getTransactionDate(), defaultLoanLifecycleStateMachine);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail reprocessChangedLoanTransactions(Loan loan,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(loan);
|
||||
}
|
||||
/***
|
||||
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
||||
* the reversed transactions first and then new transactions.
|
||||
*/
|
||||
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand command) {
|
||||
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiver(command);
|
||||
|
||||
final Long loanId = command.getLoanId();
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestPaymentWaiverPreBusinessEvent(loan));
|
||||
|
||||
// save already existing transaction ids
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
|
||||
LoanTransaction newInterestPaymentWaiverTransaction = loanTransactionAssembler.assembleTransactionAndCalculateChanges(loan, command,
|
||||
changes);
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = recalculateLoanWithInterestPaymentWaiverTxn(loan,
|
||||
newInterestPaymentWaiverTransaction);
|
||||
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiverAfterRecalculation(loan);
|
||||
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newInterestPaymentWaiverTransaction);
|
||||
|
||||
saveChangedTransactionDetail(changedTransactionDetail);
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
saveNote(noteText, loan, newInterestPaymentWaiverTransaction);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
// disable all active standing orders linked to this loan if status changes to closed
|
||||
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
|
||||
updateAndSavePostDatedChecksForIndividualAccount(loan, newInterestPaymentWaiverTransaction);
|
||||
updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, newInterestPaymentWaiverTransaction);
|
||||
|
||||
// Finished Notification
|
||||
|
||||
LoanTransactionBusinessEvent transactionRepaymentEvent = new LoanTransactionInterestPaymentWaiverPostBusinessEvent(
|
||||
newInterestPaymentWaiverTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
.withEntityId(newInterestPaymentWaiverTransaction.getId()) //
|
||||
.withEntityExternalId(newInterestPaymentWaiverTransaction.getExternalId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private void saveNote(String noteText, Loan loan, LoanTransaction loanTransaction) {
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanTransactionNote(loan, loanTransaction, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChangedTransactionDetail(ChangedTransactionDetail changedTransactionDetail) {
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
// Trigger transaction replayed event
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAndSavePostDatedChecksForIndividualAccount(Loan loan, LoanTransaction loanTransaction) {
|
||||
if (loan.getLoanType().isIndividualAccount()) {
|
||||
// Mark Post Dated Check as paid.
|
||||
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = loanTransaction
|
||||
.getLoanTransactionToRepaymentScheduleMappings();
|
||||
|
||||
if (loanTransactionToRepaymentScheduleMappings != null) {
|
||||
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) {
|
||||
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
|
||||
.getLoanRepaymentScheduleInstallment();
|
||||
if (loanRepaymentScheduleInstallment != null) {
|
||||
final boolean isPaid = loanRepaymentScheduleInstallment.isNotFullyPaidOff();
|
||||
PostDatedChecks postDatedChecks = this.postDatedChecksRepository
|
||||
.getPendingPostDatedCheck(loanRepaymentScheduleInstallment);
|
||||
|
||||
if (postDatedChecks != null) {
|
||||
if (!isPaid) {
|
||||
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PAID);
|
||||
} else {
|
||||
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PENDING);
|
||||
}
|
||||
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction loanTransaction) {
|
||||
if (loan.getLoanType().isIndividualAccount()) {
|
||||
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
|
||||
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
|
||||
loanCollateralManagement.setLoanTransactionData(loanTransaction);
|
||||
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
|
||||
|
||||
if (loan.getStatus().isClosed()) {
|
||||
loanCollateralManagement.setIsReleased(true);
|
||||
BigDecimal quantity = loanCollateralManagement.getQuantity();
|
||||
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
|
||||
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
|
||||
}
|
||||
}
|
||||
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final LoanTransactionType repaymentTransactionType,
|
||||
final Long loanId, final JsonCommand command, final boolean isRecoveryRepayment, final String chargeRefundChargeType) {
|
||||
|
||||
this.loanUtilService.validateRepaymentTransactionType(repaymentTransactionType);
|
||||
this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json());
|
||||
this.loanTransactionValidator.validateNewRepaymentTransaction(command.json());
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
@@ -1083,7 +1388,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
||||
this.loanTransactionValidator.validateTransaction(command.json());
|
||||
LoanTransaction transactionToAdjust = this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), command.getLoanId())
|
||||
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
|
||||
|
||||
@@ -1255,7 +1560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult chargebackLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
|
||||
this.loanEventApiJsonValidator.validateChargebackTransaction(command.json());
|
||||
this.loanTransactionValidator.validateChargebackTransaction(command.json());
|
||||
|
||||
LoanTransaction loanTransaction = this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), command.getLoanId())
|
||||
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
|
||||
@@ -1369,7 +1674,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
||||
this.loanTransactionValidator.validateTransaction(command.json());
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
@@ -1456,7 +1761,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
public CommandProcessingResult writeOff(final Long loanId, final JsonCommand command) {
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
|
||||
this.loanTransactionValidator.validateTransactionWithNoAmount(command.json());
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
@@ -1534,7 +1839,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
|
||||
this.loanTransactionValidator.validateTransactionWithNoAmount(command.json());
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -1643,7 +1948,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
|
||||
this.loanTransactionValidator.validateTransactionWithNoAmount(command.json());
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -1839,7 +2144,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult loanReassignment(final Long loanId, final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateUpdateOfLoanOfficer(command.json());
|
||||
this.loanTransactionValidator.validateUpdateOfLoanOfficer(command.json());
|
||||
|
||||
final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId");
|
||||
final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId");
|
||||
@@ -1875,7 +2180,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult bulkLoanReassignment(final JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateForBulkLoanReassignment(command.json());
|
||||
this.loanTransactionValidator.validateForBulkLoanReassignment(command.json());
|
||||
|
||||
final Long fromLoanOfficerId = command.longValueOfParameterNamed("fromLoanOfficerId");
|
||||
final Long toLoanOfficerId = command.longValueOfParameterNamed("toLoanOfficerId");
|
||||
@@ -2412,7 +2717,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
"Update Loan: " + loanId + " disbursement details is not allowed. Loan Account is Charged-off", loanId);
|
||||
}
|
||||
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
|
||||
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
|
||||
this.loanTransactionValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
|
||||
|
||||
CommandProcessingResult result = processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUpdateDisbursementDataBusinessEvent(loan));
|
||||
@@ -2506,7 +2811,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command) {
|
||||
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
|
||||
this.loanTransactionValidator.validateNewRefundTransaction(command.json());
|
||||
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
@@ -2551,7 +2856,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult markLoanAsFraud(Long loanId, JsonCommand command) {
|
||||
this.loanEventApiJsonValidator.validateMarkAsFraudLoan(command.json());
|
||||
this.loanTransactionValidator.validateMarkAsFraudLoan(command.json());
|
||||
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
@@ -2578,7 +2883,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Transactional
|
||||
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
|
||||
|
||||
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
|
||||
this.loanTransactionValidator.validateNewRefundTransaction(command.json());
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
@@ -2696,7 +3001,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.transactionDateParamName, element);
|
||||
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
this.loanEventApiJsonValidator.validateLoanForeclosure(command.json());
|
||||
this.loanTransactionValidator.validateLoanForeclosure(command.json());
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
// Got changed to match with the rest of the APIs
|
||||
changes.put("dateFormat", command.dateFormat());
|
||||
@@ -2732,7 +3037,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Transactional
|
||||
public CommandProcessingResult chargeOff(JsonCommand command) {
|
||||
|
||||
loanEventApiJsonValidator.validateChargeOffTransaction(command.json());
|
||||
loanTransactionValidator.validateChargeOffTransaction(command.json());
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
changes.put(LoanApiConstants.transactionDateParamName,
|
||||
@@ -2807,7 +3112,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult undoChargeOff(JsonCommand command) {
|
||||
this.loanEventApiJsonValidator.validateUndoChargeOff(command.json());
|
||||
this.loanTransactionValidator.validateUndoChargeOff(command.json());
|
||||
final Long loanId = command.getLoanId();
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||
|
||||
+17
-5
@@ -43,6 +43,7 @@ import org.apache.fineract.organisation.teller.data.CashierTransactionDataValida
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountNumberGenerator;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
@@ -87,7 +88,7 @@ import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationM
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.BulkLoansReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.BulkLoansReadPlatformServiceImpl;
|
||||
@@ -121,6 +122,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
|
||||
@@ -253,6 +255,14 @@ public class LoanAccountConfiguration {
|
||||
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanTransactionAssembler.class)
|
||||
public LoanTransactionAssembler loanTransactionAssembler(ExternalIdFactory externalIdFactory,
|
||||
PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
|
||||
|
||||
return new LoanTransactionAssembler(externalIdFactory, paymentDetailWritePlatformService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanCalculateRepaymentPastDueService.class)
|
||||
public LoanCalculateRepaymentPastDueService loanCalculateRepaymentPastDueService() {
|
||||
@@ -362,7 +372,7 @@ public class LoanAccountConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanWritePlatformService.class)
|
||||
public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context,
|
||||
LoanEventApiJsonValidator loanEventApiJsonValidator,
|
||||
LoanTransactionValidator loanTransactionValidator,
|
||||
LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository,
|
||||
LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository,
|
||||
@@ -386,8 +396,9 @@ public class LoanAccountConfiguration {
|
||||
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService,
|
||||
ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler,
|
||||
LoanDownPaymentHandlerService loanDownPaymentHandlerService) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanEventApiJsonValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
LoanDownPaymentHandlerService loanDownPaymentHandlerService, AccountTransferRepository accountTransferRepository,
|
||||
LoanTransactionAssembler loanTransactionAssembler) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
|
||||
@@ -398,7 +409,8 @@ public class LoanAccountConfiguration {
|
||||
transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
|
||||
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
|
||||
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService);
|
||||
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository,
|
||||
loanTransactionAssembler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -160,4 +160,5 @@
|
||||
<include file="parts/0138_add_external_event_for_loan_reaging_reamortization_2.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0139_add_disburse_without_auto_payment_command.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0140_trial_balance_with_asset_transfer_update.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0141_add_interest_payment_waiver_transaction_type.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?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.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="transaction_loan" />
|
||||
<column name="code" value="INTERESTPAYMENTWAIVER_LOAN" />
|
||||
<column name="entity_name" value="LOAN" />
|
||||
<column name="action_name" value="INTERESTPAYMENTWAIVER" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
<insert tableName="m_external_event_configuration">
|
||||
<column name="type" value="LoanTransactionInterestPaymentWaiverPostBusinessEvent"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_external_event_configuration">
|
||||
<column name="type" value="LoanTransactionInterestPaymentWaiverPreBusinessEvent"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="r_enum_value">
|
||||
<column name="enum_name" value="transaction_type_enum"/>
|
||||
<column name="enum_id" valueNumeric="31"/>
|
||||
<column name="enum_message_property" value="Interest Payment Waiver"/>
|
||||
<column name="enum_value" value="Interest Payment Waiver"/>
|
||||
<column name="enum_type" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+4
-2
@@ -101,7 +101,8 @@ public class ExternalEventConfigurationValidationServiceTest {
|
||||
"LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent",
|
||||
"LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent",
|
||||
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent");
|
||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent");
|
||||
|
||||
List<FineractPlatformTenant> tenants = Arrays
|
||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||
@@ -184,7 +185,8 @@ public class ExternalEventConfigurationValidationServiceTest {
|
||||
"LoanAccountDelinquencyPauseChangedBusinessEvent", "LoanAccountCustomSnapshotBusinessEvent",
|
||||
"LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent",
|
||||
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent");
|
||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent");
|
||||
|
||||
List<FineractPlatformTenant> tenants = Arrays
|
||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||
|
||||
Reference in New Issue
Block a user