Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public enum CashAccountsForLoan {
CLASSIFICATION_INCOME(22), //
DEFERRED_INCOME_LIABILITY(23), //
INCOME_FROM_DISCOUNT_FEE(24), //
FEES_RECEIVABLE(25), //
PENALTIES_RECEIVABLE(26), //
;

private final Integer value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public enum AccountTypeAssetOptions {
LOANS_RECEIVABLE(1), //
INTEREST_FEE_RECEIVABLE(2), //
OTHER_RECEIVABLES(3), //
UNC_RECEIVABLE(4), //
FUND_RECEIVABLES(18), //
TRANSFER_IN_SUSPENSE_ACCOUNT(14), //
GOODWILL_TRANSFER_ACCOUNT(19); //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum AccountTypeLiabilityOptions {

AA_SUSPENSE_BALANCE(5), //
SUSPENSE_CLEARING_ACCOUNT(6), //
OTHER_CREDIT_LIABILITY(4), //
OVERPAYMENT_ACCOUNT(17); //

public final Integer value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public enum DefaultAccountType implements AccountType {
LOANS_RECEIVABLE("Loans Receivable"), //
INTEREST_FEE_RECEIVABLE("Interest/Fee Receivable"), //
OTHER_RECEIVABLES("Other Receivables"), //
UNC_RECEIVABLE("UNC Receivable"), //
FUND_RECEIVABLES("Fund Receivables"), //
TRANSFER_IN_SUSPENSE_ACCOUNT("Transfer in suspense account"), //
ASSET_TRANSFER("Asset transfer"), //
Expand All @@ -41,6 +40,7 @@ public enum DefaultAccountType implements AccountType {
AA_SUSPENSE_BALANCE("AA Suspense Balance"), //
SUSPENSE_CLEARING_ACCOUNT("Suspense/Clearing account"), //
OVERPAYMENT_ACCOUNT("Overpayment account"), //
OTHER_CREDIT_LIABILITY("Other Credit Liability"), //
DEFERRED_CAPITALIZED_INCOME("Deferred Capitalized Income"), //
// Expense
CREDIT_LOSS_BAD_DEBT("Credit Loss/Bad Debt"), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public enum DefaultWorkingCapitalLoanProduct implements WorkingCapitalLoanProduc
WCLP_BREACH, //
WCLP_BREACH_NEAR_BREACH, //
WCLP_BREACH_DISALLOW_ATTRIBUTES_OVERRIDE, //
WCLP_BREACH_NEAR_BREACH_DISALLOW_ATTRIBUTES_OVERRIDE; //
WCLP_BREACH_NEAR_BREACH_DISALLOW_ATTRIBUTES_OVERRIDE, //
WCLP_ADVANCED_ACCOUNTING, //
WCLP_ACCOUNTING_CASH_BASED; //

@Override
public String getName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public record WCGLAccountMapping(String responseKey, boolean required, Function<
public static final WCGLAccountMapping OVERPAYMENT_LIABILITY = new WCGLAccountMapping("overpaymentLiabilityAccount", true,
PostWorkingCapitalLoanProductsRequest::getOverpaymentLiabilityAccountId);

// Assets — receivables (required)
public static final WCGLAccountMapping RECEIVABLE_FEE = new WCGLAccountMapping("receivableFeeAccount", true,
PostWorkingCapitalLoanProductsRequest::getReceivableFeeAccountId);
public static final WCGLAccountMapping RECEIVABLE_PENALTY = new WCGLAccountMapping("receivablePenaltyAccount", true,
PostWorkingCapitalLoanProductsRequest::getReceivablePenaltyAccountId);

// Income (required)
public static final WCGLAccountMapping INCOME_FROM_DISCOUNT_FEE = new WCGLAccountMapping("incomeFromDiscountFeeAccount", true,
PostWorkingCapitalLoanProductsRequest::getIncomeFromDiscountFeeAccountId);
Expand Down Expand Up @@ -71,9 +77,10 @@ public record WCGLAccountMapping(String responseKey, boolean required, Function<
PostWorkingCapitalLoanProductsRequest::getIncomeFromGoodwillCreditPenaltyAccountId);

private static final List<WCGLAccountMapping> VALUES = List.of(FUND_SOURCE, LOAN_PORTFOLIO, TRANSFERS_IN_SUSPENSE,
DEFERRED_INCOME_LIABILITY, OVERPAYMENT_LIABILITY, INCOME_FROM_DISCOUNT_FEE, INCOME_FROM_FEE, INCOME_FROM_PENALTY,
INCOME_FROM_RECOVERY, WRITE_OFF, GOODWILL_CREDIT, CHARGE_OFF_EXPENSE, CHARGE_OFF_FRAUD_EXPENSE, INCOME_FROM_CHARGE_OFF_FEES,
INCOME_FROM_CHARGE_OFF_PENALTY, INCOME_FROM_GOODWILL_CREDIT_FEES, INCOME_FROM_GOODWILL_CREDIT_PENALTY);
DEFERRED_INCOME_LIABILITY, OVERPAYMENT_LIABILITY, RECEIVABLE_FEE, RECEIVABLE_PENALTY, INCOME_FROM_DISCOUNT_FEE, INCOME_FROM_FEE,
INCOME_FROM_PENALTY, INCOME_FROM_RECOVERY, WRITE_OFF, GOODWILL_CREDIT, CHARGE_OFF_EXPENSE, CHARGE_OFF_FRAUD_EXPENSE,
INCOME_FROM_CHARGE_OFF_FEES, INCOME_FROM_CHARGE_OFF_PENALTY, INCOME_FROM_GOODWILL_CREDIT_FEES,
INCOME_FROM_GOODWILL_CREDIT_PENALTY);

public static List<WCGLAccountMapping> all() {
return VALUES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductReq
.incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))//
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD));//
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
.receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
.receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE));//
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
Expand All @@ -59,6 +60,7 @@
import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetWorkingCapitalLoanTransactionIdResponse;
import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdResponse;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.client.models.LoanTransactionEnumData;
import org.apache.fineract.client.models.PostAllowAttributeOverrides;
import org.apache.fineract.client.models.PostClientsResponse;
Expand Down Expand Up @@ -92,6 +94,7 @@
import org.apache.fineract.test.helper.WorkingCapitalScheduleMatcher;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.stepdef.common.JournalEntriesStepDef;
import org.apache.fineract.test.support.TestContextKey;
import org.junit.jupiter.api.Assertions;

Expand All @@ -101,6 +104,7 @@ public class WorkingCapitalLoanAccountStepDef extends AbstractStepDef {

private static final String DATE_FORMAT = "dd MMMM yyyy";
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
private static final Long NON_EXISTENT_LOAN_ID = 999_999_999L;
private static final String WC_DISBURSE_CLASSIFICATION_ID = "wcDisburseClassificationId";
private static final String WC_DISBURSE_CLASSIFICATION_CODE_NAME = "working_capital_loan_disbursement_classification";
Expand All @@ -117,6 +121,7 @@ public class WorkingCapitalLoanAccountStepDef extends AbstractStepDef {
private final EventCheckHelper eventCheckHelper;
private final PaymentTypeResolver paymentTypeResolver;
private final BusinessDateHelper businessDateHelper;
private final JournalEntriesStepDef journalEntriesStepDef;

@When("Admin creates a working capital loan with the following data:")
public void createWorkingCapitalLoan(final DataTable table) {
Expand Down Expand Up @@ -2508,4 +2513,78 @@ private <T> void assertTable(Class<T> tClass, List<String> header, List<List<Str
}
}
}

@Then("Working Capital Loan Transactions tab has a {string} transaction with date {string} which has the following Journal entries:")
public void verifyWorkingCapitalLoanTransactionJournalEntries(String transactionType, String transactionDate, DataTable table)
throws IOException {
Long loanId = getCreatedLoanId();
List<GetWorkingCapitalLoanTransactionIdResponse> transactionsMatch = findMatchingTransactions(loanId, transactionType,
transactionDate, false);
verifyJournalEntries(transactionsMatch, loanId, table);
}

@Then("Working Capital Loan Transactions tab has {int} {string} transactions with date {string} which have the following Journal entries:")
public void verifyMultipleWorkingCapitalLoanTransactionsJournalEntries(int expectedCount, String transactionType,
String transactionDate, DataTable table) throws IOException {
Long loanId = getCreatedLoanId();
List<GetWorkingCapitalLoanTransactionIdResponse> transactionsMatch = findMatchingTransactions(loanId, transactionType,
transactionDate, false);

assertThat(transactionsMatch.size()).as("The number of transactions does not match the expected count! Expected: " + expectedCount
+ ", Actual: " + transactionsMatch.size()).isEqualTo(expectedCount);

verifyJournalEntries(transactionsMatch, loanId, table);
}

@Then("Working Capital Loan Transactions tab has a reversed {string} transaction with date {string} which has the following Journal entries:")
public void verifyReversedWorkingCapitalLoanTransactionJournalEntries(String transactionType, String transactionDate, DataTable table)
throws IOException {
Long loanId = getCreatedLoanId();
List<GetWorkingCapitalLoanTransactionIdResponse> transactionsMatch = findMatchingTransactions(loanId, transactionType,
transactionDate, true);
verifyJournalEntries(transactionsMatch, loanId, table);
}

private List<GetWorkingCapitalLoanTransactionIdResponse> findMatchingTransactions(Long loanId, String transactionType,
String transactionDate, boolean reversed) {
GetWorkingCapitalLoansLoanIdResponse loanDetailsResponse = ok(
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));

return loanDetailsResponse.getTransactions().stream()
.filter(t -> t.getType() != null && transactionDate.equals(DATE_FORMATTER.format(t.getTransactionDate()))
&& transactionType.equalsIgnoreCase(t.getType().getValue())
&& (reversed ? Boolean.TRUE.equals(t.getReversed()) : !Boolean.TRUE.equals(t.getReversed())))
.collect(Collectors.toList());
}

private void verifyJournalEntries(List<GetWorkingCapitalLoanTransactionIdResponse> transactions, Long loanId, DataTable table) {
List<List<JournalEntryTransactionItem>> journalLinesActualList = getWorkingCapitalJournalLinesActualList(transactions);
journalEntriesStepDef.checkJournalEntryData(journalLinesActualList, loanId, table);
}

private List<List<JournalEntryTransactionItem>> getWorkingCapitalJournalLinesActualList(
List<GetWorkingCapitalLoanTransactionIdResponse> transactions) {
log.debug("Processing {} working capital loan transactions for journal entries", transactions.size());
return transactions.stream().map(this::retrieveJournalEntriesForTransaction).collect(Collectors.toList());
}

private List<JournalEntryTransactionItem> retrieveJournalEntriesForTransaction(GetWorkingCapitalLoanTransactionIdResponse transaction) {
String transactionId = "WC" + transaction.getId();
log.debug("Retrieving journal entries for working capital transaction: {}", transactionId);

JournalEntriesApi.RetrieveAllJournalEntriesQueryParams params = new JournalEntriesApi.RetrieveAllJournalEntriesQueryParams()
.transactionId(transactionId).runningBalance(true);

GetJournalEntriesTransactionIdResponse journalEntryDataResponse = ok(
() -> fineractClient.journalEntries().retrieveAllJournalEntries(params));

return journalEntryDataResponse != null && journalEntryDataResponse.getPageItems() != null ? journalEntryDataResponse.getPageItems()
: List.of();
}

@When("Customer undo {string}th {string} transaction made on {string} on Working Capital loan")
public void undoWorkingCapitalLoanTransaction(String nthItemStr, String transactionType, String transactionDate) throws IOException {
// TODO: Implement undo transaction for working capital loans when backend support is available (PS-3194)
throw new UnsupportedOperationException("Undo transaction for working capital loans is not yet implemented");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,9 @@ private PutWorkingCapitalLoanProductsProductIdRequest buildCashBasedUpdateReques
.incomeFromPenaltyAccountId(source.getIncomeFromPenaltyAccountId())//
.incomeFromRecoveryAccountId(source.getIncomeFromRecoveryAccountId())//
.writeOffAccountId(source.getWriteOffAccountId())//
.overpaymentLiabilityAccountId(source.getOverpaymentLiabilityAccountId());
.overpaymentLiabilityAccountId(source.getOverpaymentLiabilityAccountId())//
.receivableFeeAccountId(source.getReceivableFeeAccountId())//
.receivablePenaltyAccountId(source.getReceivablePenaltyAccountId());
}

public void checkWorkingCapitalLoanProductCreate() {
Expand Down Expand Up @@ -2012,6 +2014,8 @@ private PostWorkingCapitalLoanProductsRequest buildAdvancedMappingsRequest(
.incomeFromRecoveryAccountId(accountTypeResolver.resolve(DefaultAccountType.RECOVERIES))
.writeOffAccountId(accountTypeResolver.resolve(DefaultAccountType.WRITTEN_OFF))
.overpaymentLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))
.receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))
.receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))
.paymentChannelToFundSourceMappings(paymentChannelMappings).chargeOffReasonToExpenseAccountMappings(chargeOffMappings)
.writeOffReasonsToExpenseMappings(writeOffMappings).feeToIncomeAccountMappings(List.of())
.penaltyToIncomeAccountMappings(List.of());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ public abstract class TestContextKey {
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_BREACH_NEAR_BREACH = "workingCapitalLoanProductCreateResponseWCLPBreachNearBreach";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_BREACH_DISALLOW_OVERRIDES = "workingCapitalLoanProductCreateResponseWCLPBreachDisallowOverrides";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_BREACH_NEAR_BREACH_DISALLOW_OVERRIDES = "workingCapitalLoanProductCreateResponseWCLPBreachNearBreachDisallowOverrides";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_ADVANCED_ACCOUNTING = "workingCapitalLoanProductCreateResponseWCLPAdvancedAccounting";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_ACCOUNTING_CASH_BASED = "workingCapitalLoanProductCreateResponseWCLPAccountingCashBased";
public static final String WC_LOAN_IDS = "wcLoanIds";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateRequestForUpdateWCLP";
public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateResponseForUpdateWCLP";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class GLGlobalInitializerStep implements FineractGlobalInitializerStep {
public static final String GLA_NAME_1 = "Loans Receivable";
public static final String GLA_NAME_2 = "Interest/Fee Receivable";
public static final String GLA_NAME_3 = "Other Receivables";
public static final String GLA_NAME_4 = "UNC Receivable";
public static final String GLA_NAME_4 = "Other Credit Liability";
public static final String GLA_NAME_5 = "AA Suspense Balance";
public static final String GLA_NAME_6 = "Suspense/Clearing account";
public static final String GLA_NAME_7 = "Deferred Interest Revenue";
Expand Down Expand Up @@ -112,7 +112,7 @@ public void initialize() {
List<GLAccountDefinition> items = List.of(new GLAccountDefinition(GLA_NAME_1, GLA_GL_CODE_1, GLA_TYPE_ASSET),
new GLAccountDefinition(GLA_NAME_2, GLA_GL_CODE_2, GLA_TYPE_ASSET),
new GLAccountDefinition(GLA_NAME_3, GLA_GL_CODE_3, GLA_TYPE_ASSET),
new GLAccountDefinition(GLA_NAME_4, GLA_GL_CODE_4, GLA_TYPE_ASSET),
new GLAccountDefinition(GLA_NAME_4, GLA_GL_CODE_4, GLA_TYPE_LIABILITY),
new GLAccountDefinition(GLA_NAME_5, GLA_GL_CODE_5, GLA_TYPE_LIABILITY),
new GLAccountDefinition(GLA_NAME_6, GLA_GL_CODE_6, GLA_TYPE_LIABILITY),
new GLAccountDefinition(GLA_NAME_7, GLA_GL_CODE_7, GLA_TYPE_INCOME),
Expand Down
Loading