@@ -129,7 +129,7 @@ public void testPostInterestWithOverdraftProduct() {
129129 BigDecimal expected = calcInterestPosting (productHelper , amount , days );
130130
131131 List <HashMap > txs = getInterestTransactions (accountId );
132- Assertions .assertEquals (expected , BigDecimal . valueOf ((( Double ) txs .get (0 ).get ("amount" ) )), "ERROR in expected" );
132+ Assertions .assertEquals (expected , toBigDecimal ( txs .get (0 ).get ("amount" )), "ERROR in expected" );
133133
134134 long interestCount = countInterestOnDate (accountId , marchDate .minusDays (1 ));
135135 long overdraftCount = countOverdraftOnDate (accountId , marchDate .minusDays (1 ));
@@ -178,7 +178,7 @@ public void testOverdraftInterestWithOverdraftProduct() {
178178 List <HashMap > txs = getInterestTransactions (accountId );
179179 Assertions .assertEquals (expected , BigDecimal .valueOf (((Double ) txs .get (0 ).get ("amount" ))));
180180
181- BigDecimal runningBalance = BigDecimal .valueOf (((Double ) txs .get (0 ).get ("runningBalance" )));
181+ BigDecimal runningBalance = BigDecimal .valueOf (((Number ) txs .get (0 ).get ("runningBalance" )). doubleValue ( ));
182182 Assertions .assertTrue (MathUtil .isLessThanZero (runningBalance ), "Running balance is not less than zero" );
183183
184184 long interestCount = countInterestOnDate (accountId , marchDate .minusDays (1 ));
@@ -230,10 +230,10 @@ public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceLess
230230
231231 List <HashMap > txs = getInterestTransactions (accountId );
232232 for (HashMap tx : txs ) {
233- BigDecimal amt = BigDecimal .valueOf (((Double ) tx .get ("amount" )));
233+ BigDecimal amt = BigDecimal .valueOf (((Number ) tx .get ("amount" )). doubleValue ( ));
234234 @ SuppressWarnings ("unchecked" )
235235 Map <String , Object > typeMap = (Map <String , Object >) tx .get ("transactionType" );
236- SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Double ) typeMap .get ("id" )).intValue ());
236+ SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Number ) typeMap .get ("id" )).intValue ());
237237
238238 if (type .isInterestPosting ()) {
239239 long days = ChronoUnit .DAYS .between (startDate , withdrawalDate );
@@ -295,10 +295,10 @@ public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceGrea
295295
296296 List <HashMap > txs = getInterestTransactions (accountId );
297297 for (HashMap tx : txs ) {
298- BigDecimal amt = BigDecimal .valueOf (((Double ) tx .get ("amount" )));
298+ BigDecimal amt = BigDecimal .valueOf (((Number ) tx .get ("amount" )). doubleValue ( ));
299299 @ SuppressWarnings ("unchecked" )
300300 Map <String , Object > typeMap = (Map <String , Object >) tx .get ("transactionType" );
301- SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Double ) typeMap .get ("id" )).intValue ());
301+ SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Number ) typeMap .get ("id" )).intValue ());
302302
303303 if (type .isOverDraftInterestPosting ()) {
304304 long days = ChronoUnit .DAYS .between (startDate , depositDate );
@@ -357,12 +357,12 @@ public void testPostInterestNotZero() {
357357
358358 long daysFebruary = ChronoUnit .DAYS .between (startDate , februaryDate );
359359 BigDecimal expectedFebruary = calcInterestPosting (productHelper , amountDeposit , daysFebruary );
360- Assertions .assertEquals (expectedFebruary , BigDecimal .valueOf (((Double ) txsFebruary .get (0 ).get ("amount" ))));
360+ Assertions .assertEquals (expectedFebruary , BigDecimal .valueOf (((Number ) txsFebruary .get (0 ).get ("amount" )). doubleValue ( )));
361361
362362 final LocalDate withdrawalDate = LocalDate .of (2025 , 2 , 1 );
363363 final String withdrawal = DateTimeFormatter .ofPattern ("dd MMMM yyyy" , Locale .US ).format (withdrawalDate );
364364
365- BigDecimal runningBalance = new BigDecimal ( txsFebruary .get (0 ).get ("runningBalance" ). toString ());
365+ BigDecimal runningBalance = BigDecimal . valueOf ((( Number ) txsFebruary .get (0 ).get ("runningBalance" )). doubleValue ());
366366 String withdrawalRunning = runningBalance .setScale (2 , RoundingMode .HALF_UP ).toString ();
367367
368368 savingsAccountHelper .withdrawalFromSavingsAccount (accountId , withdrawalRunning , withdrawal ,
@@ -377,13 +377,13 @@ public void testPostInterestNotZero() {
377377 List <HashMap > txs = getInterestTransactions (accountId );
378378
379379 for (HashMap tx : txs ) {
380- BigDecimal amt = BigDecimal .valueOf (((Double ) tx .get ("amount" )));
380+ BigDecimal amt = BigDecimal .valueOf (((Number ) tx .get ("amount" )). doubleValue ( ));
381381 @ SuppressWarnings ("unchecked" )
382382 Map <String , Object > typeMap = (Map <String , Object >) tx .get ("transactionType" );
383- SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Double ) typeMap .get ("id" )).intValue ());
383+ SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Number ) typeMap .get ("id" )).intValue ());
384384 if (type .isOverDraftInterestPosting ()) {
385385 long days = ChronoUnit .DAYS .between (withdrawalDate , marchDate );
386- BigDecimal decimalsss = new BigDecimal ( txsFebruary .get (0 ).get ("runningBalance" ). toString ())
386+ BigDecimal decimalsss = BigDecimal . valueOf ((( Number ) txsFebruary .get (0 ).get ("runningBalance" )). doubleValue ())
387387 .subtract (runningBalance .setScale (2 , RoundingMode .HALF_UP ));
388388 BigDecimal withdraw = new BigDecimal (amountWithdrawal );
389389 BigDecimal res = withdraw .subtract (decimalsss );
@@ -444,6 +444,103 @@ public void testPostInterestForDuplicatePrevention() {
444444 });
445445 }
446446
447+ @ Test
448+ public void testPostInterestPreventsDuplicateOnSameDay () {
449+ runAt ("15 April 2025" , () -> {
450+ final String amount = "5000" ;
451+
452+ final Account assetAccount = accountHelper .createAssetAccount ();
453+ final Account incomeAccount = accountHelper .createIncomeAccount ();
454+ final Account expenseAccount = accountHelper .createExpenseAccount ();
455+ final Account liabilityAccount = accountHelper .createLiabilityAccount ();
456+ final Account interestReceivableAccount = accountHelper .createAssetAccount ("interestReceivableAccount" );
457+ final Account savingsControlAccount = accountHelper .createLiabilityAccount ("Savings Control" );
458+ final Account interestPayableAccount = accountHelper .createLiabilityAccount ("Interest Payable" );
459+
460+ final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed (
461+ interestPayableAccount .getAccountID ().toString (), savingsControlAccount .getAccountID ().toString (),
462+ interestReceivableAccount .getAccountID ().toString (), assetAccount , incomeAccount , expenseAccount , liabilityAccount );
463+
464+ final Integer clientId = ClientHelper .createClient (requestSpec , responseSpec , "01 January 2025" );
465+ final LocalDate startDate = LocalDate .of (2025 , 3 , 1 );
466+ final String startDateString = DateTimeFormatter .ofPattern ("dd MMMM yyyy" , Locale .US ).format (startDate );
467+
468+ final Integer accountId = savingsAccountHelper .applyForSavingsApplicationOnDate (clientId , productId ,
469+ SavingsAccountHelper .ACCOUNT_TYPE_INDIVIDUAL , startDateString );
470+ savingsAccountHelper .approveSavingsOnDate (accountId , startDateString );
471+ savingsAccountHelper .activateSavings (accountId , startDateString );
472+ savingsAccountHelper .depositToSavingsAccount (accountId , amount , startDateString , CommonConstants .RESPONSE_RESOURCE_ID );
473+
474+ schedulerJobHelper .executeAndAwaitJob (POST_INTEREST_JOB_NAME );
475+
476+ List <HashMap > txsAfterFirstRun = getInterestTransactions (accountId );
477+ Assertions .assertEquals (1 , txsAfterFirstRun .size (), "Expected exactly one interest transaction after first job run" );
478+
479+ HashMap summaryAfterFirstRun = savingsAccountHelper .getSavingsSummary (accountId );
480+ BigDecimal balanceAfterFirstRun = BigDecimal .valueOf (((Number ) summaryAfterFirstRun .get ("accountBalance" )).doubleValue ());
481+
482+ schedulerJobHelper .executeAndAwaitJob (POST_INTEREST_JOB_NAME );
483+
484+ List <HashMap > txsAfterSecondRun = getInterestTransactions (accountId );
485+ Assertions .assertEquals (1 , txsAfterSecondRun .size (),
486+ "Expected still only one interest transaction after second job run on same day - duplicate should be prevented" );
487+
488+ HashMap summaryAfterSecondRun = savingsAccountHelper .getSavingsSummary (accountId );
489+ BigDecimal balanceAfterSecondRun = BigDecimal .valueOf (((Number ) summaryAfterSecondRun .get ("accountBalance" )).doubleValue ());
490+
491+ Assertions .assertEquals (balanceAfterFirstRun , balanceAfterSecondRun ,
492+ "Account balance should remain unchanged after second job run - no duplicate interest should be posted" );
493+ });
494+ }
495+
496+ @ Test
497+ public void testPostInterestSkipsCurrentPeriodButAllowsNewPeriod () {
498+ runAt ("20 May 2025" , () -> {
499+ final String amount = "8000" ;
500+
501+ final Account assetAccount = accountHelper .createAssetAccount ();
502+ final Account incomeAccount = accountHelper .createIncomeAccount ();
503+ final Account expenseAccount = accountHelper .createExpenseAccount ();
504+ final Account liabilityAccount = accountHelper .createLiabilityAccount ();
505+ final Account interestReceivableAccount = accountHelper .createAssetAccount ("interestReceivableAccount" );
506+ final Account savingsControlAccount = accountHelper .createLiabilityAccount ("Savings Control" );
507+ final Account interestPayableAccount = accountHelper .createLiabilityAccount ("Interest Payable" );
508+
509+ final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed (
510+ interestPayableAccount .getAccountID ().toString (), savingsControlAccount .getAccountID ().toString (),
511+ interestReceivableAccount .getAccountID ().toString (), assetAccount , incomeAccount , expenseAccount , liabilityAccount );
512+
513+ final Integer clientId = ClientHelper .createClient (requestSpec , responseSpec , "01 January 2025" );
514+ final LocalDate startDate = LocalDate .of (2025 , 4 , 1 );
515+ final String startDateString = DateTimeFormatter .ofPattern ("dd MMMM yyyy" , Locale .US ).format (startDate );
516+
517+ final Integer accountId = savingsAccountHelper .applyForSavingsApplicationOnDate (clientId , productId ,
518+ SavingsAccountHelper .ACCOUNT_TYPE_INDIVIDUAL , startDateString );
519+ savingsAccountHelper .approveSavingsOnDate (accountId , startDateString );
520+ savingsAccountHelper .activateSavings (accountId , startDateString );
521+ savingsAccountHelper .depositToSavingsAccount (accountId , amount , startDateString , CommonConstants .RESPONSE_RESOURCE_ID );
522+
523+ schedulerJobHelper .executeAndAwaitJob (POST_INTEREST_JOB_NAME );
524+
525+ List <HashMap > txsAfterFirstPeriod = getInterestTransactions (accountId );
526+ Assertions .assertEquals (1 , txsAfterFirstPeriod .size (), "Expected exactly one interest transaction after first posting period" );
527+
528+ HashMap summaryAfterFirstPeriod = savingsAccountHelper .getSavingsSummary (accountId );
529+ BigDecimal balanceAfterFirstPeriod = BigDecimal .valueOf (((Number ) summaryAfterFirstPeriod .get ("accountBalance" )).doubleValue ());
530+
531+ schedulerJobHelper .executeAndAwaitJob (POST_INTEREST_JOB_NAME );
532+
533+ List <HashMap > txsAfterSecondRunSameDay = getInterestTransactions (accountId );
534+ Assertions .assertEquals (1 , txsAfterSecondRunSameDay .size (),
535+ "Expected still only one interest transaction after second run on same day - current period should be skipped" );
536+
537+ HashMap summaryAfterSecondRun = savingsAccountHelper .getSavingsSummary (accountId );
538+ BigDecimal balanceAfterSecondRun = BigDecimal .valueOf (((Number ) summaryAfterSecondRun .get ("accountBalance" )).doubleValue ());
539+ Assertions .assertEquals (balanceAfterFirstPeriod , balanceAfterSecondRun ,
540+ "Account balance should remain unchanged after second run on same day - current period posting should be skipped" );
541+ });
542+ }
543+
447544 private void cleanupSavingsAccountsFromDuplicatePreventionTest () {
448545 try {
449546 LOG .info ("Starting cleanup of savings accounts after duplicate prevention test" );
@@ -482,7 +579,7 @@ private List<HashMap> getInterestTransactions(Integer savingsAccountId) {
482579 for (HashMap tx : all ) {
483580 @ SuppressWarnings ("unchecked" )
484581 Map <String , Object > txType = (Map <String , Object >) tx .get ("transactionType" );
485- SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Double ) txType .get ("id" )).intValue ());
582+ SavingsAccountTransactionType type = SavingsAccountTransactionType .fromInt (((Number ) txType .get ("id" )).intValue ());
486583 if (type .isInterestPosting () || type .isOverDraftInterestPosting ()) {
487584 filtered .add (tx );
488585 }
@@ -565,7 +662,7 @@ private boolean isDate(HashMap tx, LocalDate expected) {
565662 @ SuppressWarnings ("unchecked" )
566663 private SavingsAccountTransactionType txType (HashMap tx ) {
567664 Map <String , Object > typeMap = (Map <String , Object >) tx .get ("transactionType" );
568- return SavingsAccountTransactionType .fromInt (((Double ) typeMap .get ("id" )).intValue ());
665+ return SavingsAccountTransactionType .fromInt (((Number ) typeMap .get ("id" )).intValue ());
569666 }
570667
571668 private long countInterestOnDate (Integer accountId , LocalDate date ) {
0 commit comments