This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 95ec9eaa3 FINERACT-1840: Fee income is not realised if the loan got 
fully repaid
95ec9eaa3 is described below

commit 95ec9eaa378304887cab171d2d2e01986ac66a2b
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Wed Jan 25 00:20:42 2023 -0600

    FINERACT-1840: Fee income is not realised if the loan got fully repaid
---
 .../api/JournalEntriesApiResource.java             |   4 +-
 .../api/JournalEntriesApiResourceSwagger.java      | 114 ++++++++++++++
 .../monetary/api/CurrenciesApiResourceSwagger.java |  22 ++-
 .../portfolio/loanaccount/domain/Loan.java         |  55 +++++--
 .../service/LoanStatusChangePlatformService.java   |  21 +++
 .../LoanStatusChangePlatformServiceImpl.java       |  55 +++++++
 .../api/LoanProductsApiResourceSwagger.java        | 116 +++-----------
 .../note/api/NotesApiResourceSwagger.java          |   2 +-
 .../api/PaymentTypeApiResourceSwagger.java         |   2 +-
 .../LoanChargeSpecificDueDateTest.java             | 173 ++++++++++++++++++++-
 .../common/accounting/JournalEntryHelper.java      |  14 ++
 .../common/loans/LoanTransactionHelper.java        |  32 ++++
 12 files changed, 489 insertions(+), 121 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
index cd6dac151..eb2cc6458 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
@@ -104,7 +104,7 @@ public class JournalEntriesApiResource {
             + "\n" + "journalentries?orderBy=transactionId&sortOrder=DESC\n" + 
"\n" + "journalentries?runningBalance=true\n" + "\n"
             + "journalentries?transactionDetails=true\n" + "\n" + 
"journalentries?loanId=12\n" + "\n" + "journalentries?savingsId=24")
     @ApiResponses({
-            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(array = @ArraySchema(schema = @Schema(implementation = 
JournalEntryData.class)))) })
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(array = @ArraySchema(schema = @Schema(implementation = 
JournalEntriesApiResourceSwagger.GetJournalEntriesTransactionIdResponse.class))))
 })
     public String retrieveAll(@Context final UriInfo uriInfo,
             @QueryParam("officeId") @Parameter(description = "officeId") final 
Long officeId,
             @QueryParam("glAccountId") @Parameter(description = "glAccountId") 
final Long glAccountId,
@@ -166,7 +166,7 @@ public class JournalEntriesApiResource {
             + 
"journalentries/1?fields=officeName,glAccountId,entryType,amount\n" + "\n" + 
"journalentries/1?runningBalance=true\n" + "\n"
             + "journalentries/1?transactionDetails=true")
     @ApiResponses({
-            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = JournalEntryData.class))) })
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
JournalEntriesApiResourceSwagger.JournalEntryTransactionItem.class))) })
     public String retrieveJournalEntryById(
             @PathParam("journalEntryId") @Parameter(description = 
"journalEntryId") final Long journalEntryId,
             @Context final UriInfo uriInfo,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java
index a1cea3d0b..9c7bf43a2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java
@@ -19,6 +19,12 @@
 package org.apache.fineract.accounting.journalentry.api;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import 
org.apache.fineract.organisation.monetary.api.CurrenciesApiResourceSwagger.CurrencyItem;
+import 
org.apache.fineract.portfolio.note.api.NotesApiResourceSwagger.GetResourceTypeResourceIdNotesResponse;
+import 
org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceSwagger.GetPaymentTypesResponse;
 
 /**
  * Created by sanyam on 25/7/17.
@@ -62,4 +68,112 @@ final class JournalEntriesApiResourceSwagger {
         @Schema(description = "1")
         public Long officeId;
     }
+
+    static final class EnumOptionType {
+
+        private EnumOptionType() {}
+
+        @Schema(example = "2")
+        public Long id;
+        @Schema(example = "accountType.asset")
+        public String code;
+        @Schema(example = "ASSET")
+        public String value;
+    }
+
+    static final class JournalEntryTransactionItem {
+
+        private JournalEntryTransactionItem() {}
+
+        static final class PaymentDetailData {
+
+            private PaymentDetailData() {}
+
+            @Schema(example = "62")
+            public Long id;
+            public GetPaymentTypesResponse paymentType;
+            @Schema(example = "acc123")
+            public String accountNumber;
+            @Schema(example = "che123")
+            public String checkNumber;
+            @Schema(example = "rou123")
+            public String routingCode;
+            @Schema(example = "rec123")
+            public String receiptNumber;
+            @Schema(example = "ban123")
+            public String bankNumber;
+        }
+
+        static final class TransactionDetails {
+
+            private TransactionDetails() {}
+
+            @Schema(example = "2")
+            public Long transactionId;
+            public EnumOptionType transactionType;
+            public GetResourceTypeResourceIdNotesResponse noteData;
+            public PaymentDetailData paymentDetails;
+        }
+
+        @Schema(example = "1")
+        public Long id;
+        @Schema(example = "L12")
+        public String transactionId;
+        @Schema(example = "1")
+        public Long entityId;
+        @Schema(example = "1")
+        public Long officeId;
+        @Schema(example = "Head Office")
+        public String officeName;
+        @Schema(example = "10")
+        public Long glAccountId;
+        @Schema(example = "Cash Account")
+        public String glAccountName;
+        @Schema(example = "0123-4567")
+        public String glAccountCode;
+        @Schema(example = "[2022, 07, 01]")
+        public LocalDate transactionDate;
+        @Schema(example = "[2022, 07, 01]")
+        public LocalDate submittedOnDate;
+
+        @Schema(example = "100.000000")
+        public Double amount;
+        @Schema(example = "false")
+        public boolean reversed;
+        @Schema(example = "false")
+        public boolean manualEntry;
+        @Schema(example = "Manual entry")
+        public String comments;
+        @Schema(example = "QWERTY")
+        public String referenceNumber;
+        @Schema(example = "1234.56")
+        public BigDecimal officeRunningBalance;
+        @Schema(example = "1234.56")
+        public BigDecimal organizationRunningBalance;
+        @Schema(example = "false")
+        public boolean runningBalanceComputed;
+        @Schema(example = "1")
+        public Long createdByUserId;
+        @Schema(example = "mifos")
+        public String createdByUserName;
+        @Schema(example = "[2022, 07, 01]")
+        public LocalDate createdDate;
+
+        public CurrencyItem currency;
+        public EnumOptionType glAccountType;
+        public EnumOptionType entryType;
+        public EnumOptionType entityType;
+        public TransactionDetails transactionDetails;
+    }
+
+    @Schema(description = "GetJournalEntriesTransactionIdResponse")
+    public static final class GetJournalEntriesTransactionIdResponse {
+
+        private GetJournalEntriesTransactionIdResponse() {}
+
+        @Schema(example = "2")
+        public Long totalFilteredRecords;
+        public List<JournalEntryTransactionItem> pageItems;
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
index 4a59beda4..549a0ffd6 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
@@ -25,12 +25,32 @@ import 
org.apache.fineract.organisation.monetary.data.CurrencyData;
 /**
  * Created by sanyam on 14/8/17.
  */
-final class CurrenciesApiResourceSwagger {
+public final class CurrenciesApiResourceSwagger {
 
     private CurrenciesApiResourceSwagger() {
 
     }
 
+    public static final class CurrencyItem {
+
+        private CurrencyItem() {}
+
+        @Schema(example = "USD")
+        public String code;
+        @Schema(example = "US Dollar")
+        public String name;
+        @Schema(example = "2")
+        public Integer decimalPlaces;
+        @Schema(example = "100")
+        public Integer inMultiplesOf;
+        @Schema(example = "$")
+        public String displaySymbol;
+        @Schema(example = "currency.USD")
+        public String nameCode;
+        @Schema(example = "US Dollar ($)")
+        public String displayLabel;
+    }
+
     @Schema(description = "GetCurrenciesResponse")
     public static final class GetCurrenciesResponse {
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index bfe7e7bf4..e9ccab2fd 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -3447,10 +3447,10 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             }
         }
         if (isAllChargesPaid) {
-            loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, 
this);
-
             this.closedOnDate = transactionDate;
             this.actualMaturityDate = transactionDate;
+            loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, 
this);
+
         } else if (LoanStatus.fromInt(this.loanStatus).isOverpaid()) {
             if (this.totalOverpaid == null || 
BigDecimal.ZERO.compareTo(this.totalOverpaid) == 0) {
                 this.overpaidOnDate = null;
@@ -3506,6 +3506,39 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         updateLoanOutstandingBalances();
     }
 
+    public void applyIncomeAccrualTransaction(LocalDate closedDate) {
+        ExternalId externalId = ExternalId.empty();
+        boolean isExternalIdAutoGenerationEnabled = 
TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled();
+        if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
+            List<LoanTransaction> updatedAccrualTransactions = 
retrieveListOfAccrualTransactions();
+            LocalDate lastAccruedDate = this.getDisbursementDate();
+            if (!updatedAccrualTransactions.isEmpty()) {
+                lastAccruedDate = 
updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 
1).getTransactionDate();
+            }
+            HashMap<String, Object> feeDetails = new HashMap<>();
+            determineFeeDetails(lastAccruedDate, closedDate, feeDetails);
+            if (isExternalIdAutoGenerationEnabled) {
+                externalId = ExternalId.generate();
+            }
+            BigDecimal fee = (BigDecimal) feeDetails.get(FEE);
+            if (fee == null) {
+                fee = BigDecimal.ZERO;
+            }
+            BigDecimal penalty = (BigDecimal) feeDetails.get(PENALTIES);
+            if (penalty == null) {
+                penalty = BigDecimal.ZERO;
+            }
+            BigDecimal total = fee.add(penalty);
+            // TODO: calculate interest?
+            if (total.compareTo(BigDecimal.ZERO) > 0) {
+                LoanTransaction accrualTransaction = 
LoanTransaction.accrueTransaction(this, this.getOffice(), closedDate, total, 
null, fee,
+                        penalty, externalId);
+                updateLoanChargesPaidBy(accrualTransaction, feeDetails, null);
+                addLoanTransaction(accrualTransaction);
+            }
+        }
+    }
+
     private void determineCumulativeIncomeFromInstallments(HashMap<String, 
BigDecimal> cumulativeIncomeFromInstallments) {
         BigDecimal interest = BigDecimal.ZERO;
         BigDecimal fee = BigDecimal.ZERO;
@@ -3797,6 +3830,10 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
         validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
 
+        final LocalDate writtenOffOnLocalDate = 
command.localDateValueOfParameterNamed(TRANSACTION_DATE);
+        this.closedOnDate = writtenOffOnLocalDate;
+        this.writtenOffOnDate = writtenOffOnLocalDate;
+        this.closedBy = currentUser;
         final LoanStatus statusEnum = 
loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, this);
 
         LoanTransaction loanTransaction = null;
@@ -3807,7 +3844,6 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             existingTransactionIds.addAll(findExistingTransactionIds());
             
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
 
-            final LocalDate writtenOffOnLocalDate = 
command.localDateValueOfParameterNamed(TRANSACTION_DATE);
             final String txnExternalId = 
command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID);
 
             ExternalId externalId = ExternalIdFactory.produce(txnExternalId);
@@ -3816,9 +3852,6 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 externalId = ExternalId.generate();
             }
 
-            this.closedOnDate = writtenOffOnLocalDate;
-            this.writtenOffOnDate = writtenOffOnLocalDate;
-            this.closedBy = currentUser;
             changes.put(CLOSED_ON_DATE, 
command.stringValueOfParameterNamed(TRANSACTION_DATE));
             changes.put(WRITTEN_OFF_ON_DATE, 
command.stringValueOfParameterNamed(TRANSACTION_DATE));
             changes.put("externalId", externalId);
@@ -3934,12 +3967,12 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             final Money totalOutstanding = 
this.summary.getTotalOutstanding(loanCurrency());
             if (totalOutstanding.isGreaterThanZero() && 
getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) {
 
+                this.closedOnDate = closureDate;
                 final LoanStatus statusEnum = 
loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this);
                 if 
(!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
                     
loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this);
                     changes.put(PARAM_STATUS, 
LoanEnumerations.status(this.loanStatus));
                 }
-                this.closedOnDate = closureDate;
                 changes.put("externalId", externalId);
                 loanTransaction = LoanTransaction.writeoff(this, getOffice(), 
closureDate, externalId);
                 final boolean isLastTransaction = 
isChronologicallyLatestTransaction(loanTransaction, getLoanTransactions());
@@ -3965,14 +3998,13 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             final Money totalLoanOverpayment = calculateTotalOverpayment();
             if (totalLoanOverpayment.isGreaterThanZero() && 
getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
                 // TODO - KW - technically should set somewhere that this loan
-                // has
-                // 'overpaid' amount
+                // has 'overpaid' amount
+                this.closedOnDate = closureDate;
                 final LoanStatus statusEnum = 
loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this);
                 if 
(!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
                     
loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this);
                     changes.put(PARAM_STATUS, 
LoanEnumerations.status(this.loanStatus));
                 }
-                this.closedOnDate = closureDate;
             } else if (totalLoanOverpayment.isGreaterThanZero()) {
                 final String errorMessage = "The loan is marked as 'Overpaid' 
and cannot be moved to 'Closed (obligations met).";
                 throw new InvalidLoanStateTransitionException("close", 
"loan.is.overpaid", errorMessage, totalLoanOverpayment.toString());
@@ -3995,13 +4027,13 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
         final LocalDate rescheduledOn = 
command.localDateValueOfParameterNamed(TRANSACTION_DATE);
 
+        this.closedOnDate = rescheduledOn;
         final LoanStatus statusEnum = 
loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_RESCHEDULE, this);
         if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
             loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, 
this);
             changes.put(PARAM_STATUS, 
LoanEnumerations.status(this.loanStatus));
         }
 
-        this.closedOnDate = rescheduledOn;
         this.rescheduledOnDate = rescheduledOn;
         changes.put(CLOSED_ON_DATE, 
command.stringValueOfParameterNamed(TRANSACTION_DATE));
         changes.put("rescheduledOnDate", 
command.stringValueOfParameterNamed(TRANSACTION_DATE));
@@ -6121,6 +6153,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
         if (this.totalOverpaid == null || 
BigDecimal.ZERO.compareTo(this.totalOverpaid) == 0) {
             this.overpaidOnDate = null;
+            this.closedOnDate = 
newCreditBalanceRefundTransaction.getTransactionDate();
             
defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_CREDIT_BALANCE_REFUND,
 this);
         }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformService.java
new file mode 100644
index 000000000..fc737fec8
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformService.java
@@ -0,0 +1,21 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+public interface LoanStatusChangePlatformService {}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
new file mode 100644
index 000000000..3f620fc38
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import javax.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class LoanStatusChangePlatformServiceImpl implements 
LoanStatusChangePlatformService {
+
+    private final BusinessEventNotifierService businessEventNotifierService;
+
+    @PostConstruct
+    public void addListeners() {
+        
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class,
 new LoanStatusChangedListener());
+    }
+
+    private static class LoanStatusChangedListener implements 
BusinessEventListener<LoanStatusChangedBusinessEvent> {
+
+        @Override
+        public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
+            final Loan loan = event.get();
+            log.debug("Loan Status change for loan {}", loan.getId());
+            // Apply common actions on Loan account closed
+            if (loan.isClosed()) {
+                log.debug("Loan Status {} for loan {}", 
loan.getStatus().getCode(), loan.getId());
+                loan.applyIncomeAccrualTransaction(loan.getClosedOnDate());
+            }
+        }
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index d16740cad..b5ebb1e31 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -1039,110 +1039,30 @@ final class LoanProductsApiResourceSwagger {
 
             private GetLoanAccountingMappings() {}
 
-            static final class GetLoanFundSourceAccount {
+            static final class GetGlAccountMapping {
 
-                private GetLoanFundSourceAccount() {}
-
-                @Schema(example = "1")
-                public Long id;
-                @Schema(example = "fund source")
-                public String name;
-                @Schema(example = "01")
-                public Integer glCode;
-            }
-
-            static final class GetLoanPortfolioAccount {
-
-                private GetLoanPortfolioAccount() {}
-
-                @Schema(example = "2")
-                public Long id;
-                @Schema(example = "Loan portfolio")
-                public String name;
-                @Schema(example = "02")
-                public Integer glCode;
-            }
-
-            static final class GetLoanTransfersInSuspenseAccount {
-
-                private GetLoanTransfersInSuspenseAccount() {}
-
-                @Schema(example = "3")
-                public Long id;
-                @Schema(example = "transfers")
-                public String name;
-                @Schema(example = "03")
-                public Integer glCode;
-            }
-
-            static final class GetLoanInterestOnLoanAccount {
-
-                private GetLoanInterestOnLoanAccount() {}
-
-                @Schema(example = "4")
-                public Long id;
-                @Schema(example = "income from interest")
-                public String name;
-                @Schema(example = "04")
-                public Integer glCode;
-            }
-
-            static final class GetLoanIncomeFromFeeAccount {
-
-                private GetLoanIncomeFromFeeAccount() {}
-
-                @Schema(example = "8")
-                public Long id;
-                @Schema(example = "income from fees 2")
-                public String name;
-                @Schema(example = "10")
-                public Integer glCode;
-            }
-
-            static final class GetLoanIncomeFromPenaltyAccount {
-
-                private GetLoanIncomeFromPenaltyAccount() {}
-
-                @Schema(example = "9")
-                public Long id;
-                @Schema(example = "income from penalities 2")
-                public String name;
-                @Schema(example = "11")
-                public Integer glCode;
-            }
-
-            static final class GetLoanWriteOffAccount {
-
-                private GetLoanWriteOffAccount() {}
+                private GetGlAccountMapping() {}
 
                 @Schema(example = "10")
                 public Long id;
-                @Schema(example = "loans written off 2")
-                public String name;
-                @Schema(example = "12")
-                public Integer glCode;
-            }
-
-            static final class GetLoanOverpaymentLiabilityAccount {
-
-                private GetLoanOverpaymentLiabilityAccount() {}
-
-                @Schema(example = "11")
-                public Long id;
-                @Schema(example = "over payment")
+                @Schema(example = "Cash Account")
                 public String name;
-                @Schema(example = "13")
-                public Integer glCode;
+                @Schema(example = "012-34-65")
+                public String glCode;
             }
 
-            public GetLoanFundSourceAccount fundSourceAccount;
-            public GetLoanPortfolioAccount loanPortfolioAccount;
-            public GetLoanTransfersInSuspenseAccount 
transfersInSuspenseAccount;
-            public GetLoanInterestOnLoanAccount interestOnLoanAccount;
-            public GetLoanIncomeFromFeeAccount incomeFromFeeAccount;
-            public GetLoanIncomeFromPenaltyAccount incomeFromPenaltyAccount;
-            public GetLoanWriteOffAccount writeOffAccount;
-            public GetLoanOverpaymentLiabilityAccount 
overpaymentLiabilityAccount;
+            public GetGlAccountMapping fundSourceAccount;
+            public GetGlAccountMapping loanPortfolioAccount;
+            public GetGlAccountMapping transfersInSuspenseAccount;
+            public GetGlAccountMapping receivableInterestAccount;
+            public GetGlAccountMapping receivablePenaltyAccount;
+            public GetGlAccountMapping interestOnLoanAccount;
+            public GetGlAccountMapping incomeFromFeeAccount;
+            public GetGlAccountMapping incomeFromPenaltyAccount;
+            public GetGlAccountMapping incomeFromRecoveryAccount;
+            public GetGlAccountMapping writeOffAccount;
+            public GetGlAccountMapping goodwillCreditAccount;
+            public GetGlAccountMapping overpaymentLiabilityAccount;
         }
 
         static final class GetLoanPaymentChannelToFundSourceMappings {
@@ -1174,7 +1094,7 @@ final class LoanProductsApiResourceSwagger {
             }
 
             public GetLoanCharge charge;
-            public GetLoanAccountingMappings.GetLoanIncomeFromFeeAccount 
incomeAccount;
+            public GetLoanAccountingMappings.GetGlAccountMapping incomeAccount;
             @Schema(example = "10")
             public Long chargeId;
             @Schema(example = "39")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
index a0524d59d..e6d2b71e7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
@@ -24,7 +24,7 @@ import java.time.ZonedDateTime;
 /**
  * Created by Chirag Gupta on 12/29/17.
  */
-final class NotesApiResourceSwagger {
+public final class NotesApiResourceSwagger {
 
     private NotesApiResourceSwagger() {}
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
index 8813ec286..a2d065944 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResourceSwagger.java
@@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
 /**
  * Created by Chirag Gupta on 01/01/18.
  */
-final class PaymentTypeApiResourceSwagger {
+public final class PaymentTypeApiResourceSwagger {
 
     private PaymentTypeApiResourceSwagger() {}
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
index 7b8e599ec..02393b488 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.integrationtests;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import io.restassured.builder.RequestSpecBuilder;
@@ -27,15 +28,25 @@ import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.time.LocalDate;
 import java.util.HashMap;
+import java.util.List;
 import lombok.extern.slf4j.Slf4j;
+import 
org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
 import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.JournalEntryTransactionItem;
 import org.apache.fineract.client.models.PostChargesResponse;
 import 
org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import 
org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import 
org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
 import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
@@ -49,6 +60,10 @@ public class LoanChargeSpecificDueDateTest {
     private ResponseSpecification responseSpec;
     private RequestSpecification requestSpec;
     private LoanTransactionHelper loanTransactionHelper;
+    private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
+    private AccountHelper accountHelper;
+    private JournalEntryHelper journalEntryHelper;
+
     private static final String principalAmount = "1000.00";
 
     @BeforeEach
@@ -60,6 +75,9 @@ public class LoanChargeSpecificDueDateTest {
         responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
 
         loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, 
this.responseSpec);
+        periodicAccrualAccountingHelper = new 
PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
+        accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+        journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
     }
 
     @Test
@@ -79,7 +97,7 @@ public class LoanChargeSpecificDueDateTest {
 
         // Create Loan Account
         final Integer loanId = createLoanAccount(loanTransactionHelper, 
clientId.toString(),
-                getLoanProductsProductResponse.getId().toString(), 
operationDate);
+                getLoanProductsProductResponse.getId().toString(), 
operationDate, "12", "0");
 
         // Get loan details
         GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -132,7 +150,7 @@ public class LoanChargeSpecificDueDateTest {
 
         // Create Loan Account
         final Integer loanId = createLoanAccount(loanTransactionHelper, 
clientId.toString(),
-                getLoanProductsProductResponse.getId().toString(), 
operationDate);
+                getLoanProductsProductResponse.getId().toString(), 
operationDate, "12", "0");
 
         // Get loan details
         GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -187,7 +205,7 @@ public class LoanChargeSpecificDueDateTest {
 
         // Create Loan Account
         final Integer loanId = createLoanAccount(loanTransactionHelper, 
clientId.toString(),
-                getLoanProductsProductResponse.getId().toString(), 
operationDate);
+                getLoanProductsProductResponse.getId().toString(), 
operationDate, "12", "0");
 
         // Get loan details
         GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -233,6 +251,132 @@ public class LoanChargeSpecificDueDateTest {
 
     }
 
+    @Test
+    public void testApplyFeeAccrualOnClosedDate() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, todaysDate);
+
+        // Client and Loan account creation
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, 
this.responseSpec, "01 January 2012");
+        final GetLoanProductsProductIdResponse getLoanProductsProductResponse 
= createLoanProductWithPeriodicAccrual(loanTransactionHelper,
+                null);
+        assertNotNull(getLoanProductsProductResponse);
+
+        LocalDate transactionDate = 
LocalDate.of(Utils.getLocalDateOfTenant().getYear(), 1, 1);
+        String operationDate = Utils.dateFormatter.format(transactionDate);
+        log.info("Disbursement date {}", transactionDate);
+
+        // Create Loan Account
+        final Integer loanId = createLoanAccount(loanTransactionHelper, 
clientId.toString(),
+                getLoanProductsProductResponse.getId().toString(), 
operationDate, "1", "0");
+
+        // Get loan details
+        GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"), true);
+
+        // Apply Loan Charge with specific due date
+        String feeAmount = "10.00";
+        String payloadJSON = 
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 feeAmount, true);
+        final PostChargesResponse postChargesResponse = 
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+        assertNotNull(postChargesResponse);
+        final Long chargeId = postChargesResponse.getResourceId();
+        assertNotNull(chargeId);
+
+        // First Loan Charge
+        transactionDate = transactionDate.plusDays(1);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, transactionDate);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        log.info("Operation date {}", transactionDate);
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(),
 operationDate, feeAmount);
+        PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+                responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+        final Long loanChargeId01 = 
postLoansLoanIdChargesResponse.getResourceId();
+        assertNotNull(loanChargeId01);
+
+        // Run Accruals
+        log.info("Running Periodic Accrual for date {}", transactionDate);
+        
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate);
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        
loanTransactionHelper.evaluateLoanTransactionData(getLoansLoanIdResponse, 
"loanTransactionType.accrual", Double.valueOf("10.00"));
+
+        // Repay the first charge fully, 10
+        Float amount = Float.valueOf("10.00");
+        transactionDate = transactionDate.plusDays(40);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, transactionDate);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        log.info("Operation date {}", transactionDate);
+        PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+                loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        log.info("Loan Transaction Id: {} {}", loanId, 
loanIdTransactionsResponse.getResourceId());
+
+        // Second Loan Charge
+        feeAmount = "15.00";
+        transactionDate = transactionDate.plusDays(1);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, transactionDate);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        log.info("Operation date {}", transactionDate);
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(),
 operationDate, feeAmount);
+        postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON, responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+        final Long loanChargeId02 = 
postLoansLoanIdChargesResponse.getResourceId();
+        assertNotNull(loanChargeId02);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("15.00"), true);
+
+        // Run Accruals
+        log.info("Running Periodic Accrual for date {}", transactionDate);
+        
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate);
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        
loanTransactionHelper.evaluateLoanTransactionData(getLoansLoanIdResponse, 
"loanTransactionType.accrual", Double.valueOf("25.00"));
+
+        // Third Loan Charge
+        feeAmount = "25.00";
+        transactionDate = transactionDate.plusDays(1);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, transactionDate);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        log.info("Operation date {}", transactionDate);
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(),
 operationDate, feeAmount);
+        postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON, responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+        final Long loanChargeId03 = 
postLoansLoanIdChargesResponse.getResourceId();
+        assertNotNull(loanChargeId03);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("40.00"), true);
+        
loanTransactionHelper.evaluateLoanTransactionData(getLoansLoanIdResponse, 
"loanTransactionType.accrual", Double.valueOf("25.00"));
+
+        amount = Float.valueOf("1040.00");
+        loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount, loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        log.info("Loan Transaction Id: {} {}", loanId, 
loanIdTransactionsResponse.getResourceId());
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, 
"loanStatusType.closed.obligations.met");
+        
loanTransactionHelper.evaluateLoanTransactionData(getLoansLoanIdResponse, 
"loanTransactionType.accrual", Double.valueOf("50.00"));
+
+        final Long transactionId = 
loanTransactionHelper.evaluateLastLoanTransactionData(getLoansLoanIdResponse,
+                "loanTransactionType.accrual", operationDate, 
Double.valueOf("25.00"));
+        assertNotNull(transactionId);
+        log.info("transactionId {}", transactionId);
+
+        final GetJournalEntriesTransactionIdResponse journalEntriesResponse = 
journalEntryHelper
+                .getJournalEntries("L" + transactionId.toString());
+        assertNotNull(journalEntriesResponse);
+        final List<JournalEntryTransactionItem> journalEntries = 
journalEntriesResponse.getPageItems();
+        assertEquals(2, journalEntries.size());
+        assertEquals(25, journalEntries.get(0).getAmount());
+        assertEquals(25, journalEntries.get(1).getAmount());
+        assertEquals(transactionDate, 
journalEntries.get(0).getTransactionDate());
+        assertEquals(transactionDate, 
journalEntries.get(1).getTransactionDate());
+
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+    }
+
     private GetLoanProductsProductIdResponse createLoanProduct(final 
LoanTransactionHelper loanTransactionHelper,
             final Integer delinquencyBucketId) {
         final HashMap<String, Object> loanProductMap = new 
LoanProductTestBuilder().build(null, delinquencyBucketId);
@@ -240,12 +384,27 @@ public class LoanChargeSpecificDueDateTest {
         return loanTransactionHelper.getLoanProduct(loanProductId);
     }
 
+    private GetLoanProductsProductIdResponse 
createLoanProductWithPeriodicAccrual(final LoanTransactionHelper 
loanTransactionHelper,
+            final Integer delinquencyBucketId) {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account assetFeeAndPenaltyAccount = 
this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = 
this.accountHelper.createLiabilityAccount();
+
+        final HashMap<String, Object> loanProductMap = new 
LoanProductTestBuilder()
+                .withAccountingRulePeriodicAccrual(new Account[] { 
assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
+                .build(null, delinquencyBucketId);
+        final Integer loanProductId = 
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+        return loanTransactionHelper.getLoanProduct(loanProductId);
+    }
+
     private Integer createLoanAccount(final LoanTransactionHelper 
loanTransactionHelper, final String clientId, final String loanProductId,
-            final String operationDate) {
-        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
-                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+            final String operationDate, final String repayments, final String 
interestRate) {
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency(repayments)
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments(repayments).withRepaymentEveryAfter("1")
                 .withRepaymentFrequencyTypeAsMonths() //
-                .withInterestRatePerPeriod("0") //
+                .withInterestRatePerPeriod(interestRate) //
                 .withExpectedDisbursementDate(operationDate) //
                 .withInterestTypeAsDecliningBalance() //
                 .withSubmittedOnDate(operationDate) //
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
index 838afcd39..155586294 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java
@@ -20,18 +20,24 @@ package 
org.apache.fineract.integrationtests.common.accounting;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import com.google.gson.Gson;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.util.ArrayList;
 import java.util.HashMap;
+import lombok.extern.slf4j.Slf4j;
+import 
org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
+import org.apache.fineract.client.util.JSON;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.junit.jupiter.api.Assertions;
 
+@Slf4j
 @SuppressWarnings("rawtypes")
 public class JournalEntryHelper {
 
     private final RequestSpecification requestSpec;
     private final ResponseSpecification responseSpec;
+    private static final Gson GSON = new JSON().getGson();
 
     public JournalEntryHelper(final RequestSpecification requestSpec, final 
ResponseSpecification responseSpec) {
         this.requestSpec = requestSpec;
@@ -104,4 +110,12 @@ public class JournalEntryHelper {
                 + "&orderBy=id&sortOrder=desc&locale=en&dateFormat=dd MMMM 
yyyy");
     }
 
+    public GetJournalEntriesTransactionIdResponse getJournalEntries(final 
String transactionId) {
+        log.info("Getting GL Journal entries for transaction id {}", 
transactionId);
+        final String url = 
createURLForGettingAccountEntriesByTransactionId(transactionId);
+        final String response = Utils.performServerGet(this.requestSpec, 
this.responseSpec, url, null);
+        log.info("response {}", response);
+        return GSON.fromJson(response, 
GetJournalEntriesTransactionIdResponse.class);
+    }
+
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 2b9045de5..d445462cb 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -55,6 +55,7 @@ import 
org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
 import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdSummary;
+import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
 import org.apache.fineract.client.models.GetPaymentTypesResponse;
@@ -1580,6 +1581,37 @@ public class LoanTransactionHelper extends 
IntegrationTest {
         }
     }
 
+    public void evaluateLoanTransactionData(GetLoansLoanIdResponse 
getLoansLoanIdResponse, String transactionType, Double amountExpected) {
+        List<GetLoansLoanIdTransactions> transactions = 
getLoansLoanIdResponse.getTransactions();
+        log.info("Loan with {} transactions", transactions.size());
+        Double transactionsAmount = 0.0;
+        for (GetLoansLoanIdTransactions transaction : transactions) {
+            log.info("  Id {} code {} date {} amount {}", transaction.getId(), 
transaction.getType().getCode(), transaction.getDate(),
+                    transaction.getAmount());
+            if (transactionType.equals(transaction.getType().getCode())) {
+                transactionsAmount += transaction.getAmount();
+            }
+        }
+        assertEquals(amountExpected, transactionsAmount);
+    }
+
+    public Long evaluateLastLoanTransactionData(GetLoansLoanIdResponse 
getLoansLoanIdResponse, String transactionType,
+            String transactionExpected, Double amountExpected) {
+        List<GetLoansLoanIdTransactions> transactions = 
getLoansLoanIdResponse.getTransactions();
+        log.info("Loan with {} transactions", transactions.size());
+        GetLoansLoanIdTransactions lastTransaction = null;
+        for (GetLoansLoanIdTransactions transaction : transactions) {
+            log.info("  Id {} code {} date {} amount {}", transaction.getId(), 
transaction.getType().getCode(), transaction.getDate(),
+                    transaction.getAmount());
+            if (transactionType.equals(transaction.getType().getCode())) {
+                lastTransaction = transaction;
+            }
+        }
+        assertEquals(transactionExpected, 
Utils.dateFormatter.format(lastTransaction.getDate()));
+        assertEquals(amountExpected, lastTransaction.getAmount());
+        return lastTransaction.getId();
+    }
+
     public void validateLoanStatus(GetLoansLoanIdResponse 
getLoansLoanIdResponse, final String statusCodeExpected) {
         final String statusCode = getLoansLoanIdResponse.getStatus().getCode();
         log.info("Loan with Id {} is with Status {}", 
getLoansLoanIdResponse.getId(), statusCode);

Reply via email to