FINERACT-2081: Fix inconsistent transaction processing order

This commit is contained in:
Adam Saghy
2024-11-11 12:32:21 +01:00
parent 832b84f574
commit 39da68820b
21 changed files with 74 additions and 86 deletions
@@ -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);
}
} }
@@ -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));
}
} }
@@ -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
@@ -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());
} }
} }
@@ -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) {
@@ -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() {
@@ -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;
} }
@@ -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);
@@ -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;
} }
@@ -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);
} }
@@ -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());
@@ -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);
} }
@@ -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);
} }
@@ -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);
} }
@@ -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);
} }
} }
} }
@@ -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);
} }
} }
} }
@@ -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;
} }
@@ -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;
} }
@@ -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;
} }