5555import java .util .function .Function ;
5656import java .util .function .Predicate ;
5757import java .util .stream .Collectors ;
58+ import java .util .stream .IntStream ;
5859import java .util .stream .Stream ;
5960import lombok .AllArgsConstructor ;
6061import lombok .Getter ;
@@ -225,8 +226,7 @@ public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> repr
225226 .collect (Collectors .toCollection (ArrayList ::new ));
226227 final Integer installmentAmountInMultiplesOf = loan .getLoanProductRelatedDetail ().getInstallmentAmountInMultiplesOf ();
227228 ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator .generateInstallmentInterestScheduleModel (installments ,
228- LoanConfigurationDetailsMapper .map (loan ), loanTermVariations , installmentAmountInMultiplesOf ,
229- overpaymentHolder .getMoneyObject ().getMc ());
229+ LoanConfigurationDetailsMapper .map (loan ), installmentAmountInMultiplesOf , overpaymentHolder .getMoneyObject ().getMc ());
230230 ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx (currency , installments , charges , overpaymentHolder ,
231231 changedTransactionDetail , scheduleModel );
232232
@@ -236,8 +236,8 @@ public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> repr
236236 List <LoanTransaction > overpaidTransactions = new ArrayList <>();
237237 for (final ChangeOperation changeOperation : changeOperations ) {
238238 if (changeOperation .isLoanTermVariationsData ()) {
239- final LoanTermVariationsData interestRateChange = changeOperation .getLoanTermVariationsData ().get ();
240- processLoanTermVariation (installments , interestRateChange , scheduleModel );
239+ final LoanTermVariationsData termVariationsData = changeOperation .getLoanTermVariationsData ().get ();
240+ processLoanTermVariation (installments , termVariationsData , scheduleModel );
241241 } else if (changeOperation .isTransaction ()) {
242242 LoanTransaction transaction = changeOperation .getLoanTransaction ().get ();
243243 if (loan .getStatus ().isOverpaid () && transaction .isAccrualActivity ()) {
@@ -309,10 +309,92 @@ private void processLoanTermVariation(final List<LoanRepaymentScheduleInstallmen
309309 case INTEREST_PAUSE -> handleInterestPause (installments , termVariationsData , scheduleModel );
310310 case INTEREST_RATE_FROM_INSTALLMENT -> handleChangeInterestRate (installments , termVariationsData , scheduleModel );
311311 case EXTEND_REPAYMENT_PERIOD -> handleExtraRepaymentPeriod (installments , termVariationsData , scheduleModel );
312+ case DUE_DATE -> handleDueDateChangeOnRepaymentPeriod (installments , termVariationsData , scheduleModel );
312313 default -> throw new IllegalStateException ("Unhandled LoanTermVariationType." );
313314 }
314315 }
315316
317+ private void handleDueDateChangeOnRepaymentPeriod (final List <LoanRepaymentScheduleInstallment > installments ,
318+ final LoanTermVariationsData termVariationsData , final ProgressiveLoanInterestScheduleModel scheduleModel ) {
319+ final LocalDate targetRepaymentPeriodDueDate = termVariationsData .getTermVariationApplicableFrom ();
320+ final LocalDate newDueDate = termVariationsData .getDateValue ();
321+ final Loan loan = installments .getFirst ().getLoan ();
322+ final LoanApplicationTerms loanApplicationTerms = new LoanApplicationTerms .Builder () //
323+ .currency (loan .getCurrency ().toData ()) //
324+ .repaymentEvery (loan .getLoanProductRelatedDetail ().getRepayEvery ()) //
325+ .repaymentPeriodFrequencyType (loan .getLoanProductRelatedDetail ().getRepaymentPeriodFrequencyType ()) //
326+ .fixedLength (loan .getLoanProductRelatedDetail ().getFixedLength ()) //
327+ .seedDate (newDueDate ) //
328+ .build ();
329+ emiCalculator .changeDueDate (scheduleModel , loanApplicationTerms , targetRepaymentPeriodDueDate , newDueDate );
330+
331+ IntStream .range (0 , installments .size ()).filter (i -> installments .get (i ).getDueDate ().equals (targetRepaymentPeriodDueDate ))
332+ .findFirst ().ifPresent (targetInstallmentIndex -> {
333+ long scheduleModelStartIndex = installments .subList (0 , targetInstallmentIndex ).stream ()
334+ .filter (inst -> !inst .isDownPayment () && !inst .isAdditional ()).count ();
335+
336+ for (int i = targetInstallmentIndex ; i < installments .size (); i ++) {
337+ final LoanRepaymentScheduleInstallment installment = installments .get (i );
338+ if (installment .isDownPayment () || installment .isAdditional ()) {
339+ continue ;
340+ }
341+ if (scheduleModelStartIndex >= scheduleModel .repaymentPeriods ().size ()) {
342+ break ;
343+ }
344+
345+ final RepaymentPeriod repaymentPeriod = scheduleModel .repaymentPeriods ().get ((int ) scheduleModelStartIndex );
346+
347+ if (isNotObligationsMet (installment )) {
348+ installment .updateFromDate (repaymentPeriod .getFromDate ());
349+ installment .updateDueDate (repaymentPeriod .getDueDate ());
350+ installment .updatePrincipal (repaymentPeriod .getDuePrincipal ().getAmount ());
351+ installment .updateInterestCharged (repaymentPeriod .getDueInterest ().getAmount ());
352+ }
353+
354+ scheduleModelStartIndex ++;
355+ }
356+ });
357+
358+ mergeAdditionalInstallmentsBeforeMaturityDate (installments , scheduleModel , loan );
359+
360+ installments .sort (Comparator .comparing (LoanRepaymentScheduleInstallment ::getDueDate ));
361+ int installmentNumber = 1 ;
362+ for (LoanRepaymentScheduleInstallment installment : installments ) {
363+ installment .updateInstallmentNumber (installmentNumber ++);
364+ }
365+ }
366+
367+ private void mergeAdditionalInstallmentsBeforeMaturityDate (final List <LoanRepaymentScheduleInstallment > installments ,
368+ final ProgressiveLoanInterestScheduleModel scheduleModel , final Loan loan ) {
369+ final LocalDate newMaturityDate = scheduleModel .repaymentPeriods ().getLast ().getDueDate ();
370+
371+ final Optional <LoanRepaymentScheduleInstallment > lastRegularInstallmentOptional = installments .stream () //
372+ .filter (i -> !i .isDownPayment () && !i .isAdditional () && !i .isReAged ()) //
373+ .reduce ((first , second ) -> second );
374+
375+ lastRegularInstallmentOptional .ifPresent (lastRegularInstallment -> {
376+ final MonetaryCurrency currency = loan .getCurrency ();
377+ installments .stream () //
378+ .filter (i -> i .isAdditional () && i .getDueDate () != null && i .getDueDate ().isBefore (newMaturityDate ))
379+ .forEach (additionalInstallment -> {
380+ final Money mergedFees = lastRegularInstallment .getFeeChargesCharged (currency )
381+ .plus (additionalInstallment .getFeeChargesCharged (currency ));
382+ lastRegularInstallment .setFeeChargesCharged (mergedFees .getAmount ());
383+
384+ final Money mergedPenalties = lastRegularInstallment .getPenaltyChargesCharged (currency )
385+ .plus (additionalInstallment .getPenaltyChargesCharged (currency ));
386+ lastRegularInstallment .setPenaltyCharges (mergedPenalties .getAmount ());
387+
388+ additionalInstallment .getInstallmentCharges ().forEach (charge -> {
389+ lastRegularInstallment .getInstallmentCharges ().add (charge );
390+ charge .setInstallment (lastRegularInstallment );
391+ });
392+ });
393+
394+ installments .removeIf (i -> i .isAdditional () && i .getDueDate () != null && i .getDueDate ().isBefore (newMaturityDate ));
395+ });
396+ }
397+
316398 private void handleExtraRepaymentPeriod (final List <LoanRepaymentScheduleInstallment > installments ,
317399 final LoanTermVariationsData termVariationsData , final ProgressiveLoanInterestScheduleModel scheduleModel ) {
318400 final LocalDate interestRateChangeSubmittedOnDate = termVariationsData .getTermVariationApplicableFrom ();
@@ -1266,7 +1348,7 @@ private List<ChangeOperation> createSortedChangeList(final List<LoanTermVariatio
12661348 .collect (Collectors .groupingBy (ltvd -> LoanTermVariationType .fromInt (ltvd .getTermType ().getId ().intValue ())));
12671349
12681350 Stream .of (LoanTermVariationType .INTEREST_RATE_FROM_INSTALLMENT , LoanTermVariationType .INTEREST_PAUSE ,
1269- LoanTermVariationType .EXTEND_REPAYMENT_PERIOD ).forEach (key -> {
1351+ LoanTermVariationType .EXTEND_REPAYMENT_PERIOD , LoanTermVariationType . DUE_DATE ).forEach (key -> {
12701352 if (loanTermVariationsMap .get (key ) != null ) {
12711353 changeOperations .addAll (loanTermVariationsMap .get (key ).stream ().map (ChangeOperation ::new ).toList ());
12721354 }
0 commit comments