FINERACT-2081: Fix inconsistent transaction processing order
This commit is contained in:
-11
@@ -33,7 +33,6 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
|
||||||
import org.springframework.data.domain.Auditable;
|
import org.springframework.data.domain.Auditable;
|
||||||
import org.springframework.data.jpa.domain.AbstractAuditable;
|
import org.springframework.data.jpa.domain.AbstractAuditable;
|
||||||
|
|
||||||
@@ -81,11 +80,6 @@ public abstract class AbstractAuditableWithUTCDateTimeCustom<T extends Serializa
|
|||||||
return Optional.ofNullable(createdDate);
|
return Optional.ofNullable(createdDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public OffsetDateTime getCreatedDateTime() {
|
|
||||||
return getCreatedDate().orElseGet(DateUtils::getAuditOffsetDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Optional<Long> getLastModifiedBy() {
|
public Optional<Long> getLastModifiedBy() {
|
||||||
@@ -97,9 +91,4 @@ public abstract class AbstractAuditableWithUTCDateTimeCustom<T extends Serializa
|
|||||||
public Optional<OffsetDateTime> getLastModifiedDate() {
|
public Optional<OffsetDateTime> getLastModifiedDate() {
|
||||||
return Optional.ofNullable(lastModifiedDate);
|
return Optional.ofNullable(lastModifiedDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public OffsetDateTime getLastModifiedDateTime() {
|
|
||||||
return getLastModifiedDate().orElseGet(DateUtils::getAuditOffsetDateTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
@@ -31,6 +31,7 @@ import java.time.format.DateTimeParseException;
|
|||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
@@ -425,4 +426,23 @@ public final class DateUtils {
|
|||||||
}
|
}
|
||||||
return formatter;
|
return formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparing dates. Null will be considered as last elements
|
||||||
|
*
|
||||||
|
* @param first
|
||||||
|
* @param second
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int compareWithNullsLast(LocalDate first, LocalDate second) {
|
||||||
|
return first == null ? (second == null ? 0 : 1) : (second == null ? -1 : first.compareTo(second));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int compareWithNullsLast(@NotNull Optional<OffsetDateTime> first, @NotNull Optional<OffsetDateTime> second) {
|
||||||
|
return DateUtils.compareWithNullsLast(first.orElse(null), second.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int compareWithNullsLast(OffsetDateTime first, OffsetDateTime second) {
|
||||||
|
return first == null ? (second == null ? 0 : 1) : (second == null ? -1 : first.compareTo(second));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ public class CalendarHistoryDataWrapper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(CalendarHistory calendarHistory1, CalendarHistory calendarHistory2) {
|
public int compare(CalendarHistory calendarHistory1, CalendarHistory calendarHistory2) {
|
||||||
return DateUtils.compare(calendarHistory1.getEndDate(), calendarHistory2.getEndDate());
|
return DateUtils.compareWithNullsLast(calendarHistory1.getEndDate(), calendarHistory2.getEndDate());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.calendarHistoryList.sort(orderByDate);
|
this.calendarHistoryList.sort(orderByDate);
|
||||||
|
|||||||
@@ -3824,8 +3824,8 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 01 February 2024 | Repayment | 255.14 | 246.75 | 8.39 | 0.0 | 0.0 | 753.25 | false | false |
|
| 01 February 2024 | Repayment | 255.14 | 246.75 | 8.39 | 0.0 | 0.0 | 753.25 | false | false |
|
||||||
| 09 February 2024 | Payout Refund | 1000.0 | 743.23 | 1.63 | 0.0 | 0.0 | 10.02 | false | false |
|
| 09 February 2024 | Payout Refund | 1000.0 | 753.25 | 1.63 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 09 February 2024 | Interest Refund | 10.02 | 10.02 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 09 February 2024 | Interest Refund | 10.02 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
When Admin sets the business date to "10 February 2024"
|
When Admin sets the business date to "10 February 2024"
|
||||||
When Admin makes Credit Balance Refund transaction on "10 February 2024" with 255.14 EUR transaction amount
|
When Admin makes Credit Balance Refund transaction on "10 February 2024" with 255.14 EUR transaction amount
|
||||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||||
@@ -3842,8 +3842,8 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 01 February 2024 | Repayment | 255.14 | 246.75 | 8.39 | 0.0 | 0.0 | 753.25 | false | false |
|
| 01 February 2024 | Repayment | 255.14 | 246.75 | 8.39 | 0.0 | 0.0 | 753.25 | false | false |
|
||||||
| 09 February 2024 | Payout Refund | 1000.0 | 743.23 | 1.63 | 0.0 | 0.0 | 10.02 | false | false |
|
| 09 February 2024 | Payout Refund | 1000.0 | 753.25 | 1.63 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 09 February 2024 | Interest Refund | 10.02 | 10.02 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 09 February 2024 | Interest Refund | 10.02 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 10 February 2024 | Credit Balance Refund | 255.14 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 10 February 2024 | Credit Balance Refund | 255.14 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 10 February 2024 | Accrual | 10.02 | 0.0 | 10.02 | 0.0 | 0.0 | 0.0 | false | false |
|
| 10 February 2024 | Accrual | 10.02 | 0.0 | 10.02 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
|
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
|
||||||
@@ -3960,8 +3960,8 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
|||||||
| 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
|
| 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
|
||||||
| 07 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 07 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 01 February 2024 | Repayment | 255.14 | 247.57 | 7.57 | 0.0 | 0.0 | 752.43 | false | false |
|
| 01 February 2024 | Repayment | 255.14 | 247.57 | 7.57 | 0.0 | 0.0 | 752.43 | false | false |
|
||||||
| 09 February 2024 | Merchant Issued Refund | 1000.0 | 743.23 | 1.63 | 0.0 | 0.0 | 9.2 | false | false |
|
| 09 February 2024 | Merchant Issued Refund | 1000.0 | 752.43 | 1.63 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 09 February 2024 | Interest Refund | 9.2 | 9.2 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 09 February 2024 | Interest Refund | 9.2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
When Admin sets the business date to "10 February 2024"
|
When Admin sets the business date to "10 February 2024"
|
||||||
When Admin makes Credit Balance Refund transaction on "10 February 2024" with 255.14 EUR transaction amount
|
When Admin makes Credit Balance Refund transaction on "10 February 2024" with 255.14 EUR transaction amount
|
||||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||||
@@ -3980,8 +3980,8 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
|||||||
| 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
|
| 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false |
|
||||||
| 07 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 07 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 01 February 2024 | Repayment | 255.14 | 247.57 | 7.57 | 0.0 | 0.0 | 752.43 | false | false |
|
| 01 February 2024 | Repayment | 255.14 | 247.57 | 7.57 | 0.0 | 0.0 | 752.43 | false | false |
|
||||||
| 09 February 2024 | Merchant Issued Refund | 1000.0 | 743.23 | 1.63 | 0.0 | 0.0 | 9.2 | false | false |
|
| 09 February 2024 | Merchant Issued Refund | 1000.0 | 752.43 | 1.63 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 09 February 2024 | Interest Refund | 9.2 | 9.2 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 09 February 2024 | Interest Refund | 9.2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 10 February 2024 | Credit Balance Refund | 255.14 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
| 10 February 2024 | Credit Balance Refund | 255.14 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 10 February 2024 | Accrual | 9.2 | 0.0 | 9.2 | 0.0 | 0.0 | 0.0 | false | false |
|
| 10 February 2024 | Accrual | 9.2 | 0.0 | 9.2 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
|
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
|
||||||
|
|||||||
@@ -3942,8 +3942,6 @@ Feature: LoanRepayment
|
|||||||
| 24 June 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 24 June 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
||||||
| 10 September 2024 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 300.0 |
|
| 10 September 2024 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 300.0 |
|
||||||
|
|
||||||
# Needs to fix Merchant issued Refund - Interest refund order
|
|
||||||
@Skip
|
|
||||||
Scenario: Verify the relationship for Interest Refund transaction after repayment by reverting related transaction
|
Scenario: Verify the relationship for Interest Refund transaction after repayment by reverting related transaction
|
||||||
When Admin sets the business date to "30 January 2024"
|
When Admin sets the business date to "30 January 2024"
|
||||||
When Admin creates a client with random data
|
When Admin creates a client with random data
|
||||||
|
|||||||
+3
-2
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.delinquency.validator;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
|
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||||
|
|
||||||
@@ -44,8 +45,8 @@ public class LoanDelinquencyActionData {
|
|||||||
|
|
||||||
loanDelinquencyAction.getCreatedBy().ifPresent(this::setCreatedById);
|
loanDelinquencyAction.getCreatedBy().ifPresent(this::setCreatedById);
|
||||||
loanDelinquencyAction.getLastModifiedBy().ifPresent(this::setUpdatedById);
|
loanDelinquencyAction.getLastModifiedBy().ifPresent(this::setUpdatedById);
|
||||||
this.createdOn = loanDelinquencyAction.getCreatedDateTime();
|
this.createdOn = loanDelinquencyAction.getCreatedDate().orElse(DateUtils.getAuditOffsetDateTime());
|
||||||
this.lastModifiedOn = loanDelinquencyAction.getLastModifiedDateTime();
|
this.lastModifiedOn = loanDelinquencyAction.getLastModifiedDate().orElse(DateUtils.getAuditOffsetDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -84,10 +84,7 @@ public final class DisbursementData implements Comparable<DisbursementData> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(final DisbursementData obj) {
|
public int compareTo(final DisbursementData obj) {
|
||||||
if (obj == null) {
|
return DateUtils.compareWithNullsLast(obj.expectedDisbursementDate, this.expectedDisbursementDate);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return DateUtils.compare(obj.expectedDisbursementDate, this.expectedDisbursementDate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDueForDisbursement(LoanScheduleType loanScheduleType, final LocalDate fromDate, final LocalDate toDate) {
|
public boolean isDueForDisbursement(LoanScheduleType loanScheduleType, final LocalDate fromDate, final LocalDate toDate) {
|
||||||
|
|||||||
+1
-16
@@ -1041,22 +1041,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean happenedBefore(LoanTransaction loanTransaction) {
|
public boolean happenedBefore(LoanTransaction loanTransaction) {
|
||||||
// compare transaction date, creation date and then transaction id
|
return LoanTransactionComparator.INSTANCE.compare(this, loanTransaction) < 0;
|
||||||
if (DateUtils.isBefore(getTransactionDate(), loanTransaction.getTransactionDate())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (DateUtils.isEqual(getTransactionDate(), loanTransaction.getTransactionDate())) {
|
|
||||||
if (DateUtils.isBefore(getCreatedDateTime(), loanTransaction.getCreatedDateTime())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (DateUtils.isEqual(getCreatedDateTime(), loanTransaction.getCreatedDateTime())) {
|
|
||||||
if (getId().compareTo(loanTransaction.getId()) < 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOverPaid() {
|
public boolean isOverPaid() {
|
||||||
|
|||||||
+14
-8
@@ -22,7 +22,8 @@ import java.util.Comparator;
|
|||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort loan transactions by transaction date, created date and transaction type placing
|
* Sort loan transactions by transaction date, submitted date, created date, transaction type placing, lastly compare
|
||||||
|
* ids
|
||||||
*/
|
*/
|
||||||
public class LoanTransactionComparator implements Comparator<LoanTransaction> {
|
public class LoanTransactionComparator implements Comparator<LoanTransaction> {
|
||||||
|
|
||||||
@@ -30,22 +31,27 @@ public class LoanTransactionComparator implements Comparator<LoanTransaction> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(final LoanTransaction o1, final LoanTransaction o2) {
|
public int compare(final LoanTransaction o1, final LoanTransaction o2) {
|
||||||
int result = DateUtils.compare(o1.getTransactionDate(), o2.getTransactionDate());
|
int result = DateUtils.compareWithNullsLast(o1.getTransactionDate(), o2.getTransactionDate());
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// For transactions bearing the same transaction date, we sort transactions based on created date (when
|
// Accrual activity as last
|
||||||
// available) after which sorting for waivers takes place
|
if (o1.isAccrualActivity() != o2.isAccrualActivity()) {
|
||||||
result = o1.getCreatedDate().isPresent()
|
return o1.isAccrualActivity() ? 1 : -1;
|
||||||
? (o2.getCreatedDate().isPresent() ? DateUtils.compare(o1.getCreatedDateTime(), o2.getCreatedDateTime()) : -1)
|
}
|
||||||
: (o2.getCreatedDate().isPresent() ? 1 : 0);
|
result = DateUtils.compareWithNullsLast(o1.getSubmittedOnDate(), o2.getSubmittedOnDate());
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// equal transaction dates
|
result = DateUtils.compareWithNullsLast(o1.getCreatedDate(), o2.getCreatedDate());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// income posting takes priority
|
||||||
if (o1.isIncomePosting() != o2.isIncomePosting()) {
|
if (o1.isIncomePosting() != o2.isIncomePosting()) {
|
||||||
return o1.isIncomePosting() ? -1 : 1;
|
return o1.isIncomePosting() ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
// waive takes priority
|
||||||
if (o1.isWaiver() != o2.isWaiver()) {
|
if (o1.isWaiver() != o2.isWaiver()) {
|
||||||
return o1.isWaiver() ? -1 : 1;
|
return o1.isWaiver() ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-14
@@ -74,6 +74,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||||
@@ -471,7 +472,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
List<LoanTransaction> chargebacks = allTransactions.stream().filter(LoanTransaction::isChargeback).toList();
|
List<LoanTransaction> chargebacks = allTransactions.stream().filter(LoanTransaction::isChargeback).toList();
|
||||||
|
|
||||||
// let's figure out the original transaction for these chargebacks, and order them by ascending order
|
// let's figure out the original transaction for these chargebacks, and order them by ascending order
|
||||||
Comparator<LoanTransaction> comparator = loanTransactionDateComparator();
|
Comparator<LoanTransaction> comparator = LoanTransactionComparator.INSTANCE;
|
||||||
List<LoanTransaction> chargebacksForTheSameOriginal = chargebacks.stream()
|
List<LoanTransaction> chargebacksForTheSameOriginal = chargebacks.stream()
|
||||||
.filter(tr -> findChargebackOriginalTransaction(tr, ctx) == originalTransaction
|
.filter(tr -> findChargebackOriginalTransaction(tr, ctx) == originalTransaction
|
||||||
&& comparator.compare(tr, chargeBackTransaction) < 0)
|
&& comparator.compare(tr, chargeBackTransaction) < 0)
|
||||||
@@ -486,19 +487,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return allocation;
|
return allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Comparator<LoanTransaction> loanTransactionDateComparator() {
|
|
||||||
return (tr1, tr2) -> {
|
|
||||||
if (tr1.getTransactionDate().compareTo(tr2.getTransactionDate()) != 0) {
|
|
||||||
return tr1.getTransactionDate().compareTo(tr2.getTransactionDate());
|
|
||||||
} else if (tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate()) != 0) {
|
|
||||||
return tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate());
|
|
||||||
} else {
|
|
||||||
return tr1.getCreatedDateTime().compareTo(tr2.getCreatedDateTime());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recognizeAmountsAfterChargeback(MonetaryCurrency currency, LocalDate localDate,
|
private void recognizeAmountsAfterChargeback(MonetaryCurrency currency, LocalDate localDate,
|
||||||
LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
|
LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
|
||||||
Money principal = chargebackAllocation.get(PRINCIPAL);
|
Money principal = chargebackAllocation.get(PRINCIPAL);
|
||||||
|
|||||||
+4
-4
@@ -108,7 +108,7 @@ public class ChangeOperation implements Comparable<ChangeOperation> {
|
|||||||
} else if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) {
|
} else if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) {
|
||||||
return loanCharge.get().getCreatedDate().get();
|
return loanCharge.get().getCreatedDate().get();
|
||||||
} else if (loanTransaction.isPresent()) {
|
} else if (loanTransaction.isPresent()) {
|
||||||
return loanTransaction.get().getCreatedDateTime();
|
return loanTransaction.get().getCreatedDate().orElse(null);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Either charge with createdDate or transaction created datetime should be present");
|
throw new RuntimeException("Either charge with createdDate or transaction created datetime should be present");
|
||||||
}
|
}
|
||||||
@@ -117,15 +117,15 @@ public class ChangeOperation implements Comparable<ChangeOperation> {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
|
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
|
||||||
public int compareTo(@NotNull ChangeOperation o) {
|
public int compareTo(@NotNull ChangeOperation o) {
|
||||||
int datePortion = DateUtils.compare(this.getEffectiveDate(), o.getEffectiveDate());
|
int datePortion = DateUtils.compareWithNullsLast(this.getEffectiveDate(), o.getEffectiveDate());
|
||||||
if (datePortion == 0) {
|
if (datePortion == 0) {
|
||||||
final boolean isAccrual = isAccrualActivity();
|
final boolean isAccrual = isAccrualActivity();
|
||||||
if (isAccrual != o.isAccrualActivity()) {
|
if (isAccrual != o.isAccrualActivity()) {
|
||||||
return isAccrual ? 1 : -1;
|
return isAccrual ? 1 : -1;
|
||||||
}
|
}
|
||||||
int submittedDate = DateUtils.compare(getSubmittedOnDate(), o.getSubmittedOnDate());
|
int submittedDate = DateUtils.compareWithNullsLast(getSubmittedOnDate(), o.getSubmittedOnDate());
|
||||||
if (submittedDate == 0) {
|
if (submittedDate == 0) {
|
||||||
return DateUtils.compare(getCreatedDateTime(), o.getCreatedDateTime(), null);
|
return DateUtils.compareWithNullsLast(getCreatedDateTime(), o.getCreatedDateTime());
|
||||||
}
|
}
|
||||||
return submittedDate;
|
return submittedDate;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -152,7 +152,7 @@ public class ChangeOperationTest {
|
|||||||
LoanTransaction transaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction transaction = Mockito.mock(LoanTransaction.class);
|
||||||
Mockito.when(transaction.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
Mockito.when(transaction.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
||||||
Mockito.when(transaction.getTransactionDate()).thenReturn(LocalDate.parse(transactionDate));
|
Mockito.when(transaction.getTransactionDate()).thenReturn(LocalDate.parse(transactionDate));
|
||||||
Mockito.when(transaction.getCreatedDateTime()).thenReturn(OffsetDateTime.parse(creationDateTime));
|
Mockito.when(transaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.parse(creationDateTime)));
|
||||||
return new ChangeOperation(transaction);
|
return new ChangeOperation(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidParameterException;
|
import java.security.InvalidParameterException;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -40,6 +41,7 @@ import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
|||||||
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsRuntimeException;
|
import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsRuntimeException;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator;
|
import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.client.ClientActivateBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.client.ClientActivateBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.client.ClientRejectBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.client.ClientRejectBusinessEvent;
|
||||||
@@ -330,9 +332,10 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
|||||||
smsParams.put("loanOfficerId", -1);
|
smsParams.put("loanOfficerId", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OffsetDateTime creationDate = loanTransaction.getCreatedDate().orElse(DateUtils.getAuditOffsetDateTime());
|
||||||
smsParams.put("repaymentAmount", loanTransaction.getAmount(loan.getCurrency()));
|
smsParams.put("repaymentAmount", loanTransaction.getAmount(loan.getCurrency()));
|
||||||
smsParams.put("RepaymentDate", loanTransaction.getCreatedDateTime().toLocalDate().format(dateFormatter));
|
smsParams.put("RepaymentDate", creationDate.toLocalDate().format(dateFormatter));
|
||||||
smsParams.put("RepaymentTime", loanTransaction.getCreatedDateTime().toLocalTime().format(timeFormatter));
|
smsParams.put("RepaymentTime", creationDate.toLocalTime().format(timeFormatter));
|
||||||
|
|
||||||
if (loanTransaction.getPaymentDetail() != null) {
|
if (loanTransaction.getPaymentDetail() != null) {
|
||||||
smsParams.put("receiptNumber", loanTransaction.getPaymentDetail().getReceiptNumber());
|
smsParams.put("receiptNumber", loanTransaction.getPaymentDetail().getReceiptNumber());
|
||||||
|
|||||||
+2
-2
@@ -84,8 +84,8 @@ public class InternalClientInformationApiResource implements InitializingBean {
|
|||||||
|
|
||||||
final Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
final Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||||
Map<String, Object> auditFields = new HashMap<>(
|
Map<String, Object> auditFields = new HashMap<>(
|
||||||
Map.of(CREATED_BY, client.getCreatedBy().orElse(null), CREATED_DATE, client.getCreatedDateTime(), LAST_MODIFIED_BY,
|
Map.of(CREATED_BY, client.getCreatedBy().orElse(null), CREATED_DATE, client.getCreatedDate().orElse(null), LAST_MODIFIED_BY,
|
||||||
client.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, client.getLastModifiedDateTime()));
|
client.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, client.getLastModifiedDate().orElse(null)));
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, auditFields);
|
return this.toApiJsonSerializer.serialize(settings, auditFields);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ public class ClientCollateralManagementReadPlatformServiceImpl implements Client
|
|||||||
LoanTransaction loanTransaction = this.loanTransactionRepository.findById(transactionId)
|
LoanTransaction loanTransaction = this.loanTransactionRepository.findById(transactionId)
|
||||||
.orElseThrow(() -> new LoanTransactionNotFoundException(transactionId));
|
.orElseThrow(() -> new LoanTransactionNotFoundException(transactionId));
|
||||||
LoanTransactionData loanTransactionData = LoanTransactionData.instance(loanTransaction.getLoan().getId(),
|
LoanTransactionData loanTransactionData = LoanTransactionData.instance(loanTransaction.getLoan().getId(),
|
||||||
loanTransaction.getCreatedDateTime(), loanTransaction.getOutstandingLoanBalance(),
|
loanTransaction.getCreatedDate().orElse(null), loanTransaction.getOutstandingLoanBalance(),
|
||||||
loanTransaction.getPrincipalPortion());
|
loanTransaction.getPrincipalPortion());
|
||||||
loanTransactionDataList.add(loanTransactionData);
|
loanTransactionDataList.add(loanTransactionData);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -98,8 +98,8 @@ public class InternalLoanInformationApiResource implements InitializingBean {
|
|||||||
|
|
||||||
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
|
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
|
||||||
Map<String, Object> auditFields = new HashMap<>(
|
Map<String, Object> auditFields = new HashMap<>(
|
||||||
Map.of(CREATED_BY, loan.getCreatedBy().orElse(null), CREATED_DATE, loan.getCreatedDateTime(), LAST_MODIFIED_BY,
|
Map.of(CREATED_BY, loan.getCreatedBy().orElse(null), CREATED_DATE, loan.getCreatedDate().orElse(null), LAST_MODIFIED_BY,
|
||||||
loan.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, loan.getLastModifiedDateTime()));
|
loan.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE, loan.getLastModifiedDate().orElse(null)));
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
|
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
|
||||||
}
|
}
|
||||||
@@ -119,8 +119,8 @@ public class InternalLoanInformationApiResource implements InitializingBean {
|
|||||||
|
|
||||||
final LoanTransaction transaction = loanTransactionRepository.findById(transactionId).orElseThrow();
|
final LoanTransaction transaction = loanTransactionRepository.findById(transactionId).orElseThrow();
|
||||||
Map<String, Object> auditFields = new HashMap<>(Map.of(CREATED_BY, transaction.getCreatedBy().orElse(null), CREATED_DATE,
|
Map<String, Object> auditFields = new HashMap<>(Map.of(CREATED_BY, transaction.getCreatedBy().orElse(null), CREATED_DATE,
|
||||||
transaction.getCreatedDateTime(), LAST_MODIFIED_BY, transaction.getLastModifiedBy().orElse(null), LAST_MODIFIED_DATE,
|
transaction.getCreatedDate().orElse(null), LAST_MODIFIED_BY, transaction.getLastModifiedBy().orElse(null),
|
||||||
transaction.getLastModifiedDateTime()));
|
LAST_MODIFIED_DATE, transaction.getLastModifiedDate().orElse(null)));
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
|
return this.toApiJsonSerializerForMap.serialize(settings, auditFields);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -3355,8 +3355,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
&& DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate()))
|
&& DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate()))
|
||||||
|| DateUtils.isBefore(transferDate, transaction.getTransactionDate())) {
|
|| DateUtils.isBefore(transferDate, transaction.getTransactionDate())) {
|
||||||
throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientLoanException,
|
throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientLoanException,
|
||||||
TransferApiConstants.transferClientLoanExceptionMessage, transaction.getCreatedDateTime().toLocalDate(),
|
TransferApiConstants.transferClientLoanExceptionMessage, transaction.getTransactionDate(), transferDate);
|
||||||
transferDate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1898,7 +1898,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
&& DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate()))
|
&& DateUtils.isEqual(transferDate, transaction.getSubmittedOnDate()))
|
||||||
|| DateUtils.isBefore(transferDate, transaction.getTransactionDate())) {
|
|| DateUtils.isBefore(transferDate, transaction.getTransactionDate())) {
|
||||||
throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientSavingsException,
|
throw new GeneralPlatformDomainRuleException(TransferApiConstants.transferClientSavingsException,
|
||||||
TransferApiConstants.transferClientSavingsException, transaction.getCreatedDateTime(), transferDate);
|
TransferApiConstants.transferClientSavingsException, transaction.getTransactionDate(), transferDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -33,6 +33,7 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
@@ -480,7 +481,7 @@ class LoanReAgingValidatorTest {
|
|||||||
|
|
||||||
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate, OffsetDateTime creationTime) {
|
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate, OffsetDateTime creationTime) {
|
||||||
LoanTransaction loanTransaction = loanTransaction(type, txDate);
|
LoanTransaction loanTransaction = loanTransaction(type, txDate);
|
||||||
given(loanTransaction.getCreatedDateTime()).willReturn(creationTime);
|
given(loanTransaction.getCreatedDate()).willReturn(Optional.of(creationTime));
|
||||||
return loanTransaction;
|
return loanTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -32,6 +32,7 @@ import java.time.ZoneOffset;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
@@ -267,7 +268,7 @@ class LoanReAmortizationValidatorTest {
|
|||||||
|
|
||||||
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate, OffsetDateTime creationTime) {
|
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate, OffsetDateTime creationTime) {
|
||||||
LoanTransaction loanTransaction = loanTransaction(type, txDate);
|
LoanTransaction loanTransaction = loanTransaction(type, txDate);
|
||||||
given(loanTransaction.getCreatedDateTime()).willReturn(creationTime);
|
given(loanTransaction.getCreatedDate()).willReturn(Optional.of(creationTime));
|
||||||
return loanTransaction;
|
return loanTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ public class SavingsAccountTransactionComparator implements Comparator<SavingsAc
|
|||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result = DateUtils.compare(o1.getCreatedDateTime(), o2.getCreatedDateTime());
|
result = DateUtils.compareWithNullsLast(o1.getCreatedDate(), o2.getCreatedDate());
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user