Skip to content

Conversation

@nidhiii128
Copy link

Description

This project implements a robust API endpoint and service layer to retrieve and display performance-related data for a client within the Apache Fineract ecosystem. The implementation focuses on aggregating real-time financial metrics, including active loan counts and a comprehensive "Total Outstanding Balance" (Principal + Interest + Fees + Penalties) for active loan accounts.

Acceptance Criteria & Implementation Proof

  1. Integrate Client Profile Performance API with the app
    What I Did: Developed a RESTful endpoint GET /clients/{clientId}/performance within the ClientsApiResource class.
Screenshot 2026-01-28 212943 Proof: Successfully mapped the resource and confirmed accessibility via the terminal using curl.
  1. Fetch and display performance data for a client in the profile view
    What I Did: Implemented the retrieveClientPerformance method in the service layer to fetch real-time data from the database using an optimized SQL query.
Screenshot 2026-01-28 200929 Proof: The API successfully returns a JSON payload containing activeLoans and totalOutstandingBalance.
  1. Ensure data is updated in real-time or via scheduled sync
    What I Did: Utilized Fineract's JdbcTemplate to perform direct reads on the m_loan and m_client tables, ensuring the metrics reflect the current state of the database immediately upon request.
Screenshot 2026-01-28 211242 Proof: Manual verification through Swagger and database checks confirmed that balance changes are reflected as soon as loans are updated.
  1. Handle API failures gracefully with appropriate error messages
    What I Did: Implemented error handling to detect non-existent client IDs, throwing a ClientNotFoundException which results in a standard Fineract 404 error response.
Screenshot 2026-01-28 212445 Proof: Added logic to verify client existence before running the aggregation query, ensuring a clear error message is returned if the ID is invalid.
  1. Add unit tests and integration tests for the API layer
    What I Did: Created ClientPerformanceApiIntegrationTest.java using the RestAssured framework to automate the verification of the API.
Screenshot 2026-01-29 073849 ?[32m2026-01-29 02:06:36.116?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.i.common.ClientHelper?[0;39m - ---------------------------------CREATING A CLIENT--------------------------------------------- ?[32m2026-01-29 02:06:36.130?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.i.common.ClientHelper?[0;39m - TestClient Request : {"firstname":"Client_FirstName_Q528L","officeId":"1","dateFormat":"dd MMMM yyyy","externalId":"b92a1915-a58e-401e-96b4-45a089036319","active":"true","locale":"en","activationDate":"04 March 2011","legalFormId":1,"lastname":"Client_LastName_ILBP"} ?[32m2026-01-29 02:06:36.132?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.integrationtests.common.Utils?[0;39m - JSON {"firstname":"Client_FirstName_Q528L","officeId":"1","dateFormat":"dd MMMM yyyy","externalId":"b92a1915-a58e-401e-96b4-45a089036319","active":"true","locale":"en","activationDate":"04 March 2011","legalFormId":1,"lastname":"Client_LastName_ILBP"}

Proof: The test suite executed with a 100% success rate.

  1. Confirm data matches the API response structure
    What I Did: Defined a dedicated Data Transfer Object (DTO), ClientPerformanceData, to ensure the response structure is consistent and types match the expected financial precision.
    Supporting Evidence:
    [Image 1: DTO Structure]: ClientPerformanceData.java showing the defined fields.
image [Image 2: Test Assertions]: assertEquals logic in the Integration Test. image [Image 3: Test Success]: The green "100% Success" bar from terminal/Gradle report. Screenshot 2026-01-29 071856 Proof: The integration test specifically asserts the values of activeLoans and totalOutstandingBalance against the JSON response.

Technical Summary of Financial Logic

The balance is aggregated using the following formula for all loans with Status 300 (Active):
TotalOutstanding = Principal_{derived} + Interest_{derived} + Fees_{derived} + Penalties_{derived}
Using the _derived columns from the m_loan table ensures that we are using pre-calculated, high-performance data rather than re-calculating the entire schedule on every API call.

Checklist

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Write the commit message as per our guidelines
  • Acknowledge that we will not review PRs that are not passing the build ("green") - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.
  • Create/update unit or integration tests for verifying the changes made.
  • Follow our coding conventions.
  • Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes
  • This PR must not be a "code dump". Large changes can be made in a branch, with assistance. Ask for help on the developer mailing list.

Your assigned reviewer(s) will follow our guidelines for code reviews.

@nidhiii128 nidhiii128 changed the title MIFOSAC-587 : Client Profile Performance API Integration FINERACT-0000 MIFOSAC-587 : Client Profile Performance API Integration Jan 29, 2026
@IOhacker
Copy link
Contributor

@nidhiii128 Apache Fineract != Mifos please join the Apache Fineract list, involve with the Apache Fineract community. If you are interested please open an account in Jira, then review if the item has not been previously reported if not then you can open a new ticket https://selfserve.apache.org/jira-account.html Read and understand how to submit contibutions to Apache Fineract

Copy link
Contributor

@IOhacker IOhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly see my comments

@nidhiii128
Copy link
Author

@nidhiii128 Apache Fineract != Mifos please join the Apache Fineract list, involve with the Apache Fineract community. If you are interested please open an account in Jira, then review if the item has not been previously reported if not then you can open a new ticket https://selfserve.apache.org/jira-account.html Read and understand how to submit contibutions to Apache Fineract

Really sorry to misunderstand things, will understand more about the fineract project and help to contribute. Thank you for your the information.

@IOhacker
Copy link
Contributor

Don worry, your effort can be applied using the proper Jira ticket also Apache Fineract will be a GSOC participant.

@nidhiii128 nidhiii128 changed the title FINERACT-0000 MIFOSAC-587 : Client Profile Performance API Integration FINERACT-2460 : Client Profile Performance API Integration Jan 29, 2026
Copy link
Contributor

@IOhacker IOhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nidhiii128 the PR and the commit must follow the title conventions, please use "Fineract-2460: Add Client Performance API for real-time financial metrics"

Squash and commit your changes because only 1 commit per PR is required.

Why not to use the https://{URL_BASE}/fineract-provider/api/v1/clients/{CLIENT_ID}/accounts endpoint ?

@nidhiii128 nidhiii128 changed the title FINERACT-2460 : Client Profile Performance API Integration Fineract-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128 nidhiii128 changed the title Fineract-2460: Add Client Performance API for real-time financial metrics FINERACT-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128 nidhiii128 changed the title FINERACT-2460: Add Client Performance API for real-time financial metrics Fineract-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128
Copy link
Author

@nidhiii128 the PR and the commit must follow the title conventions, please use "Fineract-2460: Add Client Performance API for real-time financial metrics"

Squash and commit your changes because only 1 commit per PR is required.

Why not to use the https://{URL_BASE}/fineract-provider/api/v1/clients/{CLIENT_ID}/accounts endpoint ?

/accounts returns a large array of objects (full account summaries for every loan). my peformance endpoint returns exactly two numbers. Calculating the total on the database side (via SUM and COALESCE query) is significantly faster than fetching 50 loan objects and summing them in the Java frontend or mobile app. By returning exactly two numbers instead of a large object tree, we reduce the time-to-first-render for the Client Profile summary, which is critical for performance in low-bandwidth environments.

image

The endpoint returns groupLoanIndividualMonitoringAccounts and guarantorAccounts. If the user just wants to see their "Total Balance," they don't need to know about guarantor status. Even if these arrays were full of loans, the user would still be seeing 0.00$ for "Performance" because this endpoint doesn't sum up the totals for them.

@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve client performance metrics", description = "Returns a summary of active loans and outstanding balances for a specific client.")
@ApiResponses({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove ApiResponses anotation, It is not needed in newer java versions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only keep ApiResponse

final String sql = "SELECT " + "(SELECT COUNT(*) FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as activeLoans, "
+ "(SELECT COALESCE(SUM(principal_outstanding_derived + interest_outstanding_derived + fee_charges_outstanding_derived + penalty_charges_outstanding_derived), 0) "
+ " FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as totalOutstanding "
+ "FROM m_client c WHERE c.id = ?";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a pure string concatenaton java 15 + can use textblock as it willl make SQL more readable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the use of StringBuilder?

Copy link
Contributor

@Aman-Mittal Aman-Mittal Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringBuilder will help if the sql written here was dynamic but i can see there is a simple static string. In that case there is no performance gain using this. This will make it harder to review lateron

Copy link
Contributor

@Aman-Mittal Aman-Mittal Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like this will be much better as it will compile only once

`
final String sql = """
SELECT
(SELECT COUNT(*)
FROM m_loan
WHERE client_id = c.id
AND loan_status_id = 300) AS activeLoans,
(SELECT COALESCE(
SUM(principal_outstanding_derived
+ interest_outstanding_derived
+ fee_charges_outstanding_derived
+ penalty_charges_outstanding_derived), 0)
FROM m_loan
WHERE client_id = c.id
AND loan_status_id = 300) AS totalOutstanding
FROM m_client c
WHERE c.id = ?
""";

`

final String sql = "SELECT " + "(SELECT COUNT(*) FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as activeLoans, "
+ "(SELECT COALESCE(SUM(principal_outstanding_derived + interest_outstanding_derived + fee_charges_outstanding_derived + penalty_charges_outstanding_derived), 0) "
+ " FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as totalOutstanding "
+ "FROM m_client c WHERE c.id = ?";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the use of StringBuilder?

@nidhiii128
Copy link
Author

nidhiii128 commented Jan 30, 2026

@IOhacker, regarding the StringBuilder, as it is traditionally used in Fineract to manage multi-line SQL. However, I believe Java 15 Text Blocks are the more technically sound choice here.
Since this SQL query is a static constant and doesn't require conditional appending, the Java compiler internizes the Text Block into the String Constant Pool. Using StringBuilder would introduce unnecessary heap allocation and runtime .append() calls for a string that never changes.
While Text Blocks eliminate the 'missing space' risk often found in String concatenation ("LINE1"+"LINE2"). It allows us to keep the SQL formatting identical to the source database schema, which simplifies future debugging of the SUM and COALESCE logic and also given that Fineract has upgraded Text Blocks helps reduce boilerplate code.

@IOhacker
Copy link
Contributor

@nidhiii128 why use Sql and why not JPQL/JQL?

@nidhiii128
Copy link
Author

nidhiii128 commented Jan 31, 2026

@nidhiii128 why use Sql and why not JPQL/JQL?
@IOhacker sql used here to adhere to fineracts's cors arch, providing better performace for read only aggregations by avoiding JPA persistence context overhead and ensuring direct, optimized streaming into DTOs ( ClientPerformanceData.java).

@nidhiii128
Copy link
Author

@Aman-Mittal I have updated the ClientsApiResource.java to remove the redundant @ApiResponses wrappers for methods with a single response code. I've also converted the SQL query in ClientReadPlatformServiceImpl.java into a Java 15 Text Block for better readability and verified the output locally via Swagger/curl. Ready for review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants