FINERACT-1773: Allow 'specific due date charges' to be applied after the maturity date
This commit is contained in:
committed by
Arnold Gálovics
parent
12e548ad82
commit
b33e3ac892
+15
-14
@@ -37,17 +37,20 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
|
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
|
||||||
return getNextState(loanEvent, loan);
|
LoanStatus nextStatus = getNextState(loanEvent, loan);
|
||||||
|
return nextStatus != null ? nextStatus : loan.status();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void transition(final LoanEvent loanEvent, final Loan loan) {
|
public void transition(final LoanEvent loanEvent, final Loan loan) {
|
||||||
LoanStatus newState = getNextState(loanEvent, loan);
|
LoanStatus newState = getNextState(loanEvent, loan);
|
||||||
loan.setLoanStatus(newState.getValue());
|
if (newState != null) {
|
||||||
|
loan.setLoanStatus(newState.getValue());
|
||||||
|
|
||||||
if (isNotLoanCreation(loanEvent)) {
|
if (isNotLoanCreation(loanEvent)) {
|
||||||
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
|
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +65,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
}
|
}
|
||||||
|
|
||||||
LoanStatus from = loan.status();
|
LoanStatus from = loan.status();
|
||||||
LoanStatus newState = from;
|
LoanStatus newState = null;
|
||||||
|
|
||||||
switch (loanEvent) {
|
switch (loanEvent) {
|
||||||
case LOAN_REJECTED:
|
case LOAN_REJECTED:
|
||||||
@@ -97,7 +100,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
break;
|
break;
|
||||||
case LOAN_CHARGE_PAYMENT:
|
case LOAN_CHARGE_PAYMENT:
|
||||||
case LOAN_REPAYMENT_OR_WAIVER:
|
case LOAN_REPAYMENT_OR_WAIVER:
|
||||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
|
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
|
||||||
newState = activeTransition();
|
newState = activeTransition();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -116,11 +119,6 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
newState = closedRescheduleOutstandingAmountTransition();
|
newState = closedRescheduleOutstandingAmountTransition();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case INTERST_REBATE_OWED:
|
|
||||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET)) {
|
|
||||||
newState = closeObligationsMetTransition();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case LOAN_OVERPAYMENT:
|
case LOAN_OVERPAYMENT:
|
||||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.ACTIVE)) {
|
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.ACTIVE)) {
|
||||||
newState = overpaidTransition();
|
newState = overpaidTransition();
|
||||||
@@ -141,8 +139,6 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
case LOAN_WITHDRAW_TRANSFER:
|
case LOAN_WITHDRAW_TRANSFER:
|
||||||
newState = activeTransition();
|
newState = activeTransition();
|
||||||
break;
|
break;
|
||||||
case LOAN_CLOSED:
|
|
||||||
break;
|
|
||||||
case WRITE_OFF_OUTSTANDING_UNDO:
|
case WRITE_OFF_OUTSTANDING_UNDO:
|
||||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_WRITTEN_OFF)) {
|
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_WRITTEN_OFF)) {
|
||||||
newState = activeTransition();
|
newState = activeTransition();
|
||||||
@@ -151,6 +147,11 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
case LOAN_CREDIT_BALANCE_REFUND:
|
case LOAN_CREDIT_BALANCE_REFUND:
|
||||||
newState = closeObligationsMetTransition();
|
newState = closeObligationsMetTransition();
|
||||||
break;
|
break;
|
||||||
|
case LOAN_CHARGE_ADDED:
|
||||||
|
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET)) {
|
||||||
|
newState = activeTransition();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case LOAN_CHARGEBACK:
|
case LOAN_CHARGEBACK:
|
||||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
|
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
|
||||||
newState = activeTransition();
|
newState = activeTransition();
|
||||||
|
|||||||
+8
-4
@@ -572,8 +572,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
|
|
||||||
public void addLoanCharge(final LoanCharge loanCharge) {
|
public void addLoanCharge(final LoanCharge loanCharge) {
|
||||||
|
|
||||||
validateLoanIsNotClosed(loanCharge);
|
|
||||||
|
|
||||||
if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
|
if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
|
||||||
// Note: added this constraint to restrict adding disbursement
|
// Note: added this constraint to restrict adding disbursement
|
||||||
// charges to a loan
|
// charges to a loan
|
||||||
@@ -596,7 +594,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
loanCharge.update(this);
|
loanCharge.update(this);
|
||||||
|
|
||||||
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
|
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
|
||||||
BigDecimal chargeAmt = BigDecimal.ZERO;
|
BigDecimal chargeAmt;
|
||||||
BigDecimal totalChargeAmt = BigDecimal.ZERO;
|
BigDecimal totalChargeAmt = BigDecimal.ZERO;
|
||||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||||
chargeAmt = loanCharge.getPercentage();
|
chargeAmt = loanCharge.getPercentage();
|
||||||
@@ -626,6 +624,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges());
|
wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges());
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
|
|
||||||
|
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_ADDED, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangedTransactionDetail reprocessTransactions() {
|
public ChangedTransactionDetail reprocessTransactions() {
|
||||||
@@ -743,7 +742,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
|
|
||||||
private void validateChargeHasValidSpecifiedDateIfApplicable(final LoanCharge loanCharge, final LocalDate disbursementDate,
|
private void validateChargeHasValidSpecifiedDateIfApplicable(final LoanCharge loanCharge, final LocalDate disbursementDate,
|
||||||
final LocalDate lastRepaymentPeriodDueDate) {
|
final LocalDate lastRepaymentPeriodDueDate) {
|
||||||
if (loanCharge.isSpecifiedDueDate()
|
if (isInterestBearing() && loanCharge.isSpecifiedDueDate()
|
||||||
&& !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) {
|
&& !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) {
|
||||||
final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
|
final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
|
||||||
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
|
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
|
||||||
@@ -5242,6 +5241,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInterestBearing() {
|
||||||
|
return getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||||
|
&& BigDecimal.ZERO.compareTo(getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDate getExpectedMaturityDate() {
|
public LocalDate getExpectedMaturityDate() {
|
||||||
return this.expectedMaturityDate;
|
return this.expectedMaturityDate;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -49,5 +49,6 @@ public enum LoanEvent {
|
|||||||
LOAN_REJECT_TRANSFER, //
|
LOAN_REJECT_TRANSFER, //
|
||||||
LOAN_WITHDRAW_TRANSFER, //
|
LOAN_WITHDRAW_TRANSFER, //
|
||||||
LOAN_CREDIT_BALANCE_REFUND, //
|
LOAN_CREDIT_BALANCE_REFUND, //
|
||||||
LOAN_CHARGEBACK;
|
LOAN_CHARGEBACK, //
|
||||||
|
LOAN_CHARGE_ADDED;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -142,7 +142,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
|
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
|
||||||
|
// TODO: analyze and remove this
|
||||||
if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
|
if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
|
||||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
||||||
|
|
||||||
|
|||||||
+16
@@ -1773,6 +1773,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!loan.isInterestBearing() && loanCharge.isSpecifiedDueDate() && loanCharge.getDueDate().isAfter(loan.getMaturityDate())) {
|
||||||
|
LoanRepaymentScheduleInstallment latestRepaymentScheduleInstalment = loan.getRepaymentScheduleInstallments()
|
||||||
|
.get(loan.getLoanRepaymentScheduleInstallmentsSize() - 1);
|
||||||
|
if (loanCharge.getDueDate().isAfter(latestRepaymentScheduleInstalment.getDueDate())) {
|
||||||
|
if (latestRepaymentScheduleInstalment.isAdditional()) {
|
||||||
|
latestRepaymentScheduleInstalment.updateDueDate(loanCharge.getDueDate());
|
||||||
|
} else {
|
||||||
|
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
|
||||||
|
(loan.getLoanRepaymentScheduleInstallmentsSize() + 1), latestRepaymentScheduleInstalment.getDueDate(),
|
||||||
|
loanCharge.getDueDate(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null);
|
||||||
|
installment.markAsAdditional();
|
||||||
|
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loan.addLoanCharge(loanCharge);
|
loan.addLoanCharge(loanCharge);
|
||||||
|
|
||||||
this.loanChargeRepository.saveAndFlush(loanCharge);
|
this.loanChargeRepository.saveAndFlush(loanCharge);
|
||||||
|
|||||||
+482
@@ -0,0 +1,482 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.path.json.JsonPath;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked" })
|
||||||
|
public class LoanSpecificDueDateChargeAfterMaturityTest {
|
||||||
|
|
||||||
|
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
|
||||||
|
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
||||||
|
public static final String DEPOSIT_AMOUNT = "7000";
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LoanSpecificDueDateChargeAfterMaturityTest.class);
|
||||||
|
private static final String DATE_OF_JOINING = "01 January 2011";
|
||||||
|
private static final Float LP_PRINCIPAL = 10000.0f;
|
||||||
|
private static final String LP_REPAYMENTS = "1";
|
||||||
|
private static final String LP_REPAYMENT_PERIOD = "1";
|
||||||
|
private static final String EXPECTED_DISBURSAL_DATE = "04 March 2011";
|
||||||
|
private static final String LOAN_APPLICATION_SUBMISSION_DATE = "03 March 2011";
|
||||||
|
private static final String LOAN_TERM_FREQUENCY = "1";
|
||||||
|
private static final String INDIVIDUAL_LOAN = "individual";
|
||||||
|
private static RequestSpecification requestSpec;
|
||||||
|
private static ResponseSpecification responseSpec;
|
||||||
|
private LoanTransactionHelper loanTransactionHelper;
|
||||||
|
private AccountHelper accountHelper;
|
||||||
|
private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
|
||||||
|
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
|
||||||
|
|
||||||
|
public static Integer createSavingsProduct(final String minOpenningBalance, final Account... accounts) {
|
||||||
|
LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
|
||||||
|
final String savingsProductJSON = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily() //
|
||||||
|
.withInterestPostingPeriodTypeAsQuarterly() //
|
||||||
|
.withInterestCalculationPeriodTypeAsDailyBalance() //
|
||||||
|
.withMinimumOpenningBalance(minOpenningBalance).withAccountingRuleAsCashBased(accounts).build();
|
||||||
|
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||||
|
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||||
|
this.accountHelper = new AccountHelper(requestSpec, responseSpec);
|
||||||
|
this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec, responseSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onlyNonInterestBearingLoanCanAcceptChargeAfterMaturity() {
|
||||||
|
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
|
||||||
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingWithInterestRecalculationEnabled(assetAccount,
|
||||||
|
incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, DATE_OF_JOINING);
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1");
|
||||||
|
|
||||||
|
final float PENALTY_PORTION = 100.0f;
|
||||||
|
|
||||||
|
Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
|
||||||
|
|
||||||
|
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan(EXPECTED_DISBURSAL_DATE, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
|
|
||||||
|
LocalDate targetDate = LocalDate.of(2011, 3, 4);
|
||||||
|
final String loanDisbursementDate = dateFormatter.format(targetDate);
|
||||||
|
|
||||||
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
|
||||||
|
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 5);
|
||||||
|
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
|
||||||
|
|
||||||
|
final String ADD_CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanID + "/charges?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
ResponseSpecification response403Spec = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||||
|
final HashMap response = Utils.performServerPost(requestSpec, response403Spec, ADD_CHARGES_URL,
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate), penaltyCharge1AddedDate,
|
||||||
|
String.valueOf(PENALTY_PORTION)),
|
||||||
|
"");
|
||||||
|
assertEquals("validation.msg.domain.rule.violation", response.get("userMessageGlobalisationCode"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkPeriodicAccrualAccountingAPIFlow() {
|
||||||
|
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
|
||||||
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
|
||||||
|
overpaymentAccount);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, DATE_OF_JOINING);
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "0");
|
||||||
|
|
||||||
|
final float FEE_PORTION = 50.0f;
|
||||||
|
final float PENALTY_PORTION = 100.0f;
|
||||||
|
final float NEXT_FEE_PORTION = 55.0f;
|
||||||
|
final float NEXT_PENALTY_PORTION = 105.0f;
|
||||||
|
|
||||||
|
Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false));
|
||||||
|
Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
|
||||||
|
|
||||||
|
Integer flatNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_FEE_PORTION), false));
|
||||||
|
Integer flatSpecifiedDueDateNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_PENALTY_PORTION), true));
|
||||||
|
|
||||||
|
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan(EXPECTED_DISBURSAL_DATE, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
|
|
||||||
|
LocalDate targetDate = LocalDate.of(2011, 3, 4);
|
||||||
|
final String loanDisbursementDate = dateFormatter.format(targetDate);
|
||||||
|
|
||||||
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
|
||||||
|
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(2, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("penaltyChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(10000.0f, loanSchedule.get(1).get("totalDueForPeriod"));
|
||||||
|
assertEquals(10000.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
|
||||||
|
targetDate = LocalDate.of(2011, 4, 5);
|
||||||
|
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate), penaltyCharge1AddedDate,
|
||||||
|
String.valueOf(PENALTY_PORTION)));
|
||||||
|
|
||||||
|
this.loanTransactionHelper.noAccrualTransactionForRepayment(loanID);
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 5), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
String runOnDateStr = penaltyCharge1AddedDate;
|
||||||
|
LocalDate runOnDate = targetDate;
|
||||||
|
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOnDateStr);
|
||||||
|
|
||||||
|
this.loanTransactionHelper.checkAccrualTransactionForRepayment(runOnDate, 0.0f, 0.0f, PENALTY_PORTION, loanID);
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 6);
|
||||||
|
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer fee1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
|
||||||
|
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flat), feeCharge1AddedDate, String.valueOf(FEE_PORTION)));
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(FEE_PORTION, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(FEE_PORTION, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 6), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 7);
|
||||||
|
final String penaltyCharge2AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer penalty2LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDateNext),
|
||||||
|
penaltyCharge2AddedDate, String.valueOf(NEXT_PENALTY_PORTION)));
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(FEE_PORTION, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(FEE_PORTION, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 7), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 8);
|
||||||
|
final String feeCharge2AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer fee2LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper
|
||||||
|
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatNext), feeCharge2AddedDate, String.valueOf(NEXT_FEE_PORTION)));
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(FEE_PORTION + NEXT_FEE_PORTION, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(FEE_PORTION + NEXT_FEE_PORTION, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION + NEXT_PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION + NEXT_PENALTY_PORTION + NEXT_FEE_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION + FEE_PORTION + NEXT_PENALTY_PORTION + NEXT_FEE_PORTION,
|
||||||
|
loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 8), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
runOnDateStr = penaltyCharge2AddedDate;
|
||||||
|
runOnDate = LocalDate.of(2011, 4, 7);
|
||||||
|
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOnDateStr);
|
||||||
|
this.loanTransactionHelper.checkAccrualTransactionForRepayment(runOnDate, 0.0f, FEE_PORTION, NEXT_PENALTY_PORTION, loanID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reopenClosedLoan() {
|
||||||
|
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
|
||||||
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount,
|
||||||
|
overpaymentAccount);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, DATE_OF_JOINING);
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "0");
|
||||||
|
|
||||||
|
final float FEE_PORTION = 50.0f;
|
||||||
|
final float PENALTY_PORTION = 100.0f;
|
||||||
|
final float NEXT_FEE_PORTION = 55.0f;
|
||||||
|
final float NEXT_PENALTY_PORTION = 105.0f;
|
||||||
|
|
||||||
|
Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(FEE_PORTION), false));
|
||||||
|
Integer flatSpecifiedDueDate = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(PENALTY_PORTION), true));
|
||||||
|
|
||||||
|
Integer flatNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_FEE_PORTION), false));
|
||||||
|
Integer flatSpecifiedDueDateNext = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||||
|
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(NEXT_PENALTY_PORTION), true));
|
||||||
|
|
||||||
|
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan(EXPECTED_DISBURSAL_DATE, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
|
|
||||||
|
LocalDate targetDate = LocalDate.of(2011, 3, 4);
|
||||||
|
final String loanDisbursementDate = dateFormatter.format(targetDate);
|
||||||
|
|
||||||
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
|
||||||
|
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(2, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("penaltyChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(1).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(10000.0f, loanSchedule.get(1).get("totalDueForPeriod"));
|
||||||
|
assertEquals(10000.0f, loanSchedule.get(1).get("totalOutstandingForPeriod"));
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 3, 10);
|
||||||
|
String repaymentDateStr = dateFormatter.format(targetDate);
|
||||||
|
loanTransactionHelper.makeRepayment(repaymentDateStr, 10000.0f, loanID);
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 13);
|
||||||
|
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate), penaltyCharge1AddedDate,
|
||||||
|
String.valueOf(PENALTY_PORTION)));
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 13), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 14);
|
||||||
|
String runOnDateStr = dateFormatter.format(targetDate);
|
||||||
|
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOnDateStr);
|
||||||
|
|
||||||
|
// Transaction date will be the due date of the instalment (in case of N+1 scenario)
|
||||||
|
this.loanTransactionHelper.checkAccrualTransactionForRepayment(LocalDate.of(2011, 4, 13), 0.0f, 0.0f, PENALTY_PORTION, loanID);
|
||||||
|
|
||||||
|
loanTransactionHelper.waiveChargesForLoan(loanID, penalty1LoanChargeId,
|
||||||
|
LoanTransactionHelper.getWaiveChargeJSON(String.valueOf(2)));
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesWaived"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalWaivedForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 13), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 14);
|
||||||
|
String penaltyCharge2AddedDate = dateFormatter.format(targetDate);
|
||||||
|
Integer penalty2LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanID,
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(flatSpecifiedDueDate), penaltyCharge2AddedDate,
|
||||||
|
String.valueOf(PENALTY_PORTION)));
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesWaived"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalWaivedForPeriod"));
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 14), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
targetDate = LocalDate.of(2011, 4, 15);
|
||||||
|
repaymentDateStr = dateFormatter.format(targetDate);
|
||||||
|
loanTransactionHelper.makeRepayment(repaymentDateStr, PENALTY_PORTION, loanID);
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesWaived"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesPaid"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalWaivedForPeriod"));
|
||||||
|
// Might need to change if refund should update the due date of N+1 instalment
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 14), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanTransactionHelper.loanChargeRefund(penalty2LoanChargeId, null, PENALTY_PORTION, null, loanID, "resourceId");
|
||||||
|
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap);
|
||||||
|
|
||||||
|
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
|
||||||
|
assertEquals(3, loanSchedule.size());
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesDue"));
|
||||||
|
assertEquals(0, loanSchedule.get(2).get("feeChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesDue"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("penaltyChargesOutstanding"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesWaived"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("penaltyChargesPaid"));
|
||||||
|
assertEquals(PENALTY_PORTION + PENALTY_PORTION, loanSchedule.get(2).get("totalDueForPeriod"));
|
||||||
|
assertEquals(0.0f, loanSchedule.get(2).get("totalOutstandingForPeriod"));
|
||||||
|
assertEquals(PENALTY_PORTION, loanSchedule.get(2).get("totalWaivedForPeriod"));
|
||||||
|
// Might need to change if refund should update the due date of N+1 instalment
|
||||||
|
assertEquals(LocalDate.of(2011, 4, 14), LocalDate.of((int) ((List) loanSchedule.get(2).get("dueDate")).get(0),
|
||||||
|
(int) ((List) loanSchedule.get(2).get("dueDate")).get(1), (int) ((List) loanSchedule.get(2).get("dueDate")).get(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String interestRate) {
|
||||||
|
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||||
|
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(LP_PRINCIPAL.toString())
|
||||||
|
.withLoanTermFrequency(LOAN_TERM_FREQUENCY).withLoanTermFrequencyAsMonths().withNumberOfRepayments(LP_REPAYMENTS)
|
||||||
|
.withRepaymentEveryAfter(LP_REPAYMENT_PERIOD).withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod(interestRate)
|
||||||
|
.withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments()
|
||||||
|
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(EXPECTED_DISBURSAL_DATE)
|
||||||
|
.withSubmittedOnDate(LOAN_APPLICATION_SUBMISSION_DATE).withLoanType(INDIVIDUAL_LOAN)
|
||||||
|
.build(clientID.toString(), loanProductID.toString(), null);
|
||||||
|
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer createLoanProductWithPeriodicAccrualAccountingWithInterestRecalculationEnabled(final Account... accounts) {
|
||||||
|
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("10000.0").withNumberOfRepayments("1")
|
||||||
|
.withinterestRatePerPeriod("1").withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance()
|
||||||
|
.withInterestCalculationPeriodTypeAsDays()
|
||||||
|
.withInterestRecalculationDetails(LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
|
||||||
|
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
|
||||||
|
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE)
|
||||||
|
.withInterestRecalculationRestFrequencyDetails(LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", null, null)
|
||||||
|
.withInterestRecalculationCompoundingFrequencyDetails(null, null, null, null).withMoratorium("0", "0")
|
||||||
|
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withAccountingRulePeriodicAccrual(accounts).build(null);
|
||||||
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer createLoanProductWithPeriodicAccrualAccountingNoInterest(final Account... accounts) {
|
||||||
|
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(LP_PRINCIPAL.toString()).withRepaymentTypeAsMonth()
|
||||||
|
.withRepaymentAfterEvery(LP_REPAYMENT_PERIOD).withNumberOfRepayments(LP_REPAYMENTS).withRepaymentTypeAsMonth()
|
||||||
|
.withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment()
|
||||||
|
.withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts).withDaysInMonth("30").withDaysInYear("365")
|
||||||
|
.withMoratorium("0", "0").build(null);
|
||||||
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+8
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.integrationtests.common.loans;
|
package org.apache.fineract.integrationtests.common.loans;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -951,7 +952,14 @@ public class LoanTransactionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertTrue(isTransactionFound, "No Accrual entries are posted");
|
assertTrue(isTransactionFound, "No Accrual entries are posted");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void noAccrualTransactionForRepayment(final Integer loanID) {
|
||||||
|
ArrayList<HashMap> transactions = (ArrayList<HashMap>) getLoanTransactions(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
for (HashMap transaction : transactions) {
|
||||||
|
HashMap transactionType = (HashMap) transaction.get("type");
|
||||||
|
assertFalse((Boolean) transactionType.get("accrual"), "Accrual entries are posted!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashMap makeRefundByCash(final String date, final Float amountToBeRefunded, final Integer loanID) {
|
public HashMap makeRefundByCash(final String date, final Float amountToBeRefunded, final Integer loanID) {
|
||||||
|
|||||||
Reference in New Issue
Block a user