Skip to content

Commit

Permalink
FINERACT-2042: chargeback with chargeoff
Browse files Browse the repository at this point in the history
  • Loading branch information
reluxa committed Mar 8, 2024
1 parent 18697f7 commit 99f89b2
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -451,33 +451,54 @@ private void createJournalEntriesForChargeback(LoanDTO loanDTO, LoanTransactionD
}

if (principalCredited.compareTo(principalPaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalCredited.subtract(principalPaid),
isReversal);
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalCredited.subtract(principalPaid), isReversal);
} else if (principalCredited.compareTo(principalPaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPaid.subtract(principalCredited),
isReversal);
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalPaid.subtract(principalCredited), isReversal);
}

if (feeCredited.compareTo(feePaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, feeCredited.subtract(feePaid), isReversal);
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, feeCredited.subtract(feePaid), isReversal);
} else if (feeCredited.compareTo(feePaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, feePaid.subtract(feeCredited), isReversal);
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, feePaid.subtract(feeCredited), isReversal);
}

if (penaltyCredited.compareTo(penaltyPaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, penaltyCredited.subtract(penaltyPaid),
isReversal);
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, penaltyCredited.subtract(penaltyPaid), isReversal);
} else if (penaltyCredited.compareTo(penaltyPaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, penaltyPaid.subtract(penaltyCredited),
isReversal);
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, penaltyPaid.subtract(penaltyCredited), isReversal);
}
}

private Integer getFeeAccount(LoanDTO loanDTO) {
Integer account = AccrualAccountsForLoan.FEES_RECEIVABLE.getValue();
if (loanDTO.isMarkedAsChargeOff()) {
account = AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue();
}
return account;
}

private Integer getPenaltyAccount(LoanDTO loanDTO) {
Integer account = AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue();
if (loanDTO.isMarkedAsChargeOff()) {
account = AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue();
}
return account;
}

private Integer getPrincipalAccount(LoanDTO loanDTO) {
if (loanDTO.isMarkedAsFraud() && loanDTO.isMarkedAsChargeOff()) {
return AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue();
} else if (!loanDTO.isMarkedAsFraud() && loanDTO.isMarkedAsChargeOff()) {
return AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue();
} else {
return AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
import static org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -68,6 +69,7 @@
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.integrationtests.common.BatchHelper;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
Expand All @@ -85,6 +87,7 @@
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.integrationtests.common.system.CodeHelper;
import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
Expand Down Expand Up @@ -140,11 +143,13 @@ public abstract class BaseLoanIntegrationTest {
protected final Account feeIncomeAccount = accountHelper.createIncomeAccount("feeIncome");
protected final Account penaltyIncomeAccount = accountHelper.createIncomeAccount("penaltyIncome");
protected final Account feeChargeOffAccount = accountHelper.createIncomeAccount("feeChargeOff");
protected final Account penaltyChargeOffAccount = accountHelper.createIncomeAccount("penaltyChargeOff");

protected final Account recoveriesAccount = accountHelper.createIncomeAccount("recoveries");
protected final Account interestIncomeChargeOffAccount = accountHelper.createIncomeAccount("interestIncomeChargeOff");
// expense
protected final Account creditLossBadDebtAccount = accountHelper.createExpenseAccount();
protected final Account creditLossBadDebtFraudAccount = accountHelper.createExpenseAccount();
protected final Account chargeOffExpenseAccount = accountHelper.createExpenseAccount("chargeOff");
protected final Account chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud");
protected final Account writtenOffAccount = accountHelper.createExpenseAccount();
protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount();

Expand Down Expand Up @@ -233,9 +238,10 @@ protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAcc
.incomeFromGoodwillCreditPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue())//
.incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
.incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
.chargeOffExpenseAccountId(creditLossBadDebtAccount.getAccountID().longValue())//
.chargeOffFraudExpenseAccountId(creditLossBadDebtFraudAccount.getAccountID().longValue())//
.incomeFromChargeOffPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue()).dateFormat(DATETIME_PATTERN)//
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())//
.chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())//
.chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())//
.dateFormat(DATETIME_PATTERN)//
.locale("en_GB")//
.disallowExpectedDisbursements(true)//
.allowApprovedDisbursedAmountsOverApplied(true)//
Expand Down Expand Up @@ -398,7 +404,7 @@ protected void undoReAmortizeLoan(Long loanId) {

protected void verifyLastClosedBusinessDate(Long loanId, String lastClosedBusinessDate) {
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails.getLastClosedBusinessDate());
assertNotNull(loanDetails.getLastClosedBusinessDate());
Assertions.assertEquals(lastClosedBusinessDate, loanDetails.getLastClosedBusinessDate().format(dateTimeFormatter));
}

Expand Down Expand Up @@ -438,19 +444,19 @@ protected void verifyTRJournalEntries(Long transactionId, Journal... entries) {
protected Long addCharge(Long loanId, boolean isPenalty, double amount, String dueDate) {
Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(amount), isPenalty));
Assertions.assertNotNull(chargeId);
assertNotNull(chargeId);
Integer loanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId.intValue(),
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(chargeId), dueDate, String.valueOf(amount)));
Assertions.assertNotNull(loanChargeId);
assertNotNull(loanChargeId);
return loanChargeId.longValue();
}

protected void verifyRepaymentSchedule(Long loanId, Installment... installments) {
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);

Assertions.assertNotNull(loanResponse.getRepaymentSchedule());
Assertions.assertNotNull(loanResponse.getRepaymentSchedule().getPeriods());
assertNotNull(loanResponse.getRepaymentSchedule());
assertNotNull(loanResponse.getRepaymentSchedule().getPeriods());
Assertions.assertEquals(installments.length, loanResponse.getRepaymentSchedule().getPeriods().size(),
"Expected installments are not matching with the installments configured on the loan");

Expand Down Expand Up @@ -621,6 +627,24 @@ protected Long addRepaymentForLoan(Long loanId, Double amount, String date) {
return response.getResourceId();
}

protected Long chargeOffLoan(Long loanId, String date) {
String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5);
Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
String transactionExternalId = UUID.randomUUID().toString();

PostLoansLoanIdTransactionsResponse chargeOffTransaction = this.loanTransactionHelper.chargeOffLoan((long) loanId,
new PostLoansLoanIdTransactionsRequest().transactionDate(date).locale("en").dateFormat("dd MMMM yyyy")
.externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
return chargeOffTransaction.getResourceId();
}

protected void changeLoanFraudState(Long loanId, boolean fraudState) {
String payload = loanTransactionHelper.getLoanFraudPayloadAsJSON("fraud", fraudState ? "true" : "false");
PutLoansLoanIdResponse response = loanTransactionHelper.modifyLoanCommand(Math.toIntExact(loanId), "markAsFraud", payload,
responseSpec);
assertNotNull(response);
}

protected Long addChargebackForLoan(Long loanId, Long transactionId, Double amount) {
PostLoansLoanIdTransactionsResponse response = loanTransactionHelper.chargebackLoanTransaction(loanId, transactionId,
new PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(amount).paymentTypeId(1L));
Expand Down
Loading

0 comments on commit 99f89b2

Please sign in to comment.