Feat: Add undo waive charge
This commit is contained in:
+9
@@ -870,6 +870,15 @@ public class CommandWrapperBuilder {
|
||||
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";
|
||||
|
||||
+13
@@ -182,6 +182,19 @@ public class Money implements Comparable<Money> {
|
||||
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;
|
||||
|
||||
+73
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -41,6 +41,7 @@ public class BusinessEventNotificationConstants {
|
||||
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"), //
|
||||
|
||||
+1
@@ -234,6 +234,7 @@ public class LoanChargesApiResource {
|
||||
} else {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
+23
-2
@@ -34,6 +34,7 @@ import java.util.Set;
|
||||
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 class LoanTransactionsApiResource {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
+97
@@ -529,6 +529,11 @@ public class Loan extends AbstractPersistableCustom {
|
||||
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 @@ public class Loan extends AbstractPersistableCustom {
|
||||
return amount;
|
||||
}
|
||||
|
||||
// public LoanTransaction undoWaiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine
|
||||
// loanLifecycleStateMachine,
|
||||
// final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long>
|
||||
// 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<LoanTransaction> 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<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge,
|
||||
|
||||
+55
@@ -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<LoanChargePaidBy> 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 class LoanCharge extends AbstractPersistableCustom {
|
||||
}
|
||||
}
|
||||
|
||||
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 class LoanCharge extends AbstractPersistableCustom {
|
||||
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 class LoanCharge extends AbstractPersistableCustom {
|
||||
return paidChargePerInstallment;
|
||||
}
|
||||
|
||||
public Set<LoanChargePaidBy> getLoanChargePaidBySet() {
|
||||
return this.loanChargePaidBySet;
|
||||
}
|
||||
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
@@ -1036,4 +1083,12 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
public boolean isDueDateCharge() {
|
||||
return this.dueDate != null;
|
||||
}
|
||||
|
||||
public void setAmountWaived(final BigDecimal amountWaived) {
|
||||
this.amountWaived = amountWaived;
|
||||
}
|
||||
|
||||
public void undoWaived() {
|
||||
this.waived = false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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;
|
||||
|
||||
|
||||
+6
@@ -20,7 +20,13 @@ package org.apache.fineract.portfolio.loanaccount.domain;
|
||||
|
||||
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<LoanChargePaidBy, Long>, JpaSpecificationExecutor<LoanCharge> {
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
+17
@@ -109,6 +109,11 @@ public class LoanInstallmentCharge extends AbstractPersistableCustom implements
|
||||
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 class LoanInstallmentCharge extends AbstractPersistableCustom implements
|
||||
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;
|
||||
|
||||
+4
@@ -840,4 +840,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
||||
}
|
||||
|
||||
public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) {
|
||||
this.feeChargesCharged = newFeeChargesCharged;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -141,6 +141,14 @@ public final class LoanSummary {
|
||||
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();
|
||||
}
|
||||
|
||||
+20
@@ -334,6 +334,18 @@ public class LoanTransaction extends AbstractPersistableCustom {
|
||||
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 class LoanTransaction extends AbstractPersistableCustom {
|
||||
return !isReversed();
|
||||
}
|
||||
|
||||
public void setReversed() {
|
||||
this.reversed = true;
|
||||
}
|
||||
|
||||
public void setManuallyAdjustedOrReversed() {
|
||||
this.manuallyAdjustedOrReversed = true;
|
||||
}
|
||||
|
||||
public boolean isAnyTypeOfRepayment() {
|
||||
return isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment();
|
||||
}
|
||||
|
||||
+1
@@ -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
|
||||
**/
|
||||
|
||||
+28
@@ -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");
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
@@ -65,6 +65,8 @@ public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult waiveLoanCharge(Long loanId, Long loanChargeId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult undoWaiveLoanCharge(JsonCommand command);
|
||||
|
||||
CommandProcessingResult loanReassignment(Long loanId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult bulkLoanReassignment(JsonCommand command);
|
||||
|
||||
+193
-1
@@ -22,6 +22,7 @@ import com.google.gson.JsonArray;
|
||||
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.LoanChargeCannotBeUpdatedE
|
||||
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.GroupLoanIndividualMonit
|
||||
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.LoanTransactionRepositor
|
||||
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 class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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 class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.cashierTransactionDataValidator = cashierTransactionDataValidator;
|
||||
this.loanRepository = loanRepository;
|
||||
this.glimRepository = glimRepository;
|
||||
this.loanChargePaidByRepository = loanChargePaidByRepository;
|
||||
}
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
@@ -1590,6 +1599,189 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.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<LoanChargePaidBy> 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<String, Object> 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<LoanChargePaidBy> 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<ApiParameterError> 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) {
|
||||
|
||||
Reference in New Issue
Block a user