Merge pull request #1772 from BLasan/undoWaiveCharge
Feat: 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;
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+16
@@ -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;
|
||||
@@ -262,4 +263,19 @@ public class LoanTransactionsApiResource {
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{transactionId}")
|
||||
@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) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().undoWaiveChargeTransaction(loanId, transactionId).build();
|
||||
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -186,4 +186,35 @@ final class LoanTransactionsApiResourceSwagger {
|
||||
@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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
+31
@@ -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,10 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
}
|
||||
}
|
||||
|
||||
public void resetOutstandingAmount(final BigDecimal amountOutstanding) {
|
||||
this.amountOutstanding = amountOutstanding;
|
||||
}
|
||||
|
||||
public Money waive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) {
|
||||
if (isInstalmentFee()) {
|
||||
final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber);
|
||||
@@ -869,6 +876,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 +1040,10 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
return paidChargePerInstallment;
|
||||
}
|
||||
|
||||
public Set<LoanChargePaidBy> getLoanChargePaidBySet() {
|
||||
return this.loanChargePaidBySet;
|
||||
}
|
||||
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
@@ -1036,4 +1059,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
-1
@@ -20,7 +20,12 @@ 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") LoanCharge loanCharge,
|
||||
@Param("installmentNumber") 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;
|
||||
|
||||
+8
@@ -223,6 +223,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
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);
|
||||
}
|
||||
@@ -840,4 +844,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
||||
}
|
||||
|
||||
public void setFeeChargesWaived(final BigDecimal newFeeChargesCharged) {
|
||||
this.feeChargesWaived = 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
@@ -166,6 +166,18 @@ public class LoanTransaction extends AbstractPersistableCustom {
|
||||
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,
|
||||
@@ -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);
|
||||
|
||||
+159
-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,155 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand command) {
|
||||
|
||||
LoanTransaction loanTransaction = this.loanTransactionRepository.findById(command.entityId())
|
||||
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId()));
|
||||
|
||||
if (!loanTransaction.getTypeOf().getCode().equals(LoanTransactionType.WAIVE_CHARGES.getCode())) {
|
||||
throw new InvalidLoanTransactionTypeException("Undo Waive Charge", "Waive an Installment Charge First",
|
||||
"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);
|
||||
|
||||
if (!loanTransaction.isNotReversed()) {
|
||||
throw new LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_REVERSED,
|
||||
loanTransaction.getId());
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
|
||||
} else {
|
||||
throw new InstallmentNotFoundException(command.entityId());
|
||||
}
|
||||
}
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
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()) //
|
||||
.withLoanId(loanId) //
|
||||
.with(changes).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult waiveLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) {
|
||||
|
||||
+1
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user