FINERACT-2081: Code quality, eliminate duplicated methods, use utility
This commit is contained in:
committed by
Adam Saghy
parent
001d586e30
commit
9812f14ad7
+1
-2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -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) {
|
||||||
|
|||||||
+34
-17
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-114
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -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() {
|
||||||
|
|||||||
+3
-61
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-17
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-68
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-136
@@ -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
|
||||||
|
|||||||
+3
-8
@@ -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));
|
||||||
|
|||||||
+3
-4
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-11
@@ -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()) {
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -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,
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -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);
|
||||||
|
|||||||
+20
-20
@@ -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;
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
|
|||||||
+12
-16
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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() {
|
||||||
|
|||||||
+13
-16
@@ -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()) {
|
||||||
|
|||||||
+2
-2
@@ -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);
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
|
|||||||
+1
-2
@@ -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(
|
||||||
|
|||||||
+2
-2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-20
@@ -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);
|
||||||
|
|||||||
+6
-2
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -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());
|
||||||
|
|||||||
+5
-5
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-13
@@ -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)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -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);
|
||||||
|
|||||||
+5
-4
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user