You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by "adamsaghy (via GitHub)" <gi...@apache.org> on 2024/06/14 10:57:58 UTC
Re: [PR] FINERACT-2092: New transaction type payment waiver [fineract]
adamsaghy commented on code in PR #3932:
URL: https://github.com/apache/fineract/pull/3932#discussion_r1639654234
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java:
##########
@@ -956,13 +967,333 @@ public CommandProcessingResult makeLoanRepayment(final LoanTransactionType repay
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;
+ }
+
+ 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 = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+ final LoanTransactionType repaymentTransactionType = LoanTransactionType.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);
+ }
+
+ ChangedTransactionDetail assembleUpdatedLoan(Loan loan, LoanTransaction newPaymentWaiverTransaction) {
+ LocalDate recalculateFrom = null;
+ if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ recalculateFrom = newPaymentWaiverTransaction.getTransactionDate();
+ }
+ final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
+
+ ChangedTransactionDetail changedTransactionDetail = null;
+ newPaymentWaiverTransaction.updateLoan(loan);
+
+ final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(newPaymentWaiverTransaction,
+ loan.getLoanTransactions());
+
+ if (newPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) {
+ loan.addLoanTransaction(newPaymentWaiverTransaction);
+ }
+
+ final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessorFactory()
+ .determineProcessor(loan.getTransactionProcessingStrategyCode());
+
+ final LoanRepaymentScheduleInstallment currentInstallment = loan
+ .fetchLoanRepaymentScheduleInstallment(newPaymentWaiverTransaction.getTransactionDate());
+
+ boolean reprocess = true;
+
+ if (!loan.isForeclosure() && isTransactionChronologicallyLatest
+ && DateUtils.isEqualBusinessDate(newPaymentWaiverTransaction.getTransactionDate()) && currentInstallment != null
+ && currentInstallment.getTotalOutstanding(loan.getCurrency())
+ .isEqualTo(newPaymentWaiverTransaction.getAmount(loan.getCurrency()))) {
+ reprocess = false;
+ }
+
+ if (isTransactionChronologicallyLatest && (!reprocess || !loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled())
+ && !loan.isForeclosure()) {
+ loanRepaymentScheduleTransactionProcessor.processLatestTransaction(newPaymentWaiverTransaction,
+ 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) {
+ if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
+ }
+ final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
+ 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());
+ }
+
+ loan.updateLoanSummaryDerivedFields();
+
+ doPostLoanTransactionChecks(loan, newPaymentWaiverTransaction.getTransactionDate(), defaultLoanLifecycleStateMachine);
+
+ // 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);
+ }
+ }
+
+ return changedTransactionDetail;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult makeLoanRepaymentWithPaymentWaiver(final JsonCommand command) {
Review Comment:
We might wanna rename this to `makePaymentWaiver`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fineract.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org