adamsaghy commented on PR #6588:
URL: https://github.com/apache/fineract/pull/6588#issuecomment-6148497073

   @Cocoa-Puffs Please review the below:
   
   ## Review: FINERACT-2816 – monthEndDueDateStrategy
   
   ### 1. Disbursement / interest-charged-from date on the 1st is unrolled as 
if it had rolled forward
   `fineract-loan/.../loanschedule/domain/LoanApplicationTerms.java:1216`
   
   `calculatePeriodsBetweenDates` passes every start date through 
`unrollChainedDueDate`, so any start date that falls on the 1st is unrolled. 
That includes the disbursement date and the interest-charged-from date, which 
are never rolled.
   
   **Example:** `FIRST_DAY_OF_NEXT_MONTH`, `repaymentsStartingFromDate` = 
2025-03-31 (seed day 31), disbursement on 2025-03-01, cumulative loan with 
`SAME_AS_REPAYMENT_PERIOD` and `allowPartialPeriodInterestCalculation`.
   - `calculatePeriodsInLoanTerm` calls 
`calculatePeriodsBetweenDates(2025-03-01, 2025-08-31)`.
   - `unroll()` turns 03-01 into 02-28.
   - The result is 6.0 periods instead of about 5.97 (5 + 30/31).
   
   The same misreading in `calculateLoanTermFrequency` inflates first-period 
interest, and total flat interest is overstated. No due date was actually 
rolled.
   
   ### 2. Whether a date was rolled is guessed from it being a 1st
   
`fineract-loan/.../loanschedule/domain/DefaultScheduledDateGenerator.java:133`
   
   The cumulative path keeps chaining from the previous due date. It infers 
that a 1st was rolled when the seed day is greater than 28 and the previous 
month is too short. Only due-date variations are treated as exceptions, so any 
genuine 1st that enters the chain gets misread.
   
   **Example:** `ApplyHolidaysToLoansTasklet:180` calls 
`generateNextRepaymentDate(adjustedRescheduleToDate, …)`. With a holiday 
reschedule-to date of 1 March and seed day 31, the chain unrolls it to 28 Feb. 
The next due date becomes 31 Mar instead of 1 Apr.
   
   Issue #1 has the same root cause. Computing each due date as `seed + n × 
repayEvery` via `MonthlyDueDateResolver.plusMonths`, as the progressive paths 
already do, would remove the guessing.
   
   ### 3. Fixed-length max date rolls forward on calendar-linked loans
   `fineract-loan/.../loanschedule/domain/LoanApplicationTerms.java:2137`
   
   `calculateMaxDateForFixedLength` applies the strategy unconditionally. 
`generateNextRepaymentDate` sets the strategy to `null` for calendar-linked 
loans, so the two disagree.
   
   **Example:** calendar-linked monthly loan with `fixedLength` = 1 month, 
starting 31 Jan, with `FIRST_DAY_OF_NEXT_MONTH`.
   - The generated last due date is 28 Feb.
   - `calculateMaxDateForFixedLength` returns 1 Mar.
   - `generateRepaymentPeriods` / `getLastRepaymentDate` overwrite the last 
installment with 1 Mar, against the meeting calendar.
   
   ### 4. Calendar loans accept a strategy that is then ignored
   `fineract-provider/.../loanschedule/service/LoanScheduleAssembler.java:558`
   
   A loan-level `FIRST_DAY_OF_NEXT_MONTH` on a calendar-synced loan is only 
logged at debug level. The value is persisted on the loan and returned by `GET 
/loans/{id}`, but due-date generation ignores it. Only the fixed-length path 
from #3 uses it.
   
   Rejecting it in validation, or not storing it, would avoid misleading API 
clients. The `@Slf4j` and the debug log, added only for this, could then go.
   
   ### 5. Changing the product on a loan application keeps the old product's 
strategy
   `fineract-provider/.../loanaccount/service/LoanAssemblerImpl.java:576`
   
   When a loan application is modified to a different `productId`, 
`loanRepaymentScheduleDetail` keeps the previous product's 
`monthEndDueDateStrategy` unless the request also sends 
`monthEndDueDateStrategy`.
   
   **Example:** a loan is created under product A (`FIRST_DAY_OF_NEXT_MONTH`) 
and modified to product B (`LAST_DAY_OF_MONTH`) without the field. The schedule 
is regenerated from the loan's stored detail and still rolls forward.
   
   ### 6. The due-date variation scan runs before the strategy short-circuit 
(efficiency)
   `fineract-loan/.../loanschedule/domain/LoanApplicationTerms.java:1946`
   
   `unrollChainedDueDate` streams all due-date variations before 
`MonthlyDueDateResolver.unroll` checks `rollsForward`. Every existing loan 
(null or `LAST_DAY_OF_MONTH`) pays an O(N) scan on each 
`generateNextRepaymentDate` / `calculatePeriodsBetweenDates` call, and then the 
result is discarded.
   
   Check `MonthEndDueDateStrategy.rollsForward(...)` and `dayOfMonth == 1` 
first.
   
   ### 7. `plusPeriods` duplicates `getRepaymentPeriodDate(…, strategy)` (reuse)
   
`fineract-progressive-loan/.../AdvancedPaymentScheduleTransactionProcessor.java:469`
   
   The new private `plusPeriods` helpers in 
`AdvancedPaymentScheduleTransactionProcessor` and `ProgressiveEMICalculator` 
re-implement the new 4-arg `ScheduledDateGenerator.getRepaymentPeriodDate`: use 
the resolver for `MONTHS`, a plain `plus` otherwise. The processor already has 
`scheduledDateGenerator` in scope.
   
   With three copies of the rule, a later change has to be made in all three, 
or schedules diverge between re-age, due-date change and generation.
   
   ### 8. `validateMonthEndDueDateStrategy` is duplicated (reuse)
   
`fineract-provider/.../loanaccount/serialization/LoanApplicationValidator.java:823`
   
   `LoanApplicationValidator` and `LoanProductDataValidator` each add an 
identical private `validateMonthEndDueDateStrategy` (`parameterExists`, then 
`extractStringNamed`, then `isOneOfEnumValues`). Both copies have to be kept in 
sync, for example if the strategy gets restricted to monthly frequency or 
rejected for calendar loans.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to