From f04af5ca8210ff2d237052eeeb242b855bca59db Mon Sep 17 00:00:00 2001 From: Marta Jankovics Date: Tue, 22 Oct 2024 00:02:33 +0200 Subject: [PATCH] FINERACT-2081: Code quality, eliminate duplicated methods, use utility --- .../UpdateTrialBalanceDetailsTasklet.java | 3 +- .../core/domain/LocalDateInterval.java | 3 +- .../core/service/DateUtils.java | 51 ++-- .../portfolio/loanaccount/domain/Loan.java | 205 ++++++++-------- .../loanaccount/domain/LoanCharge.java | 10 +- .../LoanRepaymentScheduleInstallment.java | 64 +---- ...oanRepaymentScheduleProcessingWrapper.java | 37 +-- .../loanaccount/domain/LoanTransaction.java | 70 +----- ...rgeRepaymentScheduleProcessingWrapper.java | 227 +++++++----------- ...RepaymentScheduleTransactionProcessor.java | 11 +- .../data/LoanSchedulePeriodData.java | 7 +- ...stractCumulativeLoanScheduleGenerator.java | 18 +- ...gBalanceInterestLoanScheduleGenerator.java | 3 +- ...aultPaymentPeriodsInOneYearCalculator.java | 6 +- .../domain/DefaultScheduledDateGenerator.java | 8 +- .../domain/LoanApplicationTerms.java | 40 +-- ...oanRescheduleRequestDataValidatorImpl.java | 6 +- ...edPaymentScheduleTransactionProcessor.java | 28 +-- .../impl/ProgressiveTransactionCtx.java | 1 - .../ProgressiveLoanInterestScheduleModel.java | 4 +- .../ProgressiveLoanScheduleGenerator.java | 21 +- ...iveLoanRescheduleRequestDataValidator.java | 4 +- ...ymentScheduleTransactionProcessorTest.java | 6 +- .../service/LoanScheduleAssembler.java | 3 +- ...cheduleHistoryReadPlatformServiceImpl.java | 4 +- .../LoanAccrualsProcessingServiceImpl.java | 27 +-- .../loanaccount/service/LoanAssembler.java | 8 +- .../service/LoanReadPlatformServiceImpl.java | 3 +- ...WritePlatformServiceJpaRepositoryImpl.java | 3 +- .../GenerateAdhocClientScheduleTasklet.java | 10 +- ...SavingsAccountReadPlatformServiceImpl.java | 22 +- .../ShareProductDividendAssembler.java | 8 +- .../domain/InterestRateChartSlabFields.java | 9 +- 33 files changed, 363 insertions(+), 567 deletions(-) diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/jobs/updatetrialbalancedetails/UpdateTrialBalanceDetailsTasklet.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/jobs/updatetrialbalancedetails/UpdateTrialBalanceDetailsTasklet.java index d3cfee01943..a68e431e0ea 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/jobs/updatetrialbalancedetails/UpdateTrialBalanceDetailsTasklet.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/jobs/updatetrialbalancedetails/UpdateTrialBalanceDetailsTasklet.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -51,7 +50,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon .append("where je.transaction_date > (select coalesce(MAX(created_date),'2010-01-01') from m_trial_balance)"); final List tbGaps = jdbcTemplate.queryForList(tbGapSqlBuilder.toString(), LocalDate.class); for (LocalDate tbGap : tbGaps) { - int days = Math.toIntExact(ChronoUnit.DAYS.between(tbGap, DateUtils.getBusinessLocalDate())); + int days = DateUtils.getExactDifferenceInDays(tbGap, DateUtils.getBusinessLocalDate()); if (days < 1) { continue; } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java index 82a8e7e26d3..d071ee05894 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/LocalDateInterval.java @@ -19,7 +19,6 @@ package org.apache.fineract.infrastructure.core.domain; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -51,7 +50,7 @@ public Integer daysInPeriodInclusiveOfEndDate() { } private Integer daysBetween() { - return Math.toIntExact(ChronoUnit.DAYS.between(this.startDate, this.endDate)); + return DateUtils.getExactDifferenceInDays(this.startDate, this.endDate); } public boolean containsPortionOf(final LocalDateInterval interval) { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index f151a37abc2..10e596954c4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -305,8 +305,23 @@ public static boolean isAfter(LocalDate first, LocalDate second) { return first != null && (second == null || first.isAfter(second)); } - public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) { - return DAYS.between(localDateBefore, localDateAfter); + public static long getDifference(LocalDate first, LocalDate second, @NotNull ChronoUnit unit) { + if (first == null || second == null) { + throw new IllegalArgumentException("Dates must not be null to get difference"); + } + return unit.between(first, second); + } + + public static int getExactDifference(LocalDate first, LocalDate second, @NotNull ChronoUnit unit) { + return Math.toIntExact(getDifference(first, second, unit)); + } + + public static long getDifferenceInDays(LocalDate first, LocalDate second) { + return getDifference(first, second, DAYS); + } + + public static int getExactDifferenceInDays(LocalDate first, LocalDate second) { + return getExactDifference(first, second, DAYS); } // Parse, format @@ -362,17 +377,29 @@ public static String format(LocalDateTime dateTime, String format, Locale locale * * @param targetDate * the date to be checked - * @param startDate + * @param fromDate * the start date of the range - * @param endDate + * @param toDate * the end date of the range * @return true if targetDate is within range or equal to start/end dates, otherwise false */ - public static boolean isDateWithinRange(LocalDate targetDate, LocalDate startDate, LocalDate endDate) { - if (targetDate == null || startDate == null || endDate == null) { + public static boolean isDateWithinRange(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + if (targetDate == null || fromDate == null || toDate == null) { throw new IllegalArgumentException("Dates must not be null"); } - return !targetDate.isBefore(startDate) && !targetDate.isAfter(endDate); + return isDateInRangeInclusive(targetDate, fromDate, toDate); + } + + public static boolean isDateInRangeInclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && !DateUtils.isBefore(targetDate, fromDate) && !DateUtils.isAfter(targetDate, toDate); + } + + public static boolean isDateInRangeExclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && DateUtils.isAfter(targetDate, fromDate) && DateUtils.isBefore(targetDate, toDate); + } + + public static boolean isDateInRangeFromExclusiveToInclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && DateUtils.isAfter(targetDate, fromDate) && !DateUtils.isAfter(targetDate, toDate); } @NotNull @@ -398,14 +425,4 @@ private static DateTimeFormatter getDateTimeFormatter(String format, Locale loca } return formatter; } - - public static boolean occursOnDayFromExclusiveAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); - } - - public static boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return target != null && !DateUtils.isBefore(target, fromAndInclusive) && !DateUtils.isAfter(target, upToAndInclusive); - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 45ecfe5a603..11618f628bc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -42,7 +42,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -60,6 +59,7 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -152,7 +152,6 @@ @Entity @Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"), @UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") }) -@Setter @Getter public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @@ -180,9 +179,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Version int version; + @Setter() @Column(name = "account_no", length = 20, unique = true, nullable = false) private String accountNumber; + @Setter() @Column(name = "external_id") private ExternalId externalId; @@ -194,6 +195,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @JoinColumn(name = "group_id") private Group group; + @Setter() @ManyToOne @JoinColumn(name = "glim_id") private GroupLoanIndividualMonitoringAccount glim; @@ -236,55 +238,72 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Embedded private LoanProductRelatedDetail loanRepaymentScheduleDetail; + @Setter() @Column(name = "term_frequency", nullable = false) private Integer termFrequency; + @Setter() @Column(name = "term_period_frequency_enum", nullable = false) private Integer termPeriodFrequencyType; + @Setter(AccessLevel.PACKAGE) @Column(name = "loan_status_id", nullable = false) private Integer loanStatus; + @Setter() @Column(name = "sync_disbursement_with_meeting") private Boolean syncDisbursementWithMeeting; // loan application states + @Setter() @Column(name = "submittedon_date") private LocalDate submittedOnDate; + + @Setter() @Column(name = "rejectedon_date") private LocalDate rejectedOnDate; + @Setter() @ManyToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "rejectedon_userid") private AppUser rejectedBy; + @Setter() @Column(name = "withdrawnon_date") private LocalDate withdrawnOnDate; + @Setter() @ManyToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "withdrawnon_userid") private AppUser withdrawnBy; + @Setter() @Column(name = "approvedon_date") private LocalDate approvedOnDate; + @Setter() @ManyToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "approvedon_userid") private AppUser approvedBy; + @Setter() @Column(name = "expected_disbursedon_date") private LocalDate expectedDisbursementDate; + @Setter() @Column(name = "disbursedon_date") private LocalDate actualDisbursementDate; + @Setter() @ManyToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "disbursedon_userid") private AppUser disbursedBy; + @Setter() @Column(name = "closedon_date") private LocalDate closedOnDate; + @Setter() @ManyToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "closedon_userid") private AppUser closedBy; @@ -302,18 +321,22 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "expected_maturedon_date") private LocalDate expectedMaturityDate; + @Setter() @Column(name = "maturedon_date") private LocalDate actualMaturityDate; + @Setter() @Column(name = "expected_firstrepaymenton_date") private LocalDate expectedFirstRepaymentOnDate; + @Setter() @Column(name = "interest_calculated_from_date") private LocalDate interestChargedFromDate; @Column(name = "total_overpaid_derived", scale = 6, precision = 19) private BigDecimal totalOverpaid; + @Setter() @Column(name = "overpaidon_date") private LocalDate overpaidOnDate; @@ -359,18 +382,23 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Transient private LoanSummaryWrapper loanSummaryWrapper; + @Setter() @Column(name = "principal_amount_proposed", scale = 6, precision = 19, nullable = false) private BigDecimal proposedPrincipal; + @Setter() @Column(name = "approved_principal", scale = 6, precision = 19, nullable = false) private BigDecimal approvedPrincipal; + @Setter() @Column(name = "net_disbursal_amount", scale = 6, precision = 19, nullable = false) private BigDecimal netDisbursalAmount; + @Setter() @Column(name = "fixed_emi_amount", scale = 6, precision = 19) private BigDecimal fixedEmiAmount; + @Setter() @Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19) private BigDecimal maxOutstandingLoanBalance; @@ -394,9 +422,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "is_npa", nullable = false) private boolean isNpa; + @Setter() @Column(name = "accrued_till") private LocalDate accruedTill; + @Setter() @Column(name = "create_standing_instruction_at_disbursement") private Boolean createStandingInstructionAtDisbursement; @@ -406,9 +436,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "interest_recalcualated_on") private LocalDate interestRecalculatedOn; + @Setter() @Column(name = "is_floating_interest_rate") private Boolean isFloatingInterestRate; + @Setter() @Column(name = "interest_rate_differential", scale = 6, precision = 19) private BigDecimal interestRateDifferential; @@ -432,9 +464,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id")) private List rates; + @Setter() @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5) private BigDecimal fixedPrincipalPercentagePerInstallment; + @Setter() @Column(name = "last_closed_business_date") private LocalDate lastClosedBusinessDate; @@ -715,8 +749,7 @@ public void addLoanCharge(final LoanCharge loanCharge) { public ChangedTransactionDetail reprocessTransactions() { ChangedTransactionDetail changedTransactionDetail; - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); @@ -787,20 +820,15 @@ private void handleChargePaidTransaction(final LoanCharge charge, final LoanTran addLoanTransaction(chargesPayment); loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, this); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List chargePaymentInstallments = new ArrayList<>(); List installments = getRepaymentScheduleInstallments(); int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments); for (final LoanRepaymentScheduleInstallment installment : installments) { - boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()) - : charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()); - if (installmentNumber == null && isFirstNormalInstallment) { - chargePaymentInstallments.add(installment); - break; - } else if (installment.getInstallmentNumber().equals(installmentNumber)) { + boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); + if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null + && charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) { chargePaymentInstallments.add(installment); break; } @@ -869,8 +897,7 @@ public void removeLoanCharge(final LoanCharge loanCharge) { removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { /* * TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no @@ -932,8 +959,7 @@ public Map updateLoanCharge(final LoanCharge loanCharge, final J updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement()) { /* * TODO Vishwas Currently we do not allow waiving updating loan charge after a loan is approved (hence there @@ -1104,8 +1130,7 @@ public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLi } // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule // to be reprocessed. - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { /* * TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan @@ -1690,7 +1715,7 @@ public Map undoApproval(final LoanLifecycleStateMachine loanLife validateAccountStatus(LoanEvent.LOAN_APPROVAL_UNDO); final Map actualChanges = new LinkedHashMap<>(); - final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus currentStatus = getStatus(); final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, this); if (!statusEnum.hasStateOf(currentStatus)) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, this); @@ -1741,7 +1766,7 @@ public boolean canDisburse() { final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this); boolean isMultiTrancheDisburse = false; - LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); + LoanStatus actualLoanStatus = getStatus(); if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) && isAllTranchesNotDisbursed()) { isMultiTrancheDisburse = true; @@ -2117,7 +2142,7 @@ public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGene validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); final Map actualChanges = new LinkedHashMap<>(); - final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus currentStatus = getStatus(); final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSAL_UNDO, this); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate()); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -2393,8 +2418,7 @@ private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(fi } } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallmentByDueDate( loanTransaction.getTransactionDate()); @@ -2471,12 +2495,6 @@ private LocalDate extractTransactionDate(LoanTransaction loanTransaction) { return loanTransactionDate; } - public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) { - return getRepaymentScheduleInstallments().stream() // - .filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() // - .orElse(null); - } - public List retrieveListOfTransactionsForReprocessing() { return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE) .collect(Collectors.toList()); @@ -2713,8 +2731,7 @@ public ChangedTransactionDetail undoWrittenOff(LoanLifecycleStateMachine loanLif final LoanTransaction writeOffTransaction = findWriteOffTransaction(); writeOffTransaction.reverse(); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); @@ -2789,8 +2806,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, loanRepaymentScheduleTransactionProcessor); @@ -2803,7 +2819,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, this); LoanTransaction loanTransaction = null; - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); @@ -2921,8 +2937,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec final String errorMessage = "The date on which a loan is closed cannot be in the future."; throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, loanRepaymentScheduleTransactionProcessor); @@ -2933,7 +2948,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec this.closedOnDate = closureDate; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -2965,7 +2980,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec // has 'overpaid' amount this.closedOnDate = closureDate; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -2992,7 +3007,7 @@ public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLife this.closedOnDate = rescheduledOn; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_RESCHEDULE, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -3080,7 +3095,7 @@ public boolean isOpen() { } public boolean isAllTranchesNotDisbursed() { - LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); + LoanStatus actualLoanStatus = getStatus(); boolean isInRightStatus = actualLoanStatus.isActive() || actualLoanStatus.isApproved() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid(); return this.loanProduct.isMultiDisburseLoan() && isInRightStatus && isDisbursementAllowed(); @@ -3745,12 +3760,6 @@ public void removeLoanTransaction(final LoanTransaction loanTransaction) { this.loanTransactions.remove(loanTransaction); } - // Intentionally kept as package-private. Nobody should set the status directly but use the - // DefaultLoanLifecycleStateMachine to transition - void setLoanStatus(final Integer loanStatus) { - this.loanStatus = loanStatus; - } - private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) { if (this.client != null && this.client.getOfficeJoiningDate() != null) { final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); @@ -4157,8 +4166,7 @@ public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final regenerateRepaymentSchedule(scheduleGeneratorDTO); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), @@ -4203,21 +4211,6 @@ public BigDecimal retriveLastEmiAmount() { return emiAmount; } - public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) { - LoanRepaymentScheduleInstallment installment = null; - if (installmentNumber == null) { - return installment; - } - List installments = getRepaymentScheduleInstallments(); - for (final LoanRepaymentScheduleInstallment scheduleInstallment : installments) { - if (scheduleInstallment.getInstallmentNumber().equals(installmentNumber)) { - installment = scheduleInstallment; - break; - } - } - return installment; - } - public Money getTotalOverpaidAsMoney() { return Money.of(this.repaymentScheduleDetail().getCurrency(), this.totalOverpaid); } @@ -4282,8 +4275,7 @@ public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRec } public ChangedTransactionDetail processTransactions() { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), @@ -4335,8 +4327,7 @@ public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleG } public void processPostDisbursementTransactions() { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List copyTransactions = new ArrayList<>(); if (!allNonContraTransactionsPostDisbursement.isEmpty()) { @@ -4363,8 +4354,7 @@ private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO gener final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom()); @@ -4381,8 +4371,7 @@ public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO sc final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() .create(loanApplicationTerms.getLoanScheduleType(), interestMethod); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this, scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor); } else { @@ -4547,45 +4536,42 @@ public void addLoanRepaymentScheduleInstallment(final LoanRepaymentScheduleInsta this.repaymentScheduleInstallments.add(installment); } + /** + * @param date + * @return a schedule installment is related to the provided date + **/ + public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { + // TODO first installment should be fromInclusive + return getRepaymentScheduleInstallment(e -> DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())); + } + + public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) { + return getRepaymentScheduleInstallment(e -> e.getInstallmentNumber().equals(installmentNumber)); + } + /** * @param dueDate * the due date of the installment * @return a schedule installment with similar due date to the one provided **/ - public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDate dueDate) { - LoanRepaymentScheduleInstallment installment = null; - - if (dueDate != null) { - List installments = getRepaymentScheduleInstallments(); - for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : installments) { - if (DateUtils.isEqual(dueDate, repaymentScheduleInstallment.getDueDate())) { - installment = repaymentScheduleInstallment; - break; - } - } - } - return installment; + public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) { + return getRepaymentScheduleInstallment(e -> DateUtils.isEqual(dueDate, e.getDueDate())); } /** - * @param date - * @return a schedule installment is related to the provided date + * @param predicate + * filter of the installments + * @return the first installment matching the filter **/ - public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { - if (date == null) { - return null; - } - return getRepaymentScheduleInstallments()// - .stream()// - .filter(installment -> date.isAfter(installment.getFromDate()) && !date.isAfter(installment.getDueDate()))// - .findAny()// - .orElse(null);// + public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment( + @NotNull Predicate predicate) { + return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null); } /** * @return loan disbursement data **/ - public List getDisbursmentData() { + public List getDisbursementData() { Iterator iterator = this.getDisbursementDetails().iterator(); List disbursementData = new ArrayList<>(); @@ -4677,7 +4663,7 @@ public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency ap final BigDecimal emiAmount = getFixedEmiAmount(); final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance(); - final List disbursementData = getDisbursmentData(); + final List disbursementData = getDisbursementData(); CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; if (loanCalendar != null) { @@ -4870,8 +4856,7 @@ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction l } } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); // If it's a refund if (adjustedTransaction == null) { @@ -4901,8 +4886,7 @@ public void handleChargebackTransaction(final LoanTransaction chargebackTransact throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.chargeback.transaction", errorMessage); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); addLoanTransaction(chargebackTransaction); loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(), @@ -5179,16 +5163,14 @@ private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, Money feeAccountedForCurrentPeriod = Money.zero(getCurrency()); Money interestForCurrentPeriod = Money.zero(getCurrency()); Money interestAccountedForCurrentPeriod = Money.zero(getCurrency()); - int totalPeriodDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate())); - int tillDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), paymentDate)); + int totalPeriodDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate()); + int tillDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), paymentDate); interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal .valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(getCurrency()).getAmount(), tillDays))); interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency())); for (LoanCharge loanCharge : this.charges) { if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = isFirstNormalInstallment - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate); + boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment); if (isDue) { if (loanCharge.isPenaltyCharge()) { penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(getCurrency())); @@ -5238,8 +5220,7 @@ public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) { balances[1] = fee; balances[2] = penalty; break; - } else if (DateUtils.isAfter(paymentDate, installment.getFromDate()) - && DateUtils.isBefore(paymentDate, installment.getDueDate())) { + } else if (DateUtils.isDateInRangeExclusive(paymentDate, installment.getFromDate(), installment.getDueDate())) { balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment); break; } @@ -5389,7 +5370,7 @@ public BigDecimal getFirstDisbursalAmount() { BigDecimal firstDisbursalAmount; if (this.isMultiDisburmentLoan()) { - List disbursementData = getDisbursmentData(); + List disbursementData = getDisbursementData(); Collections.sort(disbursementData); firstDisbursalAmount = disbursementData.get(disbursementData.size() - 1).getPrincipal(); } else { @@ -5527,4 +5508,8 @@ public void deductFromNetDisbursalAmount(final BigDecimal subtrahend) { public void setIsTopup(boolean topup) { isTopup = topup; } + + public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() { + return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 249be53208f..9e3dcfdb1b7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -616,14 +616,8 @@ public boolean hasLoanIdentifiedBy(final Long loanId) { return this.loan.hasIdentifyOf(loanId); } - public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { - final LocalDate dueDate = getDueLocalDate(); - return DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); - } - - public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive) { - final LocalDate dueDate = getDueLocalDate(); - return DateUtils.occursOnDayFromAndUpToAndIncluding(fromAndInclusive, upToAndInclusive, dueDate); + public boolean isDueInPeriod(final LocalDate fromDate, final LocalDate toDate, boolean isFirstPeriod) { + return LoanRepaymentScheduleProcessingWrapper.isInPeriod(getDueLocalDate(), fromDate, toDate, isFirstPeriod); } public boolean isFeeCharge() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index f5cbd2ea99a..51a3dacbd52 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import lombok.Getter; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; @@ -38,6 +39,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.AllocationType; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; +@Getter @Entity @Table(name = "m_loan_repayment_schedule") public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDateTimeCustom @@ -265,30 +267,6 @@ private BigDecimal defaultToNullIfZero(final BigDecimal value) { return result; } - public Loan getLoan() { - return this.loan; - } - - public Integer getInstallmentNumber() { - return this.installmentNumber; - } - - public LocalDate getFromDate() { - return this.fromDate; - } - - public void setPostDatedChecksToNull() { - this.postDatedChecks = null; - } - - public Set getPostDatedCheck() { - return this.postDatedChecks; - } - - public LocalDate getDueDate() { - return this.dueDate; - } - public Money getCreditedPrincipal(final MonetaryCurrency currency) { return Money.of(currency, this.creditedPrincipal); } @@ -413,7 +391,7 @@ public Money getTotalOutstanding(final MonetaryCurrency currency) { .plus(getPenaltyChargesOutstanding(currency)); } - public void updateLoan(final Loan loan) { + void updateLoan(final Loan loan) { this.loan = loan; } @@ -421,10 +399,6 @@ public boolean isPartlyPaid() { return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null); } - public boolean isObligationsMet() { - return this.obligationsMet; - } - public boolean isNotFullyPaidOff() { return !this.obligationsMet; } @@ -879,18 +853,6 @@ public void addToCreditedPenalty(final BigDecimal amount) { } } - public BigDecimal getTotalPaidInAdvance() { - return this.totalPaidInAdvance; - } - - public BigDecimal getTotalPaidLate() { - return this.totalPaidLate; - } - - public LocalDate getObligationsMetOnDate() { - return this.obligationsMetOnDate; - } - /********** UNPAY COMPONENTS ****/ public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { @@ -1023,10 +985,6 @@ public Money getTotalPaid(final MonetaryCurrency currency) { .plus(getPrincipalCompleted(currency)); } - public BigDecimal getRescheduleInterestPortion() { - return rescheduleInterestPortion; - } - public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { this.rescheduleInterestPortion = rescheduleInterestPortion; } @@ -1039,14 +997,6 @@ public void setPenaltyChargesWaived(final BigDecimal newPenaltyChargesCharged) { this.penaltyChargesWaived = newPenaltyChargesCharged; } - public Set getInstallmentCharges() { - return installmentCharges; - } - - public boolean isAdditional() { - return additional; - } - public void markAsAdditional() { this.additional = true; } @@ -1055,10 +1005,6 @@ public Set getLoanTransactionToRepaym return this.loanTransactionToRepaymentScheduleMappings; } - public boolean isDownPayment() { - return isDownPayment; - } - public void resetBalances() { resetDerivedComponents(); resetPrincipalDue(); @@ -1077,8 +1023,4 @@ public void resetPrincipalDue() { public enum PaymentAction { PAY, UNPAY } - - public boolean isReAged() { - return isReAged; - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index fef7d0f6635..643c114f1b4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -22,6 +22,7 @@ import java.time.LocalDate; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -87,7 +88,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -133,7 +134,7 @@ private Money cumulativeChargesWaivedWithin(final LocalDate periodStart, final L for (final LoanCharge loanCharge : loanCharges) { if (predicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -156,7 +157,7 @@ private Money cumulativeChargesWrittenOffWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (chargePredicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -175,11 +176,6 @@ private Predicate feeCharge() { return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); } - private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { - return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - } - private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { @@ -188,7 +184,7 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = cumulative.plus(getInstallmentFee(currency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -242,17 +238,24 @@ public static int fetchFirstNormalInstallmentNumber(List !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber(); } - public static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, + public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment targetInstallment, List installments) { int firstPeriod = fetchFirstNormalInstallmentNumber(installments); - return isInPeriod(transactionDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod)); + return isInPeriod(targetDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod)); + } + + public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) { + return isInPeriod(targetDate, installment.getFromDate(), installment.getDueDate(), isFirstPeriod); } - private static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, - boolean isFirstPeriod) { - LocalDate fromDate = targetInstallment.getFromDate(); - LocalDate dueDate = targetInstallment.getDueDate(); - return isFirstPeriod ? DateUtils.occursOnDayFromAndUpToAndIncluding(fromDate, dueDate, transactionDate) - : DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromDate, dueDate, transactionDate); + public static boolean isInPeriod(LocalDate targetDate, LocalDate fromDate, LocalDate toDate, boolean isFirstPeriod) { + return isFirstPeriod ? DateUtils.isDateInRangeInclusive(targetDate, fromDate, toDate) + : DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate); + } + + public static Optional findInPeriod(LocalDate targetDate, + List installments) { + int firstNumber = fetchFirstNormalInstallmentNumber(installments); + return installments.stream().filter(e -> isInPeriod(targetDate, e, e.getInstallmentNumber().equals(firstNumber))).findFirst(); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 7a416c5679d..54ca17930bb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -39,6 +39,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; +import lombok.Getter; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -60,6 +61,7 @@ * All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers, * Write-off etc */ +@Getter @Entity @Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") }) public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom { @@ -537,22 +539,6 @@ public Money getPrincipalPortion(final MonetaryCurrency currency) { return Money.of(currency, this.principalPortion); } - public BigDecimal getPrincipalPortion() { - return this.principalPortion; - } - - public BigDecimal getInterestPortion() { - return this.interestPortion; - } - - public BigDecimal getPenaltyChargesPortion() { - return this.penaltyChargesPortion; - } - - public BigDecimal getFeeChargesPortion() { - return this.feeChargesPortion; - } - public Money getInterestPortion(final MonetaryCurrency currency) { return Money.of(currency, this.interestPortion); } @@ -581,10 +567,6 @@ public LocalDate getTransactionDate() { return this.dateOf; } - public LocalDate getDateOf() { - return this.dateOf; - } - public LoanTransactionType getTypeOf() { return LoanTransactionType.fromInt(this.typeOf); } @@ -872,22 +854,10 @@ public Map toMapData(final String currencyCode) { return thisTransactionData; } - public Loan getLoan() { - return this.loan; - } - - public Set getLoanChargesPaid() { - return this.loanChargesPaid; - } - public void setLoanChargesPaid(final Set loanChargesPaid) { this.loanChargesPaid = loanChargesPaid; } - public ExternalId getExternalId() { - return this.externalId; - } - public boolean isRefund() { return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed(); } @@ -926,14 +896,6 @@ public boolean isRefundForActiveLoan() { return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed(); } - public boolean isManuallyAdjustedOrReversed() { - return this.manuallyAdjustedOrReversed; - } - - public boolean isNotManuallyAdjustedOrReversed() { - return !this.manuallyAdjustedOrReversed; - } - public void manuallyAdjustedOrReversed() { this.manuallyAdjustedOrReversed = true; } @@ -1030,27 +992,15 @@ public boolean isAccrualTransaction() { return isAccrual(); } - public BigDecimal getOutstandingLoanBalance() { - return outstandingLoanBalance; - } - public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) { return Money.of(currency, this.outstandingLoanBalance); } - public PaymentDetail getPaymentDetail() { - return this.paymentDetail; - } - public boolean isPaymentTransaction() { return this.isNotReversed() && !(this.isDisbursement() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this.isIncomePosting()); } - public Set getLoanCollateralManagementSet() { - return this.loanCollateralManagementSet; - } - public LocalDate getSubmittedOnDate() { return submittedOnDate; } @@ -1059,10 +1009,6 @@ public boolean hasLoanTransactionRelations() { return !loanTransactionRelations.isEmpty(); } - public Set getLoanTransactionRelations() { - return loanTransactionRelations; - } - public List getLoanTransactionRelations(Predicate predicate) { return loanTransactionRelations.stream().filter(predicate).toList(); } @@ -1078,10 +1024,6 @@ public void copyLoanTransactionRelations(Set sourceLoan } } - public BigDecimal getAmount() { - return amount; - } - public boolean isBefore(final LocalDate date) { return DateUtils.isBefore(getTransactionDate(), date); } @@ -1094,10 +1036,6 @@ public boolean isOn(final LocalDate date) { return DateUtils.isEqual(getTransactionDate(), date); } - public LoanReAgeParameter getLoanReAgeParameter() { - return loanReAgeParameter; - } - public void setLoanReAgeParameter(LoanReAgeParameter loanReAgeParameter) { this.loanReAgeParameter = loanReAgeParameter; } @@ -1121,10 +1059,6 @@ public boolean happenedBefore(LoanTransaction loanTransaction) { return false; } - public String getChargeRefundChargeType() { - return chargeRefundChargeType; - } - public boolean isOverPaid() { return MathUtil.isGreaterThanZero(overPaymentPortion); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java index 83c0bc07f31..a3237dd7e4e 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java @@ -27,6 +27,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; /** @@ -35,18 +36,18 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper { public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, - final List repaymentPeriods, LoanCharge loanCharge) { + final List installments, LoanCharge loanCharge) { Loan loan = loanCharge.getLoan(); Money zero = Money.zero(currency); Money totalInterest = zero; Money totalPrincipal = zero; - for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { + for (final LoanRepaymentScheduleInstallment installment : installments) { totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); } LoanChargePaidBy accrualBy = null; if (!loan.isInterestBearing() && loanCharge.isSpecifiedDueDate()) { // TODO: why only if not interest bearing - LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, repaymentPeriods); + LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, installments); if (addedPeriod != null) { addedPeriod.updateObligationsMet(currency, disbursementDate); } @@ -54,168 +55,126 @@ public void reprocess(final MonetaryCurrency currency, final LocalDate disbursem .orElse(null); } LocalDate startDate = disbursementDate; - int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(repaymentPeriods); - for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { - if (period.isDownPayment()) { + int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); + for (final LoanRepaymentScheduleInstallment installment : installments) { + if (installment.isDownPayment()) { continue; } - boolean installmentChargeApplicable = !period.isRecalculatedInterestComponent(); - boolean isFirstNonDownPaymentPeriod = period.getInstallmentNumber().equals(firstNormalInstallmentNumber); - LocalDate dueDate = period.getDueDate(); - final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, dueDate, loanCharge, currency, period, - totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); - final Money feeChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); - final Money feeChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); + boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent(); + boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); + Predicate feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement(); + LocalDate dueDate = installment.getDueDate(); + final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest, + installmentChargeApplicable, isFirstPeriod, feePredicate); + final Money feeChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, feePredicate); + final Money feeChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, feePredicate); Predicate penaltyPredicate = LoanCharge::isPenaltyCharge; - final Money penaltyChargesDueForRepaymentPeriod = penaltyChargesDueWithin(startDate, dueDate, loanCharge, currency, period, - totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); - final Money penaltyChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); - final Money penaltyChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); + final Money penaltyChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, + totalInterest, installmentChargeApplicable, isFirstPeriod, penaltyPredicate); + final Money penaltyChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, penaltyPredicate); + final Money penaltyChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency, + installmentChargeApplicable, isFirstPeriod, penaltyPredicate); - period.addToChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, - feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, - penaltyChargesWrittenOffForRepaymentPeriod); + installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived, + penaltyChargesWrittenOff); - if (accrualBy != null && period.isAdditional() - && loanChargeIsDue(startDate, dueDate, isFirstNonDownPaymentPeriod, loanCharge)) { + if (accrualBy != null && installment.isAdditional() && loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) { Money amount = Money.of(currency, accrualBy.getAmount()); boolean isFee = loanCharge.isFeeCharge(); - period.updateAccrualPortion(period.getInterestAccrued(currency), - MathUtil.plus(period.getFeeAccrued(currency), (isFee ? amount : null)), - MathUtil.plus(period.getPenaltyAccrued(currency), (isFee ? null : amount))); - accrualBy.setInstallmentNumber(period.getInstallmentNumber()); + installment.updateAccrualPortion(installment.getInterestAccrued(currency), + MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? amount : null)), + MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : amount))); + accrualBy.setInstallmentNumber(installment.getInstallmentNumber()); } startDate = dueDate; } } - private Money feeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, - final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, - final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { - - if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - return Money.of(monetaryCurrency, getInstallmentFee(monetaryCurrency, period, loanCharge)); - } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - return Money.of(monetaryCurrency, loanCharge.chargeAmount()); - } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterest.getAmount()); - } else { - // If charge type is specified due date and loan is - // multi disburment loan. - // Then we need to get as of this loan charge due date - // how much amount disbursed. - if (loanCharge.getLoan() != null && loanCharge.isSpecifiedDueDate() && loanCharge.getLoan().isMultiDisburmentLoan()) { - for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) { - if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { - amount = amount.add(loanDisbursementDetails.principal()); - } - } - } else { - amount = amount.add(totalPrincipal.getAmount()); - } + @NotNull + private Money calcChargeDue(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, + boolean isInstallmentChargeApplicable, boolean isFirstPeriod, Predicate predicate) { + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; + } + if (loanCharge.isFeeCharge() && loanCharge.isDueAtDisbursement()) { + return zero; + } + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + return Money.of(currency, getInstallmentFee(currency, period, loanCharge)); + } + if (!loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return zero; + } + ChargeCalculationType calculationType = loanCharge.getChargeCalculation(); + if (loanCharge.isOverdueInstallmentCharge() && calculationType.isPercentageBased()) { + return Money.of(currency, loanCharge.chargeAmount()); + } + if (calculationType.isFlat()) { + return loanCharge.getAmount(currency); + } + BigDecimal baseAmount = BigDecimal.ZERO; + Loan loan = loanCharge.getLoan(); + if (loan != null && loanCharge.isFeeCharge() && !calculationType.hasInterest() && loanCharge.isSpecifiedDueDate() + && loan.isMultiDisburmentLoan()) { + // If charge type is specified due date and loan is multi disburment loan. + // Then we need to get as of this loan charge due date how much amount disbursed. + for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { + baseAmount = MathUtil.add(baseAmount, loanDisbursementDetails.principal()); } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - return Money.of(monetaryCurrency, loanChargeAmt); - } else if (isDue) { - return Money.of(monetaryCurrency, loanCharge.amount()); } + } else { + baseAmount = getBaseAmount(loanCharge, totalPrincipal.getAmount(), totalInterest.getAmount()); } - return Money.zero(monetaryCurrency); + return Money.of(currency, MathUtil.percentageOf(baseAmount, loanCharge.getPercentage(), MoneyHelper.getMathContext())); } - private Money chargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + private Money calcChargeWaived(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, Predicate predicate) { - - if (predicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - return loanChargePerInstallment.getAmountWaived(currency); - } - } else if (isDue) { - return loanCharge.getAmountWaived(currency); - } + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; } - - return Money.zero(currency); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge installmentCharge = loanCharge.getInstallmentLoanCharge(periodEnd); + return installmentCharge == null ? zero : installmentCharge.getAmountWaived(currency); + } + if (loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return loanCharge.getAmountWaived(currency); + } + return zero; } - private Money loanChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + private Money calcChargeWrittenOff(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, - Predicate chargePredicate) { - if (chargePredicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - return loanChargePerInstallment.getAmountWrittenOff(currency); - } - } else if (isDue) { - return loanCharge.getAmountWrittenOff(currency); - } + Predicate predicate) { + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; } - return Money.zero(currency); - } - - private Predicate feeCharge() { - return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); - } - - private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { - return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - } - - private Money penaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, - final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, - boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { - - if (loanCharge.isPenaltyCharge()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - return Money.of(currency, getInstallmentFee(currency, period, loanCharge)); - } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - return Money.of(currency, loanCharge.chargeAmount()); - } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterest.getAmount()); - } else { - amount = amount.add(totalPrincipal.getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - return Money.of(currency, loanChargeAmt); - } else if (isDue) { - return Money.of(currency, loanCharge.amount()); - } + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge installmentCharge = loanCharge.getInstallmentLoanCharge(periodEnd); + return installmentCharge == null ? zero : installmentCharge.getAmountWrittenOff(currency); } - - return Money.zero(currency); + if (loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return loanCharge.getAmountWrittenOff(currency); + } + return zero; } private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - amount = getBaseAmount(currency, period, loanCharge, amount); - return amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - } else { + if (loanCharge.getChargeCalculation().isFlat()) { return loanCharge.amountOrPercentage(); } + return MathUtil.percentageOf(getBaseAmount(currency, period, loanCharge, null), loanCharge.getPercentage(), + MoneyHelper.getMathContext()); } @NotNull diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index da674327749..3b40c7cb3a9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -113,9 +113,7 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur for (final LoanRepaymentScheduleInstallment installment : installments) { boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); for (final LoanCharge loanCharge : transferCharges) { - boolean isDue = isFirstPeriod - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate()); + boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(), isFirstPeriod); if (isDue) { Money amountForProcess = loanCharge.getAmount(currency); if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) { @@ -227,11 +225,8 @@ protected void calculateAccrualActivity(LoanTransaction loanTransaction, Monetar final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); final LoanRepaymentScheduleInstallment currentInstallment = installments.stream() - .filter(installment -> installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), - loanTransaction.getTransactionDate()) - : DateUtils.occursOnDayFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), - loanTransaction.getTransactionDate())) + .filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment, + installment.getInstallmentNumber().equals(firstNormalInstallmentNumber))) .findFirst().orElseThrow(); interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency)); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java index 22a3eb17388..5be2758e9ff 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import lombok.Getter; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -155,7 +154,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.obligationsMetOnDate = null; this.complete = null; if (fromDate != null) { - this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); + this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate); } else { this.daysInPeriod = null; } @@ -222,7 +221,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.obligationsMetOnDate = null; this.complete = null; if (fromDate != null) { - this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); + this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate); } else { this.daysInPeriod = null; } @@ -347,7 +346,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.obligationsMetOnDate = obligationsMetOnDate; this.complete = complete; if (fromDate != null) { - this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); + this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate); } else { this.daysInPeriod = null; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index 2d6382b55de..916ebfef6c1 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -21,7 +21,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -222,8 +221,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe } // Updates total days in term - scheduleParams - .addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate))); + scheduleParams.addLoanTermInDays(DateUtils.getExactDifferenceInDays(scheduleParams.getPeriodStartDate(), scheduledDueDate)); if (termVariationParams.skipPeriod()) { continue; } @@ -737,7 +735,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, currentTransactions.addAll(createCurrentTransactionList(detail)); if (!DateUtils.isEqual(transactionDate, scheduleParams.getPeriodStartDate()) || scheduleParams.isFirstPeriod()) { - int periodDays = Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), transactionDate)); + int periodDays = DateUtils.getExactDifferenceInDays(scheduleParams.getPeriodStartDate(), transactionDate); // calculates period start date for interest // calculation as per the configuration periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, @@ -745,8 +743,8 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); - int daysInPeriodApplicable = Math - .toIntExact(ChronoUnit.DAYS.between(periodStartDateApplicableForInterest, transactionDate)); + int daysInPeriodApplicable = DateUtils.getExactDifferenceInDays(periodStartDateApplicableForInterest, + transactionDate); Money interestForCurrentInstallment = Money.zero(currency); if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction @@ -2076,8 +2074,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L for (final LoanCharge loanCharge : loanCharges) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -2139,8 +2136,7 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -2409,7 +2405,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency)); instalmentNumber++; - loanTermInDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate())); + loanTermInDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate()); if (loanApplicationTerms.isInterestRecalculationEnabled()) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java index 53c0cbe2cf4..31c1bdfd313 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java @@ -21,7 +21,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -108,7 +107,7 @@ public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final Pay for (Map.Entry principal : principalVariation.entrySet()) { if (!DateUtils.isAfter(principal.getKey(), periodEndDate)) { - int interestForDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, principal.getKey())); + int interestForDays = DateUtils.getExactDifferenceInDays(interestStartDate, principal.getKey()); if (interestForDays > 0) { final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java index 4c7cc946433..973f53d18c4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultPaymentPeriodsInOneYearCalculator.java @@ -21,9 +21,9 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.springframework.stereotype.Component; @@ -71,8 +71,8 @@ public BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(final L periodFraction = BigDecimal.ONE; } else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) { - final int numberOfDaysInterestCalculationGraceInPeriod = Math - .toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate)); + final int numberOfDaysInterestCalculationGraceInPeriod = DateUtils.getExactDifferenceInDays(repaymentPeriodStartDate, + interestChargedFromLocalDate); periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery, numberOfDaysInterestCalculationGraceInPeriod, mc); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index c0e9f4d9bba..2b9e1b77cb9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -317,11 +317,11 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final boolean isScheduledDate = false; switch (frequency) { case DAYS: - int diff = Math.toIntExact(ChronoUnit.DAYS.between(startDate, date)); + int diff = DateUtils.getExactDifferenceInDays(startDate, date); isScheduledDate = (diff % repaidEvery) == 0; break; case WEEKS: - int weekDiff = Math.toIntExact(ChronoUnit.WEEKS.between(startDate, date)); + int weekDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.WEEKS); isScheduledDate = (weekDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusWeeks(weekDiff); @@ -329,7 +329,7 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final } break; case MONTHS: - int monthDiff = Math.toIntExact(ChronoUnit.MONTHS.between(startDate, date)); + int monthDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.MONTHS); isScheduledDate = (monthDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusMonths(monthDiff); @@ -337,7 +337,7 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final } break; case YEARS: - int yearDiff = Math.toIntExact(ChronoUnit.YEARS.between(startDate, date)); + int yearDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.YEARS); isScheduledDate = (yearDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusYears(yearDiff); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index a441f595bff..da8560127c4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -953,7 +953,7 @@ private BigDecimal calculatePeriodsInLoanTerm() { loanStartDate = getInterestChargedFromLocalDate(); } - final int periodsInLoanTermInteger = Math.toIntExact(ChronoUnit.DAYS.between(loanStartDate, this.loanEndDate)); + final int periodsInLoanTermInteger = DateUtils.getExactDifferenceInDays(loanStartDate, this.loanEndDate); periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); break; case INVALID: @@ -976,17 +976,17 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final BigDecimal numberOfPeriods = BigDecimal.ZERO; switch (this.repaymentPeriodFrequencyType) { case DAYS: - int numOfDays = Math.toIntExact(ChronoUnit.DAYS.between(startDate, endDate)); + int numOfDays = DateUtils.getExactDifferenceInDays(startDate, endDate); numberOfPeriods = BigDecimal.valueOf((double) numOfDays); break; case WEEKS: - int numberOfWeeks = Math.toIntExact(ChronoUnit.WEEKS.between(startDate, endDate)); - int daysLeftAfterWeeks = Math.toIntExact(ChronoUnit.DAYS.between(startDate.plusWeeks(numberOfWeeks), endDate)); + int numberOfWeeks = DateUtils.getExactDifference(startDate, endDate, ChronoUnit.WEEKS); + int daysLeftAfterWeeks = DateUtils.getExactDifferenceInDays(startDate.plusWeeks(numberOfWeeks), endDate); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)) .add(BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); break; case MONTHS: - int numberOfMonths = Math.toIntExact(ChronoUnit.MONTHS.between(startDate, endDate)); + int numberOfMonths = DateUtils.getExactDifference(startDate, endDate, ChronoUnit.MONTHS); LocalDate startDateAfterConsideringMonths = null; LocalDate endDateAfterConsideringMonths = null; int diffDays = 0; @@ -1007,7 +1007,7 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } if (!DateUtils.isEqual(expectedStartDate, startDate)) { - diffDays = Math.toIntExact(ChronoUnit.DAYS.between(startDate, expectedStartDate)); + diffDays = DateUtils.getExactDifferenceInDays(startDate, expectedStartDate); } if (numberOfMonths == 0) { startDateAfterConsideringMonths = expectedStartDate; @@ -1022,19 +1022,19 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } - int daysLeftAfterMonths = Math.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringMonths, endDate)) + diffDays; - int daysInPeriodAfterMonths = Math - .toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringMonths, endDateAfterConsideringMonths)); + int daysLeftAfterMonths = DateUtils.getExactDifferenceInDays(startDateAfterConsideringMonths, endDate) + diffDays; + int daysInPeriodAfterMonths = DateUtils.getExactDifferenceInDays(startDateAfterConsideringMonths, + endDateAfterConsideringMonths); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)) .add(BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths)); break; case YEARS: - int numberOfYears = Math.toIntExact(ChronoUnit.YEARS.between(startDate, endDate)); + int numberOfYears = DateUtils.getExactDifference(startDate, endDate, ChronoUnit.YEARS); LocalDate startDateAfterConsideringYears = startDate.plusYears(numberOfYears); LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1); - int daysLeftAfterYears = Math.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringYears, endDate)); - int daysInPeriodAfterYears = Math - .toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringYears, endDateAfterConsideringYears)); + int daysLeftAfterYears = DateUtils.getExactDifferenceInDays(startDateAfterConsideringYears, endDate); + int daysInPeriodAfterYears = DateUtils.getExactDifferenceInDays(startDateAfterConsideringYears, + endDateAfterConsideringYears); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)) .add(BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears)); break; @@ -1208,7 +1208,7 @@ private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator break; case DAILY: // For daily work out number of days in the period - BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(ChronoUnit.DAYS.between(periodStartDate, periodEndDate)); + BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(DateUtils.getDifferenceInDays(periodStartDate, periodEndDate)); final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc) .divide(divisor, mc); @@ -1270,7 +1270,7 @@ public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calcul final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); - int repaymentEvery = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, toDate)); + int repaymentEvery = DateUtils.getExactDifferenceInDays(fromDate, toDate); if (isFallingInRepaymentPeriod(fromDate, toDate)) { loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); repaymentEvery = getPeriodsBetween(fromDate, toDate); @@ -1681,7 +1681,7 @@ private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { switch (this.repaymentPeriodFrequencyType) { case WEEKS: - int days = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, toDate)); + int days = DateUtils.getExactDifferenceInDays(fromDate, toDate); isSameAsRepaymentPeriod = (days % 7) == 0; break; case MONTHS: @@ -1715,16 +1715,16 @@ private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { Integer numberOfPeriods = 0; switch (this.repaymentPeriodFrequencyType) { case DAYS: - numberOfPeriods = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, toDate)); + numberOfPeriods = DateUtils.getExactDifferenceInDays(fromDate, toDate); break; case WEEKS: - numberOfPeriods = Math.toIntExact(ChronoUnit.WEEKS.between(fromDate, toDate)); + numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.WEEKS); break; case MONTHS: - numberOfPeriods = Math.toIntExact(ChronoUnit.MONTHS.between(fromDate, toDate)); + numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.MONTHS); break; case YEARS: - numberOfPeriods = Math.toIntExact(ChronoUnit.YEARS.between(fromDate, toDate)); + numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.YEARS); break; default: break; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java index 4d22bde432e..bb6c02a389a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java @@ -100,7 +100,7 @@ public static void validateEMIAndEndDate(FromJsonHelper fromJsonHelper, Loan loa dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount(); if (endDate != null) { - LoanRepaymentScheduleInstallment endInstallment = loan.getRepaymentScheduleInstallment(endDate); + LoanRepaymentScheduleInstallment endInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(endDate); if (endInstallment == null) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName) @@ -257,7 +257,7 @@ public void validateForCreateAction(final JsonCommand jsonCommand, final Loan lo validateIsThereAnyIncomingChange(fromJsonHelper, jsonElement, dataValidatorBuilder); validateMultiDisburseLoan(loan, dataValidatorBuilder); - LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); + LoanRepaymentScheduleInstallment installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); validateReschedulingInstallment(dataValidatorBuilder, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment); @@ -307,7 +307,7 @@ public void validateForApproveAction(final JsonCommand jsonCommand, LoanReschedu LoanRepaymentScheduleInstallment installment; validateLoanIsActive(loan, dataValidatorBuilder); - installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); + installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); validateReschedulingInstallment(dataValidatorBuilder, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 6fa0875e3ad..6a8b3b04a7f 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -203,8 +203,7 @@ public Pair repr LoanCharge loanCharge = changeOperation.getLoanCharge().get(); processSingleCharge(loanCharge, currency, installments, disbursementDate); if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) { - overpaidTransactions = processOverpaidTransactions(overpaidTransactions, currency, installments, charges, - changedTransactionDetail, overpaymentHolder, scheduleModel); + overpaidTransactions = processOverpaidTransactions(overpaidTransactions, ctx); } } } @@ -636,12 +635,11 @@ private void processSingleTransaction(LoanTransaction loanTransaction, final Pro } } - private List processOverpaidTransactions(List overpaidTransactions, MonetaryCurrency currency, - List installments, Set charges, ChangedTransactionDetail changedTransactionDetail, - MoneyHolder overpaymentHolder, ProgressiveLoanInterestScheduleModel scheduleModel) { + private List processOverpaidTransactions(List overpaidTransactions, ProgressiveTransactionCtx ctx) { List remainingTransactions = new ArrayList<>(overpaidTransactions); - TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail, - scheduleModel); + MonetaryCurrency currency = ctx.getCurrency(); + MoneyHolder overpaymentHolder = ctx.getOverpaymentHolder(); + Set charges = ctx.getCharges(); Money zero = Money.zero(currency); for (LoanTransaction transaction : overpaidTransactions) { Money overpayment = transaction.getOverPaymentPortion(currency); @@ -1150,9 +1148,8 @@ private void handleChargePayment(LoanTransaction loanTransaction, TransactionCtx int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper .fetchFirstNormalInstallmentNumber(transactionCtx.getInstallments()); for (final LoanRepaymentScheduleInstallment installment : transactionCtx.getInstallments()) { - boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate()); + boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(), + installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)); if (isDue) { Integer installmentNumber = installment.getInstallmentNumber(); Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero); @@ -1612,12 +1609,11 @@ private LocalDate calculateNewPayDateInCaseOfInAdvancePayment(LoanTransaction lo } @NotNull - private Set getLoanChargesOfInstallment(Set charges, LoanRepaymentScheduleInstallment currentInstallment, - int firstNormalInstallmentNumber) { - return charges.stream().filter(loanCharge -> currentInstallment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(currentInstallment.getFromDate(), - currentInstallment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(currentInstallment.getFromDate(), currentInstallment.getDueDate())) + private Set getLoanChargesOfInstallment(Set charges, LoanRepaymentScheduleInstallment installment, + int firstInstallmentNumber) { + boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstInstallmentNumber); + return charges.stream() + .filter(loanCharge -> loanCharge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment)) .collect(Collectors.toSet()); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java index a3d7daa0824..05fe590ff2f 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java @@ -44,5 +44,4 @@ public ProgressiveTransactionCtx(MonetaryCurrency currency, List 1 ? repaymentPeriods.get(repaymentPeriods.size() - 1) : firstPeriod; - return Math.toIntExact(ChronoUnit.DAYS.between(firstPeriod.getFromDate(), lastPeriod.getDueDate())); + return DateUtils.getExactDifferenceInDays(firstPeriod.getFromDate(), lastPeriod.getDueDate()); } public LocalDate getMaturityDate() { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 8cd0d887899..58e16fc1edd 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; @@ -248,26 +249,23 @@ public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicatio public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { + throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); + } + List installments = loan.getRepaymentScheduleInstallments(); + LoanRepaymentScheduleInstallment actualInstallment = LoanRepaymentScheduleProcessingWrapper.findInPeriod(onDate, installments) + .orElse(installments.get(0)); LocalDate transactionDate = switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) { case TILL_PRE_CLOSURE_DATE -> onDate; - case TILL_REST_FREQUENCY_DATE -> // find due date of current installment - installments.stream().filter(it -> it.getFromDate().isBefore(onDate) && it.getDueDate().isAfter(onDate)).findFirst() - .orElse(installments.get(0)).getDueDate(); + case TILL_REST_FREQUENCY_DATE -> actualInstallment.getDueDate(); // due date of current installment case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE"); }; - if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { - throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); - } ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), loan.retrieveListOfTransactionsForReprocessing(), currency, installments, loan.getActiveCharges()).getRight(); - LoanRepaymentScheduleInstallment actualInstallment = installments.stream() - .filter(it -> transactionDate.isAfter(it.getFromDate()) && !transactionDate.isAfter(it.getDueDate())).findFirst() - .orElse(installments.get(0)); - PayableDetails result = emiCalculator.getPayableDetails(model, actualInstallment.getDueDate(), transactionDate); // TODO: We should add all the past due outstanding amounts as well OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) // @@ -333,8 +331,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod, Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java index 4f767f5fc58..872e8b7bfb0 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java @@ -109,7 +109,7 @@ public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) { if (hasInterestRateChange) { installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); } else { - installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); + installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); } validateReschedulingInstallment(dataValidatorBuilder, installment); @@ -161,7 +161,7 @@ public void validateForApproveAction(JsonCommand jsonCommand, LoanRescheduleRequ loan.getId(), rescheduleFromDate); } } else { - installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); + installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); } validateReschedulingInstallment(dataValidatorBuilder, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment); diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index d1f9de0e0de..f2960035ea2 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -144,7 +144,7 @@ public void chargePaymentTransactionTestWithExactAmount() { when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(loanTransaction.getLoan()).thenReturn(loan); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); @@ -188,7 +188,7 @@ public void chargePaymentTransactionTestWithLessTransactionAmount() { when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(loanTransaction.getLoan()).thenReturn(loan); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); @@ -236,7 +236,7 @@ public void chargePaymentTransactionTestWithMoreTransactionAmount() { when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 7ae511925dd..cafeb9f613f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -32,7 +32,6 @@ import java.math.MathContext; import java.time.LocalDate; import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -906,7 +905,7 @@ public void assempleVariableScheduleFrom(final Loan loan, final String json) { LocalDate previousDate = loan.getDisbursementDate(); for (LocalDate duedate : dueDates) { - int gap = Math.toIntExact(ChronoUnit.DAYS.between(previousDate, duedate)); + int gap = DateUtils.getExactDifferenceInDays(previousDate, duedate); previousDate = duedate; if (gap < minGap || (maxGap != null && gap > maxGap)) { baseDataValidator.reset().value(duedate).failWithCodeNoParameterAddedToErrorCode( diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java index 8b35fef4760..308a5410746 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java @@ -22,12 +22,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import org.apache.commons.lang3.ObjectUtils; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; @@ -181,7 +181,7 @@ public LoanScheduleData extractData(final ResultSet rs) throws SQLException, Dat Integer daysInPeriod = 0; if (fromDate != null) { - daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, dueDate)); + daysInPeriod = DateUtils.getExactDifferenceInDays(fromDate, dueDate); loanTermInDays = loanTermInDays + daysInPeriod; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index 266d52c6640..9a730875101 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -158,7 +157,6 @@ private void addPeriodicAccruals(final LocalDate tillDate, Collection loanScheduleAccrualData) { @@ -239,7 +237,6 @@ public void reprocessExistingAccruals(Loan loan) { reprocessNonPeriodicAccruals(loan, accruals); } } - } /** @@ -299,7 +296,6 @@ public void processIncomePostingAndAccruals(Loan loan) { /** * method calculates accruals for loan on loan closure */ - @Override public void processAccrualsForLoanClosure(Loan loan) { // check and process accruals for loan WITHOUT interest recalculation details and compounding posted as income @@ -307,13 +303,11 @@ public void processAccrualsForLoanClosure(Loan loan) { // check and process accruals for loan WITH interest recalculation details and compounding posted as income processIncomeAndAccrualTransactionOnLoanClosure(loan); - } /** * method calculates accruals for loan on loan fore closure */ - @Override public void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate, Collection newAccrualTransactions) { @@ -337,7 +331,6 @@ public void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDa feePortion, penaltyPortion, total); } } - } private void calculateFinalAccrualsForScheduleTillSpecificDateAndAddAccrualAccounting(final LocalDate tillDate, @@ -414,7 +407,7 @@ private BigDecimal getInterestAccruedTillDate(LocalDate tillDate, LoanScheduleAc } } - int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate())); + int totalNumberOfDays = DateUtils.getExactDifferenceInDays(interestStartDate, accrualData.getDueDateAsLocaldate()); LocalDate startDate = accrualData.getFromDateAsLocaldate(); if (DateUtils.isBefore(startDate, accrualData.getInterestCalculatedFrom())) { if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), tillDate)) { @@ -423,7 +416,7 @@ private BigDecimal getInterestAccruedTillDate(LocalDate tillDate, LoanScheduleAc startDate = tillDate; } } - int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tillDate)); + int daysToBeAccrued = DateUtils.getExactDifferenceInDays(startDate, tillDate); double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays; if (daysToBeAccrued >= totalNumberOfDays) { @@ -535,7 +528,8 @@ private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, B // update repayment schedule portions - LoanRepaymentScheduleInstallment loanScheduleInstallment = loan.getRepaymentScheduleInstallment(scheduleAccrualData.getDueDate()); + LoanRepaymentScheduleInstallment loanScheduleInstallment = loan + .fetchLoanRepaymentScheduleInstallmentByDueDate(scheduleAccrualData.getDueDate()); loanScheduleInstallment.updateAccrualPortion(Money.of(currency, totalAccInterest), Money.of(currency, totalAccFee), Money.of(currency, totalAccPenalty)); @@ -972,9 +966,7 @@ private void generateLoanScheduleAccrualData(final LocalDate accruedTill, } for (final LoanCharge loanCharge : loanCharges) { - boolean isDue = isFirstNormalInstallment - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate); + boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), chargesTillDate, isFirstNormalInstallment); if (isDue) { if (loanCharge.isFeeCharge()) { dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); @@ -1010,9 +1002,7 @@ private void createAccrualTransactionAndUpdateChargesPaidBy(Loan loan, LocalDate loan.addLoanTransaction(accrualTransaction); Set accrualCharges = accrualTransaction.getLoanChargesPaid(); for (LoanCharge loanCharge : loan.getActiveCharges()) { - boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, foreClosureDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate); + boolean isDue = loanCharge.isDueInPeriod(fromDate, foreClosureDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate())); if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) { final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge, loanCharge.getAmountOutstanding(currency).getAmount(), null); @@ -1177,9 +1167,7 @@ private void determineFeeDetails(Loan loan, LocalDate fromDate, LocalDate toDate List loanCharges = new ArrayList<>(); List loanInstallmentCharges = new ArrayList<>(); for (LoanCharge loanCharge : loan.getActiveCharges()) { - boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, toDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate); + boolean isDue = loanCharge.isDueInPeriod(fromDate, toDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate())); if (isDue) { if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) { penalties = penalties.add(loanCharge.amount()); @@ -1339,7 +1327,6 @@ private LoanTransaction createAccrualTransaction(Loan loan, Money interestPortio } private void determineReceivableIncomeDetailsForLoanClosure(Loan loan, Map incomeDetails) { - MonetaryCurrency currency = loan.getCurrency(); Money interestPortion = Money.zero(currency); Money feePortion = Money.zero(currency); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index ac65d2c3340..34cf975ba93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -405,12 +405,16 @@ private void copyAdvancedPaymentRulesIfApplicable(String transactionProcessingSt .map(r -> new LoanPaymentAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes(), r.getFutureInstallmentAllocationRule())) .toList(); - loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules); + List paymentAllocationRules = loanApplication.getPaymentAllocationRules(); + paymentAllocationRules.clear(); + paymentAllocationRules.addAll(loanPaymentAllocationRules); if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) { List loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream() .map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList(); - loanApplication.setCreditAllocationRules(loanCreditAllocationRules); + List creditAllocationRules = loanApplication.getCreditAllocationRules(); + creditAllocationRules.clear(); + creditAllocationRules.addAll(loanCreditAllocationRules); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index c80f79d8907..652fe67f03a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -25,7 +25,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -1245,7 +1244,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep Integer daysInPeriod = 0; if (fromDate != null) { - daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, dueDate)); + daysInPeriod = DateUtils.getExactDifferenceInDays(fromDate, dueDate); loanTermInDays = loanTermInDays + daysInPeriod; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 0c24d03b93d..3b6e1aa3a40 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1081,8 +1081,7 @@ private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loa loan.addLoanTransaction(newInterestPaymentWaiverTransaction); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessorFactory() - .determineProcessor(loan.getTransactionProcessingStrategyCode()); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor(); final LoanRepaymentScheduleInstallment currentInstallment = loan .fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generateadhocclientschhedule/GenerateAdhocClientScheduleTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generateadhocclientschhedule/GenerateAdhocClientScheduleTasklet.java index 1bbdd9ffee9..45d3c44cb5d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generateadhocclientschhedule/GenerateAdhocClientScheduleTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/generateadhocclientschhedule/GenerateAdhocClientScheduleTasklet.java @@ -57,23 +57,23 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon switch (ReportRunFrequency.fromId(adhoc.getReportRunFrequency())) { case DAILY -> { next = start.plusDays(1); - run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= 1; + run = DateUtils.getExactDifferenceInDays(start, end) >= 1; } case WEEKLY -> { next = start.plusDays(7); - run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= 7; + run = DateUtils.getExactDifferenceInDays(start, end) >= 7; } case MONTHLY -> { next = start.plusMonths(1); - run = Math.toIntExact(ChronoUnit.MONTHS.between(start, end)) >= 1; + run = DateUtils.getExactDifference(start, end, ChronoUnit.MONTHS) >= 1; } case YEARLY -> { next = start.plusYears(1); - run = Math.toIntExact(ChronoUnit.YEARS.between(start, end)) >= 1; + run = DateUtils.getExactDifference(start, end, ChronoUnit.YEARS) >= 1; } case CUSTOM -> { next = start.plusDays((long) adhoc.getReportRunEvery()); - run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= adhoc.getReportRunEvery(); + run = DateUtils.getExactDifferenceInDays(start, end) >= adhoc.getReportRunEvery(); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 7480918b3e9..dcb7e534b6b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -25,7 +25,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -462,16 +461,16 @@ public List extractData(final ResultSet rs) throws SQLExcept LocalDate currentDate = DateUtils.getBusinessLocalDate(); if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { - daysToEscheat = Math - .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); + daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToEscheat)); } if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { - daysToDormancy = Math.toIntExact( - ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); + daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToDormancy)); } if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { - daysToInactive = Math.toIntExact( - ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); + daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToInactive)); } } final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); @@ -837,16 +836,13 @@ public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") LocalDate currentDate = DateUtils.getBusinessLocalDate(); if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { - daysToEscheat = Math - .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); + daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat)); } if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { - daysToDormancy = Math - .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); + daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy)); } if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { - daysToInactive = Math - .toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); + daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java index 57e444b6fb6..ef2d8d7fc26 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -102,13 +101,13 @@ private long calculateNumberOfShareDays(final LocalDate postingDate, final Local if (DateUtils.isBefore(shareStartDate, lastDividendPostDate)) { shareStartDate = lastDividendPostDate; } - int numberOfPurchseDays = Math.toIntExact(ChronoUnit.DAYS.between(shareStartDate, postingDate)); + int numberOfPurchseDays = DateUtils.getExactDifferenceInDays(shareStartDate, postingDate); if (type.isPurchased() && numberOfPurchseDays < minimumActivePeriod) { continue; } if (lastDividendAppliedDate != null) { - numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, shareStartDate)) + numberOfShareDaysPerAccount += DateUtils.getExactDifferenceInDays(lastDividendAppliedDate, shareStartDate) * numberOfShares; } lastDividendAppliedDate = shareStartDate; @@ -121,8 +120,7 @@ private long calculateNumberOfShareDays(final LocalDate postingDate, final Local } } if (lastDividendAppliedDate != null) { - numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, postingDate)) - * numberOfShares; + numberOfShareDaysPerAccount += DateUtils.getExactDifferenceInDays(lastDividendAppliedDate, postingDate) * numberOfShares; } numberOfShareDays += numberOfShareDaysPerAccount; numberOfSharesdaysPerAccount.put(accountData.getId(), numberOfShareDaysPerAccount); diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java index a1b7868d853..2e97e34e93d 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java @@ -37,6 +37,7 @@ import java.util.Objects; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; @Embeddable @@ -335,16 +336,16 @@ public Integer depositPeriod(final LocalDate periodStartDate, final LocalDate pe final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(periodType()); switch (periodFrequencyType) { case DAYS: - actualDepositPeriod = Math.toIntExact(ChronoUnit.DAYS.between(periodStartDate, periodEndDate)); + actualDepositPeriod = DateUtils.getExactDifferenceInDays(periodStartDate, periodEndDate); break; case WEEKS: - actualDepositPeriod = Math.toIntExact(ChronoUnit.WEEKS.between(periodStartDate, periodEndDate)); + actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.WEEKS); break; case MONTHS: - actualDepositPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(periodStartDate, periodEndDate)); + actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.MONTHS); break; case YEARS: - actualDepositPeriod = Math.toIntExact(ChronoUnit.YEARS.between(periodStartDate, periodEndDate)); + actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.YEARS); break; case INVALID: actualDepositPeriod = 0;// default value