From 6d08fa56618004088ffd150cf6ea2dbdcc98de4a Mon Sep 17 00:00:00 2001 From: BLasan Date: Fri, 18 Jun 2021 20:55:30 +0530 Subject: [PATCH 001/232] Feat: Add undo waive charge --- .../service/CommandWrapperBuilder.java | 9 + .../organisation/monetary/domain/Money.java | 13 ++ ...nChargeWaiveCannotBeReversedException.java | 73 +++++++ .../BusinessEventNotificationConstants.java | 1 + .../api/LoanChargesApiResource.java | 1 + .../api/LoanTransactionsApiResource.java | 25 ++- .../portfolio/loanaccount/domain/Loan.java | 97 +++++++++ .../loanaccount/domain/LoanCharge.java | 55 +++++ .../loanaccount/domain/LoanChargePaidBy.java | 3 +- .../domain/LoanChargePaidByRepository.java | 6 + .../domain/LoanInstallmentCharge.java | 17 ++ .../LoanRepaymentScheduleInstallment.java | 4 + .../loanaccount/domain/LoanSummary.java | 8 + .../loanaccount/domain/LoanTransaction.java | 20 ++ .../domain/LoanTransactionType.java | 1 + .../InstallmentNotFoundException.java | 28 +++ .../UndoLoanChargeWaiveCommandHandler.java | 46 +++++ .../service/LoanWritePlatformService.java | 2 + ...WritePlatformServiceJpaRepositoryImpl.java | 194 +++++++++++++++++- 19 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InstallmentNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLoanChargeWaiveCommandHandler.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 29f6dd64a3a..1fb0f4c905c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -870,6 +870,15 @@ public CommandWrapperBuilder loanForeclosure(final Long loanId) { return this; } + public CommandWrapperBuilder undoWaiveChargeTransaction(final Long loanId, final Long transactionId) { + this.actionName = "UNDO"; + this.entityName = "WAIVECHARGE"; + this.entityId = transactionId; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/transactions?command=undo"; + return this; + } + public CommandWrapperBuilder createLoanApplication() { this.actionName = "CREATE"; this.entityName = "LOAN"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java index 6479a4f64c4..eafa09fdf1a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java @@ -182,6 +182,19 @@ public Money minus(final Money moneyToSubtract) { return this.minus(toSubtract.getAmount()); } + public Money add(final Money moneyToAdd) { + final Money toAdd = checkCurrencyEqual(moneyToAdd); + return this.add(toAdd.getAmount()); + } + + public Money add(final BigDecimal amountToAdd) { + if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { + return this; + } + final BigDecimal newAmount = this.amount.add(amountToAdd); + return Money.of(monetaryCurrency(), newAmount); + } + public Money minus(final BigDecimal amountToSubtract) { if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) { return this; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java new file mode 100644 index 00000000000..58c5f5bcc66 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java @@ -0,0 +1,73 @@ +/** + * 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.charge.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class LoanChargeWaiveCannotBeReversedException extends AbstractPlatformDomainRuleException { + + /*** enum of reasons of why Loan Charge waive cannot undo **/ + public enum LoanChargeWaiveCannotUndoReason { + + ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, WAIVE_NOT_ALLOWED_FOR_CHARGE, NOT_WAIVED, ALREADY_REVERSED; + + public String errorMessage() { + /** + * TODO: Recheck the error messages + */ + if (name().toString().equalsIgnoreCase("ALREADY_PAID")) { + return "This loan charge has been completely paid"; + } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) { + return "This loan charge has already been waived"; + } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { + return "This loan charge can be waived as the loan associated with it is currently inactive"; + } else if (name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) { + return "This loan charge can be waived"; + } else if (name().toString().equalsIgnoreCase("NOT_WAIVED")) { + return "This loan charge waive cannot be reversed as this charge is not waived"; + } else if (name().toString().equalsIgnoreCase("ALREADY_REVERSED")) { + return "This loan charge waive cannot be reversed as this transaction is not reversed"; + } + + return name().toString(); + } + + public String errorCode() { + if (name().toString().equalsIgnoreCase("ALREADY_PAID")) { + return "error.msg.loan.charge.already.paid"; + } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) { + return "error.msg.loan.charge.already.waived"; + } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { + return "error.msg.loan.charge.associated.loan.inactive"; + } else if (name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) { + return "error.msg.loan.charge.waive.not.allowed"; + } else if (name().toString().equalsIgnoreCase("NOT_WAIVED")) { + return "error.msg.loan.charge.waive.cannot.undo"; + } else if (name().toString().equalsIgnoreCase("ALREADY_REVERSED")) { + return "error.msg.transaction.cannot.reverse"; + } + return name().toString(); + } + } + + public LoanChargeWaiveCannotBeReversedException(final LoanChargeWaiveCannotUndoReason reason, final Long id) { + super(reason.errorCode(), reason.errorMessage(), id); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java index 53aee65cfee..eb014492da9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java @@ -41,6 +41,7 @@ public enum BusinessEvents { LOAN_CLOSE_AS_RESCHEDULE("loan_close_as_reschedule"), // LOAN_ADD_CHARGE("loan_add_charge"), // LOAN_UPDATE_CHARGE("loan_update_charge"), // + LOAN_WAIVE_CHARGE_UNDO("loan_waive_charge_undo"), // LOAN_WAIVE_CHARGE("loan_waive_charge"), // LOAN_DELETE_CHARGE("loan_delete_charge"), // LOAN_CHARGE_PAYMENT("loan_charge_payment"), // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java index 14021d37669..032e94a5dce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java @@ -234,6 +234,7 @@ public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "l } else { throw new UnrecognizedQueryParamException("command", commandParam); } + if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index fec81bfc012..e8a86f74b05 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -34,6 +34,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -255,11 +256,31 @@ public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description @Parameter(hidden = true) final String apiRequestBodyAsJson) { final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); - final CommandWrapper commandRequest = builder.adjustTransaction(loanId, transactionId).build(); + CommandProcessingResult result = null; - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + final CommandWrapper commandRequest = builder.adjustTransaction(loanId, transactionId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } + /** + * TODO: Add Swagger annotations. + * + * @param loanId + * @param transactionId + * @return + */ + @PUT + @Path("{transactionId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction") + public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, + @PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().undoWaiveChargeTransaction(loanId, transactionId).build(); + CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 1e62691df61..259841f2fac 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -529,6 +529,11 @@ private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigD return this.summary; } + public void updateLoanSummaryForUndoWaiveCharge(final BigDecimal amountWaived) { + this.summary.updateFeeChargesWaived(this.summary.getTotalFeeChargesWaived().subtract(amountWaived)); + this.summary.updateFeeChargeOutstanding(this.summary.getTotalFeeChargesOutstanding().add(amountWaived)); + } + private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() { Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO); @@ -972,6 +977,98 @@ private Money calculateInstallmentChargeAmount(final ChargeCalculationType calcu return amount; } + // public LoanTransaction undoWaiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine + // loanLifecycleStateMachine, + // final Map changes, final List existingTransactionIds, final List + // existingReversedTransactionIds, + // final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, + // final AppUser currentUser) { + // + // validateLoanIsNotClosed(loanCharge); + // + // //final Money amountUndoWaived = loanCharge.undoWaive(loanCurrency(), loanInstallmentNumber); + // + // changes.put("amount", amountUndoWaived.getAmount()); + // + // Money unrecognizedIncome = amountUndoWaived.zero(); + // Money chargeComponent = amountUndoWaived; + // if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + // Money receivableCharge = Money.zero(getCurrency()); + // if (loanInstallmentNumber != null) { + // receivableCharge = accruedCharge + // .add(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(getCurrency())); + // } else { + // receivableCharge = accruedCharge.add(loanCharge.getAmountPaid(getCurrency())); + // } + // + // if (amountUndoWaived.isLessThan(receivableCharge)) { + // chargeComponent = receivableCharge; + // unrecognizedIncome = amountUndoWaived.add(receivableCharge); + // } + // } + // Money feeChargesUndoWaived = chargeComponent; + // Money penaltyChargesUndoWaived = Money.zero(loanCurrency()); + // if (loanCharge.isPenaltyCharge()) { + // penaltyChargesUndoWaived = chargeComponent; + // feeChargesUndoWaived = Money.zero(loanCurrency()); + // } + // + // LocalDate transactionDate = getDisbursementDate(); + // if (loanCharge.isDueDateCharge()) { + // if (loanCharge.getDueLocalDate().isAfter(DateUtils.getLocalDateOfTenant())) { + // transactionDate = DateUtils.getLocalDateOfTenant(); + // } else { + // transactionDate = loanCharge.getDueLocalDate(); + // } + // } else if (loanCharge.isInstalmentFee()) { + // transactionDate = + // loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getRepaymentInstallment().getDueDate(); + // } + // + // scheduleGeneratorDTO.setRecalculateFrom(transactionDate); + // + // updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); + // + // // existingTransactionIds.addAll(findExistingTransactionIds()); + // // existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + // + // final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), + // amountUndoWaived, + // transactionDate, feeChargesUndoWaived, penaltyChargesUndoWaived, unrecognizedIncome, + // DateUtils.getLocalDateTimeOfTenant(), + // currentUser); + // final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, + // waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber); + // waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); + // addLoanTransaction(waiveLoanChargeTransaction); + // if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (loanCharge.getDueLocalDate() == null + // || LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).isAfter(loanCharge.getDueLocalDate()))) { + // regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); + // } + // // Waive of charges whose due date falls after latest 'repayment' + // // transaction dont require entire loan schedule to be reprocessed. + // final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = + // this.transactionProcessorFactory + // .determineProcessor(this.transactionProcessingStrategy); + // if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { + // final List allNonContraTransactionsPostDisbursement = + // retreiveListOfTransactionsPostDisbursement(); + // loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), + // allNonContraTransactionsPostDisbursement, + // getCurrency(), getRepaymentScheduleInstallments(), charges()); + // } else { + // // reprocess loan schedule based on charge been waived. + // final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + // wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges()); + // } + // + // updateLoanSummaryDerivedFields(); + // + // doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); + // + // return waiveLoanChargeTransaction; + // } + public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 5301746d3d3..4e381ef2a24 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -131,6 +131,9 @@ public class LoanCharge extends AbstractPersistableCustom { @OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER) private LoanTrancheDisbursementCharge loanTrancheDisbursementCharge; + @OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private Set loanChargePaidBySet; + public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) { final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate"); if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && dueDate == null) { @@ -346,6 +349,34 @@ public void resetPaidAmount(final MonetaryCurrency currency) { } } + public void resetOutstandingAmount(final BigDecimal amountOutstanding) { + this.amountOutstanding = amountOutstanding; + } + + // public Money undoWaive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) { + // if (isInstalmentFee()) { + // final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber); + // final Money amountWaived = chargePerInstallment.waive(currency); + // if (this.amountWaived != null) { + // this.amountWaived = this.amountWaived.add(amountWaived.getAmount()); + // } else { + // } + // + // this.amountOutstanding = this.amountOutstanding.add(amountWaived.getAmount()); + // if (determineIfFullyPaid()) { + // this.paid = false; + // this.waived = false; + // } + // return amountWaived; + // } + // this.amountWaived = BigDecimal.ZERO; + // // this.amountOutstanding = BigDecimal.ZERO; + // this.paid = false; + // this.waived = false; + // return getAmountWaived(currency); + // + // } + public Money waive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) { if (isInstalmentFee()) { final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber); @@ -869,6 +900,18 @@ public LoanInstallmentCharge getInstallmentLoanCharge(final Integer installmentN return null; } + public void setInstallmentLoanCharge(final LoanInstallmentCharge loanInstallmentCharge, final Integer installmentNumber) { + LoanInstallmentCharge loanInstallmentChargeToBeRemoved = null; + for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { + if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue())) { + loanInstallmentChargeToBeRemoved = loanChargePerInstallment; + break; + } + } + this.loanInstallmentCharge.remove(loanInstallmentChargeToBeRemoved); + this.loanInstallmentCharge.add(loanInstallmentCharge); + } + public void clearLoanInstallmentCharges() { this.loanInstallmentCharge.clear(); } @@ -1021,6 +1064,10 @@ public LoanInstallmentCharge getLastPaidOrPartiallyPaidInstallmentLoanCharge(Mon return paidChargePerInstallment; } + public Set getLoanChargePaidBySet() { + return this.loanChargePaidBySet; + } + public Loan getLoan() { return this.loan; } @@ -1036,4 +1083,12 @@ public boolean isTrancheDisbursementCharge() { public boolean isDueDateCharge() { return this.dueDate != null; } + + public void setAmountWaived(final BigDecimal amountWaived) { + this.amountWaived = amountWaived; + } + + public void undoWaived() { + this.waived = false; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java index 8924ba16c70..ac067afc7ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidBy.java @@ -19,7 +19,6 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.math.BigDecimal; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; @@ -35,7 +34,7 @@ public class LoanChargePaidBy extends AbstractPersistableCustom { @JoinColumn(name = "loan_transaction_id", nullable = false) private LoanTransaction loanTransaction; - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne(optional = false) @JoinColumn(name = "loan_charge_id", nullable = false) private LoanCharge loanCharge; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java index 0c7df943a7f..1f1c76b288f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java @@ -20,7 +20,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface LoanChargePaidByRepository extends JpaRepository, JpaSpecificationExecutor { + // no added behaviour + @Query("select lp from LoanChargePaidBy lp where lp.loanCharge=:loanCharge and lp.installmentNumber=:installmentNumber") + LoanChargePaidBy getLoanChargePaidByLoanCharge(@Param("loanCharge") final LoanCharge loanCharge, + @Param("installmentNumber") final Integer installmentNo); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java index 5737155e2e0..eb207c82b25 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java @@ -109,6 +109,11 @@ private boolean determineIfFullyPaid() { return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0; } + public void undoWaive(final BigDecimal amountOutstanding, final BigDecimal amountWaived) { + this.amountOutstanding = amountOutstanding; + this.amountWaived = amountWaived; + } + private BigDecimal calculateOutstanding() { if (this.amount == null) { return null; @@ -221,6 +226,18 @@ public void resetPaidAmount(final MonetaryCurrency currency) { this.paid = false; } + public void resetAmountWaived(final BigDecimal amountWaived) { + this.amountWaived = amountWaived; + } + + public void undoWaiveFlag() { + this.waived = false; + } + + public void resetOutstandingAmount(final BigDecimal amountOutstanding) { + this.amountOutstanding = amountOutstanding; + } + public void resetToOriginal(final MonetaryCurrency currency) { this.amountPaid = BigDecimal.ZERO; this.amountWaived = BigDecimal.ZERO; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index e190ebf6f39..1e8a9f61de4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -840,4 +840,8 @@ public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { this.rescheduleInterestPortion = rescheduleInterestPortion; } + public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) { + this.feeChargesCharged = newFeeChargesCharged; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java index bf5ca2f21ae..9eea2641d91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java @@ -141,6 +141,14 @@ public Money getTotalOutstanding(final MonetaryCurrency currency) { return Money.of(currency, this.totalOutstanding); } + public void updateFeeChargeOutstanding(final BigDecimal totalFeeChargesOutstanding) { + this.totalFeeChargesOutstanding = totalFeeChargesOutstanding; + } + + public void updateFeeChargesWaived(final BigDecimal totalFeeChargesWaived) { + this.totalFeeChargesWaived = totalFeeChargesWaived; + } + public boolean isRepaidInFull(final MonetaryCurrency currency) { return getTotalOutstanding(currency).isZero(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 65f8ff5f6c3..1e0a890fd8e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -334,6 +334,18 @@ public static LoanTransaction waiveLoanCharge(final Loan loan, final Office offi return waiver; } + // public static LoanTransaction undoWaiveLoanCharge(final Loan loan, final Office office, final Money undoWaived, + // final LocalDate undoWaiveDate, final Money feeChargesWaived, final Money penaltyChargesWaived, final Money + // unrecognizedCharge, + // final LocalDateTime createdDate, final AppUser appUser) { + // final LoanTransaction undoWaiver = new LoanTransaction(loan, office, LoanTransactionType.UNDO_WAIVE_CHARGE, + // undoWaived.getAmount(), + // undoWaiveDate, null, createdDate, appUser); + // undoWaiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); + // + // return undoWaiver; + // } + public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId, createdDate, appUser); @@ -494,6 +506,14 @@ public boolean isNotReversed() { return !isReversed(); } + public void setReversed() { + this.reversed = true; + } + + public void setManuallyAdjustedOrReversed() { + this.manuallyAdjustedOrReversed = true; + } + public boolean isAnyTypeOfRepayment() { return isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java index 0c2851cd51b..b053327ab7e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java @@ -34,6 +34,7 @@ public enum LoanTransactionType { */ RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), // WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), // + // UNDO_WAIVE_CHARGE(20, "loanTransactionType.undoWaiveCharge"), /** * Transaction represents an Accrual (For either interest, charge or a penalty **/ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InstallmentNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InstallmentNotFoundException.java new file mode 100644 index 00000000000..cf478b35a2e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InstallmentNotFoundException.java @@ -0,0 +1,28 @@ +/** + * 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.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class InstallmentNotFoundException extends AbstractPlatformResourceNotFoundException { + + public InstallmentNotFoundException(final Long id) { + super("error.msg.transaction.id.invalid", "Transaction with identifier " + id + " does not contain any installment"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLoanChargeWaiveCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLoanChargeWaiveCommandHandler.java new file mode 100644 index 00000000000..a737d3407b4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/UndoLoanChargeWaiveCommandHandler.java @@ -0,0 +1,46 @@ +/** + * 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 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.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "WAIVECHARGE", action = "UNDO") +public class UndoLoanChargeWaiveCommandHandler implements NewCommandSourceHandler { + + final LoanWritePlatformService loanWritePlatformService; + + @Autowired + public UndoLoanChargeWaiveCommandHandler(final LoanWritePlatformService loanWritePlatformService) { + this.loanWritePlatformService = loanWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.loanWritePlatformService.undoWaiveLoanCharge(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java index 4370ea27ba7..9be2fdbe888 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -65,6 +65,8 @@ Map bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDi CommandProcessingResult waiveLoanCharge(Long loanId, Long loanChargeId, JsonCommand command); + CommandProcessingResult undoWaiveLoanCharge(JsonCommand command); + CommandProcessingResult loanReassignment(Long loanId, JsonCommand command); CommandProcessingResult bulkLoanReassignment(JsonCommand command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 4bf120ede17..91ea1dba850 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -22,6 +22,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.math.BigDecimal; +import java.time.Instant; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -109,6 +110,8 @@ import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LoanChargeCannotBeWaivedReason; import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; +import org.apache.fineract.portfolio.charge.exception.LoanChargeWaiveCannotBeReversedException; +import org.apache.fineract.portfolio.charge.exception.LoanChargeWaiveCannotBeReversedException.LoanChargeWaiveCannotUndoReason; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; @@ -135,6 +138,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidByRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; @@ -156,6 +161,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException; import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException; +import org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException; +import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException; import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException; @@ -238,6 +245,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final CashierTransactionDataValidator cashierTransactionDataValidator; private final GLIMAccountInfoRepository glimRepository; private final LoanRepository loanRepository; + private final LoanChargePaidByRepository loanChargePaidByRepository; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -267,7 +275,7 @@ public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext c final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper, final CashierTransactionDataValidator cashierTransactionDataValidator, final GLIMAccountInfoRepository glimRepository, - final LoanRepository loanRepository) { + final LoanRepository loanRepository, final LoanChargePaidByRepository loanChargePaidByRepository) { this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; @@ -308,6 +316,7 @@ public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext c this.cashierTransactionDataValidator = cashierTransactionDataValidator; this.loanRepository = loanRepository; this.glimRepository = glimRepository; + this.loanChargePaidByRepository = loanChargePaidByRepository; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -1590,6 +1599,189 @@ public CommandProcessingResult updateLoanCharge(final Long loanId, final Long lo .build(); } + @Transactional + @Override + public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { + + // validateUndoWaiveCharge(command); + + LoanTransaction loanTransaction = this.loanTransactionRepository.findById(command.entityId()) + .orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId())); + + if (!loanTransaction.getTypeOf().getCode().equals(LoanTransactionType.WAIVE_CHARGES.getCode())) { + /** + * TODO: Add proper validation + */ + throw new InvalidLoanTransactionTypeException("", "", "Transaction is not a waive charge type."); + } + + Set loanChargePaidBySet = loanTransaction.getLoanChargesPaid(); + Integer installmentNumber = null; + Long loanChargeId = null; + final Long loanId = loanTransaction.getLoan().getId(); + + for (LoanChargePaidBy loanChargePaidBy : loanChargePaidBySet) { + installmentNumber = loanChargePaidBy.getInstallmentNumber(); + loanChargeId = loanChargePaidBy.getLoanCharge().getId(); + break; + } + + AppUser currentUser = getAppUserIfPresent(); + final Loan loan = this.loanAssembler.assembleFrom(loanId); + checkClientOrGroupActive(loan); + final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); + + // Charges may be waived only when the loan associated with them are + // active + if (!loan.status().isActive()) { + throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE, loanCharge.getId()); + } + + // Validate loan charge is not already paid + if (loanCharge.isPaid()) { + throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_PAID, loanCharge.getId()); + } + + final Map changes = new LinkedHashMap<>(3); + + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BusinessEvents.LOAN_WAIVE_CHARGE_UNDO, + constructEntityMap(BusinessEntity.LOAN_CHARGE, loanCharge)); + + if (loanCharge.isInstalmentFee()) { + LoanInstallmentCharge chargePerInstallment = null; + + // final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber"); + if (installmentNumber != null) { + + // Get installment charge. + chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber); + + // // Get relevant LoanChargePaidBy row + // final LoanChargePaidBy loanChargePaidBy = + // this.loanChargePaidByRepository.getLoanChargePaidByLoanCharge(loanCharge, + // chargePerInstallment.getInstallment().getInstallmentNumber()); + // LoanTransaction loanTransaction = loanChargePaidBy.getLoanTransaction(); + + if (!loanTransaction.isNotReversed()) { + throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_REVERSED, + loanTransaction.getId()); + } + + // Reverse waived transaction + loanTransaction.setReversed(); + + // Set manually adjusted value to `1` + loanTransaction.setManuallyAdjustedOrReversed(); + + // Save updated data + this.loanTransactionRepository.saveAndFlush(loanTransaction); + + // Get installment amount waived. + BigDecimal amountWaived = chargePerInstallment.getAmountWaived(loan.getCurrency()).getAmount(); + + // Get installment outstanding amount + BigDecimal amountOutstandingPerInstallment = chargePerInstallment.getAmountOutstanding(); + + // Check whether the installment charge is not waived. If so throw new error + if (!chargePerInstallment.isWaived() || amountWaived == null) { + throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.NOT_WAIVED, loanChargeId); + } + + // Get loan charge total amount waived + BigDecimal totalAmountWaved = loanCharge.getAmountWaived(loan.getCurrency()).getAmount(); + + // Get loan charge outstanding amount + BigDecimal amountOutstanding = loanCharge.getAmountOutstanding(loan.getCurrency()).getAmount(); + + // Add the amount waived to outstanding amount + loanCharge.resetOutstandingAmount(amountOutstanding.add(amountWaived)); + + // Subtract the amount waived from the existing amount waived. + loanCharge.setAmountWaived(totalAmountWaved.subtract(amountWaived)); + + // Add the amount waived to the outstanding amount of the installment + chargePerInstallment.resetOutstandingAmount(amountOutstandingPerInstallment.add(amountWaived)); + + // Set the amount waived value to ZERO + chargePerInstallment.resetAmountWaived(BigDecimal.ZERO); + + // Reset waived flag + chargePerInstallment.undoWaiveFlag(); + + // Get the fee charges waived amount per installment + BigDecimal feeChargesWaivedAmount = chargePerInstallment.getInstallment().getFeeChargesWaived(loan.getCurrency()) + .getAmount(); + + // Subtract the amount waived from the existing fee charges waived amount. + chargePerInstallment.getInstallment().setFeeChargesWaived(feeChargesWaivedAmount.subtract(amountWaived)); + + // Set the last modification date. + chargePerInstallment.getInstallment().setLastModifiedDate(Instant.now()); + + // Update loan charge. + loanCharge.setInstallmentLoanCharge(chargePerInstallment, chargePerInstallment.getInstallment().getInstallmentNumber()); + + if (loanCharge.getAmount(loan.getCurrency()).compareTo(loanCharge.getAmountOutstanding(loan.getCurrency())) == 0 + && loanCharge.isWaived()) { + loanCharge.undoWaived(); + } + + this.loanChargeRepository.saveAndFlush(loanCharge); + + loan.updateLoanSummaryForUndoWaiveCharge(amountWaived); + + changes.put("amount", amountWaived); + + // final Set loanChargePaidBySet = loanCharge.getLoanChargePaidBySet(); + // for (LoanChargePaidBy loanChargePaidBy: loanChargePaidBySet) { + // if (installmentNumber.equals(loanChargePaidBy.getInstallmentNumber())) { + // LoanTransaction transaction = loanChargePaidBy.getLoanTransaction(); + // if (transaction.isNotReversed()) { + // transaction.setReversed(); + // } + // } + // } + + } else { + /** + * TODO: Throw installment number should not be empty error. + */ + throw new InstallmentNotFoundException(command.entityId()); + } + } + + saveLoanWithDataIntegrityViolationChecks(loan); + + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_WAIVE_CHARGE_UNDO, + constructEntityMap(BusinessEntity.LOAN_CHARGE, loanCharge)); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(command.entityId()) // + .withOfficeId(loan.getOfficeId()) // + .withClientId(loan.getClientId()) // + .withGroupId(loan.getGroupId()) // + .withLoanId(loanId) // + .with(changes).build(); + } + + // private void validateUndoWaiveCharge(JsonCommand command) { + // final List dataValidationErrors = new ArrayList<>(); + // final DataValidatorBuilder baseDataValidator = new + // DataValidatorBuilder(dataValidationErrors).resource("transaction"); + // + // if (!command.parameterExists("transactionId")) { + // baseDataValidator.reset().parameter("transactionId").failWithCode("trasactionId.not.exists"); + // } else { + // final Long transactionId = command.longValueOfParameterNamed("transactionId"); + // baseDataValidator.reset().parameter("transactionId").value(transactionId).notNull().notBlank(); + // } + // + // if (!dataValidationErrors.isEmpty()) { + // throw new PlatformApiDataValidationException(dataValidationErrors); + // } + // } + @Transactional @Override public CommandProcessingResult waiveLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) { From 88df504390d9cbfc8a2c4988d6617cbd0f3c00b7 Mon Sep 17 00:00:00 2001 From: BLasan Date: Fri, 18 Jun 2021 20:55:52 +0530 Subject: [PATCH 002/232] Add: Entity Permission --- .../core_db/V3__mifosx-permissions-and-authorisation-utf8.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql index 6c9d94880b7..4318929b325 100644 --- a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql @@ -293,6 +293,7 @@ INSERT INTO `m_permission` ('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'), ('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'), ('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'), +{'transaction_lon', 'UNDO_WAIVECHARGE', 'WAIVECHARGE', 'UNDO', '0'}, ('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0'); -- == accounting related permissions From 8b428d347a512e87ce727604507019f4e64c9abe Mon Sep 17 00:00:00 2001 From: BLasan Date: Sat, 19 Jun 2021 14:16:26 +0530 Subject: [PATCH 003/232] Add: Swagger Annotations for Undoing Transaction API --- .../api/LoanTransactionsApiResource.java | 3 ++ .../LoanTransactionsApiResourceSwagger.java | 31 +++++++++++++++++++ .../domain/LoanChargePaidByRepository.java | 5 ++- ...WritePlatformServiceJpaRepositoryImpl.java | 13 -------- ...osx-permissions-and-authorisation-utf8.sql | 2 +- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index e8a86f74b05..834c371a9c5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -276,6 +276,9 @@ public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) }) public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java index 53b1c669f8f..d5bd220cad5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java @@ -186,4 +186,35 @@ private PostLoansLoanIdTransactionsTransactionIdResponse() {} @Schema(example = "16") public Integer resourceId; } + + @Schema(description = "PutChargeTransactionChangesResponse") + public static final class PutChargeTransactionChangesResponse { + + private PutChargeTransactionChangesResponse() {} + + static final class Changes { + + private Changes() {} + + @Schema(example = "amount") + public String amount; + } + + @Schema(example = "1") + public Integer resourceId; + @Schema(example = "48") + public Integer loanId; + public PutChargeTransactionChangesResponse.Changes changes; + + } + + @Schema(description = "PutChargeTransactionChangesRequest") + public static final class PutChargeTransactionChangesRequest { + + private PutChargeTransactionChangesRequest() {} + + @Schema(example = "1") + public Integer transactionId; + + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java index 1f1c76b288f..dc8d36a4fb1 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargePaidByRepository.java @@ -25,8 +25,7 @@ public interface LoanChargePaidByRepository extends JpaRepository, JpaSpecificationExecutor { - // no added behaviour @Query("select lp from LoanChargePaidBy lp where lp.loanCharge=:loanCharge and lp.installmentNumber=:installmentNumber") - LoanChargePaidBy getLoanChargePaidByLoanCharge(@Param("loanCharge") final LoanCharge loanCharge, - @Param("installmentNumber") final Integer installmentNo); + LoanChargePaidBy getLoanChargePaidByLoanCharge(@Param("loanCharge") LoanCharge loanCharge, + @Param("installmentNumber") Integer installmentNo); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 91ea1dba850..623228c41c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1732,16 +1732,6 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { changes.put("amount", amountWaived); - // final Set loanChargePaidBySet = loanCharge.getLoanChargePaidBySet(); - // for (LoanChargePaidBy loanChargePaidBy: loanChargePaidBySet) { - // if (installmentNumber.equals(loanChargePaidBy.getInstallmentNumber())) { - // LoanTransaction transaction = loanChargePaidBy.getLoanTransaction(); - // if (transaction.isNotReversed()) { - // transaction.setReversed(); - // } - // } - // } - } else { /** * TODO: Throw installment number should not be empty error. @@ -1758,9 +1748,6 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(command.entityId()) // - .withOfficeId(loan.getOfficeId()) // - .withClientId(loan.getClientId()) // - .withGroupId(loan.getGroupId()) // .withLoanId(loanId) // .with(changes).build(); } diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql index 4318929b325..6e2d681999d 100644 --- a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql @@ -286,6 +286,7 @@ INSERT INTO `m_permission` ('transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF', '0'), ('transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE', '0'), ('transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED', '0'), +{'transaction_loan', 'UNDO_WAIVECHARGE', 'WAIVECHARGE', 'UNDO', '0'}, ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'), ('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'), @@ -293,7 +294,6 @@ INSERT INTO `m_permission` ('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'), ('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'), ('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'), -{'transaction_lon', 'UNDO_WAIVECHARGE', 'WAIVECHARGE', 'UNDO', '0'}, ('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0'); -- == accounting related permissions From 0f175edb37bbb58765caa7c4eb11dca9dfb457e3 Mon Sep 17 00:00:00 2001 From: BLasan Date: Sat, 19 Jun 2021 14:38:18 +0530 Subject: [PATCH 004/232] Fix: Mysql Syntax Issue --- .../core_db/V3__mifosx-permissions-and-authorisation-utf8.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql index 6e2d681999d..fc9a66190b3 100644 --- a/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V3__mifosx-permissions-and-authorisation-utf8.sql @@ -286,7 +286,7 @@ INSERT INTO `m_permission` ('transaction_loan', 'WRITEOFF_LOAN_CHECKER', 'LOAN', 'WRITEOFF', '0'), ('transaction_loan', 'CLOSE_LOAN_CHECKER', 'LOAN', 'CLOSE', '0'), ('transaction_loan', 'CLOSEASRESCHEDULED_LOAN_CHECKER', 'LOAN', 'CLOSEASRESCHEDULED', '0'), -{'transaction_loan', 'UNDO_WAIVECHARGE', 'WAIVECHARGE', 'UNDO', '0'}, +('transaction_loan', 'UNDO_WAIVECHARGE', 'WAIVECHARGE', 'UNDO', '0'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'), ('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'), From 3170051660983c4968e1551d33066c677d70e1d8 Mon Sep 17 00:00:00 2001 From: BLasan Date: Wed, 23 Jun 2021 23:43:05 +0530 Subject: [PATCH 005/232] Refine: Sending changes on update --- .../LoanWritePlatformServiceJpaRepositoryImpl.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 623228c41c7..675c2933a9b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1745,6 +1745,15 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_WAIVE_CHARGE_UNDO, constructEntityMap(BusinessEntity.LOAN_CHARGE, loanCharge)); + LoanTransaction loanTransactionData = this.loanTransactionRepository.getOne(command.entityId()); + changes.put("principalPortion", loanTransactionData.getPrincipalPortion()); + changes.put("interestPortion", loanTransactionData.getInterestPortion(loan.getCurrency())); + changes.put("feeChargesPortion", loanTransactionData.getFeeChargesPortion(loan.getCurrency())); + changes.put("penaltyChargesPortion", loanTransactionData.getPenaltyChargesPortion(loan.getCurrency())); + changes.put("outstandingLoanBalance", loanTransactionData.getOutstandingLoanBalance()); + changes.put("id", loanTransactionData.getId()); + changes.put("date", loanTransactionData.getTransactionDate()); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(command.entityId()) // From a2c937b164143b1183f1a692893f344004ff6cb9 Mon Sep 17 00:00:00 2001 From: BLasan Date: Mon, 28 Jun 2021 03:12:27 +0530 Subject: [PATCH 006/232] Fix: Fee Charge Amount Issue --- .../LoanRepaymentScheduleInstallment.java | 6 +++++- .../loanaccount/domain/LoanTransaction.java | 12 ++++++++++++ ...WritePlatformServiceJpaRepositoryImpl.java | 19 ++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 1e8a9f61de4..8219bd48055 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -223,6 +223,10 @@ public Money getPrincipalCompleted(final MonetaryCurrency currency) { return Money.of(currency, this.principalCompleted); } + public void updateLoanRepaymentSchedule(final BigDecimal amountWaived) { + this.feeChargesWaived = this.feeChargesWaived.subtract(amountWaived); + } + public Money getPrincipalWrittenOff(final MonetaryCurrency currency) { return Money.of(currency, this.principalWrittenOff); } @@ -841,7 +845,7 @@ public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { } public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) { - this.feeChargesCharged = newFeeChargesCharged; + this.feeChargesWaived = newFeeChargesCharged; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 1e0a890fd8e..9735c350bff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -166,6 +166,18 @@ public static LoanTransaction repayment(final Office office, final Money amount, createdDate, appUser); } + public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) { + for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) { + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping + .getLoanRepaymentScheduleInstallment(); + if (loanRepaymentScheduleInstallment.getInstallmentNumber().equals(installmentId)) { + loanRepaymentScheduleInstallment.updateLoanRepaymentSchedule(chargePerInstallment); + break; + } + } + + } + public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 675c2933a9b..0a7a31c0c51 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1670,15 +1670,15 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { // Reverse waived transaction loanTransaction.setReversed(); + // Get installment amount waived. + BigDecimal amountWaived = chargePerInstallment.getAmountWaived(loan.getCurrency()).getAmount(); + // Set manually adjusted value to `1` loanTransaction.setManuallyAdjustedOrReversed(); // Save updated data this.loanTransactionRepository.saveAndFlush(loanTransaction); - // Get installment amount waived. - BigDecimal amountWaived = chargePerInstallment.getAmountWaived(loan.getCurrency()).getAmount(); - // Get installment outstanding amount BigDecimal amountOutstandingPerInstallment = chargePerInstallment.getAmountOutstanding(); @@ -1730,6 +1730,19 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { loan.updateLoanSummaryForUndoWaiveCharge(amountWaived); + // Set reschedule installment + + // final List loanRepaymentScheduleInstallments = + // loan.getRepaymentScheduleInstallments(); + // for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : + // loanRepaymentScheduleInstallments) { + // if (loanRepaymentScheduleInstallment.getInstallmentNumber().equals(installmentNumber)) { + // loanRepaymentScheduleInstallment.updateLoanRepaymentSchedule(amountWaived); + // this.repaymentScheduleInstallmentRepository.saveAndFlush(loanRepaymentScheduleInstallment); + // break; + // } + // } + changes.put("amount", amountWaived); } else { From 31860f0960d6280b4c26f2544afe088869772f23 Mon Sep 17 00:00:00 2001 From: BLasan Date: Sun, 11 Jul 2021 22:23:31 +0530 Subject: [PATCH 007/232] Remove commented codes --- .../portfolio/loanaccount/domain/Loan.java | 92 ------------------- .../loanaccount/domain/LoanCharge.java | 24 ----- .../loanaccount/domain/LoanTransaction.java | 12 --- ...WritePlatformServiceJpaRepositoryImpl.java | 38 -------- 4 files changed, 166 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 259841f2fac..5974c3d83db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -977,98 +977,6 @@ private Money calculateInstallmentChargeAmount(final ChargeCalculationType calcu return amount; } - // public LoanTransaction undoWaiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine - // loanLifecycleStateMachine, - // final Map changes, final List existingTransactionIds, final List - // existingReversedTransactionIds, - // final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, - // final AppUser currentUser) { - // - // validateLoanIsNotClosed(loanCharge); - // - // //final Money amountUndoWaived = loanCharge.undoWaive(loanCurrency(), loanInstallmentNumber); - // - // changes.put("amount", amountUndoWaived.getAmount()); - // - // Money unrecognizedIncome = amountUndoWaived.zero(); - // Money chargeComponent = amountUndoWaived; - // if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { - // Money receivableCharge = Money.zero(getCurrency()); - // if (loanInstallmentNumber != null) { - // receivableCharge = accruedCharge - // .add(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(getCurrency())); - // } else { - // receivableCharge = accruedCharge.add(loanCharge.getAmountPaid(getCurrency())); - // } - // - // if (amountUndoWaived.isLessThan(receivableCharge)) { - // chargeComponent = receivableCharge; - // unrecognizedIncome = amountUndoWaived.add(receivableCharge); - // } - // } - // Money feeChargesUndoWaived = chargeComponent; - // Money penaltyChargesUndoWaived = Money.zero(loanCurrency()); - // if (loanCharge.isPenaltyCharge()) { - // penaltyChargesUndoWaived = chargeComponent; - // feeChargesUndoWaived = Money.zero(loanCurrency()); - // } - // - // LocalDate transactionDate = getDisbursementDate(); - // if (loanCharge.isDueDateCharge()) { - // if (loanCharge.getDueLocalDate().isAfter(DateUtils.getLocalDateOfTenant())) { - // transactionDate = DateUtils.getLocalDateOfTenant(); - // } else { - // transactionDate = loanCharge.getDueLocalDate(); - // } - // } else if (loanCharge.isInstalmentFee()) { - // transactionDate = - // loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getRepaymentInstallment().getDueDate(); - // } - // - // scheduleGeneratorDTO.setRecalculateFrom(transactionDate); - // - // updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); - // - // // existingTransactionIds.addAll(findExistingTransactionIds()); - // // existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - // - // final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), - // amountUndoWaived, - // transactionDate, feeChargesUndoWaived, penaltyChargesUndoWaived, unrecognizedIncome, - // DateUtils.getLocalDateTimeOfTenant(), - // currentUser); - // final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, - // waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber); - // waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); - // addLoanTransaction(waiveLoanChargeTransaction); - // if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (loanCharge.getDueLocalDate() == null - // || LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).isAfter(loanCharge.getDueLocalDate()))) { - // regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); - // } - // // Waive of charges whose due date falls after latest 'repayment' - // // transaction dont require entire loan schedule to be reprocessed. - // final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = - // this.transactionProcessorFactory - // .determineProcessor(this.transactionProcessingStrategy); - // if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { - // final List allNonContraTransactionsPostDisbursement = - // retreiveListOfTransactionsPostDisbursement(); - // loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - // allNonContraTransactionsPostDisbursement, - // getCurrency(), getRepaymentScheduleInstallments(), charges()); - // } else { - // // reprocess loan schedule based on charge been waived. - // final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - // wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges()); - // } - // - // updateLoanSummaryDerivedFields(); - // - // doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); - // - // return waiveLoanChargeTransaction; - // } - public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 4e381ef2a24..88a622f00e2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -353,30 +353,6 @@ public void resetOutstandingAmount(final BigDecimal amountOutstanding) { this.amountOutstanding = amountOutstanding; } - // public Money undoWaive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) { - // if (isInstalmentFee()) { - // final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber); - // final Money amountWaived = chargePerInstallment.waive(currency); - // if (this.amountWaived != null) { - // this.amountWaived = this.amountWaived.add(amountWaived.getAmount()); - // } else { - // } - // - // this.amountOutstanding = this.amountOutstanding.add(amountWaived.getAmount()); - // if (determineIfFullyPaid()) { - // this.paid = false; - // this.waived = false; - // } - // return amountWaived; - // } - // this.amountWaived = BigDecimal.ZERO; - // // this.amountOutstanding = BigDecimal.ZERO; - // this.paid = false; - // this.waived = false; - // return getAmountWaived(currency); - // - // } - public Money waive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) { if (isInstalmentFee()) { final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 9735c350bff..c3bbeaf4e83 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -346,18 +346,6 @@ public static LoanTransaction waiveLoanCharge(final Loan loan, final Office offi return waiver; } - // public static LoanTransaction undoWaiveLoanCharge(final Loan loan, final Office office, final Money undoWaived, - // final LocalDate undoWaiveDate, final Money feeChargesWaived, final Money penaltyChargesWaived, final Money - // unrecognizedCharge, - // final LocalDateTime createdDate, final AppUser appUser) { - // final LoanTransaction undoWaiver = new LoanTransaction(loan, office, LoanTransactionType.UNDO_WAIVE_CHARGE, - // undoWaived.getAmount(), - // undoWaiveDate, null, createdDate, appUser); - // undoWaiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); - // - // return undoWaiver; - // } - public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId, createdDate, appUser); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 0a7a31c0c51..f28a021b312 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1603,8 +1603,6 @@ public CommandProcessingResult updateLoanCharge(final Long loanId, final Long lo @Override public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { - // validateUndoWaiveCharge(command); - LoanTransaction loanTransaction = this.loanTransactionRepository.findById(command.entityId()) .orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId())); @@ -1656,12 +1654,6 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { // Get installment charge. chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber); - // // Get relevant LoanChargePaidBy row - // final LoanChargePaidBy loanChargePaidBy = - // this.loanChargePaidByRepository.getLoanChargePaidByLoanCharge(loanCharge, - // chargePerInstallment.getInstallment().getInstallmentNumber()); - // LoanTransaction loanTransaction = loanChargePaidBy.getLoanTransaction(); - if (!loanTransaction.isNotReversed()) { throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_REVERSED, loanTransaction.getId()); @@ -1730,19 +1722,6 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { loan.updateLoanSummaryForUndoWaiveCharge(amountWaived); - // Set reschedule installment - - // final List loanRepaymentScheduleInstallments = - // loan.getRepaymentScheduleInstallments(); - // for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : - // loanRepaymentScheduleInstallments) { - // if (loanRepaymentScheduleInstallment.getInstallmentNumber().equals(installmentNumber)) { - // loanRepaymentScheduleInstallment.updateLoanRepaymentSchedule(amountWaived); - // this.repaymentScheduleInstallmentRepository.saveAndFlush(loanRepaymentScheduleInstallment); - // break; - // } - // } - changes.put("amount", amountWaived); } else { @@ -1774,23 +1753,6 @@ public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) { .with(changes).build(); } - // private void validateUndoWaiveCharge(JsonCommand command) { - // final List dataValidationErrors = new ArrayList<>(); - // final DataValidatorBuilder baseDataValidator = new - // DataValidatorBuilder(dataValidationErrors).resource("transaction"); - // - // if (!command.parameterExists("transactionId")) { - // baseDataValidator.reset().parameter("transactionId").failWithCode("trasactionId.not.exists"); - // } else { - // final Long transactionId = command.longValueOfParameterNamed("transactionId"); - // baseDataValidator.reset().parameter("transactionId").value(transactionId).notNull().notBlank(); - // } - // - // if (!dataValidationErrors.isEmpty()) { - // throw new PlatformApiDataValidationException(dataValidationErrors); - // } - // } - @Transactional @Override public CommandProcessingResult waiveLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) { From c48cc694ddfa09ca2ab71d916c93f15aeecb217e Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 24 Jul 2021 08:20:13 +0000 Subject: [PATCH 008/232] chore(deps): update dependency org.mnode.ical4j:ical4j to v3.0.29 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index df1aa1f3f31..e2c968f1c85 100644 --- a/build.gradle +++ b/build.gradle @@ -115,7 +115,7 @@ allprojects { dependency 'commons-io:commons-io:2.10.0' dependency 'org.drizzle.jdbc:drizzle-jdbc:1.4' dependency 'com.github.librepdf:openpdf:1.3.26' - dependency 'org.mnode.ical4j:ical4j:3.0.28' + dependency 'org.mnode.ical4j:ical4j:3.0.29' dependency 'org.quartz-scheduler:quartz:2.3.2' dependency 'com.amazonaws:aws-java-sdk-s3:1.12.19' dependency 'org.ehcache:ehcache:3.9.4' From c3d8d3cb7eeb0bdaa9de133386134c339ea0083a Mon Sep 17 00:00:00 2001 From: BLasan Date: Mon, 2 Aug 2021 09:06:55 +0530 Subject: [PATCH 009/232] Remove unwanted comments --- .../LoanChargeWaiveCannotBeReversedException.java | 4 +--- .../loanaccount/api/LoanTransactionsApiResource.java | 12 ++---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java index 58c5f5bcc66..1ed8e194e88 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java @@ -28,9 +28,7 @@ public enum LoanChargeWaiveCannotUndoReason { ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, WAIVE_NOT_ALLOWED_FOR_CHARGE, NOT_WAIVED, ALREADY_REVERSED; public String errorMessage() { - /** - * TODO: Recheck the error messages - */ + if (name().toString().equalsIgnoreCase("ALREADY_PAID")) { return "This loan charge has been completely paid"; } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 834c371a9c5..af5e2d3d5da 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -256,21 +256,13 @@ public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description @Parameter(hidden = true) final String apiRequestBodyAsJson) { final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); - CommandProcessingResult result = null; - final CommandWrapper commandRequest = builder.adjustTransaction(loanId, transactionId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } - /** - * TODO: Add Swagger annotations. - * - * @param loanId - * @param transactionId - * @return - */ @PUT @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) From 2250ee1e21ac90c6fa5da4952fdd1eaf77f7a05f Mon Sep 17 00:00:00 2001 From: Manoj <56669674+fynmanoj@users.noreply.github.com> Date: Tue, 3 Aug 2021 18:52:38 +0530 Subject: [PATCH 010/232] FINERACT-1277-Message-Gateway-webhook (#1801) --- .../hooks/api/HookApiConstants.java | 4 + .../processor/HookProcessorProvider.java | 3 + .../MessageGatewayHookProcessor.java | 121 ++++++++++++++++++ .../template/domain/TemplateRepository.java | 5 + .../V367__message_gateway_hook_template.sql | 27 ++++ 5 files changed, 160 insertions(+) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V367__message_gateway_hook_template.sql diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java index c11deea3468..381e36997df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java @@ -40,6 +40,8 @@ private HookApiConstants() { public static final String elasticSearchTemplateName = "Elastic Search"; + public static final String httpSMSTemplateName = "Message Gateway"; + public static final String smsTemplateName = "SMS Bridge"; public static final String payloadURLName = "Payload URL"; @@ -68,6 +70,8 @@ private HookApiConstants() { public static final String templateNameParamName = "templateName"; + public static final String SMSProviderIdParamName = "SMS Provider Id"; + static final Set RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(nameParamName, displayNameParamName, templateIdParamName, isActiveParamName, configParamName, eventsParamName, templateNameParamName)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java index 23d57096f0f..5f637ba71be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.hooks.processor; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.elasticSearchTemplateName; +import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.httpSMSTemplateName; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsTemplateName; import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.webTemplateName; @@ -47,6 +48,8 @@ public HookProcessor getProcessor(final Hook hook) { processor = this.applicationContext.getBean("webHookProcessor", WebHookProcessor.class); } else if (templateName.equals(elasticSearchTemplateName)) { processor = this.applicationContext.getBean("elasticSearchHookProcessor", ElasticSearchHookProcessor.class); + } else if (templateName.equals(httpSMSTemplateName)) { + processor = this.applicationContext.getBean("messageGatewayHookProcessor", MessageGatewayHookProcessor.class); } else { processor = null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java new file mode 100644 index 00000000000..4cfd89fe5bf --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/MessageGatewayHookProcessor.java @@ -0,0 +1,121 @@ +/** + * 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.hooks.processor; + +import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.SMSProviderIdParamName; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; +import org.apache.fineract.infrastructure.hooks.domain.Hook; +import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; +import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.template.domain.Template; +import org.apache.fineract.template.domain.TemplateRepository; +import org.apache.fineract.template.service.TemplateMergeService; +import org.apache.fineract.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MessageGatewayHookProcessor implements HookProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(MessageGatewayHookProcessor.class); + + private final ClientRepositoryWrapper clientRepository; + private final TemplateRepository templateRepository; + private final TemplateMergeService templateMergeService; + + private final SmsMessageRepository smsMessageRepository; + private final SmsMessageScheduledJobService smsMessageScheduledJobService; + + @Autowired + public MessageGatewayHookProcessor(ClientRepositoryWrapper clientRepository, TemplateRepository templateRepository, + TemplateMergeService templateMergeService, SmsMessageRepository smsMessageRepository, + SmsMessageScheduledJobService smsMessageScheduledJobService) { + this.clientRepository = clientRepository; + this.templateRepository = templateRepository; + this.templateMergeService = templateMergeService; + this.smsMessageRepository = smsMessageRepository; + this.smsMessageScheduledJobService = smsMessageScheduledJobService; + } + + @Override + public void process(final Hook hook, @SuppressWarnings("unused") final AppUser appUser, final String payload, final String entityName, + final String actionName, final String tenantIdentifier, final String authToken) throws IOException { + + final Set config = hook.getHookConfig(); + + Integer SMSProviderId = null; + + for (final HookConfiguration conf : config) { + final String fieldName = conf.getFieldName(); + if (fieldName.equals(SMSProviderIdParamName)) { + SMSProviderId = Integer.parseInt(conf.getFieldValue()); + } + } + + String templateName = entityName + "_" + actionName; + + // 1 : find template via mapper using entity and action + Template template; + List