3030import java .util .Map ;
3131import java .util .Optional ;
3232import java .util .Set ;
33+ import java .util .TreeSet ;
3334import lombok .RequiredArgsConstructor ;
3435import lombok .extern .slf4j .Slf4j ;
3536import org .apache .fineract .infrastructure .codes .domain .CodeValue ;
6869import org .apache .fineract .portfolio .loanaccount .loanschedule .domain .LoanRepaymentScheduleHistoryRepository ;
6970import org .apache .fineract .portfolio .loanaccount .loanschedule .domain .LoanScheduleGenerator ;
7071import org .apache .fineract .portfolio .loanaccount .loanschedule .domain .LoanScheduleGeneratorFactory ;
72+ import org .apache .fineract .portfolio .loanaccount .loanschedule .domain .LoanScheduleModelPeriod ;
7173import org .apache .fineract .portfolio .loanaccount .loanschedule .service .LoanScheduleHistoryWritePlatformService ;
7274import org .apache .fineract .portfolio .loanaccount .mapper .LoanTermVariationsMapper ;
7375import org .apache .fineract .portfolio .loanaccount .rescheduleloan .RescheduleLoansApiConstants ;
@@ -253,34 +255,22 @@ private void createLoanTermVariationsForRegularLoans(final Loan loan, final Inte
253255 List <LoanRescheduleRequestToTermVariationMapping > loanRescheduleRequestToTermVariationMappings , final Boolean isActive ,
254256 final boolean isSpecificToInstallment , BigDecimal decimalValue , LocalDate dueDate , LocalDate endDate , BigDecimal emi ) {
255257
256- if (rescheduleFromDate != null && endDate != null && emi != null ) {
257- LoanTermVariations parent = null ;
258- final Integer termType = LoanTermVariationType .EMI_AMOUNT .getValue ();
259- List <LoanRepaymentScheduleInstallment > installments = loan .getRepaymentScheduleInstallments ();
260- for (LoanRepaymentScheduleInstallment installment : installments ) {
261- if (!DateUtils .isBefore (installment .getDueDate (), rescheduleFromDate )
262- && !DateUtils .isAfter (installment .getDueDate (), endDate )) {
263- createLoanTermVariations (loanRescheduleRequest , termType , loan , installment .getDueDate (), installment .getDueDate (),
264- loanRescheduleRequestToTermVariationMappings , isActive , true , emi , parent );
265- }
266- if (DateUtils .isAfter (installment .getDueDate (), endDate )) {
267- break ;
268- }
269- }
270- }
258+ List <LoanTermVariations > pendingRescheduleVariations = new ArrayList <>();
271259
272260 if (rescheduleFromDate != null && adjustedDueDate != null ) {
273261 LoanTermVariations parent = null ;
274262 final Integer termType = LoanTermVariationType .DUE_DATE .getValue ();
275- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , adjustedDueDate ,
276- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , decimalValue , parent );
263+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
264+ adjustedDueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , decimalValue , parent );
265+ pendingRescheduleVariations .add (loanTermVariation );
277266 }
278267
279268 if (rescheduleFromDate != null && interestRate != null ) {
280269 LoanTermVariations parent = null ;
281270 final Integer termType = LoanTermVariationType .INTEREST_RATE_FROM_INSTALLMENT .getValue ();
282- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
283- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , interestRate , parent );
271+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
272+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , interestRate , parent );
273+ pendingRescheduleVariations .add (loanTermVariation );
284274 }
285275
286276 if (rescheduleFromDate != null && graceOnPrincipal != null ) {
@@ -289,32 +279,111 @@ private void createLoanTermVariationsForRegularLoans(final Loan loan, final Inte
289279 parent = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
290280 loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (graceOnPrincipal ),
291281 parent );
282+ pendingRescheduleVariations .add (parent );
292283
293284 BigDecimal extraTermsBasedOnGracePeriods = BigDecimal .valueOf (graceOnPrincipal );
294- createLoanTermVariations (loanRescheduleRequest , LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue (), loan ,
295- rescheduleFromDate , dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
296- extraTermsBasedOnGracePeriods , parent );
285+ LoanTermVariations extraTermsVariation = createLoanTermVariations (loanRescheduleRequest ,
286+ LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue (), loan , rescheduleFromDate , dueDate ,
287+ loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , extraTermsBasedOnGracePeriods , parent );
288+ pendingRescheduleVariations .add (extraTermsVariation );
297289
298290 }
299291
300292 if (rescheduleFromDate != null && graceOnInterest != null ) {
301293 LoanTermVariations parent = null ;
302294 final Integer termType = LoanTermVariationType .GRACE_ON_INTEREST .getValue ();
303- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
304- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (graceOnInterest ),
305- parent );
295+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
296+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
297+ BigDecimal .valueOf (graceOnInterest ), parent );
298+ pendingRescheduleVariations .add (loanTermVariation );
306299 }
307300
308301 if (rescheduleFromDate != null && extraTerms != null ) {
309302 LoanTermVariations parent = null ;
310303 final Integer termType = LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue ();
311- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
312- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (extraTerms ),
313- parent );
304+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
305+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
306+ BigDecimal .valueOf (extraTerms ), parent );
307+ pendingRescheduleVariations .add (loanTermVariation );
308+ }
309+
310+ if (rescheduleFromDate != null && endDate != null && emi != null ) {
311+ LoanTermVariations parent = null ;
312+ final Integer termType = LoanTermVariationType .EMI_AMOUNT .getValue ();
313+ int emiVariationsCreated = 0 ;
314+ List <LocalDate > projectedInstallmentDueDates = getProjectedInstallmentDueDates (loan , rescheduleFromDate ,
315+ pendingRescheduleVariations );
316+ for (LocalDate installmentDueDate : projectedInstallmentDueDates ) {
317+ if (!DateUtils .isBefore (installmentDueDate , rescheduleFromDate ) && !DateUtils .isAfter (installmentDueDate , endDate )) {
318+ createLoanTermVariations (loanRescheduleRequest , termType , loan , installmentDueDate , installmentDueDate ,
319+ loanRescheduleRequestToTermVariationMappings , isActive , true , emi , parent );
320+ emiVariationsCreated ++;
321+ }
322+ if (DateUtils .isAfter (installmentDueDate , endDate )) {
323+ break ;
324+ }
325+ }
326+ if (emiVariationsCreated == 0 ) {
327+ List <ApiParameterError > dataValidationErrors = new ArrayList <>();
328+ final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder (dataValidationErrors )
329+ .resource (RescheduleLoansApiConstants .ENTITY_NAME );
330+ dataValidatorBuilder .reset ().parameter (RescheduleLoansApiConstants .endDateParamName ).failWithCode (
331+ "end.date.before.next.installment" , "End date must be on or after the next projected installment date" );
332+ throw new PlatformApiDataValidationException (dataValidationErrors );
333+ }
314334 }
315335 loanRescheduleRequest .updateLoanRescheduleRequestToTermVariationMappings (loanRescheduleRequestToTermVariationMappings );
316336 }
317337
338+ private List <LocalDate > getProjectedInstallmentDueDates (final Loan loan , final LocalDate rescheduleFromDate ,
339+ final List <LoanTermVariations > pendingRescheduleVariations ) {
340+ ScheduleGeneratorDTO scheduleGeneratorDTO = this .loanUtilService .buildScheduleGeneratorDTO (loan , rescheduleFromDate );
341+ final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper .constructLoanApplicationTerms (scheduleGeneratorDTO ,
342+ loan );
343+ List <LoanTermVariationsData > projectedVariations = buildProjectedLoanTermVariatons (loanApplicationTerms ,
344+ pendingRescheduleVariations );
345+ loanApplicationTerms .getLoanTermVariations ().setExceptionData (projectedVariations );
346+
347+ final MathContext mathContext = MoneyHelper .getMathContext ();
348+ final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this .loanRepaymentScheduleTransactionProcessorFactory
349+ .determineProcessor (loan .transactionProcessingStrategy ());
350+ final LoanScheduleGenerator loanScheduleGenerator = this .loanScheduleFactory .create (loanApplicationTerms .getLoanScheduleType (),
351+ loanApplicationTerms .getInterestMethod ());
352+ final LoanScheduleDTO projectedLoanSchedule = loanScheduleGenerator .rescheduleNextInstallments (mathContext , loanApplicationTerms ,
353+ loan , loanApplicationTerms .getHolidayDetailDTO (), loanRepaymentScheduleTransactionProcessor , rescheduleFromDate );
354+
355+ Set <LocalDate > dueDates = new TreeSet <>();
356+ if (projectedLoanSchedule .getInstallments () != null ) {
357+ for (LoanRepaymentScheduleInstallment installment : projectedLoanSchedule .getInstallments ()) {
358+ dueDates .add (installment .getDueDate ());
359+ }
360+ } else {
361+ for (LoanScheduleModelPeriod period : projectedLoanSchedule .getLoanScheduleModel ().getPeriods ()) {
362+ if (period .isRepaymentPeriod () || period .isDownPaymentPeriod ()) {
363+ dueDates .add (period .periodDueDate ());
364+ }
365+ }
366+ }
367+ return new ArrayList <>(dueDates );
368+ }
369+
370+ private List <LoanTermVariationsData > buildProjectedLoanTermVariatons (final LoanApplicationTerms loanApplicationTerms ,
371+ final List <LoanTermVariations > pendingRescheduleVariations ) {
372+ final List <LoanTermVariationsData > projectedVariations = new ArrayList <>();
373+
374+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getExceptionData ());
375+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getDueDateVariation ());
376+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestRateChanges ());
377+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestRateFromInstallment ());
378+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestPauseVariations ());
379+
380+ for (LoanTermVariations pendingVariation : pendingRescheduleVariations ) {
381+ projectedVariations .add (pendingVariation .toData ());
382+ }
383+
384+ return projectedVariations ;
385+ }
386+
318387 private LoanTermVariations createLoanTermVariations (LoanRescheduleRequest loanRescheduleRequest , final Integer termType ,
319388 final Loan loan , LocalDate rescheduleFromDate , LocalDate adjustedDueDate ,
320389 List <LoanRescheduleRequestToTermVariationMapping > loanRescheduleRequestToTermVariationMappings , final Boolean isActive ,
0 commit comments