@hayeeder/fineract-api-client
Version:
TypeScript client for Fineract APIs
1,115 lines (921 loc) • 52 kB
Markdown
# Fineract API Client
TypeScript client for interacting with specific Fineract client-related APIs.
This library provides type-safe methods for creating and retrieving client information from a Fineract instance.
## Installation
```bash
npm install @hayeeder/fineract-api-client
# or
yarn add @hayeeder/fineract-api-client
```
## Usage
First, configure and create an instance of the `FineractSDK`:
```typescript
import {
createFineractSDK,
FineractSDKConfig,
} from "@hayeeder/fineract-api-client";
const sdkConfig: FineractSDKConfig = {
baseURL: "https://your-fineract-host.com", // Replace with your Fineract base URL
tenantId: "default", // Your Fineract tenant ID
username: "mifos", // Your Fineract username
password: "password", // Your Fineract password
timeout: 30000, // Optional: request timeout
};
const fineractSDK = createFineractSDK(sdkConfig);
```
### Example 1: Create a New Client
```typescript
import { CreateClientRequest } from "@hayeeder/fineract-api-client";
async function createNewClient() {
const clientData: CreateClientRequest = {
officeId: 1,
fullname: "Jane Doe API",
active: true,
activationDate: "2024-07-28", // Use yyyy-MM-dd format
dateFormat: "yyyy-MM-dd",
locale: "en",
legalFormId: 1, // Ensure this ID is valid in your Fineract setup
};
try {
const response = await fineractSDK.client.createClient(clientData);
console.log("Client Created:", response);
// The actual ID of the created client is usually in response.resourceId or response.clientId
const clientId = response.resourceId;
if (clientId) {
console.log("New Client ID:", clientId);
}
} catch (error) {
console.error("Failed to create client:", error);
}
}
createNewClient();
```
### Example 2: Get Client Details
```typescript
async function getClient(clientId: number) {
try {
const clientDetails = await fineractSDK.client.getClientDetails(clientId);
console.log("Client Details:", clientDetails);
} catch (error) {
console.error(`Failed to get client ${clientId}:`, error);
}
}
// Assuming a client with ID 123 exists
// getClient(123);
```
### Complete Client Creation Workflow
To successfully create a client and set up all required accounts and details, follow these steps using the relevant APIs from the SDK:
#### Step 1. Create the SME Client
- **API:** `fineractSDK.sme.create()`
- **Description:** Use the SME API to create a new client (SME) in Fineract.
- **Example:**
```typescript
const smeResponse = await fineractSDK.sme.create({
officeId: 1, // Hardcoded to 1 because currently we only have head office
fullname: "Acme Corporation",
active: true, // Hardcoded to always true
activationDate: "2024-07-28", // Use yyyy-MM-dd format i.e. can be Today's date
dateFormat: "yyyy-MM-dd",
locale: "en",
legalFormId: 2, // Hardcoded to 2 meaning client in fineract langauge
externalId: "LOS-EXT-123456", // Replace with actual externalId i.e. masterCompanyId from LOS
});
const clientExternalId = smeResponse.resourceExternalId; // external unique identifier i.e. externalId from LOS
```
#### 2. Add Virtual Account Number (Datatables API)
- **API:** `fineractSDK.datatable.addEntry(datatableName, entryData)`
- **Description:** Add virtual account number for the client using the Datatables API. The datatable name and required fields depend on your Fineract configuration.
- **Example:**
```typescript
// adding virtual account number
await fineractSDK.datatable.addEntry("dt_client_virtual_account_number", {
client_id: clientId, // get the clientId with fineractSDK.client.getClientDetails(externalId) API
virtualAccountNumber: 123456, // replace with actual virtual account number from LOS
});
```
#### 2.1. Add Remitter Details (Datatables API) IMP: This step is only required for SMEs with RBF product enabled
- **API:** `fineractSDK.datatable.addEntry(datatableName, entryData)`
- **Description:** Add remitter details for the client using the Datatables API. The datatable name and required fields depend on your Fineract configuration.
- **Example:**
```typescript
// adding remitter name
await fineractSDK.datatable.addEntry("dt_client_remitter_names", {
client_id: clientId, // get the clientId with fineractSDK.client.getClientDetails(externalId) API
remitterName: "Remitter Name", // replace with actual remitter name from LOS
});
```
### 2.2. Update Datatable Entry (Datatables API)
- **API:** `fineractSDK.datatable.updateEntry(datatableName, entityId, entryData)`
- **Description:** Update an existing datatable entry for a specific entity using the Datatables API. The datatable name, entity ID, and fields depend on your Fineract configuration.
- **Example:**
```typescript
// updating virtual account number for a client
await fineractSDK.datatable.updateEntry(
"dt_client_virtual_account_number", // datatable name
1635, // entityId (e.g., client ID)
{
virtual_account_number: "123123123123",
locale: "en",
}
);
```
### 2.3. Get All Datatables (Datatables API)
- **API:** `fineractSDK.datatable.getAll()`
- **Description:** Retrieve a list of all datatables configured in Fineract. This is useful for discovering available datatables and their metadata.
- **Example:**
```typescript
// get all datatables
const datatables = await fineractSDK.datatable.getAll();
console.log(datatables);
```
#### 2.2. The 3-Step Process for Creating Savings Accounts
Creating any type of savings account (including Fallback, Cash Margin, or Collection accounts) is a three-step process. The account must be created, then approved, and finally activated to become fully functional.
1. **Create:** Call `fineractSDK.savingsaccounts.create()` to create the account record. This returns a `savingsId`.
2. **Approve:** Call `fineractSDK.savingsproducts.approve()` using the `savingsId`. This moves the account to an "approved" state.
3. **Activate:** Call `fineractSDK.savingsproducts.activate()` using the `savingsId`. This makes the account "active" and ready for transactions.
- **API:** `fineractSDK.savingsaccounts.create()`, `fineractSDK.savingsproducts.approve()`, `fineractSDK.savingsproducts.activate()`
- **Description:** A complete workflow for creating, approving, and activating a savings account.
- **Example:**
```typescript
// The ID of the client for whom the account is being created
const clientId = 123;
// The ID of the savings product (e.g., for a Fallback Account)
const productId = 456;
// Step 1: Create the savings account
const savingsAccount = await fineractSDK.savingsaccounts.create({
clientId: clientId,
productId: productId,
submittedOnDate: "2024-07-28", // Use current date
dateFormat: "yyyy-MM-dd",
locale: "en",
});
const savingsId = savingsAccount.savingsId;
// Step 2: Approve the savings account
const approvalResponse = await fineractSDK.savingsproducts.approve(
savingsId,
{
approvedOnDate: "2024-07-28", // Use current date
dateFormat: "yyyy-MM-dd",
locale: "en",
}
);
// Step 3: Activate the savings account
const activationResponse = await fineractSDK.savingsproducts.activate(
savingsId,
{
activatedOnDate: "2024-07-28", // Use current date
dateFormat: "yyyy-MM-dd",
locale: "en",
}
);
console.log("Savings account created, approved, and activated successfully!");
```
#### 3. Create Fallback Account (Savings Account API)
- **API:** `fineractSDK.savingsaccounts.create()`
- **Description:** Every SME should have one fallback account at the time of creation. It is type of savings account which will be used to store funds which cannot be categorised.
- **Example:**
```typescript
const fallbackAccount = await fineractSDK.savingsaccounts.create({
clientId: clientId, // get the clientId with fineractSDK.client.getClientDetails(externalId) API
productId: FALLBACK_PRODUCT_ID, // get all savings products with fineractSDK.savingsproducts.getAll() and filter by shortName=FBSA i.e. fallback savings account to get productId to be sent
submittedOnDate: "2024-07-28", // This should be current date
dateFormat: "yyyy-MM-dd",
locale: "en",
});
const fallbackAccountId = fallbackAccount.resourceId;
```
Make sure to approve and activate the account as described above
#### 4. Create Cash Margin Account (Savings Account API)
- **API:** `fineractSDK.savingsaccounts.create()`
- **Description:** Every SME should have cash margin account at the time of creation. It is type of savings account which will be used to store security deposits of 5%, 10% etc while disbursing loan
- **Example:**
```typescript
const cashMarginAccount = await fineractSDK.savingsaccounts.create({
clientId: clientId,
productId: CASH_MARGIN_PRODUCT_ID, // get all savings products with fineractSDK.savingsproducts.getAll() and filter by shortName=CMSA i.e. cash margin savings account
dateFormat: "yyyy-MM-dd",
locale: "en",
});
const cashMarginAccountId = cashMarginAccount.resourceId;
```
Make sure to approve and activate the account as described above
---
**Summary of Steps and APIs:**
1. **Create SME Client:** `fineractSDK.sme.create()`
2. **Add Virtual Account & Remitter Details:** `fineractSDK.datatable.addEntry()`
3. **Create Fallback Savings Account:** `fineractSDK.savingsaccounts.create()`
4. **Create Cash Margin Savings Account:** `fineractSDK.savingsaccounts.create()`
> **Note:** Replace datatable names, product IDs, and field names as per your Fineract instance configuration.
**Important API Paths:**
The API paths used in this SDK (`/fineract-provider/api/v1/clients` and `/fineract-provider/api/v1/clients/{clientId}`) are based on common Fineract setups and the provided `clients.ts` example. You **must verify** these paths against your Fineract instance's API documentation or the underlying `ClientApi` implementation you were using. You may need to adjust these paths in `src/api/fineract-client-api.ts` if they differ.
### How to get repayment schedule without creating loan
#### 1. Calculate Loan Repayment Schedule (Loan Accounts API)
- **API:** `fineractSDK.loansaccounts.getRepaymentSchedule()`
- **Description:** Calculate and retrieve the repayment schedule for a loan before actually creating the loan account. This is useful for showing the user the expected repayment plan based on their input parameters.
- **Example:**
```typescript
const repaymentSchedule =
await fineractSDK.loansaccounts.getRepaymentSchedule({
productId: 1, // get all loan products with fineractSDK.loanproducts.getAll() and filter by externalId=REVENUE_BASED_FINANCING
loanOfficerId: "",
loanPurposeId: "",
fundId: "",
submittedOnDate: "28 May 2025", // Date the loan application is submitted
expectedDisbursementDate: "30 May 2025", // Expected date of disbursement
externalId: "",
linkAccountId: "", // Optional but mandatory when createStandingInstructionAtDisbursement = true
createStandingInstructionAtDisbursement: false, // Optional
loanTermFrequency: 3, // Number of periods (e.g., months)
loanTermFrequencyType: 2, // Frequency type (0=Days, 1=Weeks, 2=Months, 3=Years)
numberOfRepayments: 3, // Total Number of installments to repay: Used like every [repaymentEvery] [repaymentFrequencyType] for [numberOfRepayments]
repaymentEvery: 1, // Repayment interval (e.g., every 1 month)
repaymentFrequencyType: 2, // Repayment frequency type (0=Days, 1=Weeks, 2=Months, 3=Years)
repaymentFrequencyNthDayType: "",
repaymentFrequencyDayOfWeekType: "",
repaymentsStartingFromDate: null,
interestChargedFromDate: null
interestType: 0, // Interest type (0 = declining balance, 1 = flat)
isEqualAmortization: false // Optional
amortizationType: 1, // Amortization type (0=Equal principle payments, 1=Equal installments) - Fetch this from loan product details in amortizationType.id
interestCalculationPeriodType: 3, // Interest calculation period type - Fetch this from loan product details in interestCalculationPeriodType.id
loanIdToClose: "",
isTopup: "",
interestRatePerPeriod: 10, // Interest rate per period means 10%
transactionProcessingStrategyCode: "mifos-standard-strategy", // Hardcoded to mifos-standard-strategy
interestRateFrequencyType: 3 // hardcoded to 3 for now
charges:[],
collateral: [],
dateFormat: "yyyy-MM-dd", // Hardcoded to yyyy-MM-dd - Date format used in the request
locale: "en", // Locale
clientId: 1, // // get the clientId with fineractSDK.client.getClientDetails(externalId) API
loanType: "individual", // Type of loan
principal: 100000, // Principal amount - Input from LOS
allowPartialPeriodInterestCalcualtion: false // Optional
});
console.log(
"Repayment Schedule:",
JSON.stringify(repaymentSchedule, null, 2)
);
```
- **API Path Used:** `/fineract-provider/api/v1/loans?command=calculateLoanSchedule`
- **Reference:** See [`src/api/fineract-loansaccounts-api.ts`](src/api/fineract-loansaccounts-api.ts) and [`src/types/loansaccounts.ts`](src/types/loansaccounts.ts) for full request/response details.
> **Note:** This API does not create a loan, it only returns the calculated schedule based on the input parameters. Use this to preview repayment plans before submitting a loan application.
### How to create loan application for SME
#### 1. Create RBF collection account (Savings Account API)
- **API:** `fineractSDK.savingsaccounts.create()`
- **Description:** Every SME should have collection account based on product at the time of creation. It is type of savings account which will be save funds collected from SME. For each Loan Account i.e Loan Application, we must have corresponding collection account
- **Example:**
```typescript
const cashMarginAccount = await fineractSDK.savingsaccounts.create({
clientId: clientId,
productId: RBF_COLLECTION_ACCOUNT, // get all savings products with fineractSDK.savingsproducts.getAll() and filter by shortName=RBF i.e. Rbf collection savings account
dateFormat: "yyyy-MM-dd",
locale: "en",
});
const cashMarginAccountId = cashMarginAccount.resourceId;
```
Make sure to approve and activate the account as described above
#### 2. Create Loan Account (Loan Accounts API)
- **API:** `fineractSDK.loansaccounts.create()`
- **Description:** Create a new loan account for a client in Fineract. This will actually create the loan in the system and return the created loan's details.
- **Example:**
```typescript
const newLoan = await fineractSDK.loansaccounts.create({
productId: 1, // get all loan products with fineractSDK.loanproducts.getAll() and filter by filter by externalId=REVENUE_BASED_FINANCING
linkAccountId: 123, // ID of the collection account you created above
submittedOnDate: "28 May 2025", // Date the loan application is submitted
expectedDisbursementDate: "30 May 2025", // Expected date of disbursement - can be anything in future
loanTermFrequency: 3, // Number of periods (e.g., months)
loanTermFrequencyType: 2, // Frequency type (0=Days, 1=Weeks, 2=Months, 3=Years)
numberOfRepayments: 3, // Total Number of installments to repay: Used like every [repaymentEvery] [repaymentFrequencyType] for [numberOfRepayments]
repaymentEvery: 1, // Repayment interval (e.g., every 1 month)
repaymentFrequencyType: 2, // Repayment frequency type (0=Days, 1=Weeks, 2=Months, 3=Years)
interestType: 0, // Interest type (0 = flat, 1 = declining balance)
amortizationType: 1, // Amortization type (0=Equal principle payments, 1=Equal installments) - Fetch this from loan product details in amortizationType.id
interestCalculationPeriodType: 1, // Interest calculation period type - Fetch this from loan product details in interestCalculationPeriodType.id
transactionProcessingStrategyCode: "mifos-standard-strategy", // Hardcoded to mifos-standard-strategy
interestRatePerPeriod: 1, // Interest rate per period -> 1 means 10%
dateFormat: "yyyy-MM-dd", // Hardcoded to yyyy-MM-dd - Date format used in the request
locale: "en", // Locale
clientId: 1, // // get the clientId with fineractSDK.client.getClientDetails(externalId) API
loanType: "individual", // Type of loan
principal: 100000, // Principal amount
});
console.log("Loan Created:", JSON.stringify(newLoan, null, 2));
```
- **API Path Used:** `/fineract-provider/api/v1/loans`
- **Reference:** See [`src/api/fineract-loansaccounts-api.ts`](src/api/fineract-loansaccounts-api.ts) and [`src/types/loansaccounts.ts`](src/types/loansaccounts.ts) for full request/response details.
> **Note:** This API will create a loan account in Fineract. Make sure all required fields are provided and valid as per your Fineract instance configuration.
#### 3. Approve Loan Account (Loan Accounts API)
- **API:** `fineractSDK.loansaccounts.approve()`
- **Description:** Approve a loan account in Fineract. This moves the loan from "Submitted and pending approval" status to "Approved" status, making it ready for disbursement.
- **Example:**
```typescript
const approvalResponse = await fineractSDK.loansaccounts.approve(loanId, {
approvedOnDate: "2024-07-30", // Date when the loan is approved
expectedDisbursementDate: "2024-08-01", // Expected date for disbursement
approvedLoanAmount: 100000, // Approved loan amount (can be same or different from requested)
note: "Loan approved after credit assessment", // Optional approval note
dateFormat: "yyyy-MM-dd", // Hardcoded to yyyy-MM-dd
locale: "en", // Locale
});
console.log("Loan Approved:", JSON.stringify(approvalResponse, null, 2));
```
- **API Path Used:** `/fineract-provider/api/v1/loans/{loanId}?command=approve`
- **Reference:** See [`src/api/fineract-loansaccounts-api.ts`](src/api/fineract-loansaccounts-api.ts) and [`src/types/loansaccounts.ts`](src/types/loansaccounts.ts) for full request/response details.
> **Note:** The loan must be in "Submitted and pending approval" status before it can be approved. After approval, the loan will be ready for disbursement.
### How to get loan application data from Fineract to show on SME dashboard
- **API:** `fineractSDK.loanaccounts.getSummaryById()`
- **Description:** Get loan summary information from Fineract
- **Example:**
```typescript
const loan = await fineractSDK.loanaccounts.getSummaryById(123);
console.log("Loan:", JSON.stringify(loan, null, 2));
const totalDisbursedAmount = loan.netDisbursalAmount;
const totalOutstandingAmount = loan.summary.totalOutstanding;
const interestRate = loan.annualInterestRate;
const duration = loan.termFrequency;
const durationUnit = loan.termPeriodFrequencyType.value;
// To get next due payment
const [year, month, day] = loan.delinquent.nextPaymentDueDate;
```
This will return you the information of the loan summary you need to show on SME dashboard for a given loan application.
### How to get loan application schedule from Fineract to show on SME dashboard per loan application
- **API:** `fineractSDK.loanaccounts.getRepaymentScheduleById()`
- **Description:** Get loan repayment schedule information from Fineract
- **Example:**
```typescript
const loan = await fineractSDK.loanaccounts.getRepaymentScheduleById(123);
console.log("Loan:", JSON.stringify(loan, null, 2));
const loanAmount = loan.approvedPrincipal;
const totalOutstandingAmount = loan.summary.totalOutstanding;
const interestRate = loan.annualInterestRate;
const duration = loan.termFrequency;
const durationUnit = loan.termPeriodFrequencyType.value;
// To get Issued on date
const [year, month, day] = loan.timeline.actualDisbursementDate;
// Repayment periods, first item in the array is always the disbursement information which we ignore
const schedule = loan.repaymentSchedule.periods;
// Follow this for all items, except for the index 0
const amount = schedule[1].totalInstallmentAmountForPeriod;
const [dueYear, dueMonth, dueDay] = schedule[1].dueDate;
const remaining = schedule[1].totalOutstandingForPeriod;
const status = schedule[1].status;
```
This will return you the information of the loan summary along with repayment schedule
### How to create a deposit transaction for a savings account
- **API:** `fineractSDK.savingsaccounts.deposit()`
- **Description:** Create a deposit transaction for a specific savings account in Fineract. This allows you to add funds to an active savings account.
- **Example:**
```typescript
const depositResponse = await fineractSDK.savingsaccounts.deposit(3873, {
transactionDate: "11 August 2025",
transactionAmount: 1000,
paymentTypeId: 1,
note: "this is 1000 deposit",
dateFormat: "dd MMMM yyyy",
locale: "en",
});
console.log("Deposit Response:", JSON.stringify(depositResponse, null, 2));
// Access response data
const officeId = depositResponse.officeId;
const clientId = depositResponse.clientId;
const savingsId = depositResponse.savingsId;
const transactionId = depositResponse.resourceId;
const paymentTypeId = depositResponse.changes.paymentTypeId;
```
- **API Path Used:** `/fineract-provider/api/v1/savingsaccounts/{accountId}/transactions?command=deposit`
- **Required Fields:**
- `transactionDate`: Date of the transaction (string)
- `transactionAmount`: Amount to deposit (number)
- `paymentTypeId`: Payment type identifier (number)
- `dateFormat`: Date format used (string)
- `locale`: Locale for formatting (string)
- **Optional Fields:**
- `note`: Description or note for the transaction (string)
> **Note:** The savings account must be in "Active" status before deposits can be made. Use the payment type IDs available in your Fineract instance.
### How to get transaction template for a savings account
- **API:** `fineractSDK.savingsaccounts.getTransactionTemplate()`
- **Description:** Retrieve the transaction template for a savings account, which provides information about available payment types, currency details, and account information needed to create transactions.
- **Example:**
```typescript
const template = await fineractSDK.savingsaccounts.getTransactionTemplate(3);
console.log("Transaction Template:", JSON.stringify(template, null, 2));
// Access template data
const accountId = template.accountId;
const accountNo = template.accountNo;
const currency = template.currency;
const paymentTypes = template.paymentTypeOptions;
// Display currency information
console.log(`Currency: ${currency.displayLabel}`); // e.g., "US Dollar ($)"
console.log(`Currency Code: ${currency.code}`); // e.g., "USD"
console.log(`Decimal Places: ${currency.decimalPlaces}`); // e.g., 2
// List available payment types
console.log("Available Payment Types:");
paymentTypes.forEach((paymentType) => {
console.log(`- ID: ${paymentType.id}, Name: ${paymentType.name}`);
console.log(` Description: ${paymentType.description}`);
console.log(` Cash Payment: ${paymentType.isCashPayment}`);
console.log(` System Defined: ${paymentType.isSystemDefined}`);
});
// Get current date information (useful for transaction dates)
const [year, month, day] = template.date;
const currentDate = new Date(year, month - 1, day); // Note: month is 0-indexed in JavaScript Date
```
- **API Path Used:** `/fineract-provider/api/v1/savingsaccounts/{accountId}/transactions/template`
- **Returns:**
- Account information (ID, account number)
- Currency details (code, name, symbol, decimal places)
- Available payment type options
- Current date and submission date information
- Transaction flags (reversed, manual transaction, etc.)
> **Note:** Use this endpoint to get the required information before creating deposit or withdrawal transactions. The payment type IDs returned here should be used in transaction requests.
### How to get holidays information
- **API:** `fineractSDK.holidays.getHolidays()` / `fineractSDK.holidays.getHolidaysByOffice()`
- **Description:** Retrieve holidays from Fineract. This can be used to check for holidays when calculating loan repayment schedules or transaction dates. Only returns holidays with active status.
- **Example:**
```typescript
// Get all active holidays
const allHolidays = await fineractSDK.holidays.getHolidays();
console.log("All Holidays:", JSON.stringify(allHolidays, null, 2));
// Get active holidays for a specific office
const officeHolidays = await fineractSDK.holidays.getHolidaysByOffice(1);
console.log("Office Holidays:", JSON.stringify(officeHolidays, null, 2));
// Process holiday information
officeHolidays.forEach((holiday) => {
const fromDate = `${holiday.fromDate[0]}-${holiday.fromDate[1]
.toString()
.padStart(2, "0")}-${holiday.fromDate[2].toString().padStart(2, "0")}`;
const toDate = `${holiday.toDate[0]}-${holiday.toDate[1]
.toString()
.padStart(2, "0")}-${holiday.toDate[2].toString().padStart(2, "0")}`;
const rescheduleTo = `${
holiday.repaymentsRescheduledTo[0]
}-${holiday.repaymentsRescheduledTo[1]
.toString()
.padStart(2, "0")}-${holiday.repaymentsRescheduledTo[2]
.toString()
.padStart(2, "0")}`;
console.log(`Holiday: ${holiday.name}`);
console.log(` From: ${fromDate} to ${toDate}`);
console.log(` Status: ${holiday.status.value}`);
console.log(` Repayments rescheduled to: ${rescheduleTo}`);
console.log(` Rescheduling Type: ${holiday.reschedulingType}`);
});
```
- **API Path Used:** `/fineract-provider/api/v1/holidays` (with optional `?officeId=` parameter)
- **Returns:**
- Array of holiday objects with the following properties:
- `id`: Holiday ID
- `name`: Holiday name
- `description`: Holiday description
- `fromDate`: Start date as `[year, month, day]` array
- `toDate`: End date as `[year, month, day]` array
- `repaymentsRescheduledTo`: Rescheduled date as `[year, month, day]` array
- `status`: Status object with `id`, `code`, and `value`
- `reschedulingType`: Type of rescheduling (numeric)
> **Note:** This API automatically filters to only return holidays with `status.code === "holidayStatusType.active"`. Use this information to determine if a date falls on a holiday and when repayments should be rescheduled to.
### How to get working days information
- **API:** `fineractSDK.workingdays.getWorkingDays()`
- **Description:** Retrieve the working days configuration from Fineract. This returns a formatted object indicating which days of the week are considered working days. This information is useful for calculating business days, scheduling loan repayments, and determining when transactions can be processed.
- **Example:**
```typescript
// Get working days configuration
const workingDays = await fineractSDK.workingdays.getWorkingDays();
console.log("Working Days:", JSON.stringify(workingDays, null, 2));
// Example output:
// {
// "Monday": true,
// "Tuesday": true,
// "Wednesday": true,
// "Thursday": true,
// "Friday": true,
// "Saturday": false,
// "Sunday": false
// }
// Check if a specific day is a working day
if (workingDays.Monday) {
console.log("Monday is a working day");
}
// Get list of working days
const workingDayNames = Object.entries(workingDays)
.filter(([day, isWorking]) => isWorking)
.map(([day]) => day);
console.log("Working days:", workingDayNames.join(", "));
// Get list of non-working days (weekends)
const nonWorkingDays = Object.entries(workingDays)
.filter(([day, isWorking]) => !isWorking)
.map(([day]) => day);
console.log("Non-working days:", nonWorkingDays.join(", "));
```
- **API Path Used:** `/fineract-provider/api/v1/workingdays`
- **Returns:**
- Object with day names as keys and boolean values indicating if it's a working day:
- `Monday`: boolean
- `Tuesday`: boolean
- `Wednesday`: boolean
- `Thursday`: boolean
- `Friday`: boolean
- `Saturday`: boolean
- `Sunday`: boolean
> **Note:** This API automatically parses the RRULE recurrence string from Fineract and converts it to a user-friendly format. The original response contains technical details like repayment rescheduling options, but this API focuses on the essential working days information.
### How to get available charges
- **API:** `fineractSDK.creditlines.getCharges()` / `fineractSDK.creditlines.getCreditLineCharges()`
- **Description:** Retrieve available charges from Fineract that can be applied to credit lines. This helps users discover which charges are available before creating a credit line.
- **Example:**
```typescript
// Get all available charges
const allCharges = await fineractSDK.creditlines.getCharges();
console.log("All Charges:", JSON.stringify(allCharges, null, 2));
// Get only charges applicable to Line of Credit and Loans
const creditLineCharges =
await fineractSDK.creditlines.getCreditLineCharges();
console.log(
"Credit Line Charges:",
JSON.stringify(creditLineCharges, null, 2)
);
// Filter charges with parameters
const activeCharges = await fineractSDK.creditlines.getCharges({
active: true,
chargeAppliesTo: "lineOfCredit",
});
// Process charges to show available options
creditLineCharges.forEach((charge) => {
console.log(`Charge: ${charge.name}`);
console.log(` ID: ${charge.id}`);
console.log(` Amount: ${charge.amount} ${charge.currency.code}`);
console.log(` Type: ${charge.chargeCalculationType.value}`);
console.log(` Applies to: ${charge.chargeAppliesTo.value}`);
console.log(` Time: ${charge.chargeTimeType.value}`);
console.log(` Active: ${charge.active}`);
console.log(` Penalty: ${charge.penalty}`);
if (charge.taxGroup) {
console.log(` Tax Group: ${charge.taxGroup.name}`);
}
console.log("---");
});
// Select charges for credit line creation
const selectedCharges = creditLineCharges
.filter(
(charge) => charge.chargeAppliesTo.code === "chargeAppliesTo.lineOfCredit"
)
.map((charge) => ({
...charge,
editableAmount: charge.amount, // Set editable amount
}));
```
- **API Path Used:** `/fineract-provider/api/v1/charges`
- **Available Methods:**
- `getCharges(params?)`: Get all charges with optional filtering
- `getCreditLineCharges()`: Get charges applicable to Line of Credit and Loans only
- **Optional Parameters:**
- `chargeAppliesTo`: Filter by what the charge applies to ('loan', 'savings', 'lineOfCredit')
- `active`: Filter by active status (boolean)
- **Returns:**
- Array of charge objects with detailed information including:
- Basic info: `id`, `name`, `active`, `penalty`
- Financial: `amount`, `currency`, `chargeCalculationType`
- Application: `chargeAppliesTo`, `chargeTimeType`, `chargePaymentMode`
- Tax: `taxGroup` (optional)
- Fees: `feeInterval`, `feeFrequency` (for recurring charges)
> **Note:** Use `getCreditLineCharges()` to get pre-filtered charges suitable for credit lines. The charges returned can be directly used in the `CreateCreditLineRequest.charges` array after adding the `editableAmount` property.
### How to get credit line template options
- **API:** `fineractSDK.creditlines.getCreditLineTemplate()` and individual option getters
- **Description:** Retrieve pre-defined template options for credit line creation. This provides deterministic values for fields like cash margin type, product type, review periods, etc., ensuring you use valid values that exist in the system.
- **Example:**
```typescript
const clientId = 6029; // Replace with actual client ID
// Get the complete template with all options
const template = await fineractSDK.creditlines.getCreditLineTemplate(
clientId
);
console.log("Full template:", JSON.stringify(template, null, 2));
// Or get specific options individually
const cashMarginTypeOptions =
await fineractSDK.creditlines.getCreditLineCashMarginTypeOptions(clientId);
const productTypeOptions =
await fineractSDK.creditlines.getCreditLineProductTypeOptions(clientId);
const reviewPeriodOptions =
await fineractSDK.creditlines.getCreditLineReviewPeriodOptions(clientId);
const interestChargeTimeOptions =
await fineractSDK.creditlines.getCreditLineInterestChargeTimeOptions(
clientId
);
const loanOfficers = await fineractSDK.creditlines.getCreditLineLoanOfficers(
clientId
);
// Display available options
console.log("Cash Margin Type Options:");
cashMarginTypeOptions.forEach((option) => {
console.log(
`- ID: ${option.id}, Code: ${option.code}, Value: ${option.value}`
);
});
// Example output:
// - ID: 1, Code: PERCENTAGE, Value: Percentage
// - ID: 2, Code: FLAT, Value: Flat
// Select options deterministically for credit line creation
const selectedCashMarginType = cashMarginTypeOptions.find(
(option) => option.code === "PERCENTAGE"
);
const selectedProductType = productTypeOptions.find(
(option) => option.code === "RECEIVABLE"
);
const selectedReviewPeriod = reviewPeriodOptions.find(
(option) => option.code === "locreviewperiod.6months"
);
const selectedLoanOfficer = loanOfficers.find(
(officer) => officer.isActive && officer.isLoanOfficer
);
// Use the selected IDs in your credit line creation request
const creditLineData = {
productType: selectedProductType?.id || 1,
cashMarginType: selectedCashMarginType?.id || 1,
reviewPeriod: selectedReviewPeriod?.id || 6,
loanOfficerId: selectedLoanOfficer?.id || 1,
// ... other fields
};
```
- **API Path Used:** `/fineract-provider/api/v1/clients/{clientId}/creditlines/template`
- **Available Methods:**
- `getCreditLineTemplate(clientId)`: Get complete template with all options
- `getCreditLineStatusOptions(clientId)`: Get status options only
- `getCreditLineProductTypeOptions(clientId)`: Get product type options only
- `getCreditLineReviewPeriodOptions(clientId)`: Get review period options only
- `getCreditLineCashMarginTypeOptions(clientId)`: Get cash margin type options only
- `getCreditLineInterestChargeTimeOptions(clientId)`: Get interest charge time options only
- `getCreditLineLoanOfficers(clientId)`: Get available loan officers only
- **Returns:**
- Complete template object with all available options
- Individual methods return specific option arrays
- Each option contains: `id`, `code`, `value`
- Loan officers include additional details: `displayName`, `officeId`, `officeName`, etc.
> **Best Practice:** Always use the template endpoints to get valid option values before creating credit lines. This ensures you're using IDs that exist in the system and prevents validation errors. The template approach makes credit line creation deterministic and reduces API failures.
### How to create a credit line
- **API:** `fineractSDK.creditlines.createCreditLine()`
- **Description:** Create a new credit line for a client in Fineract. This API handles the creation of Line of Credit (LOC) facilities with comprehensive configuration including charges, approved buyers, and settlement accounts. The recommended approach is to use template APIs to get deterministic values.
- **Example:**
```typescript
import { CreateCreditLineRequest } from "@hayeeder/fineract-api-client";
const clientId = 6029; // Replace with actual client ID
// Step 1: Get template options for deterministic value selection
const cashMarginTypeOptions =
await fineractSDK.creditlines.getCreditLineCashMarginTypeOptions(clientId);
const productTypeOptions =
await fineractSDK.creditlines.getCreditLineProductTypeOptions(clientId);
const reviewPeriodOptions =
await fineractSDK.creditlines.getCreditLineReviewPeriodOptions(clientId);
const interestChargeTimeOptions =
await fineractSDK.creditlines.getCreditLineInterestChargeTimeOptions(
clientId
);
const loanOfficers = await fineractSDK.creditlines.getCreditLineLoanOfficers(
clientId
);
// Step 2: Get available charges
const availableCharges = await fineractSDK.creditlines.getCreditLineCharges();
// Step 3: Select values based on your business requirements
const selectedProductType =
productTypeOptions.find(
(option) => option.code === "RECEIVABLE" || option.id === 1
) || productTypeOptions[0];
const selectedCashMarginType =
cashMarginTypeOptions.find(
(option) => option.code === "PERCENTAGE" || option.id === 1
) || cashMarginTypeOptions[0];
const selectedReviewPeriod =
reviewPeriodOptions.find(
(option) => option.code === "locreviewperiod.6months" || option.id === 6
) || reviewPeriodOptions[0];
const selectedInterestChargeTime =
interestChargeTimeOptions.find(
(option) => option.code === "UPFRONT" || option.id === 1
) || interestChargeTimeOptions[0];
const selectedLoanOfficer =
loanOfficers.find(
(officer) => officer.isActive && officer.isLoanOfficer && officer.id === 1
) ||
loanOfficers.find((officer) => officer.isActive && officer.isLoanOfficer);
const selectedCharges = availableCharges
.filter(
(charge) =>
charge.active &&
charge.chargeTimeType.code === "chargeTimeType.lineOfCreditActivation"
)
.slice(0, 2)
.map((charge) => ({
...charge,
editableAmount: charge.amount,
}));
// Step 4: Create credit line with template-derived values
const creditLineData: CreateCreditLineRequest = {
productType: selectedProductType?.id || 1,
currencyCode: "AED",
clientCompanyName: "Test Company Ltd",
clientContactPersonName: "John Doe",
clientContactPersonPhone: "0501234567",
clientContactPersonEmail: "john.doe@testcompany.com",
authorizedSignatoryName: "Jane Smith",
authorizedSignatoryPhone: "0507654321",
authorizedSignatoryEmail: "jane.smith@testcompany.com",
virtualAccount: "VA2000",
externalId: "CL-TEST-001",
specialConditions: "Test credit line with standard terms",
maxCreditLimit: "1000000",
reviewPeriod: selectedReviewPeriod?.id || 6,
interimReviewDate: "15 June 2026",
annualInterestRate: 1200, // 12% in basis points
tenorDays: 365,
advancePercentage: "85",
cashMarginType: selectedCashMarginType?.id || 1,
cashMarginValue: 15,
interestChargeTime: selectedInterestChargeTime?.id || 1,
loanOfficerId: selectedLoanOfficer?.id || 1,
distributionPartner: "Test Distribution Partner",
approvedBuyers: [
{ name: "Approved Buyer One" },
{ name: "Approved Buyer Two" },
],
settlementSavingsAccountId: 7135,
charges: selectedCharges, // Use template-derived charges
dateFormat: "dd MMMM yyyy",
locale: "en",
};
// Step 5: Create the credit line
const response = await fineractSDK.creditlines.createCreditLine(
clientId,
creditLineData
);
console.log("Credit Line Created:", JSON.stringify(response, null, 2));
console.log("Credit Line Resource ID:", response.resourceId);
```
- **API Path Used:** `/fineract-provider/api/v1/clients/{clientId}/creditlines`
- **Parameters:**
- `clientId`: The ID of the client to create the credit line for
- `creditLineData`: Comprehensive credit line configuration object
- **Required Fields:**
- Basic Info: `productType`, `currencyCode`, `clientCompanyName`
- Contact Details: `clientContactPersonName`, `clientContactPersonPhone`, `clientContactPersonEmail`
- Signatory: `authorizedSignatoryName`, `authorizedSignatoryPhone`, `authorizedSignatoryEmail`
- Financial: `maxCreditLimit`, `annualInterestRate`, `settlementSavingsAccountId`
- System: `externalId`, `virtualAccount`, `dateFormat`, `locale`
> **Best Practice Workflow:**
>
> 1. **Get Template Options**: Use template APIs to retrieve valid option values
> 2. **Get Available Charges**: Use charges API to get applicable charges
> 3. **Filter and Select**: Choose values based on your business logic with fallbacks
> 4. **Create with Confidence**: Use the selected IDs in your credit line creation request
> **Why This Approach:** This method ensures you're using valid, current system values, prevents validation errors, and makes your credit line creation deterministic and reliable.
> **Template-Based Value Selection Benefits:**
>
> - **Deterministic**: No guessing of field values or IDs
> - **Validation-Safe**: Uses only valid system values
> - **Future-Proof**: Adapts to changes in your Fineract configuration
> - **Error-Reduced**: Eliminates common field validation failures
> - **Business-Aligned**: Select values based on your business logic
> **Note:** This API creates a comprehensive Line of Credit facility with all associated charges and configurations. The template-based approach ensures all field values are valid for your Fineract instance.
### Retrieving Credit Lines
Get all credit lines for a client, including associated loan details:
- **API:** `fineractSDK.creditlines.getCreditLines(clientId)`
```typescript
// Get all credit lines for a client
const clientId = 123;
const creditLines = await fineractSDK.creditlines.getCreditLines(clientId);
console.log(`Found ${creditLines.length} credit line(s)`);
// Process each credit line
for (const creditLineWithLoans of creditLines) {
const creditLine = creditLineWithLoans.lineOfCredit;
console.log(`Credit Line ID: ${creditLine.id}`);
console.log(`Account Number: ${creditLine.accountNumber}`);
console.log(`Maximum Amount: ${creditLine.maximumAmount}`);
console.log(`Available Balance: ${creditLine.availableBalance}`);
console.log(`Consumed Amount: ${creditLine.consumedAmount}`);
console.log(`Status: ${creditLine.status.value}`);
console.log(`Product Type: ${creditLine.productType}`);
// Process associated loans
if (creditLineWithLoans.loans && creditLineWithLoans.loans.length > 0) {
console.log(`Associated Loans (${creditLineWithLoans.loans.length}):`);
for (const loan of creditLineWithLoans.loans) {
console.log(` Loan #${loan.accountNo} - ${loan.productName}`);
console.log(` Status: ${loan.status.value}`);
console.log(` Loan Balance: ${loan.loanBalance || 0}`);
console.log(` Amount Paid: ${loan.amountPaid || 0}`);
console.log(` In Arrears: ${loan.inArrears ? "Yes" : "No"}`);
// Timeline information
if (loan.timeline) {
if (loan.timeline.submittedOnDate) {
console.log(
` Submitted: ${loan.timeline.submittedOnDate.join("-")}`
);
}
if (loan.timeline.approvedOnDate) {
console.log(
` Approved: ${loan.timeline.approvedOnDate.join("-")}`
);
}
if (loan.timeline.disbursementDate) {
console.log(
` Disbursed: ${loan.timeline.disbursementDate.join("-")}`
);
}
}
}
} else {
console.log("No associated loans.");
}
}
```
- **API Path Used:** `/fineract-provider/api/v1/clients/{clientId}/creditlines`
- **Parameters:**
- `clientId`: The ID of the client to retrieve credit lines for
- **Returns:** Array of `CreditLineWithLoans` objects containing:
- **Credit Line Info**: `id`, `accountNumber`, `productType`, `maximumAmount`, `availableBalance`, `consumedAmount`, `status`, `externalId`
- **Associated Loans**: Array of loan objects with account details, balances, status, and timeline information
- **Response Structure:**
```typescript
interface CreditLineWithLoans {
lineOfCredit: LineOfCredit;
loans: CreditLineLoan[];
}
```
> **Note:** This endpoint provides a comprehensive view of all credit lines and their associated loans, making it ideal for portfolio management and client relationship oversight.
## Development
### Run Reports Overview
The Fineract "run reports" API returns tabular datasets (headers + positional rows). This SDK exposes three curated report wrappers and returns a consumer-friendly mapped structure:
1. `getLoanPaymentsDue`
2. `getLoanPaymentsDueOverdue`
3. `getExpectedPaymentsByDateBasic`
Mapped response shape:
```typescript
interface MappedReportResponse {
data: Array<Record<string, any>>; // each row keyed by columnName
}
```
Key Points:
- Report names are matched exactly (case and spaces) and URL-encoded automatically.
- Rows are already mapped to objects; you can iterate `report.data` directly.
- Any date arrays like `[YYYY, M, D]` are still raw; convert if needed (kept unmodified for neutrality).
- Office and loan officer filters are fixed (see rationale below).
- Installment, overdue, and date range parameters let you tailor dataset size and analytical focus.
#### Parameter Rationale & Fixed Filters
The office filter is currently fixed to head office (id = 1) and the loan officer filter to all officers (value -1) because present usage is single-office and portfolio-wide. This keeps the surface small and avoids accidental under-filtering. If multi-office support becomes necessary later, these can be introduced as explicit method parameters in a versioned update.
#### Installment & Overdue Window Parameters
Some report parameters control which installments (scheduled repayments) or overdue windows are included:
- `fromInstallment` / `toInstallment`: Inclusive installment sequence bounds. Each repayment period is numbered starting at 1. Setting both to the same value isolates a single installment; widening the range lets you retrieve multiple sequential installments (e.g. 1–3 for the first three periods). Use these to:
- Reduce payload size for dashboard widgets (single next due installment).
- Segment near-term vs. mid-term expected cash flows (e.g. 1–2 vs. 3–6).
- Emulate paging by requesting adjacent windows (1–3, then 4–6).
- Target analysis of a problematic period by narrowing to its sequence number.
- `overdueFromDays` / `overdueToDays`: Inclusive day-overdue bounds (used in the overdue variant). They filter installments whose due date has passed by a number of days in the specified range (e.g. 1–30 days overdue). Use these to:
- Focus collection efforts on a specific aging bucket (e.g. 1–7 early delinquency vs. 30–60 moderate delinquency).
- Build aging tiers by running the report multiple times with different windows.
- Avoid pulling very old irrecoverable items unless explicitly needed.
Tips:
1. Start narrow (e.g. single installment or small overdue window) for fast UI loads; expand only when users request detail.
2. Wide ranges (large installment span or 1–365 overdue) increase latency and payload size—consider batching.
3. Combining installment and overdue ranges lets you zoom into a specific slice (e.g. installment 2 only, overdue 15–30 days).
4. If `fromInstallment > toInstallment` or `overdueFromDays > overdueToDays`, the underlying report will typically return empty results (or error depending on configuration).
> These parameters exist to let you shape result size and focus. Future filters (e.g., dynamic officer/office filtering) would work alongside them.
#### Date Range Parameters (Friendly Names)
- `startDate` / `endDate`: Inclusive due-date window for expected payments. Must match `dateFormat`.
- `locale` and `dateFormat`: these are required by Fineract to parse the dates.
---
### Loan Payments Due
- **API:** `fineractSDK.reports.getLoanPaymentsDue()`
- **Description:** Retrieves loans with installments in the specified installment sequence window. Response is mapped (each row keyed by column name).
- **Example:**
```typescript
import { LoanPaymentsDueParams } from "@hayeeder/fineract-api-client";
const params: LoanPaymentsDueParams = {
fromInstallment: 2, // Start from installment sequence 2 (inclusive)
toInstallment: 2, // End at installment sequence 2 (single period)
}; // officeId=1 & loanOfficerId=-1 auto-injected
const report = await fineractSDK.reports.getLoanPaymentsDue(params);
console.log(report.data[0]); // { loanId: 123, dueAmount: 500, ... }
```
> Notes:
>
> 1. Use `fromInstallment` / `toInstallment` to define an inclusive installment sequence range; keeping them equal isolates a single installment.
> 2. Response rows are already mapped to objects keyed by column name (no header parsing needed).
> 3. Office and loan officer filters are auto-applied; you don't need to supply them.
> 4. Larger installment ranges increase payload size and latency—prefer narrow windows for dashboards.
> 5. Some dates may appear as `[year, month, day]` arrays.
### Loan Payments Due (Overdue Loans)
- **API:** `fineractSDK.reports.getLoanPaymentsDueOverdue()`
- **Description:** Same as the base variant but constrained to a day-overdue window, returning only installments whose due date has passed within the specified overdue day range.
- **Example:**
```typescript
import { LoanPaymentsDueOverdueParams } from "@hayeeder/fineract-api-client";
const params: LoanPaymentsDueOverdueParams = {
fromInstallment: 1, // Start from installment sequence 1 (first due installment)
toInstallment: 1, // Single installment
overdueFromDays: 1, // Minimum days overdue (>=1 day past due)
overdueToDays: 30, // Maximum days overdue (<=30 days past due)
}; // officeId=1 & loanOfficerId=-1 auto-injected
const report = await fineractSDK.reports.getLoanPaymentsDueOverdue(params);
console.log(report.data[0]); // { loanId: 456, overdueDays: 7, ... }
```
> Notes:
>
> 1. Adds overdue day window via `overdueFromDays` / `overdueToDays` (inclusive). Setting both equal isolates a single aging bucket.
> 2. You can still narrow installments with `fromInsta