@@ -2952,6 +2952,125 @@ private Integer createTaxGroup(final String percentage, final Account liabilityA
29522952 /**
29532953 * Delete the Liability transfer account
29542954 */
2955+ @ Test
2956+ public void testFixedDepositAccountUndoTransaction () {
2957+ this .fixedDepositProductHelper = new FixedDepositProductHelper (this .requestSpec , this .responseSpec );
2958+ this .fixedDepositAccountHelper = new FixedDepositAccountHelper (this .requestSpec , this .responseSpec );
2959+ this .accountHelper = new AccountHelper (this .requestSpec , this .responseSpec );
2960+ this .journalEntryHelper = new JournalEntryHelper (this .requestSpec , this .responseSpec );
2961+
2962+ final Account assetAccount = this .accountHelper .createAssetAccount ();
2963+ final Account liabilityAccount = this .accountHelper .createLiabilityAccount ();
2964+ final Account incomeAccount = this .accountHelper .createIncomeAccount ();
2965+ final Account expenseAccount = this .accountHelper .createExpenseAccount ();
2966+
2967+ DateFormat dateFormat = new SimpleDateFormat ("dd MMMM yyyy" , Locale .US );
2968+
2969+ Calendar todaysDate = Calendar .getInstance ();
2970+ todaysDate .add (Calendar .MONTH , -3 );
2971+ final String VALID_FROM = dateFormat .format (todaysDate .getTime ());
2972+ todaysDate .add (Calendar .YEAR , 10 );
2973+ final String VALID_TO = dateFormat .format (todaysDate .getTime ());
2974+
2975+ todaysDate = Calendar .getInstance ();
2976+ todaysDate .add (Calendar .MONTH , -1 );
2977+ final String SUBMITTED_ON_DATE = dateFormat .format (todaysDate .getTime ());
2978+ final String APPROVED_ON_DATE = dateFormat .format (todaysDate .getTime ());
2979+ final String ACTIVATION_DATE = dateFormat .format (todaysDate .getTime ());
2980+
2981+ Integer clientId = ClientHelper .createClient (this .requestSpec , this .responseSpec );
2982+ Assertions .assertNotNull (clientId );
2983+
2984+ Integer fixedDepositProductId = createFixedDepositProduct (VALID_FROM , VALID_TO , CASH_BASED , assetAccount , liabilityAccount ,
2985+ incomeAccount , expenseAccount );
2986+ Assertions .assertNotNull (fixedDepositProductId );
2987+
2988+ Integer fixedDepositAccountId = applyForFixedDepositApplication (clientId .toString (), fixedDepositProductId .toString (),
2989+ SUBMITTED_ON_DATE , WHOLE_TERM );
2990+ Assertions .assertNotNull (fixedDepositAccountId );
2991+
2992+ HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker .getStatusOfFixedDepositAccount (this .requestSpec ,
2993+ this .responseSpec , fixedDepositAccountId .toString ());
2994+ FixedDepositAccountStatusChecker .verifyFixedDepositIsPending (fixedDepositAccountStatusHashMap );
2995+
2996+ fixedDepositAccountStatusHashMap = this .fixedDepositAccountHelper .approveFixedDeposit (fixedDepositAccountId , APPROVED_ON_DATE );
2997+ FixedDepositAccountStatusChecker .verifyFixedDepositIsApproved (fixedDepositAccountStatusHashMap );
2998+
2999+ fixedDepositAccountStatusHashMap = this .fixedDepositAccountHelper .activateFixedDeposit (fixedDepositAccountId , ACTIVATION_DATE );
3000+ FixedDepositAccountStatusChecker .verifyFixedDepositIsActive (fixedDepositAccountStatusHashMap );
3001+
3002+ this .fixedDepositAccountHelper .calculateInterestForFixedDeposit (fixedDepositAccountId );
3003+
3004+ Integer postInterestResult = this .fixedDepositAccountHelper .postInterestForFixedDeposit (fixedDepositAccountId );
3005+ Assertions .assertNotNull (postInterestResult );
3006+
3007+ // Compute INTEREST_POSTED_DATE as end of the activation month - Fineract posts interest
3008+ // at the last day of the posting period (MONTHLY) = end of the month containing ACTIVATION_DATE.
3009+ // All other FD tests use this same pattern (e.g. line 314).
3010+ todaysDate = Calendar .getInstance ();
3011+ todaysDate .add (Calendar .MONTH , -1 );
3012+ Integer currentDay = Integer .valueOf (new SimpleDateFormat ("dd" , Locale .US ).format (todaysDate .getTime ()));
3013+ Integer daysInMonth = todaysDate .getActualMaximum (Calendar .DATE );
3014+ Integer numberOfDaysLeft = daysInMonth - currentDay + 1 ;
3015+ todaysDate .add (Calendar .DATE , numberOfDaysLeft );
3016+ final String INTEREST_POSTED_DATE = dateFormat .format (todaysDate .getTime ());
3017+
3018+ // Capture interest amount before undo for journal entry assertions
3019+ HashMap accountSummaryBeforeUndo = this .fixedDepositAccountHelper .getFixedDepositSummary (fixedDepositAccountId );
3020+ Float totalInterestPostedBeforeUndo = (Float ) accountSummaryBeforeUndo .get ("totalInterestPosted" );
3021+ Assertions .assertNotNull (totalInterestPostedBeforeUndo );
3022+ Assertions .assertTrue (totalInterestPostedBeforeUndo > 0f , "Expected interest > 0 before undo" );
3023+
3024+ // Verify journal entries exist after interest posting
3025+ this .journalEntryHelper .checkJournalEntryForAssetAccount (expenseAccount , INTEREST_POSTED_DATE ,
3026+ new JournalEntry [] { new JournalEntry (totalInterestPostedBeforeUndo , JournalEntry .TransactionType .DEBIT ) });
3027+ this .journalEntryHelper .checkJournalEntryForLiabilityAccount (liabilityAccount , INTEREST_POSTED_DATE ,
3028+ new JournalEntry [] { new JournalEntry (totalInterestPostedBeforeUndo , JournalEntry .TransactionType .CREDIT ) });
3029+
3030+ ArrayList transactions = this .fixedDepositAccountHelper .getFixedDepositTransactions (fixedDepositAccountId );
3031+ Assertions .assertNotNull (transactions );
3032+
3033+ Integer interestTransactionId = null ;
3034+ for (Object txnObj : transactions ) {
3035+ HashMap txn = (HashMap ) txnObj ;
3036+ HashMap txnType = (HashMap ) txn .get ("transactionType" );
3037+ if (Boolean .TRUE .equals (txnType .get ("interestPosting" ))) {
3038+ interestTransactionId = (Integer ) txn .get ("id" );
3039+ break ;
3040+ }
3041+ }
3042+ Assertions .assertNotNull (interestTransactionId );
3043+
3044+ Integer undoResult = this .fixedDepositAccountHelper .undoFixedDepositTransaction (fixedDepositAccountId , interestTransactionId );
3045+ Assertions .assertNotNull (undoResult );
3046+
3047+ // 1. Verify transaction is marked reversed
3048+ ArrayList transactionsAfterUndo = this .fixedDepositAccountHelper .getFixedDepositTransactions (fixedDepositAccountId );
3049+ boolean foundReversed = false ;
3050+ for (Object txnObj : transactionsAfterUndo ) {
3051+ HashMap txn = (HashMap ) txnObj ;
3052+ if (interestTransactionId .equals (txn .get ("id" ))) {
3053+ Assertions .assertTrue (Boolean .TRUE .equals (txn .get ("reversed" )),
3054+ "Interest posting transaction must be marked reversed after undo" );
3055+ foundReversed = true ;
3056+ break ;
3057+ }
3058+ }
3059+ Assertions .assertTrue (foundReversed , "Interest transaction must still be present after undo" );
3060+
3061+ // 2. Verify balance returns to zero
3062+ HashMap accountSummaryAfterUndo = this .fixedDepositAccountHelper .getFixedDepositSummary (fixedDepositAccountId );
3063+ Float totalInterestPostedAfterUndo = (Float ) accountSummaryAfterUndo .get ("totalInterestPosted" );
3064+ Assertions .assertEquals (0f , totalInterestPostedAfterUndo == null ? 0f : totalInterestPostedAfterUndo , 0.01f ,
3065+ "totalInterestPosted must be zero after undo" );
3066+
3067+ // 3. Verify reversal journal entries
3068+ this .journalEntryHelper .checkJournalEntryForAssetAccount (expenseAccount , INTEREST_POSTED_DATE ,
3069+ new JournalEntry [] { new JournalEntry (totalInterestPostedBeforeUndo , JournalEntry .TransactionType .CREDIT ) });
3070+ this .journalEntryHelper .checkJournalEntryForLiabilityAccount (liabilityAccount , INTEREST_POSTED_DATE ,
3071+ new JournalEntry [] { new JournalEntry (totalInterestPostedBeforeUndo , JournalEntry .TransactionType .DEBIT ) });
3072+ }
3073+
29553074 @ AfterEach
29563075 public void tearDown () {
29573076 this .responseSpec = new ResponseSpecBuilder ().expectStatusCode (200 ).build ();
0 commit comments