FINERACT-2081: Code quality, eliminate duplicated methods, use utility

This commit is contained in:
Marta Jankovics
2024-10-30 15:54:48 +01:00
committed by Adam Saghy
parent 001d586e30
commit 9812f14ad7
33 changed files with 373 additions and 577 deletions
@@ -20,7 +20,6 @@ package org.apache.fineract.accounting.glaccount.jobs.updatetrialbalancedetails;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -51,7 +50,7 @@ public class UpdateTrialBalanceDetailsTasklet implements Tasklet {
.append("where je.transaction_date > (select coalesce(MAX(created_date),'2010-01-01') from m_trial_balance)"); .append("where je.transaction_date > (select coalesce(MAX(created_date),'2010-01-01') from m_trial_balance)");
final List<LocalDate> tbGaps = jdbcTemplate.queryForList(tbGapSqlBuilder.toString(), LocalDate.class); final List<LocalDate> tbGaps = jdbcTemplate.queryForList(tbGapSqlBuilder.toString(), LocalDate.class);
for (LocalDate tbGap : tbGaps) { for (LocalDate tbGap : tbGaps) {
int days = Math.toIntExact(ChronoUnit.DAYS.between(tbGap, DateUtils.getBusinessLocalDate())); int days = DateUtils.getExactDifferenceInDays(tbGap, DateUtils.getBusinessLocalDate());
if (days < 1) { if (days < 1) {
continue; continue;
} }
@@ -19,7 +19,6 @@
package org.apache.fineract.infrastructure.core.domain; package org.apache.fineract.infrastructure.core.domain;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -51,7 +50,7 @@ public class LocalDateInterval {
} }
private Integer daysBetween() { 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) { public boolean containsPortionOf(final LocalDateInterval interval) {
@@ -305,8 +305,23 @@ public final class DateUtils {
return first != null && (second == null || first.isAfter(second)); return first != null && (second == null || first.isAfter(second));
} }
public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) { public static long getDifference(LocalDate first, LocalDate second, @NotNull ChronoUnit unit) {
return DAYS.between(localDateBefore, localDateAfter); 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 // Parse, format
@@ -362,17 +377,29 @@ public final class DateUtils {
* *
* @param targetDate * @param targetDate
* the date to be checked * the date to be checked
* @param startDate * @param fromDate
* the start date of the range * the start date of the range
* @param endDate * @param toDate
* the end date of the range * the end date of the range
* @return true if targetDate is within range or equal to start/end dates, otherwise false * @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) { public static boolean isDateWithinRange(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) {
if (targetDate == null || startDate == null || endDate == null) { if (targetDate == null || fromDate == null || toDate == null) {
throw new IllegalArgumentException("Dates must not be 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 @NotNull
@@ -398,14 +425,4 @@ public final class DateUtils {
} }
return formatter; 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);
}
} }
@@ -42,7 +42,6 @@ import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -60,6 +59,7 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -152,7 +152,6 @@ import org.apache.fineract.useradministration.domain.AppUser;
@Entity @Entity
@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"), @Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
@UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") }) @UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") })
@Setter
@Getter @Getter
public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> { public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@@ -180,9 +179,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Version @Version
int version; int version;
@Setter()
@Column(name = "account_no", length = 20, unique = true, nullable = false) @Column(name = "account_no", length = 20, unique = true, nullable = false)
private String accountNumber; private String accountNumber;
@Setter()
@Column(name = "external_id") @Column(name = "external_id")
private ExternalId externalId; private ExternalId externalId;
@@ -194,6 +195,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@JoinColumn(name = "group_id") @JoinColumn(name = "group_id")
private Group group; private Group group;
@Setter()
@ManyToOne @ManyToOne
@JoinColumn(name = "glim_id") @JoinColumn(name = "glim_id")
private GroupLoanIndividualMonitoringAccount glim; private GroupLoanIndividualMonitoringAccount glim;
@@ -236,55 +238,72 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Embedded @Embedded
private LoanProductRelatedDetail loanRepaymentScheduleDetail; private LoanProductRelatedDetail loanRepaymentScheduleDetail;
@Setter()
@Column(name = "term_frequency", nullable = false) @Column(name = "term_frequency", nullable = false)
private Integer termFrequency; private Integer termFrequency;
@Setter()
@Column(name = "term_period_frequency_enum", nullable = false) @Column(name = "term_period_frequency_enum", nullable = false)
private Integer termPeriodFrequencyType; private Integer termPeriodFrequencyType;
@Setter(AccessLevel.PACKAGE)
@Column(name = "loan_status_id", nullable = false) @Column(name = "loan_status_id", nullable = false)
private Integer loanStatus; private Integer loanStatus;
@Setter()
@Column(name = "sync_disbursement_with_meeting") @Column(name = "sync_disbursement_with_meeting")
private Boolean syncDisbursementWithMeeting; private Boolean syncDisbursementWithMeeting;
// loan application states // loan application states
@Setter()
@Column(name = "submittedon_date") @Column(name = "submittedon_date")
private LocalDate submittedOnDate; private LocalDate submittedOnDate;
@Setter()
@Column(name = "rejectedon_date") @Column(name = "rejectedon_date")
private LocalDate rejectedOnDate; private LocalDate rejectedOnDate;
@Setter()
@ManyToOne(optional = true, fetch = FetchType.LAZY) @ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "rejectedon_userid") @JoinColumn(name = "rejectedon_userid")
private AppUser rejectedBy; private AppUser rejectedBy;
@Setter()
@Column(name = "withdrawnon_date") @Column(name = "withdrawnon_date")
private LocalDate withdrawnOnDate; private LocalDate withdrawnOnDate;
@Setter()
@ManyToOne(optional = true, fetch = FetchType.LAZY) @ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "withdrawnon_userid") @JoinColumn(name = "withdrawnon_userid")
private AppUser withdrawnBy; private AppUser withdrawnBy;
@Setter()
@Column(name = "approvedon_date") @Column(name = "approvedon_date")
private LocalDate approvedOnDate; private LocalDate approvedOnDate;
@Setter()
@ManyToOne(optional = true, fetch = FetchType.LAZY) @ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "approvedon_userid") @JoinColumn(name = "approvedon_userid")
private AppUser approvedBy; private AppUser approvedBy;
@Setter()
@Column(name = "expected_disbursedon_date") @Column(name = "expected_disbursedon_date")
private LocalDate expectedDisbursementDate; private LocalDate expectedDisbursementDate;
@Setter()
@Column(name = "disbursedon_date") @Column(name = "disbursedon_date")
private LocalDate actualDisbursementDate; private LocalDate actualDisbursementDate;
@Setter()
@ManyToOne(optional = true, fetch = FetchType.LAZY) @ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "disbursedon_userid") @JoinColumn(name = "disbursedon_userid")
private AppUser disbursedBy; private AppUser disbursedBy;
@Setter()
@Column(name = "closedon_date") @Column(name = "closedon_date")
private LocalDate closedOnDate; private LocalDate closedOnDate;
@Setter()
@ManyToOne(optional = true, fetch = FetchType.LAZY) @ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "closedon_userid") @JoinColumn(name = "closedon_userid")
private AppUser closedBy; private AppUser closedBy;
@@ -302,18 +321,22 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "expected_maturedon_date") @Column(name = "expected_maturedon_date")
private LocalDate expectedMaturityDate; private LocalDate expectedMaturityDate;
@Setter()
@Column(name = "maturedon_date") @Column(name = "maturedon_date")
private LocalDate actualMaturityDate; private LocalDate actualMaturityDate;
@Setter()
@Column(name = "expected_firstrepaymenton_date") @Column(name = "expected_firstrepaymenton_date")
private LocalDate expectedFirstRepaymentOnDate; private LocalDate expectedFirstRepaymentOnDate;
@Setter()
@Column(name = "interest_calculated_from_date") @Column(name = "interest_calculated_from_date")
private LocalDate interestChargedFromDate; private LocalDate interestChargedFromDate;
@Column(name = "total_overpaid_derived", scale = 6, precision = 19) @Column(name = "total_overpaid_derived", scale = 6, precision = 19)
private BigDecimal totalOverpaid; private BigDecimal totalOverpaid;
@Setter()
@Column(name = "overpaidon_date") @Column(name = "overpaidon_date")
private LocalDate overpaidOnDate; private LocalDate overpaidOnDate;
@@ -359,18 +382,23 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Transient @Transient
private LoanSummaryWrapper loanSummaryWrapper; private LoanSummaryWrapper loanSummaryWrapper;
@Setter()
@Column(name = "principal_amount_proposed", scale = 6, precision = 19, nullable = false) @Column(name = "principal_amount_proposed", scale = 6, precision = 19, nullable = false)
private BigDecimal proposedPrincipal; private BigDecimal proposedPrincipal;
@Setter()
@Column(name = "approved_principal", scale = 6, precision = 19, nullable = false) @Column(name = "approved_principal", scale = 6, precision = 19, nullable = false)
private BigDecimal approvedPrincipal; private BigDecimal approvedPrincipal;
@Setter()
@Column(name = "net_disbursal_amount", scale = 6, precision = 19, nullable = false) @Column(name = "net_disbursal_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal netDisbursalAmount; private BigDecimal netDisbursalAmount;
@Setter()
@Column(name = "fixed_emi_amount", scale = 6, precision = 19) @Column(name = "fixed_emi_amount", scale = 6, precision = 19)
private BigDecimal fixedEmiAmount; private BigDecimal fixedEmiAmount;
@Setter()
@Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19) @Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19)
private BigDecimal maxOutstandingLoanBalance; private BigDecimal maxOutstandingLoanBalance;
@@ -394,9 +422,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "is_npa", nullable = false) @Column(name = "is_npa", nullable = false)
private boolean isNpa; private boolean isNpa;
@Setter()
@Column(name = "accrued_till") @Column(name = "accrued_till")
private LocalDate accruedTill; private LocalDate accruedTill;
@Setter()
@Column(name = "create_standing_instruction_at_disbursement") @Column(name = "create_standing_instruction_at_disbursement")
private Boolean createStandingInstructionAtDisbursement; private Boolean createStandingInstructionAtDisbursement;
@@ -406,9 +436,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "interest_recalcualated_on") @Column(name = "interest_recalcualated_on")
private LocalDate interestRecalculatedOn; private LocalDate interestRecalculatedOn;
@Setter()
@Column(name = "is_floating_interest_rate") @Column(name = "is_floating_interest_rate")
private Boolean isFloatingInterestRate; private Boolean isFloatingInterestRate;
@Setter()
@Column(name = "interest_rate_differential", scale = 6, precision = 19) @Column(name = "interest_rate_differential", scale = 6, precision = 19)
private BigDecimal interestRateDifferential; private BigDecimal interestRateDifferential;
@@ -432,9 +464,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id")) @JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id"))
private List<Rate> rates; private List<Rate> rates;
@Setter()
@Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5) @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5)
private BigDecimal fixedPrincipalPercentagePerInstallment; private BigDecimal fixedPrincipalPercentagePerInstallment;
@Setter()
@Column(name = "last_closed_business_date") @Column(name = "last_closed_business_date")
private LocalDate lastClosedBusinessDate; private LocalDate lastClosedBusinessDate;
@@ -715,8 +749,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public ChangedTransactionDetail reprocessTransactions() { public ChangedTransactionDetail reprocessTransactions() {
ChangedTransactionDetail changedTransactionDetail; ChangedTransactionDetail changedTransactionDetail;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
@@ -787,20 +820,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
addLoanTransaction(chargesPayment); addLoanTransaction(chargesPayment);
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, this); loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, this);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>(); final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
.fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments); .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()) if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null
: charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()); && charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) {
if (installmentNumber == null && isFirstNormalInstallment) {
chargePaymentInstallments.add(installment);
break;
} else if (installment.getInstallmentNumber().equals(installmentNumber)) {
chargePaymentInstallments.add(installment); chargePaymentInstallments.add(installment);
break; break;
} }
@@ -869,8 +897,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge); removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { 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 * 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 class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
/* /*
* TODO Vishwas Currently we do not allow waiving updating loan charge after a loan is approved (hence there * TODO Vishwas Currently we do not allow waiving updating loan charge after a loan is approved (hence there
@@ -1104,8 +1130,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
// to be reprocessed. // to be reprocessed.
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
/* /*
* TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan * TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan
@@ -1690,7 +1715,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
validateAccountStatus(LoanEvent.LOAN_APPROVAL_UNDO); validateAccountStatus(LoanEvent.LOAN_APPROVAL_UNDO);
final Map<String, Object> actualChanges = new LinkedHashMap<>(); final Map<String, Object> actualChanges = new LinkedHashMap<>();
final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); final LoanStatus currentStatus = getStatus();
final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, this); final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, this);
if (!statusEnum.hasStateOf(currentStatus)) { if (!statusEnum.hasStateOf(currentStatus)) {
loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, this); loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, this);
@@ -1741,7 +1766,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this); final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this);
boolean isMultiTrancheDisburse = false; boolean isMultiTrancheDisburse = false;
LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); LoanStatus actualLoanStatus = getStatus();
if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid())
&& isAllTranchesNotDisbursed()) { && isAllTranchesNotDisbursed()) {
isMultiTrancheDisburse = true; isMultiTrancheDisburse = true;
@@ -2117,7 +2142,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO);
final Map<String, Object> actualChanges = new LinkedHashMap<>(); final Map<String, Object> 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); final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSAL_UNDO, this);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate()); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate());
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
@@ -2393,8 +2418,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallmentByDueDate( final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallmentByDueDate(
loanTransaction.getTransactionDate()); loanTransaction.getTransactionDate());
@@ -2471,12 +2495,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return loanTransactionDate; return loanTransactionDate;
} }
public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) {
return getRepaymentScheduleInstallments().stream() //
.filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() //
.orElse(null);
}
public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() { public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() {
return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE) return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE)
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -2726,8 +2744,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanTransaction writeOffTransaction = findWriteOffTransaction(); final LoanTransaction writeOffTransaction = findWriteOffTransaction();
writeOffTransaction.reverse(); writeOffTransaction.reverse();
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
@@ -2802,8 +2819,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor); loanRepaymentScheduleTransactionProcessor);
@@ -2816,7 +2832,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, this); final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, this);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { if (!statusEnum.hasStateOf(getStatus())) {
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, this); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, this);
changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
@@ -2934,8 +2950,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final String errorMessage = "The date on which a loan is closed cannot be in the future."; 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); throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor); loanRepaymentScheduleTransactionProcessor);
@@ -2946,7 +2961,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.closedOnDate = closureDate; this.closedOnDate = closureDate;
final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); 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); loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this);
changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
} }
@@ -2978,7 +2993,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
// has 'overpaid' amount // has 'overpaid' amount
this.closedOnDate = closureDate; this.closedOnDate = closureDate;
final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); 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); loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this);
changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
} }
@@ -3005,7 +3020,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.closedOnDate = rescheduledOn; this.closedOnDate = rescheduledOn;
final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_RESCHEDULE, this); 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); loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, this);
changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
} }
@@ -3093,7 +3108,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
public boolean isAllTranchesNotDisbursed() { public boolean isAllTranchesNotDisbursed() {
LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); LoanStatus actualLoanStatus = getStatus();
boolean isInRightStatus = actualLoanStatus.isActive() || actualLoanStatus.isApproved() || actualLoanStatus.isClosedObligationsMet() boolean isInRightStatus = actualLoanStatus.isActive() || actualLoanStatus.isApproved() || actualLoanStatus.isClosedObligationsMet()
|| actualLoanStatus.isOverpaid(); || actualLoanStatus.isOverpaid();
return this.loanProduct.isMultiDisburseLoan() && isInRightStatus && isDisbursementAllowed(); return this.loanProduct.isMultiDisburseLoan() && isInRightStatus && isDisbursementAllowed();
@@ -3758,12 +3773,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.loanTransactions.remove(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) { private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) {
if (this.client != null && this.client.getOfficeJoiningDate() != null) { if (this.client != null && this.client.getOfficeJoiningDate() != null) {
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate();
@@ -4170,8 +4179,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
regenerateRepaymentSchedule(scheduleGeneratorDTO); regenerateRepaymentSchedule(scheduleGeneratorDTO);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
@@ -4216,21 +4224,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return emiAmount; return emiAmount;
} }
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
LoanRepaymentScheduleInstallment installment = null;
if (installmentNumber == null) {
return installment;
}
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment scheduleInstallment : installments) {
if (scheduleInstallment.getInstallmentNumber().equals(installmentNumber)) {
installment = scheduleInstallment;
break;
}
}
return installment;
}
public Money getTotalOverpaidAsMoney() { public Money getTotalOverpaidAsMoney() {
return Money.of(this.repaymentScheduleDetail().getCurrency(), this.totalOverpaid); return Money.of(this.repaymentScheduleDetail().getCurrency(), this.totalOverpaid);
} }
@@ -4295,8 +4288,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
public ChangedTransactionDetail processTransactions() { public ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
@@ -4348,8 +4340,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
public void processPostDisbursementTransactions() { public void processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> copyTransactions = new ArrayList<>(); final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) { if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
@@ -4376,8 +4367,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(), return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom()); loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
@@ -4394,8 +4384,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod); .create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this, outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor); scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
} else { } else {
@@ -4560,45 +4549,42 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.repaymentScheduleInstallments.add(installment); this.repaymentScheduleInstallments.add(installment);
} }
/**
* @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<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : installments) {
if (DateUtils.isEqual(dueDate, repaymentScheduleInstallment.getDueDate())) {
installment = repaymentScheduleInstallment;
break;
}
}
}
return installment;
}
/** /**
* @param date * @param date
* @return a schedule installment is related to the provided date * @return a schedule installment is related to the provided date
**/ **/
public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) {
if (date == null) { // TODO first installment should be fromInclusive
return null; return getRepaymentScheduleInstallment(e -> DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate()));
} }
return getRepaymentScheduleInstallments()//
.stream()// public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
.filter(installment -> date.isAfter(installment.getFromDate()) && !date.isAfter(installment.getDueDate()))// return getRepaymentScheduleInstallment(e -> e.getInstallmentNumber().equals(installmentNumber));
.findAny()// }
.orElse(null);//
/**
* @param dueDate
* the due date of the installment
* @return a schedule installment with similar due date to the one provided
**/
public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) {
return getRepaymentScheduleInstallment(e -> DateUtils.isEqual(dueDate, e.getDueDate()));
}
/**
* @param predicate
* filter of the installments
* @return the first installment matching the filter
**/
public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(
@NotNull Predicate<LoanRepaymentScheduleInstallment> predicate) {
return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null);
} }
/** /**
* @return loan disbursement data * @return loan disbursement data
**/ **/
public List<DisbursementData> getDisbursmentData() { public List<DisbursementData> getDisbursementData() {
Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator(); Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator();
List<DisbursementData> disbursementData = new ArrayList<>(); List<DisbursementData> disbursementData = new ArrayList<>();
@@ -4690,7 +4676,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final BigDecimal emiAmount = getFixedEmiAmount(); final BigDecimal emiAmount = getFixedEmiAmount();
final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance(); final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
final List<DisbursementData> disbursementData = getDisbursmentData(); final List<DisbursementData> disbursementData = getDisbursementData();
CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
if (loanCalendar != null) { if (loanCalendar != null) {
@@ -4883,8 +4869,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
} }
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
// If it's a refund // If it's a refund
if (adjustedTransaction == null) { if (adjustedTransaction == null) {
@@ -4914,8 +4899,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.chargeback.transaction", errorMessage); throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.chargeback.transaction", errorMessage);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
.determineProcessor(this.transactionProcessingStrategyCode);
addLoanTransaction(chargebackTransaction); addLoanTransaction(chargebackTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(), loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(),
@@ -5192,16 +5176,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
Money feeAccountedForCurrentPeriod = Money.zero(getCurrency()); Money feeAccountedForCurrentPeriod = Money.zero(getCurrency());
Money interestForCurrentPeriod = Money.zero(getCurrency()); Money interestForCurrentPeriod = Money.zero(getCurrency());
Money interestAccountedForCurrentPeriod = Money.zero(getCurrency()); Money interestAccountedForCurrentPeriod = Money.zero(getCurrency());
int totalPeriodDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate())); int totalPeriodDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate());
int tillDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), paymentDate)); int tillDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), paymentDate);
interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal
.valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(getCurrency()).getAmount(), tillDays))); .valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(getCurrency()).getAmount(), tillDays)));
interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency())); interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency()));
for (LoanCharge loanCharge : this.charges) { for (LoanCharge loanCharge : this.charges) {
if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
boolean isDue = isFirstNormalInstallment boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment);
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate);
if (isDue) { if (isDue) {
if (loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(getCurrency())); penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(getCurrency()));
@@ -5251,8 +5233,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
balances[1] = fee; balances[1] = fee;
balances[2] = penalty; balances[2] = penalty;
break; break;
} else if (DateUtils.isAfter(paymentDate, installment.getFromDate()) } else if (DateUtils.isDateInRangeExclusive(paymentDate, installment.getFromDate(), installment.getDueDate())) {
&& DateUtils.isBefore(paymentDate, installment.getDueDate())) {
balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment); balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment);
break; break;
} }
@@ -5402,7 +5383,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
BigDecimal firstDisbursalAmount; BigDecimal firstDisbursalAmount;
if (this.isMultiDisburmentLoan()) { if (this.isMultiDisburmentLoan()) {
List<DisbursementData> disbursementData = getDisbursmentData(); List<DisbursementData> disbursementData = getDisbursementData();
Collections.sort(disbursementData); Collections.sort(disbursementData);
firstDisbursalAmount = disbursementData.get(disbursementData.size() - 1).getPrincipal(); firstDisbursalAmount = disbursementData.get(disbursementData.size() - 1).getPrincipal();
} else { } else {
@@ -5540,4 +5521,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public void setIsTopup(boolean topup) { public void setIsTopup(boolean topup) {
isTopup = topup; isTopup = topup;
} }
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() {
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
}
} }
@@ -616,14 +616,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return this.loan.hasIdentifyOf(loanId); return this.loan.hasIdentifyOf(loanId);
} }
public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { public boolean isDueInPeriod(final LocalDate fromDate, final LocalDate toDate, boolean isFirstPeriod) {
final LocalDate dueDate = getDueLocalDate(); return LoanRepaymentScheduleProcessingWrapper.isInPeriod(getDueLocalDate(), fromDate, toDate, isFirstPeriod);
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 isFeeCharge() { public boolean isFeeCharge() {
@@ -31,6 +31,7 @@ import java.time.LocalDate;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -38,6 +39,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType; import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
@Getter
@Entity @Entity
@Table(name = "m_loan_repayment_schedule") @Table(name = "m_loan_repayment_schedule")
public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDateTimeCustom<Long> public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDateTimeCustom<Long>
@@ -265,30 +267,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return result; 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<PostDatedChecks> getPostDatedCheck() {
return this.postDatedChecks;
}
public LocalDate getDueDate() {
return this.dueDate;
}
public Money getCreditedPrincipal(final MonetaryCurrency currency) { public Money getCreditedPrincipal(final MonetaryCurrency currency) {
return Money.of(currency, this.creditedPrincipal); return Money.of(currency, this.creditedPrincipal);
} }
@@ -413,7 +391,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
.plus(getPenaltyChargesOutstanding(currency)); .plus(getPenaltyChargesOutstanding(currency));
} }
public void updateLoan(final Loan loan) { void updateLoan(final Loan loan) {
this.loan = loan; this.loan = loan;
} }
@@ -421,10 +399,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null); return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null);
} }
public boolean isObligationsMet() {
return this.obligationsMet;
}
public boolean isNotFullyPaidOff() { public boolean isNotFullyPaidOff() {
return !this.obligationsMet; return !this.obligationsMet;
} }
@@ -879,18 +853,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
} }
} }
public BigDecimal getTotalPaidInAdvance() {
return this.totalPaidInAdvance;
}
public BigDecimal getTotalPaidLate() {
return this.totalPaidLate;
}
public LocalDate getObligationsMetOnDate() {
return this.obligationsMetOnDate;
}
/********** UNPAY COMPONENTS ****/ /********** UNPAY COMPONENTS ****/
public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
@@ -1023,10 +985,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
.plus(getPrincipalCompleted(currency)); .plus(getPrincipalCompleted(currency));
} }
public BigDecimal getRescheduleInterestPortion() {
return rescheduleInterestPortion;
}
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
this.rescheduleInterestPortion = rescheduleInterestPortion; this.rescheduleInterestPortion = rescheduleInterestPortion;
} }
@@ -1039,14 +997,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
this.penaltyChargesWaived = newPenaltyChargesCharged; this.penaltyChargesWaived = newPenaltyChargesCharged;
} }
public Set<LoanInstallmentCharge> getInstallmentCharges() {
return installmentCharges;
}
public boolean isAdditional() {
return additional;
}
public void markAsAdditional() { public void markAsAdditional() {
this.additional = true; this.additional = true;
} }
@@ -1055,10 +1005,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return this.loanTransactionToRepaymentScheduleMappings; return this.loanTransactionToRepaymentScheduleMappings;
} }
public boolean isDownPayment() {
return isDownPayment;
}
public void resetBalances() { public void resetBalances() {
resetDerivedComponents(); resetDerivedComponents();
resetPrincipalDue(); resetPrincipalDue();
@@ -1077,8 +1023,4 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
public enum PaymentAction { public enum PaymentAction {
PAY, UNPAY PAY, UNPAY
} }
public boolean isReAged() {
return isReAged;
}
} }
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -87,7 +88,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge)); cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge));
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -133,7 +134,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (predicate.test(loanCharge)) { if (predicate.test(loanCharge)) {
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) { if (loanChargePerInstallment != null) {
@@ -156,7 +157,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (chargePredicate.test(loanCharge)) { if (chargePredicate.test(loanCharge)) {
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) { if (loanChargePerInstallment != null) {
@@ -175,11 +176,6 @@ public class LoanRepaymentScheduleProcessingWrapper {
return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); 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, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period,
final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) {
@@ -188,7 +184,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = cumulative.plus(getInstallmentFee(currency, period, loanCharge)); cumulative = cumulative.plus(getInstallmentFee(currency, period, loanCharge));
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -242,17 +238,24 @@ public class LoanRepaymentScheduleProcessingWrapper {
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber(); .filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber();
} }
public static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment targetInstallment,
List<LoanRepaymentScheduleInstallment> installments) { List<LoanRepaymentScheduleInstallment> installments) {
int firstPeriod = fetchFirstNormalInstallmentNumber(installments); int firstPeriod = fetchFirstNormalInstallmentNumber(installments);
return isInPeriod(transactionDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod)); return isInPeriod(targetDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod));
} }
private static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) {
boolean isFirstPeriod) { return isInPeriod(targetDate, installment.getFromDate(), installment.getDueDate(), isFirstPeriod);
LocalDate fromDate = targetInstallment.getFromDate(); }
LocalDate dueDate = targetInstallment.getDueDate();
return isFirstPeriod ? DateUtils.occursOnDayFromAndUpToAndIncluding(fromDate, dueDate, transactionDate) public static boolean isInPeriod(LocalDate targetDate, LocalDate fromDate, LocalDate toDate, boolean isFirstPeriod) {
: DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromDate, dueDate, transactionDate); return isFirstPeriod ? DateUtils.isDateInRangeInclusive(targetDate, fromDate, toDate)
: DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate);
}
public static Optional<LoanRepaymentScheduleInstallment> findInPeriod(LocalDate targetDate,
List<LoanRepaymentScheduleInstallment> installments) {
int firstNumber = fetchFirstNormalInstallmentNumber(installments);
return installments.stream().filter(e -> isInPeriod(targetDate, e, e.getInstallmentNumber().equals(firstNumber))).findFirst();
} }
} }
@@ -39,6 +39,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -60,6 +61,7 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
* All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers, * All monetary transactions against a loan are modelled through this entity. Disbursements, Repayments, Waivers,
* Write-off etc * Write-off etc
*/ */
@Getter
@Entity @Entity
@Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") }) @Table(name = "m_loan_transaction", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long> { public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@@ -537,22 +539,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return Money.of(currency, this.principalPortion); 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) { public Money getInterestPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.interestPortion); return Money.of(currency, this.interestPortion);
} }
@@ -581,10 +567,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return this.dateOf; return this.dateOf;
} }
public LocalDate getDateOf() {
return this.dateOf;
}
public LoanTransactionType getTypeOf() { public LoanTransactionType getTypeOf() {
return LoanTransactionType.fromInt(this.typeOf); return LoanTransactionType.fromInt(this.typeOf);
} }
@@ -872,22 +854,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return thisTransactionData; return thisTransactionData;
} }
public Loan getLoan() {
return this.loan;
}
public Set<LoanChargePaidBy> getLoanChargesPaid() {
return this.loanChargesPaid;
}
public void setLoanChargesPaid(final Set<LoanChargePaidBy> loanChargesPaid) { public void setLoanChargesPaid(final Set<LoanChargePaidBy> loanChargesPaid) {
this.loanChargesPaid = loanChargesPaid; this.loanChargesPaid = loanChargesPaid;
} }
public ExternalId getExternalId() {
return this.externalId;
}
public boolean isRefund() { public boolean isRefund() {
return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.REFUND.equals(getTypeOf()) && isNotReversed();
} }
@@ -926,14 +896,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed();
} }
public boolean isManuallyAdjustedOrReversed() {
return this.manuallyAdjustedOrReversed;
}
public boolean isNotManuallyAdjustedOrReversed() {
return !this.manuallyAdjustedOrReversed;
}
public void manuallyAdjustedOrReversed() { public void manuallyAdjustedOrReversed() {
this.manuallyAdjustedOrReversed = true; this.manuallyAdjustedOrReversed = true;
} }
@@ -1030,27 +992,15 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return isAccrual(); return isAccrual();
} }
public BigDecimal getOutstandingLoanBalance() {
return outstandingLoanBalance;
}
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) { public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
return Money.of(currency, this.outstandingLoanBalance); return Money.of(currency, this.outstandingLoanBalance);
} }
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
public boolean isPaymentTransaction() { public boolean isPaymentTransaction() {
return this.isNotReversed() && !(this.isDisbursement() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() return this.isNotReversed() && !(this.isDisbursement() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction()
|| this.isIncomePosting()); || this.isIncomePosting());
} }
public Set<LoanCollateralManagement> getLoanCollateralManagementSet() {
return this.loanCollateralManagementSet;
}
public LocalDate getSubmittedOnDate() { public LocalDate getSubmittedOnDate() {
return submittedOnDate; return submittedOnDate;
} }
@@ -1059,10 +1009,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return !loanTransactionRelations.isEmpty(); return !loanTransactionRelations.isEmpty();
} }
public Set<LoanTransactionRelation> getLoanTransactionRelations() {
return loanTransactionRelations;
}
public List<LoanTransactionRelation> getLoanTransactionRelations(Predicate<LoanTransactionRelation> predicate) { public List<LoanTransactionRelation> getLoanTransactionRelations(Predicate<LoanTransactionRelation> predicate) {
return loanTransactionRelations.stream().filter(predicate).toList(); return loanTransactionRelations.stream().filter(predicate).toList();
} }
@@ -1078,10 +1024,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
} }
} }
public BigDecimal getAmount() {
return amount;
}
public boolean isBefore(final LocalDate date) { public boolean isBefore(final LocalDate date) {
return DateUtils.isBefore(getTransactionDate(), date); return DateUtils.isBefore(getTransactionDate(), date);
} }
@@ -1094,10 +1036,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return DateUtils.isEqual(getTransactionDate(), date); return DateUtils.isEqual(getTransactionDate(), date);
} }
public LoanReAgeParameter getLoanReAgeParameter() {
return loanReAgeParameter;
}
public void setLoanReAgeParameter(LoanReAgeParameter loanReAgeParameter) { public void setLoanReAgeParameter(LoanReAgeParameter loanReAgeParameter) {
this.loanReAgeParameter = loanReAgeParameter; this.loanReAgeParameter = loanReAgeParameter;
} }
@@ -1121,10 +1059,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return false; return false;
} }
public String getChargeRefundChargeType() {
return chargeRefundChargeType;
}
public boolean isOverPaid() { public boolean isOverPaid() {
return MathUtil.isGreaterThanZero(overPaymentPortion); return MathUtil.isGreaterThanZero(overPaymentPortion);
} }
@@ -27,6 +27,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
/** /**
@@ -35,18 +36,18 @@ import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
public class SingleLoanChargeRepaymentScheduleProcessingWrapper { public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> repaymentPeriods, LoanCharge loanCharge) { final List<LoanRepaymentScheduleInstallment> installments, LoanCharge loanCharge) {
Loan loan = loanCharge.getLoan(); Loan loan = loanCharge.getLoan();
Money zero = Money.zero(currency); Money zero = Money.zero(currency);
Money totalInterest = zero; Money totalInterest = zero;
Money totalPrincipal = zero; Money totalPrincipal = zero;
for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { for (final LoanRepaymentScheduleInstallment installment : installments) {
totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
} }
LoanChargePaidBy accrualBy = null; LoanChargePaidBy accrualBy = null;
if (!loan.isInterestBearing() && loanCharge.isSpecifiedDueDate()) { // TODO: why only if not interest bearing 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) { if (addedPeriod != null) {
addedPeriod.updateObligationsMet(currency, disbursementDate); addedPeriod.updateObligationsMet(currency, disbursementDate);
} }
@@ -54,168 +55,126 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
.orElse(null); .orElse(null);
} }
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(repaymentPeriods); int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { for (final LoanRepaymentScheduleInstallment installment : installments) {
if (period.isDownPayment()) { if (installment.isDownPayment()) {
continue; continue;
} }
boolean installmentChargeApplicable = !period.isRecalculatedInterestComponent(); boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent();
boolean isFirstNonDownPaymentPeriod = period.getInstallmentNumber().equals(firstNormalInstallmentNumber); boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
LocalDate dueDate = period.getDueDate(); Predicate<LoanCharge> feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement();
final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, dueDate, loanCharge, currency, period, LocalDate dueDate = installment.getDueDate();
totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest,
final Money feeChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, isFirstPeriod, feePredicate);
installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); final Money feeChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable,
final Money feeChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, isFirstPeriod, feePredicate);
installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); final Money feeChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency, installmentChargeApplicable,
isFirstPeriod, feePredicate);
Predicate<LoanCharge> penaltyPredicate = LoanCharge::isPenaltyCharge; Predicate<LoanCharge> penaltyPredicate = LoanCharge::isPenaltyCharge;
final Money penaltyChargesDueForRepaymentPeriod = penaltyChargesDueWithin(startDate, dueDate, loanCharge, currency, period, final Money penaltyChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal,
totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); totalInterest, installmentChargeApplicable, isFirstPeriod, penaltyPredicate);
final Money penaltyChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, final Money penaltyChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable,
installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); isFirstPeriod, penaltyPredicate);
final Money penaltyChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, final Money penaltyChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency,
installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); installmentChargeApplicable, isFirstPeriod, penaltyPredicate);
period.addToChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived,
feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, penaltyChargesWrittenOff);
penaltyChargesWrittenOffForRepaymentPeriod);
if (accrualBy != null && period.isAdditional() if (accrualBy != null && installment.isAdditional() && loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) {
&& loanChargeIsDue(startDate, dueDate, isFirstNonDownPaymentPeriod, loanCharge)) {
Money amount = Money.of(currency, accrualBy.getAmount()); Money amount = Money.of(currency, accrualBy.getAmount());
boolean isFee = loanCharge.isFeeCharge(); boolean isFee = loanCharge.isFeeCharge();
period.updateAccrualPortion(period.getInterestAccrued(currency), installment.updateAccrualPortion(installment.getInterestAccrued(currency),
MathUtil.plus(period.getFeeAccrued(currency), (isFee ? amount : null)), MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? amount : null)),
MathUtil.plus(period.getPenaltyAccrued(currency), (isFee ? null : amount))); MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : amount)));
accrualBy.setInstallmentNumber(period.getInstallmentNumber()); accrualBy.setInstallmentNumber(installment.getInstallmentNumber());
} }
startDate = dueDate; startDate = dueDate;
} }
} }
private Money feeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, @NotNull
final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, private Money calcChargeDue(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge,
final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest,
boolean isInstallmentChargeApplicable, boolean isFirstPeriod, Predicate<LoanCharge> predicate) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { Money zero = Money.zero(currency);
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (!predicate.test(loanCharge)) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { return zero;
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());
}
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
return Money.of(monetaryCurrency, loanChargeAmt);
} else if (isDue) {
return Money.of(monetaryCurrency, loanCharge.amount());
}
} }
return Money.zero(monetaryCurrency); 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());
}
}
} else {
baseAmount = getBaseAmount(loanCharge, totalPrincipal.getAmount(), totalInterest.getAmount());
}
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, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod,
Predicate<LoanCharge> predicate) { Predicate<LoanCharge> predicate) {
Money zero = Money.zero(currency);
if (predicate.test(loanCharge)) { if (!predicate.test(loanCharge)) {
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); return zero;
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
return loanChargePerInstallment.getAmountWaived(currency);
}
} else if (isDue) {
return loanCharge.getAmountWaived(currency);
}
} }
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
return Money.zero(currency); 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, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod,
Predicate<LoanCharge> chargePredicate) { Predicate<LoanCharge> predicate) {
if (chargePredicate.test(loanCharge)) { Money zero = Money.zero(currency);
boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); if (!predicate.test(loanCharge)) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { return zero;
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
return loanChargePerInstallment.getAmountWrittenOff(currency);
}
} else if (isDue) {
return loanCharge.getAmountWrittenOff(currency);
}
} }
return Money.zero(currency); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
} LoanInstallmentCharge installmentCharge = loanCharge.getInstallmentLoanCharge(periodEnd);
return installmentCharge == null ? zero : installmentCharge.getAmountWrittenOff(currency);
private Predicate<LoanCharge> 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.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) {
return Money.zero(currency); return loanCharge.getAmountWrittenOff(currency);
}
return zero;
} }
private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) { private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) {
if (loanCharge.getChargeCalculation().isPercentageBased()) { if (loanCharge.getChargeCalculation().isFlat()) {
BigDecimal amount = BigDecimal.ZERO;
amount = getBaseAmount(currency, period, loanCharge, amount);
return amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
} else {
return loanCharge.amountOrPercentage(); return loanCharge.amountOrPercentage();
} }
return MathUtil.percentageOf(getBaseAmount(currency, period, loanCharge, null), loanCharge.getPercentage(),
MoneyHelper.getMathContext());
} }
@NotNull @NotNull
@@ -113,9 +113,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
for (final LoanCharge loanCharge : transferCharges) { for (final LoanCharge loanCharge : transferCharges) {
boolean isDue = isFirstPeriod boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(), isFirstPeriod);
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
if (isDue) { if (isDue) {
Money amountForProcess = loanCharge.getAmount(currency); Money amountForProcess = loanCharge.getAmount(currency);
if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) { if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) {
@@ -227,11 +225,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
final LoanRepaymentScheduleInstallment currentInstallment = installments.stream() final LoanRepaymentScheduleInstallment currentInstallment = installments.stream()
.filter(installment -> installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) .filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
? DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
loanTransaction.getTransactionDate())
: DateUtils.occursOnDayFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(),
loanTransaction.getTransactionDate()))
.findFirst().orElseThrow(); .findFirst().orElseThrow();
interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency)); interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency));
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import lombok.Getter; import lombok.Getter;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -155,7 +154,7 @@ public final class LoanSchedulePeriodData {
this.obligationsMetOnDate = null; this.obligationsMetOnDate = null;
this.complete = null; this.complete = null;
if (fromDate != null) { if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate);
} else { } else {
this.daysInPeriod = null; this.daysInPeriod = null;
} }
@@ -222,7 +221,7 @@ public final class LoanSchedulePeriodData {
this.obligationsMetOnDate = null; this.obligationsMetOnDate = null;
this.complete = null; this.complete = null;
if (fromDate != null) { if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate);
} else { } else {
this.daysInPeriod = null; this.daysInPeriod = null;
} }
@@ -347,7 +346,7 @@ public final class LoanSchedulePeriodData {
this.obligationsMetOnDate = obligationsMetOnDate; this.obligationsMetOnDate = obligationsMetOnDate;
this.complete = complete; this.complete = complete;
if (fromDate != null) { if (fromDate != null) {
this.daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(this.fromDate, this.dueDate)); this.daysInPeriod = DateUtils.getExactDifferenceInDays(this.fromDate, this.dueDate);
} else { } else {
this.daysInPeriod = null; this.daysInPeriod = null;
} }
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -222,8 +221,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
} }
// Updates total days in term // Updates total days in term
scheduleParams scheduleParams.addLoanTermInDays(DateUtils.getExactDifferenceInDays(scheduleParams.getPeriodStartDate(), scheduledDueDate));
.addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate)));
if (termVariationParams.skipPeriod()) { if (termVariationParams.skipPeriod()) {
continue; continue;
} }
@@ -737,7 +735,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
currentTransactions.addAll(createCurrentTransactionList(detail)); currentTransactions.addAll(createCurrentTransactionList(detail));
if (!DateUtils.isEqual(transactionDate, scheduleParams.getPeriodStartDate()) || scheduleParams.isFirstPeriod()) { 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 // calculates period start date for interest
// calculation as per the configuration // calculation as per the configuration
periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
@@ -745,8 +743,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(),
loanApplicationTerms.getExpectedDisbursementDate()); loanApplicationTerms.getExpectedDisbursementDate());
int daysInPeriodApplicable = Math int daysInPeriodApplicable = DateUtils.getExactDifferenceInDays(periodStartDateApplicableForInterest,
.toIntExact(ChronoUnit.DAYS.between(periodStartDateApplicableForInterest, transactionDate)); transactionDate);
Money interestForCurrentInstallment = Money.zero(currency); Money interestForCurrentInstallment = Money.zero(currency);
if (daysInPeriodApplicable > 0) { if (daysInPeriodApplicable > 0) {
// 5 determine interest till the transaction // 5 determine interest till the transaction
@@ -2076,8 +2074,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -2139,8 +2136,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -2409,7 +2405,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency));
totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency)); totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency));
instalmentNumber++; instalmentNumber++;
loanTermInDays = Math.toIntExact(ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate())); loanTermInDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate());
if (loanApplicationTerms.isInterestRecalculationEnabled()) { if (loanApplicationTerms.isInterestRecalculationEnabled()) {
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -108,7 +107,7 @@ public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends Abs
for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) { for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) {
if (!DateUtils.isAfter(principal.getKey(), periodEndDate)) { 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) { if (interestForDays > 0) {
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
@@ -21,9 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; 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.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -71,8 +71,8 @@ public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsI
periodFraction = BigDecimal.ONE; periodFraction = BigDecimal.ONE;
} else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) { } else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
final int numberOfDaysInterestCalculationGraceInPeriod = Math final int numberOfDaysInterestCalculationGraceInPeriod = DateUtils.getExactDifferenceInDays(repaymentPeriodStartDate,
.toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate)); interestChargedFromLocalDate);
periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery, periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
numberOfDaysInterestCalculationGraceInPeriod, mc); numberOfDaysInterestCalculationGraceInPeriod, mc);
} }
@@ -317,11 +317,11 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
boolean isScheduledDate = false; boolean isScheduledDate = false;
switch (frequency) { switch (frequency) {
case DAYS: case DAYS:
int diff = Math.toIntExact(ChronoUnit.DAYS.between(startDate, date)); int diff = DateUtils.getExactDifferenceInDays(startDate, date);
isScheduledDate = (diff % repaidEvery) == 0; isScheduledDate = (diff % repaidEvery) == 0;
break; break;
case WEEKS: case WEEKS:
int weekDiff = Math.toIntExact(ChronoUnit.WEEKS.between(startDate, date)); int weekDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.WEEKS);
isScheduledDate = (weekDiff % repaidEvery) == 0; isScheduledDate = (weekDiff % repaidEvery) == 0;
if (isScheduledDate) { if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusWeeks(weekDiff); LocalDate modifiedDate = startDate.plusWeeks(weekDiff);
@@ -329,7 +329,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
} }
break; break;
case MONTHS: case MONTHS:
int monthDiff = Math.toIntExact(ChronoUnit.MONTHS.between(startDate, date)); int monthDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.MONTHS);
isScheduledDate = (monthDiff % repaidEvery) == 0; isScheduledDate = (monthDiff % repaidEvery) == 0;
if (isScheduledDate) { if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusMonths(monthDiff); LocalDate modifiedDate = startDate.plusMonths(monthDiff);
@@ -337,7 +337,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
} }
break; break;
case YEARS: case YEARS:
int yearDiff = Math.toIntExact(ChronoUnit.YEARS.between(startDate, date)); int yearDiff = DateUtils.getExactDifference(startDate, date, ChronoUnit.YEARS);
isScheduledDate = (yearDiff % repaidEvery) == 0; isScheduledDate = (yearDiff % repaidEvery) == 0;
if (isScheduledDate) { if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusYears(yearDiff); LocalDate modifiedDate = startDate.plusYears(yearDiff);
@@ -953,7 +953,7 @@ public final class LoanApplicationTerms {
loanStartDate = getInterestChargedFromLocalDate(); 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); periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger);
break; break;
case INVALID: case INVALID:
@@ -976,17 +976,17 @@ public final class LoanApplicationTerms {
BigDecimal numberOfPeriods = BigDecimal.ZERO; BigDecimal numberOfPeriods = BigDecimal.ZERO;
switch (this.repaymentPeriodFrequencyType) { switch (this.repaymentPeriodFrequencyType) {
case DAYS: case DAYS:
int numOfDays = Math.toIntExact(ChronoUnit.DAYS.between(startDate, endDate)); int numOfDays = DateUtils.getExactDifferenceInDays(startDate, endDate);
numberOfPeriods = BigDecimal.valueOf((double) numOfDays); numberOfPeriods = BigDecimal.valueOf((double) numOfDays);
break; break;
case WEEKS: case WEEKS:
int numberOfWeeks = Math.toIntExact(ChronoUnit.WEEKS.between(startDate, endDate)); int numberOfWeeks = DateUtils.getExactDifference(startDate, endDate, ChronoUnit.WEEKS);
int daysLeftAfterWeeks = Math.toIntExact(ChronoUnit.DAYS.between(startDate.plusWeeks(numberOfWeeks), endDate)); int daysLeftAfterWeeks = DateUtils.getExactDifferenceInDays(startDate.plusWeeks(numberOfWeeks), endDate);
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)) numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks))
.add(BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); .add(BigDecimal.valueOf((double) daysLeftAfterWeeks / 7));
break; break;
case MONTHS: case MONTHS:
int numberOfMonths = Math.toIntExact(ChronoUnit.MONTHS.between(startDate, endDate)); int numberOfMonths = DateUtils.getExactDifference(startDate, endDate, ChronoUnit.MONTHS);
LocalDate startDateAfterConsideringMonths = null; LocalDate startDateAfterConsideringMonths = null;
LocalDate endDateAfterConsideringMonths = null; LocalDate endDateAfterConsideringMonths = null;
int diffDays = 0; int diffDays = 0;
@@ -1007,7 +1007,7 @@ public final class LoanApplicationTerms {
this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
} }
if (!DateUtils.isEqual(expectedStartDate, startDate)) { if (!DateUtils.isEqual(expectedStartDate, startDate)) {
diffDays = Math.toIntExact(ChronoUnit.DAYS.between(startDate, expectedStartDate)); diffDays = DateUtils.getExactDifferenceInDays(startDate, expectedStartDate);
} }
if (numberOfMonths == 0) { if (numberOfMonths == 0) {
startDateAfterConsideringMonths = expectedStartDate; startDateAfterConsideringMonths = expectedStartDate;
@@ -1022,19 +1022,19 @@ public final class LoanApplicationTerms {
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()),
this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
} }
int daysLeftAfterMonths = Math.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringMonths, endDate)) + diffDays; int daysLeftAfterMonths = DateUtils.getExactDifferenceInDays(startDateAfterConsideringMonths, endDate) + diffDays;
int daysInPeriodAfterMonths = Math int daysInPeriodAfterMonths = DateUtils.getExactDifferenceInDays(startDateAfterConsideringMonths,
.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringMonths, endDateAfterConsideringMonths)); endDateAfterConsideringMonths);
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)) numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths))
.add(BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths)); .add(BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths));
break; break;
case YEARS: 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 startDateAfterConsideringYears = startDate.plusYears(numberOfYears);
LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1); LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1);
int daysLeftAfterYears = Math.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringYears, endDate)); int daysLeftAfterYears = DateUtils.getExactDifferenceInDays(startDateAfterConsideringYears, endDate);
int daysInPeriodAfterYears = Math int daysInPeriodAfterYears = DateUtils.getExactDifferenceInDays(startDateAfterConsideringYears,
.toIntExact(ChronoUnit.DAYS.between(startDateAfterConsideringYears, endDateAfterConsideringYears)); endDateAfterConsideringYears);
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)) numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears))
.add(BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears)); .add(BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears));
break; break;
@@ -1208,7 +1208,7 @@ public final class LoanApplicationTerms {
break; break;
case DAILY: case DAILY:
// For daily work out number of days in the period // 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) final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc)
.divide(divisor, mc); .divide(divisor, mc);
@@ -1270,7 +1270,7 @@ public final class LoanApplicationTerms {
final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) {
long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); 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)) { if (isFallingInRepaymentPeriod(fromDate, toDate)) {
loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
repaymentEvery = getPeriodsBetween(fromDate, toDate); repaymentEvery = getPeriodsBetween(fromDate, toDate);
@@ -1681,7 +1681,7 @@ public final class LoanApplicationTerms {
if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) {
switch (this.repaymentPeriodFrequencyType) { switch (this.repaymentPeriodFrequencyType) {
case WEEKS: case WEEKS:
int days = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, toDate)); int days = DateUtils.getExactDifferenceInDays(fromDate, toDate);
isSameAsRepaymentPeriod = (days % 7) == 0; isSameAsRepaymentPeriod = (days % 7) == 0;
break; break;
case MONTHS: case MONTHS:
@@ -1715,16 +1715,16 @@ public final class LoanApplicationTerms {
Integer numberOfPeriods = 0; Integer numberOfPeriods = 0;
switch (this.repaymentPeriodFrequencyType) { switch (this.repaymentPeriodFrequencyType) {
case DAYS: case DAYS:
numberOfPeriods = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, toDate)); numberOfPeriods = DateUtils.getExactDifferenceInDays(fromDate, toDate);
break; break;
case WEEKS: case WEEKS:
numberOfPeriods = Math.toIntExact(ChronoUnit.WEEKS.between(fromDate, toDate)); numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.WEEKS);
break; break;
case MONTHS: case MONTHS:
numberOfPeriods = Math.toIntExact(ChronoUnit.MONTHS.between(fromDate, toDate)); numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.MONTHS);
break; break;
case YEARS: case YEARS:
numberOfPeriods = Math.toIntExact(ChronoUnit.YEARS.between(fromDate, toDate)); numberOfPeriods = DateUtils.getExactDifference(fromDate, toDate, ChronoUnit.YEARS);
break; break;
default: default:
break; break;
@@ -100,7 +100,7 @@ public class LoanRescheduleRequestDataValidatorImpl implements LoanRescheduleReq
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount(); dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
if (endDate != null) { if (endDate != null) {
LoanRepaymentScheduleInstallment endInstallment = loan.getRepaymentScheduleInstallment(endDate); LoanRepaymentScheduleInstallment endInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(endDate);
if (endInstallment == null) { if (endInstallment == null) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName) dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
@@ -257,7 +257,7 @@ public class LoanRescheduleRequestDataValidatorImpl implements LoanRescheduleReq
validateIsThereAnyIncomingChange(fromJsonHelper, jsonElement, dataValidatorBuilder); validateIsThereAnyIncomingChange(fromJsonHelper, jsonElement, dataValidatorBuilder);
validateMultiDisburseLoan(loan, dataValidatorBuilder); validateMultiDisburseLoan(loan, dataValidatorBuilder);
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); LoanRepaymentScheduleInstallment installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate);
validateReschedulingInstallment(dataValidatorBuilder, installment); validateReschedulingInstallment(dataValidatorBuilder, installment);
validateForOverdueCharges(dataValidatorBuilder, loan, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment);
@@ -307,7 +307,7 @@ public class LoanRescheduleRequestDataValidatorImpl implements LoanRescheduleReq
LoanRepaymentScheduleInstallment installment; LoanRepaymentScheduleInstallment installment;
validateLoanIsActive(loan, dataValidatorBuilder); validateLoanIsActive(loan, dataValidatorBuilder);
installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate);
validateReschedulingInstallment(dataValidatorBuilder, installment); validateReschedulingInstallment(dataValidatorBuilder, installment);
validateForOverdueCharges(dataValidatorBuilder, loan, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment);
@@ -203,8 +203,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
LoanCharge loanCharge = changeOperation.getLoanCharge().get(); LoanCharge loanCharge = changeOperation.getLoanCharge().get();
processSingleCharge(loanCharge, currency, installments, disbursementDate); processSingleCharge(loanCharge, currency, installments, disbursementDate);
if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) { if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) {
overpaidTransactions = processOverpaidTransactions(overpaidTransactions, currency, installments, charges, overpaidTransactions = processOverpaidTransactions(overpaidTransactions, ctx);
changedTransactionDetail, overpaymentHolder, scheduleModel);
} }
} }
} }
@@ -636,12 +635,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
} }
} }
private List<LoanTransaction> processOverpaidTransactions(List<LoanTransaction> overpaidTransactions, MonetaryCurrency currency, private List<LoanTransaction> processOverpaidTransactions(List<LoanTransaction> overpaidTransactions, ProgressiveTransactionCtx ctx) {
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, ChangedTransactionDetail changedTransactionDetail,
MoneyHolder overpaymentHolder, ProgressiveLoanInterestScheduleModel scheduleModel) {
List<LoanTransaction> remainingTransactions = new ArrayList<>(overpaidTransactions); List<LoanTransaction> remainingTransactions = new ArrayList<>(overpaidTransactions);
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail, MonetaryCurrency currency = ctx.getCurrency();
scheduleModel); MoneyHolder overpaymentHolder = ctx.getOverpaymentHolder();
Set<LoanCharge> charges = ctx.getCharges();
Money zero = Money.zero(currency); Money zero = Money.zero(currency);
for (LoanTransaction transaction : overpaidTransactions) { for (LoanTransaction transaction : overpaidTransactions) {
Money overpayment = transaction.getOverPaymentPortion(currency); Money overpayment = transaction.getOverPaymentPortion(currency);
@@ -1150,9 +1148,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
.fetchFirstNormalInstallmentNumber(transactionCtx.getInstallments()); .fetchFirstNormalInstallmentNumber(transactionCtx.getInstallments());
for (final LoanRepaymentScheduleInstallment installment : transactionCtx.getInstallments()) { for (final LoanRepaymentScheduleInstallment installment : transactionCtx.getInstallments()) {
boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(),
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) installment.getInstallmentNumber().equals(firstNormalInstallmentNumber));
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
if (isDue) { if (isDue) {
Integer installmentNumber = installment.getInstallmentNumber(); Integer installmentNumber = installment.getInstallmentNumber();
Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero); Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero);
@@ -1612,12 +1609,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
} }
@NotNull @NotNull
private Set<LoanCharge> getLoanChargesOfInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment currentInstallment, private Set<LoanCharge> getLoanChargesOfInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment installment,
int firstNormalInstallmentNumber) { int firstInstallmentNumber) {
return charges.stream().filter(loanCharge -> currentInstallment.getInstallmentNumber().equals(firstNormalInstallmentNumber) boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstInstallmentNumber);
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(currentInstallment.getFromDate(), return charges.stream()
currentInstallment.getDueDate()) .filter(loanCharge -> loanCharge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(currentInstallment.getFromDate(), currentInstallment.getDueDate()))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@@ -44,5 +44,4 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail); super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
this.model = model; this.model = model;
} }
} }
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -30,6 +29,7 @@ import java.util.TreeSet;
import java.util.function.Consumer; import java.util.function.Consumer;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -117,7 +117,7 @@ public class ProgressiveLoanInterestScheduleModel {
} }
final RepaymentPeriod firstPeriod = repaymentPeriods.get(0); final RepaymentPeriod firstPeriod = repaymentPeriods.get(0);
final RepaymentPeriod lastPeriod = repaymentPeriods.size() > 1 ? repaymentPeriods.get(repaymentPeriods.size() - 1) : firstPeriod; final RepaymentPeriod lastPeriod = repaymentPeriods.size() > 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() { public LocalDate getMaturityDate() {
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; 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.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
@@ -248,26 +249,23 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO, LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
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 NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
};
if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
} }
List<LoanRepaymentScheduleInstallment> 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 -> actualInstallment.getDueDate(); // due date of current installment
case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
};
ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
loan.retrieveListOfTransactionsForReprocessing(), currency, installments, loan.getActiveCharges()).getRight(); 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); PayableDetails result = emiCalculator.getPayableDetails(model, actualInstallment.getDueDate(), transactionDate);
// TODO: We should add all the past due outstanding amounts as well // TODO: We should add all the past due outstanding amounts as well
OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) // OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) //
@@ -333,8 +331,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod, private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) { boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod);
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -109,7 +109,7 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche
if (hasInterestRateChange) { if (hasInterestRateChange) {
installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
} else { } else {
installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate);
} }
validateReschedulingInstallment(dataValidatorBuilder, installment); validateReschedulingInstallment(dataValidatorBuilder, installment);
@@ -161,7 +161,7 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche
loan.getId(), rescheduleFromDate); loan.getId(), rescheduleFromDate);
} }
} else { } else {
installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate); installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate);
} }
validateReschedulingInstallment(dataValidatorBuilder, installment); validateReschedulingInstallment(dataValidatorBuilder, installment);
validateForOverdueCharges(dataValidatorBuilder, loan, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment);
@@ -144,7 +144,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
when(loanTransaction.getLoan()).thenReturn(loan); when(loanTransaction.getLoan()).thenReturn(loan);
when(loan.getDisbursementDate()).thenReturn(disbursementDate); 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(installment.getInstallmentNumber()).thenReturn(1);
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
when(loanTransaction.isPenaltyPayment()).thenReturn(false); when(loanTransaction.isPenaltyPayment()).thenReturn(false);
@@ -188,7 +188,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
when(loanTransaction.getLoan()).thenReturn(loan); when(loanTransaction.getLoan()).thenReturn(loan);
when(loan.getDisbursementDate()).thenReturn(disbursementDate); 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(installment.getInstallmentNumber()).thenReturn(1);
when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney); when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
when(loanTransaction.isPenaltyPayment()).thenReturn(false); when(loanTransaction.isPenaltyPayment()).thenReturn(false);
@@ -236,7 +236,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL); when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
when(loan.getDisbursementDate()).thenReturn(disbursementDate); 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(installment.getInstallmentNumber()).thenReturn(1);
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
when(loanTransaction.isPenaltyPayment()).thenReturn(false); when(loanTransaction.isPenaltyPayment()).thenReturn(false);
@@ -32,7 +32,6 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -906,7 +905,7 @@ public class LoanScheduleAssembler {
LocalDate previousDate = loan.getDisbursementDate(); LocalDate previousDate = loan.getDisbursementDate();
for (LocalDate duedate : dueDates) { for (LocalDate duedate : dueDates) {
int gap = Math.toIntExact(ChronoUnit.DAYS.between(previousDate, duedate)); int gap = DateUtils.getExactDifferenceInDays(previousDate, duedate);
previousDate = duedate; previousDate = duedate;
if (gap < minGap || (maxGap != null && gap > maxGap)) { if (gap < minGap || (maxGap != null && gap > maxGap)) {
baseDataValidator.reset().value(duedate).failWithCodeNoParameterAddedToErrorCode( baseDataValidator.reset().value(duedate).failWithCodeNoParameterAddedToErrorCode(
@@ -22,12 +22,12 @@ import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; 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.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -181,7 +181,7 @@ public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleH
Integer daysInPeriod = 0; Integer daysInPeriod = 0;
if (fromDate != null) { if (fromDate != null) {
daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, dueDate)); daysInPeriod = DateUtils.getExactDifferenceInDays(fromDate, dueDate);
loanTermInDays = loanTermInDays + daysInPeriod; loanTermInDays = loanTermInDays + daysInPeriod;
} }
@@ -22,7 +22,6 @@ import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction.a
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
@@ -158,7 +157,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
/** /**
* method adds accrual for batch job "Add Accrual Transactions" * method adds accrual for batch job "Add Accrual Transactions"
*/ */
@Override @Override
@Transactional @Transactional
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualData) { public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
@@ -239,7 +237,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
reprocessNonPeriodicAccruals(loan, accruals); reprocessNonPeriodicAccruals(loan, accruals);
} }
} }
} }
/** /**
@@ -299,7 +296,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
/** /**
* method calculates accruals for loan on loan closure * method calculates accruals for loan on loan closure
*/ */
@Override @Override
public void processAccrualsForLoanClosure(Loan loan) { public void processAccrualsForLoanClosure(Loan loan) {
// check and process accruals for loan WITHOUT interest recalculation details and compounding posted as income // check and process accruals for loan WITHOUT interest recalculation details and compounding posted as income
@@ -307,13 +303,11 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
// check and process accruals for loan WITH interest recalculation details and compounding posted as income // check and process accruals for loan WITH interest recalculation details and compounding posted as income
processIncomeAndAccrualTransactionOnLoanClosure(loan); processIncomeAndAccrualTransactionOnLoanClosure(loan);
} }
/** /**
* method calculates accruals for loan on loan fore closure * method calculates accruals for loan on loan fore closure
*/ */
@Override @Override
public void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate, public void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate,
Collection<LoanTransaction> newAccrualTransactions) { Collection<LoanTransaction> newAccrualTransactions) {
@@ -337,7 +331,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
feePortion, penaltyPortion, total); feePortion, penaltyPortion, total);
} }
} }
} }
private void calculateFinalAccrualsForScheduleTillSpecificDateAndAddAccrualAccounting(final LocalDate tillDate, private void calculateFinalAccrualsForScheduleTillSpecificDateAndAddAccrualAccounting(final LocalDate tillDate,
@@ -414,7 +407,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
} }
} }
int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate())); int totalNumberOfDays = DateUtils.getExactDifferenceInDays(interestStartDate, accrualData.getDueDateAsLocaldate());
LocalDate startDate = accrualData.getFromDateAsLocaldate(); LocalDate startDate = accrualData.getFromDateAsLocaldate();
if (DateUtils.isBefore(startDate, accrualData.getInterestCalculatedFrom())) { if (DateUtils.isBefore(startDate, accrualData.getInterestCalculatedFrom())) {
if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), tillDate)) { if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), tillDate)) {
@@ -423,7 +416,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
startDate = tillDate; startDate = tillDate;
} }
} }
int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tillDate)); int daysToBeAccrued = DateUtils.getExactDifferenceInDays(startDate, tillDate);
double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays; double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays;
if (daysToBeAccrued >= totalNumberOfDays) { if (daysToBeAccrued >= totalNumberOfDays) {
@@ -535,7 +528,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
// update repayment schedule portions // 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), loanScheduleInstallment.updateAccrualPortion(Money.of(currency, totalAccInterest), Money.of(currency, totalAccFee),
Money.of(currency, totalAccPenalty)); Money.of(currency, totalAccPenalty));
@@ -972,9 +966,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
} }
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
boolean isDue = isFirstNormalInstallment boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), chargesTillDate, isFirstNormalInstallment);
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate);
if (isDue) { if (isDue) {
if (loanCharge.isFeeCharge()) { if (loanCharge.isFeeCharge()) {
dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount());
@@ -1010,9 +1002,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
loan.addLoanTransaction(accrualTransaction); loan.addLoanTransaction(accrualTransaction);
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid(); Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
for (LoanCharge loanCharge : loan.getActiveCharges()) { for (LoanCharge loanCharge : loan.getActiveCharges()) {
boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) boolean isDue = loanCharge.isDueInPeriod(fromDate, foreClosureDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate()));
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, foreClosureDate)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate);
if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) { if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge,
loanCharge.getAmountOutstanding(currency).getAmount(), null); loanCharge.getAmountOutstanding(currency).getAmount(), null);
@@ -1177,9 +1167,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
List<LoanCharge> loanCharges = new ArrayList<>(); List<LoanCharge> loanCharges = new ArrayList<>();
List<LoanInstallmentCharge> loanInstallmentCharges = new ArrayList<>(); List<LoanInstallmentCharge> loanInstallmentCharges = new ArrayList<>();
for (LoanCharge loanCharge : loan.getActiveCharges()) { for (LoanCharge loanCharge : loan.getActiveCharges()) {
boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) boolean isDue = loanCharge.isDueInPeriod(fromDate, toDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate()));
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, toDate)
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate);
if (isDue) { if (isDue) {
if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) { if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) {
penalties = penalties.add(loanCharge.amount()); penalties = penalties.add(loanCharge.amount());
@@ -1339,7 +1327,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
} }
private void determineReceivableIncomeDetailsForLoanClosure(Loan loan, Map<String, Object> incomeDetails) { private void determineReceivableIncomeDetailsForLoanClosure(Loan loan, Map<String, Object> incomeDetails) {
MonetaryCurrency currency = loan.getCurrency(); MonetaryCurrency currency = loan.getCurrency();
Money interestPortion = Money.zero(currency); Money interestPortion = Money.zero(currency);
Money feePortion = Money.zero(currency); Money feePortion = Money.zero(currency);
@@ -405,12 +405,16 @@ public class LoanAssembler {
.map(r -> new LoanPaymentAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes(), .map(r -> new LoanPaymentAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes(),
r.getFutureInstallmentAllocationRule())) r.getFutureInstallmentAllocationRule()))
.toList(); .toList();
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules); List<LoanPaymentAllocationRule> paymentAllocationRules = loanApplication.getPaymentAllocationRules();
paymentAllocationRules.clear();
paymentAllocationRules.addAll(loanPaymentAllocationRules);
if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) { if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) {
List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream() List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream()
.map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList(); .map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList();
loanApplication.setCreditAllocationRules(loanCreditAllocationRules); List<LoanCreditAllocationRule> creditAllocationRules = loanApplication.getCreditAllocationRules();
creditAllocationRules.clear();
creditAllocationRules.addAll(loanCreditAllocationRules);
} }
} }
} }
@@ -25,7 +25,6 @@ import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -1245,7 +1244,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
Integer daysInPeriod = 0; Integer daysInPeriod = 0;
if (fromDate != null) { if (fromDate != null) {
daysInPeriod = Math.toIntExact(ChronoUnit.DAYS.between(fromDate, dueDate)); daysInPeriod = DateUtils.getExactDifferenceInDays(fromDate, dueDate);
loanTermInDays = loanTermInDays + daysInPeriod; loanTermInDays = loanTermInDays + daysInPeriod;
} }
@@ -1081,8 +1081,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.addLoanTransaction(newInterestPaymentWaiverTransaction); loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessorFactory() final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
.determineProcessor(loan.getTransactionProcessingStrategyCode());
final LoanRepaymentScheduleInstallment currentInstallment = loan final LoanRepaymentScheduleInstallment currentInstallment = loan
.fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate()); .fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate());
@@ -57,23 +57,23 @@ public class GenerateAdhocClientScheduleTasklet implements Tasklet {
switch (ReportRunFrequency.fromId(adhoc.getReportRunFrequency())) { switch (ReportRunFrequency.fromId(adhoc.getReportRunFrequency())) {
case DAILY -> { case DAILY -> {
next = start.plusDays(1); next = start.plusDays(1);
run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= 1; run = DateUtils.getExactDifferenceInDays(start, end) >= 1;
} }
case WEEKLY -> { case WEEKLY -> {
next = start.plusDays(7); next = start.plusDays(7);
run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= 7; run = DateUtils.getExactDifferenceInDays(start, end) >= 7;
} }
case MONTHLY -> { case MONTHLY -> {
next = start.plusMonths(1); next = start.plusMonths(1);
run = Math.toIntExact(ChronoUnit.MONTHS.between(start, end)) >= 1; run = DateUtils.getExactDifference(start, end, ChronoUnit.MONTHS) >= 1;
} }
case YEARLY -> { case YEARLY -> {
next = start.plusYears(1); next = start.plusYears(1);
run = Math.toIntExact(ChronoUnit.YEARS.between(start, end)) >= 1; run = DateUtils.getExactDifference(start, end, ChronoUnit.YEARS) >= 1;
} }
case CUSTOM -> { case CUSTOM -> {
next = start.plusDays((long) adhoc.getReportRunEvery()); next = start.plusDays((long) adhoc.getReportRunEvery());
run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= adhoc.getReportRunEvery(); run = DateUtils.getExactDifferenceInDays(start, end) >= adhoc.getReportRunEvery();
} }
} }
} }
@@ -25,7 +25,6 @@ import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -462,16 +461,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
LocalDate currentDate = DateUtils.getBusinessLocalDate(); LocalDate currentDate = DateUtils.getBusinessLocalDate();
if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
daysToEscheat = Math daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate,
.toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat))); lastActiveTransactionDate.plusDays(numDaysToEscheat));
} }
if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) {
daysToDormancy = Math.toIntExact( daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate,
ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy))); lastActiveTransactionDate.plusDays(numDaysToDormancy));
} }
if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
daysToInactive = Math.toIntExact( daysToInactive = DateUtils.getExactDifferenceInDays(currentDate,
ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive))); lastActiveTransactionDate.plusDays(numDaysToInactive));
} }
} }
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
@@ -837,16 +836,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
LocalDate currentDate = DateUtils.getBusinessLocalDate(); LocalDate currentDate = DateUtils.getBusinessLocalDate();
if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
daysToEscheat = Math daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat));
.toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat)));
} }
if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) {
daysToDormancy = Math daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy));
.toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy)));
} }
if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
daysToInactive = Math daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive));
.toIntExact(ChronoUnit.DAYS.between(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive)));
} }
} }
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.shareproducts.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -102,13 +101,13 @@ public class ShareProductDividendAssembler {
if (DateUtils.isBefore(shareStartDate, lastDividendPostDate)) { if (DateUtils.isBefore(shareStartDate, lastDividendPostDate)) {
shareStartDate = lastDividendPostDate; shareStartDate = lastDividendPostDate;
} }
int numberOfPurchseDays = Math.toIntExact(ChronoUnit.DAYS.between(shareStartDate, postingDate)); int numberOfPurchseDays = DateUtils.getExactDifferenceInDays(shareStartDate, postingDate);
if (type.isPurchased() && numberOfPurchseDays < minimumActivePeriod) { if (type.isPurchased() && numberOfPurchseDays < minimumActivePeriod) {
continue; continue;
} }
if (lastDividendAppliedDate != null) { if (lastDividendAppliedDate != null) {
numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, shareStartDate)) numberOfShareDaysPerAccount += DateUtils.getExactDifferenceInDays(lastDividendAppliedDate, shareStartDate)
* numberOfShares; * numberOfShares;
} }
lastDividendAppliedDate = shareStartDate; lastDividendAppliedDate = shareStartDate;
@@ -121,8 +120,7 @@ public class ShareProductDividendAssembler {
} }
} }
if (lastDividendAppliedDate != null) { if (lastDividendAppliedDate != null) {
numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, postingDate)) numberOfShareDaysPerAccount += DateUtils.getExactDifferenceInDays(lastDividendAppliedDate, postingDate) * numberOfShares;
* numberOfShares;
} }
numberOfShareDays += numberOfShareDaysPerAccount; numberOfShareDays += numberOfShareDaysPerAccount;
numberOfSharesdaysPerAccount.put(accountData.getId(), numberOfShareDaysPerAccount); numberOfSharesdaysPerAccount.put(accountData.getId(), numberOfShareDaysPerAccount);
@@ -37,6 +37,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
@Embeddable @Embeddable
@@ -335,16 +336,16 @@ public class InterestRateChartSlabFields {
final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(periodType()); final SavingsPeriodFrequencyType periodFrequencyType = SavingsPeriodFrequencyType.fromInt(periodType());
switch (periodFrequencyType) { switch (periodFrequencyType) {
case DAYS: case DAYS:
actualDepositPeriod = Math.toIntExact(ChronoUnit.DAYS.between(periodStartDate, periodEndDate)); actualDepositPeriod = DateUtils.getExactDifferenceInDays(periodStartDate, periodEndDate);
break; break;
case WEEKS: case WEEKS:
actualDepositPeriod = Math.toIntExact(ChronoUnit.WEEKS.between(periodStartDate, periodEndDate)); actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.WEEKS);
break; break;
case MONTHS: case MONTHS:
actualDepositPeriod = Math.toIntExact(ChronoUnit.MONTHS.between(periodStartDate, periodEndDate)); actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.MONTHS);
break; break;
case YEARS: case YEARS:
actualDepositPeriod = Math.toIntExact(ChronoUnit.YEARS.between(periodStartDate, periodEndDate)); actualDepositPeriod = DateUtils.getExactDifference(periodStartDate, periodEndDate, ChronoUnit.YEARS);
break; break;
case INVALID: case INVALID:
actualDepositPeriod = 0;// default value actualDepositPeriod = 0;// default value