Revert "FINERACT-1905: Same day repayment + charge handling improvement"

This reverts commit c50fc832c1.
This commit is contained in:
Adam Saghy
2023-08-01 17:12:05 +02:00
parent c50fc832c1
commit 3869c19296
12 changed files with 1136 additions and 1185 deletions
@@ -5741,7 +5741,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
copyTransactions.add(LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction));
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
}
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), copyTransactions, getCurrency(),
getRepaymentScheduleInstallments(), getActiveCharges());
@@ -43,7 +43,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -61,7 +61,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@Entity
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
public class LoanCharge extends AbstractPersistableCustom {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false)
@@ -269,13 +269,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
public static LoanTransaction copyTransactionPropertiesForReprocessing(final LoanTransaction loanTransaction) {
LoanTransaction loanTransactionCopy = new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf,
loanTransaction.dateOf, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId);
loanTransactionCopy.setCreatedDate(loanTransaction.getCreatedDateTime());
return loanTransactionCopy;
}
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- Sequence is starting from 1000 to make it easier to move existing liquibase changesets here -->
<include relativeToChangelogFile="true" file="parts/1001_add_audit_fields_to_loan_charge.xml"/>
</databaseChangeLog>
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1001-1" context="mysql">
<addColumn tableName="m_loan_charge">
<column name="created_on_utc" type="DATETIME(6)"/>
<column name="last_modified_on_utc" type="DATETIME(6)"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="1001-1" context="postgresql">
<addColumn tableName="m_loan_charge">
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
</addColumn>
</changeSet>
<changeSet id="1001-2" author="fineract">
<addColumn tableName="m_loan_charge">
<column name="created_by" type="BIGINT"/>
<column name="last_modified_by" type="BIGINT"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="1001-3">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="m_loan_charge"
constraintName="FK_loan_charge_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="m_loan_charge"
constraintName="FK_loan_charge_last_modified_by" deferrable="false"
initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
</changeSet>
<changeSet id="1001-4" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan_charge</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan_charge" columnName="created_on_utc" columnDataType="DATETIME(6)"/>
<addNotNullConstraint tableName="m_loan_charge" columnName="last_modified_on_utc" columnDataType="DATETIME(6)"/>
</changeSet>
<changeSet id="1001-4" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan_charge</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan_charge" columnName="created_on_utc"
columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="m_loan_charge" columnName="last_modified_on_utc"
columnDataType="TIMESTAMP WITH TIME ZONE"/>
</changeSet>
<changeSet id="1001-5" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from m_loan_charge</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="m_loan_charge" columnName="created_by" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="m_loan_charge" columnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
</databaseChangeLog>
@@ -175,7 +175,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
* For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties)
* has changed.<br>
**/
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction);
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
// Reset derived component of new loan transaction and
// re-process transaction
@@ -214,7 +214,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction);
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
newLoanTransaction.resetDerivedComponents();
// determine how much is outstanding total and breakdown for principal, interest and charges
Money principalPortion = Money.zero(currency);
@@ -280,7 +280,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.getId() == null) {
return;
}
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction);
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
List<LoanTransaction> mergedList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
Money overpaidAmount = calculateOverpaidAmount(loanTransaction, mergedList, installments, currency);
@@ -61,81 +61,9 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
*/
@Override
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
if (transactionAmountRemaining.isGreaterThanZero()) {
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
private static Money doTransactionAmountAllocation(LoanRepaymentScheduleInstallment currentInstallment, LoanTransaction loanTransaction,
Money paymentInAdvance, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
@@ -175,8 +103,6 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid)
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate))
.filter(loanCharge -> loanCharge.getCreatedDate().isEmpty() || loanTransaction.getCreatedDate().isEmpty()
|| !loanCharge.getCreatedDate().get().isAfter(loanTransaction.getCreatedDate().get()))
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
Money calculatedPenaltyCharge = Money.zero(currency);
Money calculatedFeeCharge = Money.zero(currency);
@@ -239,4 +165,126 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
}
return transactionAmountRemaining;
}
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
transactionMappings, charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isChargesWaiver()) {
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate,
loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if (loanTransaction.isChargePayment()) {
if (loanTransaction.isPenaltyPayment()) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
if (transactionAmountRemaining.isGreaterThanZero()) {
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
}
@@ -61,81 +61,8 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
*/
@Override
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money inAdvancePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings,
charges);
}
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
if (transactionAmountRemaining.isGreaterThanZero()) {
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
private static Money doTransactionAmountAllocation(LoanRepaymentScheduleInstallment currentInstallment, LoanTransaction loanTransaction,
Money inAdvancePayment, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = inAdvancePayment.getCurrency();
@@ -176,8 +103,6 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid)
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate))
.filter(loanCharge -> loanCharge.getCreatedDate().isEmpty() || loanTransaction.getCreatedDate().isEmpty()
|| loanCharge.getCreatedDate().get().isBefore(loanTransaction.getCreatedDate().get()))
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
Money calculatedPenaltyCharge = Money.zero(currency);
Money calculatedFeeCharge = Money.zero(currency);
@@ -243,4 +168,125 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
}
return transactionAmountRemaining;
}
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money latePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, latePayment, transactionMappings,
charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money onTimePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = onTimePayment.getCurrency();
Money transactionAmountRemaining = onTimePayment;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isChargesWaiver()) {
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate,
loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if (loanTransaction.isChargePayment()) {
if (loanTransaction.isPenaltyPayment()) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
if (transactionAmountRemaining.isGreaterThanZero()) {
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
if (transactionAmountRemaining.isGreaterThanZero()) {
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
}
@@ -2098,7 +2098,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
for (LoanTransaction loanTransaction : transactions) {
if (loanTransaction.isPaymentTransaction()) {
recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(),
LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction)));
LoanTransaction.copyTransactionProperties(loanTransaction)));
}
}
final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled();
@@ -6802,8 +6802,7 @@ public class ClientLoanIntegrationTest {
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY);
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(new PostChargesRequest().penalty(true).amount(10.0)
@@ -6863,337 +6862,6 @@ public class ClientLoanIntegrationTest {
}
}
@Test
public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeBeforeRepayment() {
try {
List<String> dueDateRelatedRepaymentStrategies = Arrays.asList(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE);
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-----------------------------------NEW CLIENT-----------------------------------------");
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
repaymentStrategyCode);
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(
new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue())
.chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5))
.chargeAppliesTo(1).locale("en").active(true));
LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------");
final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual(
clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
.approvedOnDate("01 January 2023").locale("en"));
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDisbursementUUID = UUID.randomUUID().toString();
PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID));
Long loanId = disbursedLoanResult.getResourceId();
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-------------------------------ADD CHARGES-------------------------------------------");
PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId,
new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN)
.locale("en").amount(10.0).dueDate("10 January 2023"));
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String firstRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023")
.locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 0.0, 9.0, 0.0);
}
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false);
}
}
@Test
public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeAfterRepayment() {
try {
List<String> dueDateRelatedRepaymentStrategies = Arrays.asList(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE);
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-----------------------------------NEW CLIENT-----------------------------------------");
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
repaymentStrategyCode);
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(
new PostChargesRequest().penalty(false).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue())
.chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5))
.chargeAppliesTo(1).locale("en").active(true));
LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------");
final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual(
clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
.approvedOnDate("01 January 2023").locale("en"));
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDisbursementUUID = UUID.randomUUID().toString();
PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID));
Long loanId = disbursedLoanResult.getResourceId();
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String firstRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023")
.locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID));
LOG.info("-------------------------------ADD CHARGES-------------------------------------------");
PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId,
new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN)
.locale("en").amount(10.0).dueDate("10 January 2023"));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
}
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false);
}
}
@Test
public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeBeforeRepaymentOnInstallmentDueDate() {
try {
List<String> dueDateRelatedRepaymentStrategies = Arrays.asList(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) {
LOG.info("-----------------------------------REPAYMENT SCEDULE CODE : {}-----------------------------------------",
repaymentStrategyCode);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE);
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-----------------------------------NEW CLIENT-----------------------------------------");
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
repaymentStrategyCode);
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(
new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue())
.chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5))
.chargeAppliesTo(1).locale("en").active(true));
LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------");
final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual(
clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
.approvedOnDate("01 January 2023").locale("en"));
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDisbursementUUID = UUID.randomUUID().toString();
PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID));
Long loanId = disbursedLoanResult.getResourceId();
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("31 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-------------------------------ADD CHARGES-------------------------------------------");
PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId,
new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN)
.locale("en").amount(10.0).dueDate("31 January 2023"));
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String firstRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("31 January 2023")
.locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 0.0, 9.0, 0.0);
}
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false);
}
}
@Test
public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeAfterRepaymentOnInstallmentDueDate() {
try {
List<String> dueDateRelatedRepaymentStrategies = Arrays.asList(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) {
LOG.info("-----------------------------------REPAYMENT SCEDULE CODE : {}-----------------------------------------",
repaymentStrategyCode);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE);
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-----------------------------------NEW CLIENT-----------------------------------------");
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
repaymentStrategyCode);
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(
new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue())
.chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5))
.chargeAppliesTo(1).locale("en").active(true));
LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------");
final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual(
clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
.approvedOnDate("01 January 2023").locale("en"));
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDisbursementUUID = UUID.randomUUID().toString();
PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID));
Long loanId = disbursedLoanResult.getResourceId();
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("31 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String firstRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("31 January 2023")
.locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID));
LOG.info("-------------------------------ADD CHARGES-------------------------------------------");
PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId,
new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN)
.locale("en").amount(10.0).dueDate("31 January 2023"));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
}
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false);
}
}
@Test
public void testLoanTransactionOrderAndRepaymentAllocationMultipleActions() {
try {
List<String> dueDateRelatedRepaymentStrategies = Arrays.asList(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE);
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-----------------------------------NEW CLIENT-----------------------------------------");
final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023");
final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient);
LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------");
PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(
repaymentStrategyCode);
final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest);
LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------");
PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(
new PostChargesRequest().penalty(false).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue())
.chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
.name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5))
.chargeAppliesTo(1).locale("en").active(true));
LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------");
final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual(
clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
.approvedOnDate("01 January 2023").locale("en"));
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDisbursementUUID = UUID.randomUUID().toString();
PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(),
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID));
Long loanId = disbursedLoanResult.getResourceId();
BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en"));
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String firstRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023")
.locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID));
LOG.info("-------------------------------ADD CHARGES-------------------------------------------");
PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId,
new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN)
.locale("en").amount(10.0).dueDate("07 January 2023"));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String secondRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse secondRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("06 January 2023")
.locale("en").transactionAmount(9.0).externalId(secondRepaymentUUID));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, secondRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------");
String thirdRepaymentUUID = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse thirdRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023")
.locale("en").transactionAmount(9.0).externalId(thirdRepaymentUUID));
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, thirdRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 9.0, 0.0, 0.0);
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, secondRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------");
checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0);
}
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false);
}
}
private void checkLoanTransactionAllocation(Long loanId, String transactionExternalId, double amount, double principalPortion,
double interestPortion, double feePortion, double penaltyPortion, double overpaymentPortion) {
GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails = LOAN_TRANSACTION_HELPER.getLoanTransactionDetails(loanId,
transactionExternalId);
assertEquals(amount, transactionDetails.getAmount());
assertEquals(principalPortion, transactionDetails.getPrincipalPortion());
assertEquals(interestPortion, transactionDetails.getInterestPortion());
assertEquals(feePortion, transactionDetails.getFeeChargesPortion());
assertEquals(penaltyPortion, transactionDetails.getPenaltyChargesPortion());
assertEquals(overpaymentPortion, transactionDetails.getOverpaymentPortion());
}
private void checkLoanTransactionOrder(Long loanId, String... transactionUUIDs) {
LOG.info("-------------------------------CHECK LOAN TRANSACTION ORDER-------------------------------------------");
GetLoansLoanIdResponse loanDetailsResult = LOAN_TRANSACTION_HELPER.getLoanDetails(loanId);
@@ -8355,7 +8023,7 @@ public class ClientLoanIntegrationTest {
.principal(BigDecimal.valueOf(1000.0)).loanType("individual"));
}
private PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(String repaymentStrategyCode) {
private PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() {
return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))//
.shortName(Utils.uniqueRandomStringGenerator("", 4))//
.description("Loan Product Description")//
@@ -8382,7 +8050,8 @@ public class ClientLoanIntegrationTest {
.interestType(0)//
.isEqualAmortization(false)//
.interestCalculationPeriodType(1)//
.transactionProcessingStrategyCode(repaymentStrategyCode)//
.transactionProcessingStrategyCode(
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY)//
.daysInYearType(1)//
.daysInMonthType(1)//
.canDefineInstallmentAmount(true)//