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