Feat: Add undo waive charge

This commit is contained in:
BLasan
2021-06-18 20:55:30 +05:30
parent f87b66d4e1
commit 6d08fa5661
19 changed files with 598 additions and 5 deletions
@@ -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";
@@ -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;
@@ -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);
}
}
@@ -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"), //
@@ -234,6 +234,7 @@ public class LoanChargesApiResource {
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
if (result == null) {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -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);
}
}
@@ -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,
@@ -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;
}
}
@@ -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;
@@ -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);
}
@@ -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;
@@ -840,4 +840,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.rescheduleInterestPortion = rescheduleInterestPortion;
}
public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) {
this.feeChargesCharged = newFeeChargesCharged;
}
}
@@ -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();
}
@@ -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();
}
@@ -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
**/
@@ -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");
}
}
@@ -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);
}
}
@@ -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);
@@ -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) {