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 ;
@@ -248,34 +250,22 @@ private void createLoanTermVariationsForRegularLoans(final Loan loan, final Inte
248250 List <LoanRescheduleRequestToTermVariationMapping > loanRescheduleRequestToTermVariationMappings , final Boolean isActive ,
249251 final boolean isSpecificToInstallment , BigDecimal decimalValue , LocalDate dueDate , LocalDate endDate , BigDecimal emi ) {
250252
251- if (rescheduleFromDate != null && endDate != null && emi != null ) {
252- LoanTermVariations parent = null ;
253- final Integer termType = LoanTermVariationType .EMI_AMOUNT .getValue ();
254- List <LoanRepaymentScheduleInstallment > installments = loan .getRepaymentScheduleInstallments ();
255- for (LoanRepaymentScheduleInstallment installment : installments ) {
256- if (!DateUtils .isBefore (installment .getDueDate (), rescheduleFromDate )
257- && !DateUtils .isAfter (installment .getDueDate (), endDate )) {
258- createLoanTermVariations (loanRescheduleRequest , termType , loan , installment .getDueDate (), installment .getDueDate (),
259- loanRescheduleRequestToTermVariationMappings , isActive , true , emi , parent );
260- }
261- if (DateUtils .isAfter (installment .getDueDate (), endDate )) {
262- break ;
263- }
264- }
265- }
253+ List <LoanTermVariations > pendingRescheduleVariations = new ArrayList <>();
266254
267255 if (rescheduleFromDate != null && adjustedDueDate != null ) {
268256 LoanTermVariations parent = null ;
269257 final Integer termType = LoanTermVariationType .DUE_DATE .getValue ();
270- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , adjustedDueDate ,
271- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , decimalValue , parent );
258+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
259+ adjustedDueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , decimalValue , parent );
260+ pendingRescheduleVariations .add (loanTermVariation );
272261 }
273262
274263 if (rescheduleFromDate != null && interestRate != null ) {
275264 LoanTermVariations parent = null ;
276265 final Integer termType = LoanTermVariationType .INTEREST_RATE_FROM_INSTALLMENT .getValue ();
277- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
278- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , interestRate , parent );
266+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
267+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , interestRate , parent );
268+ pendingRescheduleVariations .add (loanTermVariation );
279269 }
280270
281271 if (rescheduleFromDate != null && graceOnPrincipal != null ) {
@@ -284,32 +274,111 @@ private void createLoanTermVariationsForRegularLoans(final Loan loan, final Inte
284274 parent = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
285275 loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (graceOnPrincipal ),
286276 parent );
277+ pendingRescheduleVariations .add (parent );
287278
288279 BigDecimal extraTermsBasedOnGracePeriods = BigDecimal .valueOf (graceOnPrincipal );
289- createLoanTermVariations (loanRescheduleRequest , LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue (), loan ,
290- rescheduleFromDate , dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
291- extraTermsBasedOnGracePeriods , parent );
280+ LoanTermVariations extraTermsVariation = createLoanTermVariations (loanRescheduleRequest ,
281+ LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue (), loan , rescheduleFromDate , dueDate ,
282+ loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , extraTermsBasedOnGracePeriods , parent );
283+ pendingRescheduleVariations .add (extraTermsVariation );
292284
293285 }
294286
295287 if (rescheduleFromDate != null && graceOnInterest != null ) {
296288 LoanTermVariations parent = null ;
297289 final Integer termType = LoanTermVariationType .GRACE_ON_INTEREST .getValue ();
298- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
299- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (graceOnInterest ),
300- parent );
290+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
291+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
292+ BigDecimal .valueOf (graceOnInterest ), parent );
293+ pendingRescheduleVariations .add (loanTermVariation );
301294 }
302295
303296 if (rescheduleFromDate != null && extraTerms != null ) {
304297 LoanTermVariations parent = null ;
305298 final Integer termType = LoanTermVariationType .EXTEND_REPAYMENT_PERIOD .getValue ();
306- createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate , dueDate ,
307- loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment , BigDecimal .valueOf (extraTerms ),
308- parent );
299+ LoanTermVariations loanTermVariation = createLoanTermVariations (loanRescheduleRequest , termType , loan , rescheduleFromDate ,
300+ dueDate , loanRescheduleRequestToTermVariationMappings , isActive , isSpecificToInstallment ,
301+ BigDecimal .valueOf (extraTerms ), parent );
302+ pendingRescheduleVariations .add (loanTermVariation );
303+ }
304+
305+ if (rescheduleFromDate != null && endDate != null && emi != null ) {
306+ LoanTermVariations parent = null ;
307+ final Integer termType = LoanTermVariationType .EMI_AMOUNT .getValue ();
308+ int emiVariationsCreated = 0 ;
309+ List <LocalDate > projectedInstallmentDueDates = getProjectedInstallmentDueDates (loan , rescheduleFromDate ,
310+ pendingRescheduleVariations );
311+ for (LocalDate installmentDueDate : projectedInstallmentDueDates ) {
312+ if (!DateUtils .isBefore (installmentDueDate , rescheduleFromDate ) && !DateUtils .isAfter (installmentDueDate , endDate )) {
313+ createLoanTermVariations (loanRescheduleRequest , termType , loan , installmentDueDate , installmentDueDate ,
314+ loanRescheduleRequestToTermVariationMappings , isActive , true , emi , parent );
315+ emiVariationsCreated ++;
316+ }
317+ if (DateUtils .isAfter (installmentDueDate , endDate )) {
318+ break ;
319+ }
320+ }
321+ if (emiVariationsCreated == 0 ) {
322+ List <ApiParameterError > dataValidationErrors = new ArrayList <>();
323+ final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder (dataValidationErrors )
324+ .resource (RescheduleLoansApiConstants .ENTITY_NAME );
325+ dataValidatorBuilder .reset ().parameter (RescheduleLoansApiConstants .endDateParamName ).failWithCode (
326+ "end.date.before.next.installment" , "End date must be on or after the next projected installment date" );
327+ throw new PlatformApiDataValidationException (dataValidationErrors );
328+ }
309329 }
310330 loanRescheduleRequest .updateLoanRescheduleRequestToTermVariationMappings (loanRescheduleRequestToTermVariationMappings );
311331 }
312332
333+ private List <LocalDate > getProjectedInstallmentDueDates (final Loan loan , final LocalDate rescheduleFromDate ,
334+ final List <LoanTermVariations > pendingRescheduleVariations ) {
335+ ScheduleGeneratorDTO scheduleGeneratorDTO = this .loanUtilService .buildScheduleGeneratorDTO (loan , rescheduleFromDate );
336+ final LoanApplicationTerms loanApplicationTerms = loanTermVariationsMapper .constructLoanApplicationTerms (scheduleGeneratorDTO ,
337+ loan );
338+ List <LoanTermVariationsData > projectedVariations = buildProjectedLoanTermVariatons (loanApplicationTerms ,
339+ pendingRescheduleVariations );
340+ loanApplicationTerms .getLoanTermVariations ().setExceptionData (projectedVariations );
341+
342+ final MathContext mathContext = MoneyHelper .getMathContext ();
343+ final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this .loanRepaymentScheduleTransactionProcessorFactory
344+ .determineProcessor (loan .transactionProcessingStrategy ());
345+ final LoanScheduleGenerator loanScheduleGenerator = this .loanScheduleFactory .create (loanApplicationTerms .getLoanScheduleType (),
346+ loanApplicationTerms .getInterestMethod ());
347+ final LoanScheduleDTO projectedLoanSchedule = loanScheduleGenerator .rescheduleNextInstallments (mathContext , loanApplicationTerms ,
348+ loan , loanApplicationTerms .getHolidayDetailDTO (), loanRepaymentScheduleTransactionProcessor , rescheduleFromDate );
349+
350+ Set <LocalDate > dueDates = new TreeSet <>();
351+ if (projectedLoanSchedule .getInstallments () != null ) {
352+ for (LoanRepaymentScheduleInstallment installment : projectedLoanSchedule .getInstallments ()) {
353+ dueDates .add (installment .getDueDate ());
354+ }
355+ } else {
356+ for (LoanScheduleModelPeriod period : projectedLoanSchedule .getLoanScheduleModel ().getPeriods ()) {
357+ if (period .isRepaymentPeriod () || period .isDownPaymentPeriod ()) {
358+ dueDates .add (period .periodDueDate ());
359+ }
360+ }
361+ }
362+ return new ArrayList <>(dueDates );
363+ }
364+
365+ private List <LoanTermVariationsData > buildProjectedLoanTermVariatons (final LoanApplicationTerms loanApplicationTerms ,
366+ final List <LoanTermVariations > pendingRescheduleVariations ) {
367+ final List <LoanTermVariationsData > projectedVariations = new ArrayList <>();
368+
369+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getExceptionData ());
370+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getDueDateVariation ());
371+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestRateChanges ());
372+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestRateFromInstallment ());
373+ projectedVariations .addAll (loanApplicationTerms .getLoanTermVariations ().getInterestPauseVariations ());
374+
375+ for (LoanTermVariations pendingVariation : pendingRescheduleVariations ) {
376+ projectedVariations .add (pendingVariation .toData ());
377+ }
378+
379+ return projectedVariations ;
380+ }
381+
313382 private LoanTermVariations createLoanTermVariations (LoanRescheduleRequest loanRescheduleRequest , final Integer termType ,
314383 final Loan loan , LocalDate rescheduleFromDate , LocalDate adjustedDueDate ,
315384 List <LoanRescheduleRequestToTermVariationMapping > loanRescheduleRequestToTermVariationMappings , final Boolean isActive ,
0 commit comments