FINERACT-2312: Reverse of Accruals to adjust late deposits
This commit is contained in:
+7
-1
@@ -158,13 +158,19 @@ public class SavingsAccrualWritePlatformServiceImpl implements SavingsAccrualWri
|
|||||||
|
|
||||||
final List<LocalDate> accrualTransactionDates = savingsAccount.retrieveOrderedAccrualTransactions().stream()
|
final List<LocalDate> accrualTransactionDates = savingsAccount.retrieveOrderedAccrualTransactions().stream()
|
||||||
.map(transaction -> transaction.getTransactionDate()).toList();
|
.map(transaction -> transaction.getTransactionDate()).toList();
|
||||||
|
final List<LocalDate> reversedAccrualTransactionDates = savingsAccount.retrieveOrderedAccrualTransactions().stream()
|
||||||
|
.filter(transaction -> transaction.isReversed()).map(transaction -> transaction.getTransactionDate()).toList();
|
||||||
|
|
||||||
LocalDate accruedTillDate = fromDate;
|
LocalDate accruedTillDate = fromDate;
|
||||||
|
|
||||||
for (PostingPeriod period : allPostingPeriods) {
|
for (PostingPeriod period : allPostingPeriods) {
|
||||||
|
LocalDate valueDate = period.getPeriodInterval().endDate();
|
||||||
|
List<LocalDate> matchingAccrualDates = reversedAccrualTransactionDates.stream()
|
||||||
|
.filter(accrualDate -> accrualDate.equals(valueDate)).toList();
|
||||||
period.calculateInterest(compoundInterestValues);
|
period.calculateInterest(compoundInterestValues);
|
||||||
final LocalDate endDate = period.getPeriodInterval().endDate();
|
final LocalDate endDate = period.getPeriodInterval().endDate();
|
||||||
if (!accrualTransactionDates.contains(period.getPeriodInterval().endDate())
|
if (!accrualTransactionDates.contains(period.getPeriodInterval().endDate())
|
||||||
&& !MathUtil.isZero(period.closingBalance().getAmount())) {
|
&& (!MathUtil.isZero(period.closingBalance().getAmount()) || !matchingAccrualDates.isEmpty())) {
|
||||||
String refNo = (refNoProvider != null) ? refNoProvider.apply(endDate) : null;
|
String refNo = (refNoProvider != null) ? refNoProvider.apply(endDate) : null;
|
||||||
SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount,
|
SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount,
|
||||||
savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned().abs(), false, refNo);
|
savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned().abs(), false, refNo);
|
||||||
|
|||||||
+21
-2
@@ -1044,7 +1044,8 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
|||||||
}
|
}
|
||||||
if (!calculateInterest || transaction.getId() == null) {
|
if (!calculateInterest || transaction.getId() == null) {
|
||||||
transaction.setOverdraftAmount(overdraftAmount);
|
transaction.setOverdraftAmount(overdraftAmount);
|
||||||
} else if (!MathUtil.isEqualTo(overdraftAmount, transaction.getOverdraftAmount(this.currency))) {
|
} else if (!MathUtil.isEqualTo(overdraftAmount, transaction.getOverdraftAmount(this.currency))
|
||||||
|
&& !transaction.isAccrual()) {
|
||||||
SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction);
|
SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction);
|
||||||
if (transaction.isChargeTransaction()) {
|
if (transaction.isChargeTransaction()) {
|
||||||
Set<SavingsAccountChargePaidBy> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
Set<SavingsAccountChargePaidBy> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
||||||
@@ -1171,6 +1172,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
|||||||
if (backdatedTxnsAllowedTill) {
|
if (backdatedTxnsAllowedTill) {
|
||||||
addTransactionToExisting(transaction);
|
addTransactionToExisting(transaction);
|
||||||
} else {
|
} else {
|
||||||
|
this.accrualsForSavingsReverse(transactionDTO, backdatedTxnsAllowedTill);
|
||||||
addTransaction(transaction);
|
addTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1306,6 +1308,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
|||||||
if (backdatedTxnsAllowedTill) {
|
if (backdatedTxnsAllowedTill) {
|
||||||
addTransactionToExisting(transaction);
|
addTransactionToExisting(transaction);
|
||||||
} else {
|
} else {
|
||||||
|
this.accrualsForSavingsReverse(transactionDTO, backdatedTxnsAllowedTill);
|
||||||
addTransaction(transaction);
|
addTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3865,10 +3868,26 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void accrualsForSavingsReverse(SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) {
|
||||||
|
List<SavingsAccountTransaction> accountTransactionsSorted = null;
|
||||||
|
|
||||||
|
if (backdatedTxnsAllowedTill) {
|
||||||
|
accountTransactionsSorted = retrieveSortedTransactions();
|
||||||
|
} else {
|
||||||
|
accountTransactionsSorted = retrieveListOfTransactions();
|
||||||
|
}
|
||||||
|
for (final SavingsAccountTransaction transaction : accountTransactionsSorted) {
|
||||||
|
boolean typeTransaccionValidation = transaction.getTransactionType() == SavingsAccountTransactionType.ACCRUAL;
|
||||||
|
if (typeTransaccionValidation && (transaction.getDateOf().isAfter(transactionDTO.getTransactionDate())
|
||||||
|
|| transaction.getDateOf().isEqual(transactionDTO.getTransactionDate()))) {
|
||||||
|
transaction.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<SavingsAccountTransactionDetailsForPostingPeriod> toSavingsAccountTransactionDetailsForPostingPeriodList() {
|
public List<SavingsAccountTransactionDetailsForPostingPeriod> toSavingsAccountTransactionDetailsForPostingPeriodList() {
|
||||||
return retreiveOrderedNonInterestPostingTransactions().stream()
|
return retreiveOrderedNonInterestPostingTransactions().stream()
|
||||||
.map(transaction -> transaction.toSavingsAccountTransactionDetailsForPostingPeriod(this.currency, this.allowOverdraft))
|
.map(transaction -> transaction.toSavingsAccountTransactionDetailsForPostingPeriod(this.currency, this.allowOverdraft))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+74
@@ -148,4 +148,78 @@ public class SavingsAccrualIntegrationTest {
|
|||||||
"The total accrual (" + actualTotalAccrual + ") does not match the expected (" + expectedTotalAccrual + ")");
|
"The total accrual (" + actualTotalAccrual + ") does not match the expected (" + expectedTotalAccrual + ")");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAccrualsAreReversedImmediatelyAfterBackdatedTransaction() {
|
||||||
|
// --- ARRANGE ---
|
||||||
|
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||||
|
final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||||
|
final String interestRate = "10.0";
|
||||||
|
final int daysToTest = 10;
|
||||||
|
final int daysUntilTransaction = 5;
|
||||||
|
|
||||||
|
final SavingsProductHelper productHelper = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily()
|
||||||
|
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
|
||||||
|
.withNominalAnnualInterestRate(new BigDecimal(interestRate))
|
||||||
|
.withAccountingRuleAsAccrualBased(new Account[] { assetAccount, liabilityAccount, incomeAccount, expenseAccount });
|
||||||
|
|
||||||
|
final Integer savingsProductId = SavingsProductHelper.createSavingsProduct(productHelper.build(), this.requestSpec,
|
||||||
|
this.responseSpec);
|
||||||
|
Assertions.assertNotNull(savingsProductId);
|
||||||
|
|
||||||
|
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2020");
|
||||||
|
Assertions.assertNotNull(clientId);
|
||||||
|
|
||||||
|
final LocalDate startDate = LocalDate.now(Utils.getZoneIdOfTenant()).minusDays(daysToTest);
|
||||||
|
final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||||
|
|
||||||
|
final Integer savingsAccountId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, savingsProductId,
|
||||||
|
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
|
||||||
|
Assertions.assertNotNull(savingsAccountId);
|
||||||
|
|
||||||
|
this.savingsAccountHelper.approveSavingsOnDate(savingsAccountId, startDateString);
|
||||||
|
this.savingsAccountHelper.activateSavings(savingsAccountId, startDateString);
|
||||||
|
|
||||||
|
this.savingsAccountHelper.depositToSavingsAccount(savingsAccountId, "10000", startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions For Savings");
|
||||||
|
LOG.info("Initial accruals for {} days have been generated.", daysToTest);
|
||||||
|
|
||||||
|
// --- ACT ---
|
||||||
|
final LocalDate backdatedTransactionDate = startDate.plusDays(daysUntilTransaction);
|
||||||
|
final String backdatedTransactionDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US)
|
||||||
|
.format(backdatedTransactionDate);
|
||||||
|
|
||||||
|
LOG.info("Performing a single backdated withdrawal on {}. Expecting immediate reversal of subsequent accruals.",
|
||||||
|
backdatedTransactionDateString);
|
||||||
|
this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsAccountId, "1000", backdatedTransactionDateString,
|
||||||
|
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
// --- ASSERT ---
|
||||||
|
List<HashMap> allTransactions = savingsAccountHelper.getSavingsTransactions(savingsAccountId);
|
||||||
|
|
||||||
|
boolean verifiedReversedTransaction = false;
|
||||||
|
|
||||||
|
for (HashMap<String, Object> transaction : allTransactions) {
|
||||||
|
Map<String, Object> type = (Map<String, Object>) transaction.get("transactionType");
|
||||||
|
if (type != null && Boolean.TRUE.equals(type.get("accrual"))) {
|
||||||
|
List<Number> dateArray = (List<Number>) transaction.get("date");
|
||||||
|
LocalDate transactionDate = LocalDate.of(dateArray.get(0).intValue(), dateArray.get(1).intValue(),
|
||||||
|
dateArray.get(2).intValue());
|
||||||
|
|
||||||
|
if (!transactionDate.isBefore(backdatedTransactionDate)) {
|
||||||
|
Assertions.assertTrue(Boolean.TRUE.equals(transaction.get("reversed")), "Accrual on or after "
|
||||||
|
+ backdatedTransactionDate + " should be REVERSED. Transaction date: " + transactionDate);
|
||||||
|
verifiedReversedTransaction = true;
|
||||||
|
} else {
|
||||||
|
Assertions.assertFalse(Boolean.TRUE.equals(transaction.get("reversed")),
|
||||||
|
"Accrual before " + backdatedTransactionDate + " should NOT be reversed. Transaction date: " + transactionDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertTrue(verifiedReversedTransaction, "Test failed because no reversed accrual transactions were found to verify.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user