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

This commit is contained in:
Marta Jankovics
2024-10-30 15:54:48 +01:00
committed by Adam Saghy
parent 001d586e30
commit 9812f14ad7
33 changed files with 373 additions and 577 deletions
@@ -20,7 +20,6 @@ package org.apache.fineract.accounting.glaccount.jobs.updatetrialbalancedetails;
import java.math.BigDecimal;
import java.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;
}
@@ -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) {
@@ -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);
}
}
@@ -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);
}
}
@@ -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() {
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
@@ -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
@@ -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));
@@ -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;
}
@@ -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()) {
@@ -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,
@@ -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);
}
@@ -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);
@@ -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;
@@ -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);
@@ -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());
}
@@ -44,5 +44,4 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
this.model = model;
}
}
@@ -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() {
@@ -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()) {
@@ -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);
@@ -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);
@@ -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(
@@ -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;
}
@@ -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);
@@ -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);
}
}
}
@@ -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;
}
@@ -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());
@@ -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();
}
}
}
@@ -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));
}
}
@@ -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);
@@ -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