4242import java .util .ArrayList ;
4343import java .util .Arrays ;
4444import java .util .Calendar ;
45+ import java .util .Map ;
4546import java .util .HashMap ;
4647import java .util .List ;
4748import java .util .Locale ;
4849import java .util .TimeZone ;
4950import lombok .extern .slf4j .Slf4j ;
5051import org .apache .fineract .accounting .common .AccountingConstants .FinancialActivity ;
5152import org .apache .fineract .infrastructure .core .api .JsonQuery ;
53+ import org .apache .fineract .client .models .PutGlobalConfigurationsRequest ;
54+ import org .apache .fineract .infrastructure .businessdate .domain .BusinessDateType ;
55+ import org .apache .fineract .infrastructure .configuration .api .GlobalConfigurationConstants ;
5256import org .apache .fineract .infrastructure .core .exception .PlatformApiDataValidationException ;
5357import org .apache .fineract .infrastructure .core .serialization .FromJsonHelper ;
5458import org .apache .fineract .integrationtests .client .IntegrationTest ;
5559import org .apache .fineract .integrationtests .common .ClientHelper ;
60+ import org .apache .fineract .integrationtests .common .BusinessDateHelper ;
5661import org .apache .fineract .integrationtests .common .CommonConstants ;
62+ import org .apache .fineract .integrationtests .common .GlobalConfigurationHelper ;
5763import org .apache .fineract .integrationtests .common .SchedulerJobHelper ;
5864import org .apache .fineract .integrationtests .common .TaxComponentHelper ;
5965import org .apache .fineract .integrationtests .common .TaxGroupHelper ;
7177import org .apache .fineract .integrationtests .common .savings .SavingsProductHelper ;
7278import org .apache .fineract .integrationtests .common .savings .SavingsStatusChecker ;
7379import org .apache .fineract .organisation .monetary .domain .MoneyHelper ;
80+ import org .apache .fineract .portfolio .savings .SavingsAccountTransactionType ;
7481import org .apache .fineract .portfolio .savings .data .DepositAccountDataValidator ;
7582import org .apache .fineract .portfolio .savings .service .FixedDepositAccountInterestCalculationServiceImpl ;
7683import org .junit .jupiter .api .AfterEach ;
@@ -92,7 +99,8 @@ public class FixedDepositTest extends IntegrationTest {
9299 private SavingsAccountHelper savingsAccountHelper ;
93100 private JournalEntryHelper journalEntryHelper ;
94101 private FinancialActivityAccountHelper financialActivityAccountHelper ;
95-
102+ private GlobalConfigurationHelper globalConfigurationHelper ;
103+ private SchedulerJobHelper schedulerJobHelper ;
96104 private FixedDepositAccountInterestCalculationServiceImpl fixedDepositAccountInterestCalculationServiceImpl ;
97105
98106 public static final String WHOLE_TERM = "1" ;
@@ -138,8 +146,11 @@ public void setup() {
138146 this .requestSpec .header ("Authorization" , "Basic " + Utils .loginIntoServerAndGetBase64EncodedAuthenticationKey ());
139147 this .requestSpec .header ("Fineract-Platform-TenantId" , "default" );
140148 this .responseSpec = new ResponseSpecBuilder ().expectStatusCode (200 ).build ();
149+ this .accountHelper = new AccountHelper (this .requestSpec , this .responseSpec );
150+ this .schedulerJobHelper = new SchedulerJobHelper (this .requestSpec );
141151 this .journalEntryHelper = new JournalEntryHelper (this .requestSpec , this .responseSpec );
142152 this .financialActivityAccountHelper = new FinancialActivityAccountHelper (this .requestSpec );
153+ this .globalConfigurationHelper = new GlobalConfigurationHelper ();
143154 TimeZone .setDefault (TimeZone .getTimeZone (Utils .TENANT_TIME_ZONE ));
144155 }
145156
@@ -2598,6 +2609,249 @@ public void testFixedDepositAccountWithRolloverPrincipal() {
25982609 FixedDepositAccountStatusChecker .verifyFixedDepositIsActive (fixedDepositAccountStatusHashMap );
25992610 }
26002611
2612+ @ Test
2613+ public void FixedDepositNoneInterestAccruals (){
2614+ try {
2615+ final String amount = "10000" ;
2616+ final Account assetAccount = this .accountHelper .createAssetAccount ();
2617+ final Account incomeAccount = this .accountHelper .createIncomeAccount ();
2618+ final Account expenseAccount = this .accountHelper .createExpenseAccount ();
2619+ final Account liabilityAccount = this .accountHelper .createLiabilityAccount ();
2620+ final Account savingsControlAccount = this .accountHelper .createLiabilityAccount ("Savings Control" );
2621+
2622+ this .fixedDepositProductHelper = new FixedDepositProductHelper (this .requestSpec , this .responseSpec );
2623+ this .fixedDepositAccountHelper = new FixedDepositAccountHelper (this .requestSpec , this .responseSpec );
2624+
2625+ DateTimeFormatter dateFormat = DateTimeFormatter .ofPattern ("dd MMMM yyyy" , Locale .US );
2626+ DateTimeFormatter monthDayFormat = DateTimeFormatter .ofPattern ("dd MMM" , Locale .US );
2627+ DateTimeFormatter currentDateFormat = DateTimeFormatter .ofPattern ("dd" );
2628+
2629+ LocalDate mayDate = LocalDate .of (LocalDate .now ().getYear ()-1 , 5 , 1 );
2630+
2631+ final String SUBMITTED_ON_DATE = mayDate .format (dateFormat );
2632+ final String APPROVED_ON_DATE = mayDate .format (dateFormat );
2633+
2634+ globalConfigurationHelper .updateGlobalConfiguration (GlobalConfigurationConstants .ENABLE_BUSINESS_DATE ,
2635+ new PutGlobalConfigurationsRequest ().enabled (true ));
2636+
2637+ BusinessDateHelper .updateBusinessDate (requestSpec , responseSpec , BusinessDateType .BUSINESS_DATE , mayDate );
2638+
2639+ log .info ("Submitted Date: {}" , SUBMITTED_ON_DATE );
2640+
2641+
2642+ final Integer fixedDepositProductId = createFixedWithAccrualAccountingWithNoneInterest (liabilityAccount , expenseAccount ,incomeAccount ,assetAccount , savingsControlAccount );
2643+ Assertions .assertNotNull (fixedDepositProductId );
2644+
2645+ Integer clientId = ClientHelper .createClient (this .requestSpec , this .responseSpec );
2646+ Assertions .assertNotNull (clientId );
2647+
2648+ Integer fixedDepositAccountId = applyForFixedDepositApplication_10000 (clientId .toString (), fixedDepositProductId .toString (),
2649+ SUBMITTED_ON_DATE , WHOLE_TERM , Integer .valueOf (CLOSURE_TYPE_REINVEST ));
2650+ Assertions .assertNotNull (fixedDepositAccountId );
2651+
2652+ this .fixedDepositAccountHelper .approveFixedDeposit (fixedDepositAccountId , APPROVED_ON_DATE );
2653+ this .fixedDepositAccountHelper .activateFixedDeposit (fixedDepositAccountId , APPROVED_ON_DATE );
2654+
2655+
2656+ final LocalDate endOfMay = LocalDate .of (LocalDate .now ().getYear ()-1 , 6 , 1 );
2657+ BusinessDateHelper .updateBusinessDate (this .requestSpec , this .responseSpec , BusinessDateType .BUSINESS_DATE , endOfMay );
2658+ log .info ("Advancing business date to: {}. Running Accrual Job for May..." , endOfMay );
2659+
2660+ this .schedulerJobHelper .executeAndAwaitJob ("Add Accrual Transactions For Savings" );
2661+
2662+ final LocalDate endOfJune = LocalDate .of (LocalDate .now ().getYear ()-1 , 7 , 1 );
2663+ BusinessDateHelper .updateBusinessDate (this .requestSpec , this .responseSpec , BusinessDateType .BUSINESS_DATE , endOfJune );
2664+ log .info ("Advancing business date to: {}. Running Accrual Job for June..." , endOfJune );
2665+
2666+ this .schedulerJobHelper .executeAndAwaitJob ("Add Accrual Transactions For Savings" );
2667+
2668+
2669+ BigDecimal expectedDailyInterest = new BigDecimal (amount )
2670+ .multiply (new BigDecimal ("0.15" ))
2671+ .divide (new BigDecimal ("360" ), 4 , RoundingMode .HALF_EVEN );
2672+
2673+ List <HashMap > allTransactions = this .fixedDepositAccountHelper .getFixedDepositTransactions (fixedDepositAccountId );
2674+ Assertions .assertNotNull (allTransactions , "The transaction list should not be null" );
2675+
2676+ int juneAccrualsVerified = 0 ;
2677+ log .info ("Verifying accrual transactions for June... Expected daily amount around: {}" , expectedDailyInterest );
2678+
2679+ for (HashMap transaction : allTransactions ) {
2680+ Map <String , Object > type = (Map <String , Object >) transaction .get ("transactionType" );
2681+
2682+ if (type != null && Boolean .TRUE .equals (type .get ("accrual" ))) {
2683+
2684+ List <Number > dateArray = (List <Number >) transaction .get ("date" );
2685+ LocalDate transactionDate = LocalDate .of (dateArray .get (0 ).intValue (), dateArray .get (1 ).intValue (), dateArray .get (2 ).intValue ());
2686+
2687+ if (transactionDate .getMonthValue () == 6 && transactionDate .getYear () == 2024 ) {
2688+ BigDecimal actualAmount = new BigDecimal (transaction .get ("amount" ).toString ()).setScale (4 , RoundingMode .HALF_EVEN );
2689+
2690+ Assertions .assertEquals (expectedDailyInterest .doubleValue (), actualAmount .doubleValue (), 0.01 ,
2691+ "The accrual for June on date " + transactionDate + " is incorrect." );
2692+
2693+ juneAccrualsVerified ++;
2694+ }
2695+ }
2696+ }
2697+
2698+ Assertions .assertEquals (30 , juneAccrualsVerified , "El número de transacciones de accrual para Junio no es 30." );
2699+ log .info ("Successfully verified {} accrual transactions for June." , juneAccrualsVerified );
2700+
2701+ } finally {
2702+ globalConfigurationHelper .updateGlobalConfiguration (GlobalConfigurationConstants .ENABLE_BUSINESS_DATE ,
2703+ new PutGlobalConfigurationsRequest ().enabled (false ));
2704+ }
2705+ }
2706+
2707+ @ Test
2708+ public void FixedDepositNoneInterestPostInterest (){
2709+ try {
2710+ final String amount = "10000" ;
2711+ final Account assetAccount = this .accountHelper .createAssetAccount ();
2712+ final Account incomeAccount = this .accountHelper .createIncomeAccount ();
2713+ final Account expenseAccount = this .accountHelper .createExpenseAccount ();
2714+ final Account liabilityAccount = this .accountHelper .createLiabilityAccount ();
2715+ final Account savingsControlAccount = this .accountHelper .createLiabilityAccount ("Savings Control" );
2716+
2717+ this .fixedDepositProductHelper = new FixedDepositProductHelper (this .requestSpec , this .responseSpec );
2718+ this .fixedDepositAccountHelper = new FixedDepositAccountHelper (this .requestSpec , this .responseSpec );
2719+
2720+ DateTimeFormatter dateFormat = DateTimeFormatter .ofPattern ("dd MMMM yyyy" , Locale .US );
2721+ DateTimeFormatter monthDayFormat = DateTimeFormatter .ofPattern ("dd MMM" , Locale .US );
2722+ DateTimeFormatter currentDateFormat = DateTimeFormatter .ofPattern ("dd" );
2723+
2724+ LocalDate mayDate = LocalDate .of (LocalDate .now ().getYear ()-1 , 5 , 1 );
2725+
2726+ final String SUBMITTED_ON_DATE = mayDate .format (dateFormat );
2727+ final String APPROVED_ON_DATE = mayDate .format (dateFormat );
2728+
2729+ globalConfigurationHelper .updateGlobalConfiguration (GlobalConfigurationConstants .ENABLE_BUSINESS_DATE ,
2730+ new PutGlobalConfigurationsRequest ().enabled (true ));
2731+
2732+ BusinessDateHelper .updateBusinessDate (requestSpec , responseSpec , BusinessDateType .BUSINESS_DATE , mayDate );
2733+
2734+ log .info ("Submitted Date: {}" , SUBMITTED_ON_DATE );
2735+
2736+
2737+ final Integer fixedDepositProductId = createFixedWithAccrualAccountingWithNoneInterest (liabilityAccount , expenseAccount ,incomeAccount ,assetAccount , savingsControlAccount );
2738+ Assertions .assertNotNull (fixedDepositProductId );
2739+
2740+ Integer clientId = ClientHelper .createClient (this .requestSpec , this .responseSpec );
2741+ Assertions .assertNotNull (clientId );
2742+
2743+ Integer fixedDepositAccountId = applyForFixedDepositApplication_10000 (clientId .toString (), fixedDepositProductId .toString (),
2744+ SUBMITTED_ON_DATE , WHOLE_TERM , Integer .valueOf (CLOSURE_TYPE_REINVEST ));
2745+ Assertions .assertNotNull (fixedDepositAccountId );
2746+
2747+ this .fixedDepositAccountHelper .approveFixedDeposit (fixedDepositAccountId , APPROVED_ON_DATE );
2748+ this .fixedDepositAccountHelper .activateFixedDeposit (fixedDepositAccountId , APPROVED_ON_DATE );
2749+
2750+
2751+ final LocalDate endOfMay = LocalDate .of (LocalDate .now ().getYear ()-1 , 6 , 1 );
2752+ BusinessDateHelper .updateBusinessDate (this .requestSpec , this .responseSpec , BusinessDateType .BUSINESS_DATE , endOfMay );
2753+ log .info ("Advancing business date to: {}. Running Post interest Job for May..." , endOfMay );
2754+
2755+ this .schedulerJobHelper .executeAndAwaitJob ("Post Interest For Savings" );
2756+
2757+ final LocalDate endOfJune = LocalDate .of (LocalDate .now ().getYear ()-1 , 7 , 1 );
2758+ BusinessDateHelper .updateBusinessDate (this .requestSpec , this .responseSpec , BusinessDateType .BUSINESS_DATE , endOfJune );
2759+ log .info ("Advancing business date to: {}. Running Post interest Job for June..." , endOfJune );
2760+
2761+ this .schedulerJobHelper .executeAndAwaitJob ("Post Interest For Savings" );
2762+
2763+ List <HashMap > allTransactions = this .fixedDepositAccountHelper .getFixedDepositTransactions (fixedDepositAccountId );
2764+ Assertions .assertNotNull (allTransactions , "The transaction list should not be null." );
2765+
2766+ BigDecimal newPrincipalAfterMay = new BigDecimal (amount );
2767+ for (HashMap transaction : allTransactions ) {
2768+ Map <String , Object > typeMap = (Map <String , Object >) transaction .get ("transactionType" );
2769+ if (typeMap != null ) {
2770+ int typeId = ((Number ) typeMap .get ("id" )).intValue ();
2771+ SavingsAccountTransactionType txType = SavingsAccountTransactionType .fromInt (typeId );
2772+ if (txType .isInterestPosting ()) {
2773+ List <Number > dateArray = (List <Number >) transaction .get ("date" );
2774+ LocalDate txDate = LocalDate .of (dateArray .get (0 ).intValue (), dateArray .get (1 ).intValue (), dateArray .get (2 ).intValue ());
2775+ if (txDate .equals (endOfMay )) {
2776+ newPrincipalAfterMay = newPrincipalAfterMay .add (new BigDecimal (transaction .get ("amount" ).toString ()));
2777+ break ;
2778+ }
2779+ }
2780+ }
2781+ }
2782+
2783+ BigDecimal daysInJune = new BigDecimal (30 );
2784+ BigDecimal expectedJuneInterest = newPrincipalAfterMay
2785+ .multiply (new BigDecimal ("0.15" ))
2786+ .divide (new BigDecimal ("365" ), 8 , RoundingMode .HALF_EVEN )
2787+ .multiply (daysInJune )
2788+ .setScale (2 , RoundingMode .HALF_EVEN );
2789+
2790+ boolean junePostingVerified = false ;
2791+ log .info ("Verifying the 'Interest Posting' transaction for June... Expected amount: {}" , expectedJuneInterest );
2792+
2793+ for (HashMap transaction : allTransactions ) {
2794+ Map <String , Object > typeMap = (Map <String , Object >) transaction .get ("transactionType" );
2795+ if (typeMap != null ) {
2796+ int typeId = ((Number ) typeMap .get ("id" )).intValue ();
2797+ SavingsAccountTransactionType txType = SavingsAccountTransactionType .fromInt (typeId );
2798+
2799+ if (txType .isInterestPosting ()) {
2800+ List <Number > dateArray = (List <Number >) transaction .get ("date" );
2801+ LocalDate txDate = LocalDate .of (dateArray .get (0 ).intValue (), dateArray .get (1 ).intValue (), dateArray .get (2 ).intValue ());
2802+
2803+ if (txDate .equals (endOfJune )) {
2804+ BigDecimal actualAmount = new BigDecimal (transaction .get ("amount" ).toString ());
2805+ log .info ("Found 'Interest Posting' transaction on {} with amount: {}" , txDate , actualAmount );
2806+
2807+ Assertions .assertEquals (0 , expectedJuneInterest .compareTo (actualAmount ),
2808+ "The interest posting amount for June (" + actualAmount + ") does not match the expected (" + expectedJuneInterest + ")" );
2809+
2810+ junePostingVerified = true ;
2811+ break ;
2812+ }
2813+ }
2814+ }
2815+ }
2816+
2817+ Assertions .assertTrue (junePostingVerified , "The 'INTEREST_POSTING' transaction for June was not found." );
2818+ log .info ("Successfully verified the interest posting transaction for June." );
2819+
2820+ } finally {
2821+ globalConfigurationHelper .updateGlobalConfiguration (GlobalConfigurationConstants .ENABLE_BUSINESS_DATE ,
2822+ new PutGlobalConfigurationsRequest ().enabled (false ));
2823+ }
2824+ }
2825+
2826+ public Integer createFixedWithAccrualAccountingWithNoneInterest (Account ... accounts ) {
2827+ this .fixedDepositProductHelper = new FixedDepositProductHelper (null ,null ).withInterestCompoundingPeriodTypeAsNone ()
2828+ .withCurrencyCode ("NGN" )
2829+ .withDigitsAfterDecimal ("2" )
2830+ .withInMultiplesOf ("" )
2831+ .withMinDepositAmount ("1" )
2832+ .withMaxDepositAmount ("1000000000" )
2833+ .withInterestPostingPeriodTypeAsMonthly ()
2834+ .withInterestCalculationPeriodTypeAsDailyBalance ()
2835+ .withLockinPeriodFrequency ("" )
2836+ .withLockingPeriodFrequencyType ("" )
2837+ .withInterestCalculationDaysInYearType_360 ()
2838+ .withMinDepositTerm ("1" )
2839+ .withMaxDepositTerm ("" )
2840+ .withInMultiplesOfDepositTerm ("" )
2841+ .with_minDepositTermTypeIdAsYears ()
2842+ .withPreClosurePenalApplicable (false )
2843+ .withAccountingRuleAsAccrualBased (accounts )
2844+ .withInterestPayableAccountId (accounts [0 ].getAccountID ().toString ())
2845+ .withSavingsReferenceAccountId (accounts [3 ].getAccountID ().toString ())
2846+ .withFixedpenaltiesReceivableAccountId (accounts [3 ].getAccountID ().toString ())
2847+ .withInterestPayableAccountId (accounts [1 ].getAccountID ().toString ())
2848+ .withInterestOnSavingsAccountId (accounts [2 ].getAccountID ().toString ())
2849+ .withSavingsControlAccountId (accounts [4 ].getAccountID ().toString ())
2850+ .withPeriodFixed ();
2851+ final String fixedProductJSON = this .fixedDepositProductHelper .build ("01 January 2024" , "01 January 2035" , true );
2852+ return FixedDepositProductHelper .createFixedDepositProduct (fixedProductJSON , requestSpec ,responseSpec );
2853+ }
2854+
26012855 private Integer createFixedDepositProduct (final String validFrom , final String validTo , final String accountingRule ,
26022856 Account ... accounts ) {
26032857 log .info ("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------" );
@@ -2691,6 +2945,16 @@ private Integer applyForFixedDepositApplication(final String clientID, final Str
26912945 this .responseSpec );
26922946 }
26932947
2948+ private Integer applyForFixedDepositApplication_10000 (final String clientID , final String productID , final String submittedOnDate ,
2949+ final String penalInterestType , final Integer maturityInstructionId ) {
2950+ log .info ("--------------------------------APPLYING FOR FIXED DEPOSIT ACCOUNT --------------------------------" );
2951+ final String fixedDepositApplicationJSON = new FixedDepositAccountHelper (this .requestSpec , this .responseSpec ) //
2952+ .withSubmittedOnDate (submittedOnDate ).withMaturityInstructionId (maturityInstructionId ).withDepositAmount ("10000" )
2953+ .build (clientID , productID , penalInterestType );
2954+ return FixedDepositAccountHelper .applyFixedDepositApplicationGetId (fixedDepositApplicationJSON , this .requestSpec ,
2955+ this .responseSpec );
2956+ }
2957+
26942958 private Integer applyForFixedDepositApplication (final String clientID , final String productID , final String submittedOnDate ,
26952959 final String penalInterestType , final String depositAmount , final String depositPeriod ) {
26962960 log .info ("--------------------------------APPLYING FOR FIXED DEPOSIT ACCOUNT --------------------------------" );
0 commit comments