adamsaghy commented on code in PR #6151:
URL: https://github.com/apache/fineract/pull/6151#discussion_r3620925217
##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.java:
##########
@@ -104,6 +114,85 @@ private ProjectedAmortizationScheduleModel
generateProjectedAmortizationSchedule
disbursementDate, mc,
WorkingCapitalLoanCurrencyResolver.resolveCurrency(loan),
DateUtils.getBusinessLocalDate());
}
+ /**
+ * Rebuilds a schedule model from scratch preserving the loan's
rate-change history: bases the model on the
+ * <strong>original</strong> period payment rate (the rate the schedule
was first generated at) and replays the
+ * non-reversed rate changes and the given principal payments
<strong>merged in date order</strong>. The merge
+ * matters because {@link
ProjectedAmortizationScheduleModel#applyRateChange} derives a segment's
balance/discount
+ * at the split from the payments received before it, so each rate change
must see the payments that precede its
+ * effective date. With no rate changes this is identical to a plain
generate-and-replay at the current rate.
+ */
+ @NonNull
+ private ProjectedAmortizationScheduleModel reconstructScheduleModel(final
WorkingCapitalLoan loan,
+ final List<PrincipalPayment> payments) {
+ final BigDecimal disbursedAmount = resolveActualDisbursedAmount(loan);
+ final LocalDate disbursementDate = resolveActualDisbursementDate(loan);
+ final ProjectedAmortizationScheduleModel model =
generateBaseModel(loan, disbursedAmount, disbursementDate,
+ resolveOriginalPeriodPaymentRate(loan));
+ replayRateChangesAndPayments(model, loan, payments);
+ return model;
+ }
+
+ /**
+ * The rate the schedule was originally generated at: the earliest rate
change records it as its
+ * {@code previousRate}; with no rate changes the loan's current rate is
the original.
+ */
+ private BigDecimal resolveOriginalPeriodPaymentRate(final
WorkingCapitalLoan loan) {
+ return
rateChangeRepository.findByWorkingCapitalLoanIdOrderByIdDesc(loan.getId()).stream()
+
.min(Comparator.comparing(WorkingCapitalLoanPeriodPaymentRateChange::getId))
+
.map(WorkingCapitalLoanPeriodPaymentRateChange::getPreviousRate)
+ .orElseGet(() ->
loan.getLoanProductRelatedDetails().getPeriodPaymentRate());
+ }
+
+ private void replayRateChangesAndPayments(final
ProjectedAmortizationScheduleModel model, final WorkingCapitalLoan loan,
+ final List<PrincipalPayment> payments) {
+ final Stream<ScheduleEvent> paymentEvents = payments.stream().filter(p
-> p.amount() != null && p.amount().signum() > 0)
+ .map(p -> new PaymentEvent(p.date(), p.amount()));
+ final Stream<ScheduleEvent> rateChangeEvents =
rateChangeRepository.findByWorkingCapitalLoanIdAndReversedFalse(loan.getId())
+
.stream().sorted(Comparator.comparing(WorkingCapitalLoanPeriodPaymentRateChange::getId))
+ .map(change -> new RateChangeEvent(change.getEffectiveDate(),
change.getNewRate()));
+
+ // Replay every dated action in date order (payments before a
same-date rate change, via replayOrder), so each
+ // rate change sees the payments that precede its split.
+ Stream.concat(paymentEvents, rateChangeEvents)
+
.sorted(Comparator.comparing(ScheduleEvent::date).thenComparingInt(ScheduleEvent::replayOrder)).forEach(event
-> {
+ switch (event) {
+ case PaymentEvent p -> model.applyPayment(p.date(),
p.amount());
+ case RateChangeEvent r ->
calculator.applyRateChange(model, r.newRate(), r.date());
+ }
+ });
+ }
+
+ /**
+ * A dated schedule modifier replayed during reconstruction. Sealed so the
dispatch in
+ * {@link #replayRateChangesAndPayments} stays exhaustive: adding a new
modifier type forces it to be handled there.
+ */
+ private sealed interface ScheduleEvent permits PaymentEvent,
RateChangeEvent {
+
+ LocalDate date();
+
+ /**
+ * Tie-break rank for a same-date collision: a payment settles before
a rate-change split (lower replays first).
+ */
+ int replayOrder();
+ }
+
+ private record PaymentEvent(LocalDate date, BigDecimal amount) implements
ScheduleEvent {
Review Comment:
On progressive loans we were tie breaking based on creation date time.
--
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]