Skip to content

FINERACT-2455: Working Capital Loan product payment amount calculation strategy - Annual EIR - #6398

Draft
mariiaKraievska wants to merge 1 commit into
apache:developfrom
openMF:FINERACT-2455/wc-product-payment-amount-calculation-strategy-annual-eir
Draft

FINERACT-2455: Working Capital Loan product payment amount calculation strategy - Annual EIR#6398
mariiaKraievska wants to merge 1 commit into
apache:developfrom
openMF:FINERACT-2455/wc-product-payment-amount-calculation-strategy-annual-eir

Conversation

@mariiaKraievska

Copy link
Copy Markdown
Contributor

Description

Describe the changes made and why they were made. (Ignore if these details are present on the associated Apache Fineract JIRA ticket.)

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.
  • If merging this PR resolves a JIRA issue, I will mark that issue as resolved and set "Fix Version/s" appropriately.

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

@mariiaKraievska
mariiaKraievska force-pushed the FINERACT-2455/wc-product-payment-amount-calculation-strategy-annual-eir branch 3 times, most recently from 8b85fbe to 5a24ce5 Compare September 7, 2026 11:18
@adamsaghy

Copy link
Copy Markdown
Contributor

@mariiaKraievska Please rebase

@galovics galovics left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This directly overlaps with #6264 and #6343 on the same JIRA ticket - 6 of the same files touched, including a three-way constructor collision on ProjectedAmortizationScheduleModel where this PR adds a field called annualEir (the operator-supplied target) right as #6343 adds annualEffectiveInterestRate (the solved output) - near-identical names, opposite meanings, on the same class. This needs to be settled with the other two PR authors before any of the three goes further.

Two reachable NPEs, both blocking:

  • withDiscount() unconditionally dereferences totalPaymentVolume.getAmount(), but generateFromAnnualEir passes null for that field. regenerate() correctly branches on whether annualEir != null, but withDiscount() doesn't - and it's reached from a routine unearned-fee query (totalActualAmortizationWithDiscount) on any Annual EIR loan.
  • resolveAnnualEir in WorkingCapitalLoanAmortizationScheduleWriteServiceImpl is missing the same getLoanProduct() != null && getRelatedDetail() != null guard that its sibling method resolvePaymentAmountCalculationStrategy has right above it.

Also blocking: the new Liquibase part (0077_wc_payment_amount_calculation_strategy.xml) duplicates a changeset number already taken by 0077_wc_loan_recovery_payment.xml on develop. Needs a rebase and renumber.

On the math itself - dailyRateFromAnnualEir computes the deannualized rate via Math.pow(...).doubleValue() and discards the passed-in MathContext on the result, rather than reusing #6343's TvmFunctions.deannualize (which has an overflow-safe Newton-Raphson implementation of the identical formula in the same file). And the stored effectiveInterestRate for this strategy is the target rate, not the realized IRR of the actual (whole-cent-rounded) schedule - which will disagree with what a client recomputes from the emitted payment rows.

A few more worth addressing: validation is asymmetric between create and update (a TPV loan can have annualEir set via update with no strategy check, and vice versa for periodPaymentRate), and the loan-level annualEir override has no min/max bounds unlike its periodPaymentRate counterpart - a real pricing-control gap, not just incompleteness.

I know it's a draft, but given the collision with two other open PRs on the same fields, I'd rather this get coordinated before more work goes into any of the three.

Recommendation: CHANGES_REQUESTED

@mariiaKraievska
mariiaKraievska force-pushed the FINERACT-2455/wc-product-payment-amount-calculation-strategy-annual-eir branch from 5a24ce5 to 2ae28e5 Compare September 8, 2026 12:04
@mariiaKraievska
mariiaKraievska force-pushed the FINERACT-2455/wc-product-payment-amount-calculation-strategy-annual-eir branch from 2ae28e5 to e5601ec Compare September 8, 2026 12:38
@mariiaKraievska

Copy link
Copy Markdown
Contributor Author

This directly overlaps with #6264 and #6343 on the same JIRA ticket - 6 of the same files touched, including a three-way constructor collision on ProjectedAmortizationScheduleModel where this PR adds a field called annualEir (the operator-supplied target) right as #6343 adds annualEffectiveInterestRate (the solved output) - near-identical names, opposite meanings, on the same class. This needs to be settled with the other two PR authors before any of the three goes further.

Two reachable NPEs, both blocking:

  • withDiscount() unconditionally dereferences totalPaymentVolume.getAmount(), but generateFromAnnualEir passes null for that field. regenerate() correctly branches on whether annualEir != null, but withDiscount() doesn't - and it's reached from a routine unearned-fee query (totalActualAmortizationWithDiscount) on any Annual EIR loan.
  • resolveAnnualEir in WorkingCapitalLoanAmortizationScheduleWriteServiceImpl is missing the same getLoanProduct() != null && getRelatedDetail() != null guard that its sibling method resolvePaymentAmountCalculationStrategy has right above it.

Also blocking: the new Liquibase part (0077_wc_payment_amount_calculation_strategy.xml) duplicates a changeset number already taken by 0077_wc_loan_recovery_payment.xml on develop. Needs a rebase and renumber.

On the math itself - dailyRateFromAnnualEir computes the deannualized rate via Math.pow(...).doubleValue() and discards the passed-in MathContext on the result, rather than reusing #6343's TvmFunctions.deannualize (which has an overflow-safe Newton-Raphson implementation of the identical formula in the same file). And the stored effectiveInterestRate for this strategy is the target rate, not the realized IRR of the actual (whole-cent-rounded) schedule - which will disagree with what a client recomputes from the emitted payment rows.

A few more worth addressing: validation is asymmetric between create and update (a TPV loan can have annualEir set via update with no strategy check, and vice versa for periodPaymentRate), and the loan-level annualEir override has no min/max bounds unlike its periodPaymentRate counterpart - a real pricing-control gap, not just incompleteness.

I know it's a draft, but given the collision with two other open PRs on the same fields, I'd rather this get coordinated before more work goes into any of the three.

Recommendation: CHANGES_REQUESTED

@galovics Thanks for the review.

Naming #6343: Coordinated with the @oleksii-novikov-onix . annualEir here stays the operator target input for the ANNUAL_EIR strategy. #6343 will rename the solved output to calculatedAnnualEir, so the two won’t collide in meaning. File overlap with those PRs we’ll handle on rebase/merge order as usual.

Blocking / other notes: Addressed on this branch — withDiscount() Annual EIR branch, resolveAnnualEir null-guards, Liquibase renumber, TvmFunctions.deannualize for daily rate, effectiveInterestRate = realized IRR, create/update strategy gates, and loan-level annualEir min/max.

@adamsaghy

Copy link
Copy Markdown
Contributor

@mariiaKraievska Please rebase

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