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 4a78901b7 
FINERACT-1806-Accounting-treatments-for-Charge-off-loan-accounts
4a78901b7 is described below

commit 4a78901b7cfccca21a17fa4cea1cd96f48799bb2
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Fri Feb 3 10:03:38 2023 +0530

    FINERACT-1806-Accounting-treatments-for-Charge-off-loan-accounts
---
 .../accounting/common/AccountingConstants.java     |  22 +-
 .../accounting/journalentry/data/LoanDTO.java      |   4 +
 .../service/AccountingProcessorHelper.java         |   4 +-
 .../AccrualBasedAccountingProcessorForLoan.java    | 455 +++++++++++++++++++++
 .../CashBasedAccountingProcessorForLoan.java       | 430 ++++++++++++++++++-
 .../LoanProductToGLAccountMappingHelper.java       |  29 ++
 .../service/ProductToGLAccountMappingHelper.java   |   5 +
 ...tToGLAccountMappingReadPlatformServiceImpl.java |  20 +
 ...ToGLAccountMappingWritePlatformServiceImpl.java |  30 ++
 .../loanaccount/data/LoanTransactionEnumData.java  |   2 +
 .../portfolio/loanaccount/domain/Loan.java         |   2 +
 .../LoanAccrualWritePlatformServiceImpl.java       |   4 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |   8 +-
 .../serialization/LoanProductDataValidator.java    |  56 ++-
 .../LoanChargeOffAccountingTest.java               | 450 ++++++++++++++++++++
 .../common/loans/LoanProductTestBuilder.java       |  10 +
 16 files changed, 1514 insertions(+), 17 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
index d2e8a3372..dcc168565 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
@@ -37,7 +37,9 @@ public final class AccountingConstants {
     public enum CashAccountsForLoan {
 
         FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), 
INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(
-                6), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), 
INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13);
+                6), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), 
INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13), INCOME_FROM_CHARGE_OFF_INTEREST(
+                        14), INCOME_FROM_CHARGE_OFF_FEES(
+                                15), CHARGE_OFF_EXPENSE(16), 
CHARGE_OFF_FRAUD_EXPENSE(17), INCOME_FROM_CHARGE_OFF_PENALTY(18);
 
         private final Integer value;
 
@@ -75,7 +77,9 @@ public final class AccountingConstants {
 
         FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), 
INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), //
         LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(7), FEES_RECEIVABLE(8), 
PENALTIES_RECEIVABLE(9), //
-        TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), 
GOODWILL_CREDIT(13);
+        TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), 
GOODWILL_CREDIT(13), INCOME_FROM_CHARGE_OFF_INTEREST(
+                14), INCOME_FROM_CHARGE_OFF_FEES(
+                        15), CHARGE_OFF_EXPENSE(16), 
CHARGE_OFF_FRAUD_EXPENSE(17), INCOME_FROM_CHARGE_OFF_PENALTY(18);
 
         private final Integer value;
 
@@ -125,7 +129,12 @@ public final class AccountingConstants {
                                                                                
         "penaltyToIncomeAccountMappings"), CHARGE_ID(
                                                                                
                 "chargeId"), INCOME_ACCOUNT_ID(
                                                                                
                         "incomeAccountId"), INCOME_FROM_RECOVERY(
-                                                                               
                                 "incomeFromRecoveryAccountId");
+                                                                               
                                 "incomeFromRecoveryAccountId"), 
INCOME_FROM_CHARGE_OFF_INTEREST(
+                                                                               
                                         
"incomeFromChargeOffInterestAccountId"), INCOME_FROM_CHARGE_OFF_FEES(
+                                                                               
                                                 
"incomeFromChargeOffFeesAccountId"), CHARGE_OFF_EXPENSE(
+                                                                               
                                                         
"chargeOffExpenseAccountId"), CHARGE_OFF_FRAUD_EXPENSE(
+                                                                               
                                                                 
"chargeOffFraudExpenseAccountId"), INCOME_FROM_CHARGE_OFF_PENALTY(
+                                                                               
                                                                         
"incomeFromChargeOffPenaltyAccountId");
 
         private final String value;
 
@@ -154,7 +163,12 @@ public final class AccountingConstants {
                                                         
"transfersInSuspenseAccount"), INCOME_ACCOUNT_ID(
                                                                 
"incomeAccount"), INCOME_FROM_RECOVERY(
                                                                         
"incomeFromRecoveryAccount"), LIABILITY_TRANSFER_SUSPENSE(
-                                                                               
 "liabilityTransferInSuspenseAccount");
+                                                                               
 "liabilityTransferInSuspenseAccount"), INCOME_FROM_CHARGE_OFF_INTEREST(
+                                                                               
         "incomeFromChargeOffInterestAccount"), INCOME_FROM_CHARGE_OFF_FEES(
+                                                                               
                 "incomeFromChargeOffFeesAccount"), CHARGE_OFF_EXPENSE(
+                                                                               
                         "chargeOffExpenseAccount"), CHARGE_OFF_FRAUD_EXPENSE(
+                                                                               
                                 "chargeOffFraudExpenseAccount"), 
INCOME_FROM_CHARGE_OFF_PENALTY(
+                                                                               
                                         "incomeFromChargeOffPenaltyAccount");
 
         private final String value;
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
index 6bfc6ac48..60b50dc80 100755
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
@@ -41,4 +41,8 @@ public class LoanDTO {
     private final boolean periodicAccrualBasedAccountingEnabled;
     @Setter
     private List<LoanTransactionDTO> newLoanTransactions;
+    @Setter
+    private boolean markedAsChargeOff;
+    @Setter
+    private boolean markedAsFraud;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
index 1f7e8b656..fec88b4f4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
@@ -108,6 +108,8 @@ public class AccountingProcessorHelper {
         final String currencyCode = (String) 
accountingBridgeData.get("currencyCode");
         final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<>();
         boolean isAccountTransfer = (Boolean) 
accountingBridgeData.get("isAccountTransfer");
+        boolean isLoanMarkedAsChargeOff = (Boolean) 
accountingBridgeData.get("isChargeOff");
+        boolean isLoanMarkedAsFraud = (Boolean) 
accountingBridgeData.get("isFraud");
 
         @SuppressWarnings("unchecked")
         final List<Map<String, Object>> newTransactionsMap = (List<Map<String, 
Object>>) accountingBridgeData.get("newLoanTransactions");
@@ -162,7 +164,7 @@ public class AccountingProcessorHelper {
         }
 
         return new LoanDTO(loanId, loanProductId, officeId, currencyCode, 
cashBasedAccountingEnabled, upfrontAccrualBasedAccountingEnabled,
-                periodicAccrualBasedAccountingEnabled, newLoanTransactions);
+                periodicAccrualBasedAccountingEnabled, newLoanTransactions, 
isLoanMarkedAsChargeOff, isLoanMarkedAsFraud);
     }
 
     public SavingsDTO populateSavingsDtoFromMap(final Map<String, Object> 
accountingBridgeData, final boolean cashBasedAccountingEnabled,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index 5c729eb6c..225047ee5 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -104,10 +104,208 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
             else if 
(loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
                 createJournalEntriesForChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
             }
+            // Logic for Charge-Off
+            else if (loanTransactionDTO.getTransactionType().isChargeoff()) {
+                createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, 
office);
+            }
+        }
+    }
+
+    private void createJournalEntriesForChargeOff(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        Map<GLAccount, BigDecimal> accountMapForCredit = new LinkedHashMap<>();
+
+        Map<Integer, BigDecimal> accountMapForDebit = new LinkedHashMap<>();
+
+        // principal payment
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            if (isMarkedFraud) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            } else {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+        }
+
+        // interest payment
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+
+            populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(),
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // handle fees payment
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, 
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // handle penalty payment
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(),
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // create credit entries
+        for (Map.Entry<GLAccount, BigDecimal> creditEntry : 
accountMapForCredit.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    creditEntry.getValue(), isReversal, creditEntry.getKey());
+        }
+
+        // create debit entries
+        for (Map.Entry<Integer, BigDecimal> debitEntry : 
accountMapForDebit.entrySet()) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, debitEntry.getKey().intValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, 
debitEntry.getValue(), isReversal);
+        }
+
+    }
+
+    private void populateCreditDebitMaps(Long loanProductId, BigDecimal 
transactionPartAmount, Long paymentTypeId,
+            Integer creditAccountType, Integer debitAccountType, 
Map<GLAccount, BigDecimal> accountMapForCredit,
+            Map<Integer, BigDecimal> accountMapForDebit) {
+        GLAccount accountCredit = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, creditAccountType, 
paymentTypeId);
+        if (accountMapForCredit.containsKey(accountCredit)) {
+            BigDecimal amount = 
accountMapForCredit.get(accountCredit).add(transactionPartAmount);
+            accountMapForCredit.put(accountCredit, amount);
+        } else {
+            accountMapForCredit.put(accountCredit, transactionPartAmount);
+        }
+        Integer accountDebit = debitAccountType;
+        if (accountMapForDebit.containsKey(accountDebit)) {
+            BigDecimal amount = 
accountMapForDebit.get(accountDebit).add(transactionPartAmount);
+            accountMapForDebit.put(accountDebit, amount);
+        } else {
+            accountMapForDebit.put(accountDebit, transactionPartAmount);
         }
     }
 
     private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+        final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
+        if (isMarkedAsChargeOff) {
+            createJournalEntriesForChargeOffLoanChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
+        } else {
+            createJournalEntriesForLoanChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
+        }
+    }
+
+    private void createJournalEntriesForChargeOffLoanChargeAdjustment(LoanDTO 
loanDTO, LoanTransactionDTO loanTransactionDTO,
+            Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = 
loanTransactionDTO.getOverPayment();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
+
+        // handle principal payment
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
+            accountMap.put(account, principalAmount);
+        }
+
+        // handle interest payment
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(interestAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, interestAmount);
+            }
+
+        }
+
+        // handle fees payment
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = accountMap.get(account).add(feesAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, feesAmount);
+            }
+        }
+
+        // handle penalty payment
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(penaltiesAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, penaltiesAmount);
+            }
+        }
+
+        // handle overpayment
+        if (overPaymentAmount != null && 
overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, 
AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                    paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(overPaymentAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, overPaymentAmount);
+            }
+        }
+
+        for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    entry.getValue(), isReversal, entry.getKey());
+        }
+
+        if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
+            Long chargeId = 
loanTransactionDTO.getLoanChargeData().getChargeId();
+            Integer accountMappingTypeId;
+            if (loanTransactionDTO.getLoanChargeData().isPenalty()) {
+                accountMappingTypeId = 
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
+            } else {
+                accountMappingTypeId = 
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue();
+            }
+            
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, 
currencyCode, accountMappingTypeId, loanProductId, chargeId,
+                    loanId, transactionId, transactionDate, totalDebitAmount, 
isReversal);
+        }
+    }
+
+    private void createJournalEntriesForLoanChargeAdjustment(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
         // loan properties
         final Long loanProductId = loanDTO.getLoanProductId();
         final Long loanId = loanDTO.getLoanId();
@@ -306,6 +504,263 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
      */
     private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO 
loanDTO, final LoanTransactionDTO loanTransactionDTO,
             final Office office, final boolean writeOff, final boolean 
isIncomeFromFee) {
+        final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
+        if (isMarkedChargeOff) {
+            createJournalEntriesForChargeOffLoanRepaymentAndWriteOffs(loanDTO, 
loanTransactionDTO, office, writeOff, isIncomeFromFee);
+
+        } else {
+            createJournalEntriesForLoansRepaymentAndWriteOffs(loanDTO, 
loanTransactionDTO, office, writeOff, isIncomeFromFee);
+        }
+    }
+
+    private void 
createJournalEntriesForChargeOffLoanRepaymentAndWriteOffs(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO,
+            Office office, boolean writeOff, boolean isIncomeFromFee) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = 
loanTransactionDTO.getOverPayment();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        Map<GLAccount, BigDecimal> accountMapForCredit = new LinkedHashMap<>();
+        Map<Integer, BigDecimal> accountMapForDebit = new LinkedHashMap<>();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        // principal payment
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                if (isMarkedFraud) {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                } else {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                }
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                if (isMarkedFraud) {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+
+                } else {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                }
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // interest payment
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // handle fees payment
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                if (isIncomeFromFee) {
+                    
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, 
currencyCode,
+                            
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, 
transactionId, transactionDate,
+                            feesAmount, isReversal, 
loanTransactionDTO.getFeePayments());
+                    Integer accountDebit = 
AccrualAccountsForLoan.FUND_SOURCE.getValue();
+                    if (accountMapForDebit.containsKey(accountDebit)) {
+                        BigDecimal amount = 
accountMapForDebit.get(accountDebit).add(feesAmount);
+                        accountMapForDebit.put(accountDebit, amount);
+                    } else {
+                        accountMapForDebit.put(accountDebit, feesAmount);
+                    }
+
+                } else {
+                    populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
+                            AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+                }
+
+            }
+
+        }
+
+        // handle penalties
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else {
+                if (isIncomeFromFee) {
+                    populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                } else {
+                    populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                            
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), 
AccrualAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                }
+            }
+
+        }
+
+        // overpayment
+        if (overPaymentAmount != null && 
overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                        AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // create credit entries
+        for (Map.Entry<GLAccount, BigDecimal> creditEntry : 
accountMapForCredit.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    creditEntry.getValue(), isReversal, creditEntry.getKey());
+        }
+
+        if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
+            if (writeOff) {
+                this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode,
+                        AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId,
+                        transactionDate, totalDebitAmount, isReversal);
+            } else {
+                if (loanTransactionDTO.isLoanToLoanTransfer()) {
+                    
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, 
FinancialActivity.ASSET_TRANSFER.getValue(),
+                            loanProductId, paymentTypeId, loanId, 
transactionId, transactionDate, totalDebitAmount, isReversal);
+                } else if (loanTransactionDTO.isAccountTransfer()) {
+                    
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
+                            FinancialActivity.LIABILITY_TRANSFER.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId,
+                            transactionDate, totalDebitAmount, isReversal);
+                } else {
+                    // create debit entries
+                    for (Map.Entry<Integer, BigDecimal> debitEntry : 
accountMapForDebit.entrySet()) {
+                        
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, 
debitEntry.getKey().intValue(),
+                                loanProductId, paymentTypeId, loanId, 
transactionId, transactionDate, debitEntry.getValue(), isReversal);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Charge Refunds (and their reversals) have an extra refund related 
pair of journal entries in addition to
+         * those related to the repayment above
+         ***/
+        if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
+            if (loanTransactionDTO.getTransactionType().isChargeRefund()) {
+                Integer incomeAccount = 
this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
+                this.helper.createJournalEntriesAndReversalsForLoan(office, 
currencyCode, incomeAccount,
+                        AccrualAccountsForLoan.FUND_SOURCE.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
+                        totalDebitAmount, isReversal);
+            }
+        }
+
+    }
+
+    private void createJournalEntriesForLoansRepaymentAndWriteOffs(final 
LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office, final boolean writeOff, final boolean 
isIncomeFromFee) {
         // loan properties
         final Long loanProductId = loanDTO.getLoanProductId();
         final Long loanId = loanDTO.getLoanId();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
index 60cb1c7dc..f8bae9f62 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.accounting.closure.domain.GLClosure;
-import org.apache.fineract.accounting.common.AccountingConstants;
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
 import 
org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
 import org.apache.fineract.accounting.glaccount.domain.GLAccount;
@@ -115,10 +114,108 @@ public class CashBasedAccountingProcessorForLoan 
implements AccountingProcessorF
             else if 
(loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
                 createJournalEntriesForChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
             }
+            // Logic for Charge-Off
+            else if (loanTransactionDTO.getTransactionType().isChargeoff()) {
+                createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, 
office);
+            }
+        }
+    }
+
+    private void createJournalEntriesForChargeOff(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        Map<GLAccount, BigDecimal> accountMapForCredit = new LinkedHashMap<>();
+
+        Map<Integer, BigDecimal> accountMapForDebit = new LinkedHashMap<>();
+
+        // principal payment
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            if (isMarkedFraud) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), accountMapForCredit, 
accountMapForDebit);
+            } else {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+        }
+
+        // interest payment
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+            populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, CashAccountsForLoan.INTEREST_ON_LOANS.getValue(),
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // handle fees payment
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, 
CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // handle penalties payment
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
accountMapForCredit, accountMapForDebit);
+        }
+
+        // create credit entries
+        for (Map.Entry<GLAccount, BigDecimal> creditEntry : 
accountMapForCredit.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    creditEntry.getValue(), isReversal, creditEntry.getKey());
+        }
+
+        // create debit entries
+        for (Map.Entry<Integer, BigDecimal> debitEntry : 
accountMapForDebit.entrySet()) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, debitEntry.getKey().intValue(), loanProductId,
+                    paymentTypeId, loanId, transactionId, transactionDate, 
debitEntry.getValue(), isReversal);
+        }
+
+    }
+
+    private void populateCreditDebitMaps(Long loanProductId, BigDecimal 
transactionPartAmount, Long paymentTypeId,
+            Integer creditAccountType, Integer debitAccountType, 
Map<GLAccount, BigDecimal> accountMapForCredit,
+            Map<Integer, BigDecimal> accountMapForDebit) {
+        GLAccount accountCredit = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, creditAccountType, 
paymentTypeId);
+        if (accountMapForCredit.containsKey(accountCredit)) {
+            BigDecimal amount = 
accountMapForCredit.get(accountCredit).add(transactionPartAmount);
+            accountMapForCredit.put(accountCredit, amount);
+        } else {
+            accountMapForCredit.put(accountCredit, transactionPartAmount);
+        }
+        Integer accountDebit = debitAccountType;
+        if (accountMapForDebit.containsKey(accountDebit)) {
+            BigDecimal amount = 
accountMapForDebit.get(accountDebit).add(transactionPartAmount);
+            accountMapForDebit.put(accountDebit, amount);
+        } else {
+            accountMapForDebit.put(accountDebit, transactionPartAmount);
         }
     }
 
     private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+        final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
+        if (isMarkedAsChargeOff) {
+            createJournalEntriesForChargeOffLoanChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
+        } else {
+            createJournalEntriesForLoanChargeAdjustment(loanDTO, 
loanTransactionDTO, office);
+        }
+
+    }
+
+    private void createJournalEntriesForChargeOffLoanChargeAdjustment(LoanDTO 
loanDTO, LoanTransactionDTO loanTransactionDTO,
+            Office office) {
         // loan properties
         final Long loanProductId = loanDTO.getLoanProductId();
         final Long loanId = loanDTO.getLoanId();
@@ -143,7 +240,7 @@ public class CashBasedAccountingProcessorForLoan implements 
AccountingProcessorF
         if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
             totalDebitAmount = totalDebitAmount.add(principalAmount);
             GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
-                    
AccountingConstants.CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), 
paymentTypeId);
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
             accountMap.put(account, principalAmount);
         }
 
@@ -151,7 +248,7 @@ public class CashBasedAccountingProcessorForLoan implements 
AccountingProcessorF
         if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
             totalDebitAmount = totalDebitAmount.add(interestAmount);
             GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
-                    
AccountingConstants.CashAccountsForLoan.INTEREST_ON_LOANS.getValue(), 
paymentTypeId);
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
             if (accountMap.containsKey(account)) {
                 BigDecimal amount = 
accountMap.get(account).add(interestAmount);
                 accountMap.put(account, amount);
@@ -164,7 +261,106 @@ public class CashBasedAccountingProcessorForLoan 
implements AccountingProcessorF
         if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
             totalDebitAmount = totalDebitAmount.add(feesAmount);
             GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
-                    
AccountingConstants.CashAccountsForLoan.INCOME_FROM_FEES.getValue(), 
paymentTypeId);
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = accountMap.get(account).add(feesAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, feesAmount);
+            }
+        }
+
+        // handle penalties payment
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(penaltiesAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, penaltiesAmount);
+            }
+        }
+
+        // handle overpayment
+        if (overPaymentAmount != null && 
overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, 
CashAccountsForLoan.OVERPAYMENT.getValue(),
+                    paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(overPaymentAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, overPaymentAmount);
+            }
+        }
+
+        for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    entry.getValue(), isReversal, entry.getKey());
+        }
+
+        if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
+            Long chargeId = 
loanTransactionDTO.getLoanChargeData().getChargeId();
+            Integer accountMappingTypeId;
+            if (loanTransactionDTO.getLoanChargeData().isPenalty()) {
+                accountMappingTypeId = 
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
+            } else {
+                accountMappingTypeId = 
CashAccountsForLoan.INCOME_FROM_FEES.getValue();
+            }
+            
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, 
currencyCode, accountMappingTypeId, loanProductId, chargeId,
+                    loanId, transactionId, transactionDate, totalDebitAmount, 
isReversal);
+        }
+    }
+
+    private void createJournalEntriesForLoanChargeAdjustment(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = 
loanTransactionDTO.getOverPayment();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
+
+        // handle principal payment (and reversals)
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, 
CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                    paymentTypeId);
+            accountMap.put(account, principalAmount);
+        }
+
+        // handle interest payment (and reversals)
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    CashAccountsForLoan.INTEREST_ON_LOANS.getValue(), 
paymentTypeId);
+            if (accountMap.containsKey(account)) {
+                BigDecimal amount = 
accountMap.get(account).add(interestAmount);
+                accountMap.put(account, amount);
+            } else {
+                accountMap.put(account, interestAmount);
+            }
+        }
+
+        // handle fees payment (and reversals)
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, 
CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
+                    paymentTypeId);
             if (accountMap.containsKey(account)) {
                 BigDecimal amount = accountMap.get(account).add(feesAmount);
                 accountMap.put(account, amount);
@@ -189,8 +385,8 @@ public class CashBasedAccountingProcessorForLoan implements 
AccountingProcessorF
         // handle overpayment
         if (overPaymentAmount != null && 
overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
             totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
-            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
-                    
AccountingConstants.CashAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId);
+            GLAccount account = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, 
CashAccountsForLoan.OVERPAYMENT.getValue(),
+                    paymentTypeId);
             if (accountMap.containsKey(account)) {
                 BigDecimal amount = 
accountMap.get(account).add(overPaymentAmount);
                 accountMap.put(account, amount);
@@ -208,9 +404,9 @@ public class CashBasedAccountingProcessorForLoan implements 
AccountingProcessorF
             Long chargeId = 
loanTransactionDTO.getLoanChargeData().getChargeId();
             Integer accountMappingTypeId;
             if (loanTransactionDTO.getLoanChargeData().isPenalty()) {
-                accountMappingTypeId = 
AccountingConstants.CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
+                accountMappingTypeId = 
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue();
             } else {
-                accountMappingTypeId = 
AccountingConstants.CashAccountsForLoan.INCOME_FROM_FEES.getValue();
+                accountMappingTypeId = 
CashAccountsForLoan.INCOME_FROM_FEES.getValue();
             }
             
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, 
currencyCode, accountMappingTypeId, loanProductId, chargeId,
                     loanId, transactionId, transactionDate, totalDebitAmount, 
isReversal);
@@ -324,6 +520,224 @@ public class CashBasedAccountingProcessorForLoan 
implements AccountingProcessorF
      */
     private void createJournalEntriesForRepayments(final LoanDTO loanDTO, 
final LoanTransactionDTO loanTransactionDTO,
             final Office office) {
+
+        final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
+        if (isMarkedChargeOff) {
+            createJournalEntriesForChargeOffLoanRepayments(loanDTO, 
loanTransactionDTO, office);
+
+        } else {
+            createJournalEntriesForLoanRepayments(loanDTO, loanTransactionDTO, 
office);
+        }
+
+    }
+
+    private void createJournalEntriesForChargeOffLoanRepayments(LoanDTO 
loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
+        final BigDecimal overPaymentAmount = 
loanTransactionDTO.getOverPayment();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final boolean isReversal = loanTransactionDTO.isReversed();
+
+        Map<GLAccount, BigDecimal> accountMapForCredit = new LinkedHashMap<>();
+        Map<Integer, BigDecimal> accountMapForDebit = new LinkedHashMap<>();
+
+        BigDecimal totalDebitAmount = new BigDecimal(0);
+
+        // principal payment
+        if (principalAmount != null && 
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(principalAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                if (isMarkedFraud) {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                } else {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                }
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                if (isMarkedFraud) {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+
+                } else {
+                    populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
+                            CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                            accountMapForCredit, accountMapForDebit);
+                }
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // interest payment
+        if (interestAmount != null && 
interestAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(interestAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
accountMapForCredit, accountMapForDebit);
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, CashAccountsForLoan.INTEREST_ON_LOANS.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // handle fees payment
+        if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(feesAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // handle penalties payment
+        if (penaltiesAmount != null && 
penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
CashAccountsForLoan.FUND_SOURCE.getValue(),
+                        accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, penaltiesAmount, 
paymentTypeId, CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // overpayment
+        if (overPaymentAmount != null && 
overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
+            totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
+            if 
(loanTransactionDTO.getTransactionType().isMerchantIssuedRefund()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isPayoutRefund()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else if 
(loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(),
+                        CashAccountsForLoan.GOODWILL_CREDIT.getValue(), 
accountMapForCredit, accountMapForDebit);
+            } else if (loanTransactionDTO.getTransactionType().isRepayment()) {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+
+            } else {
+                populateCreditDebitMaps(loanProductId, overPaymentAmount, 
paymentTypeId, CashAccountsForLoan.OVERPAYMENT.getValue(),
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
accountMapForCredit, accountMapForDebit);
+            }
+
+        }
+
+        // create credit entries
+        for (Map.Entry<GLAccount, BigDecimal> creditEntry : 
accountMapForCredit.entrySet()) {
+            this.helper.createCreditJournalEntryOrReversalForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                    creditEntry.getValue(), isReversal, creditEntry.getKey());
+        }
+
+        /*** create a single debit entry (or reversal) for the entire amount 
**/
+        if (loanTransactionDTO.isLoanToLoanTransfer()) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, totalDebitAmount, isReversal);
+        } else if (loanTransactionDTO.isAccountTransfer()) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, totalDebitAmount, isReversal);
+        } else {
+            // create debit entries
+            for (Map.Entry<Integer, BigDecimal> debitEntry : 
accountMapForDebit.entrySet()) {
+                this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, debitEntry.getKey().intValue(), loanProductId,
+                        paymentTypeId, loanId, transactionId, transactionDate, 
debitEntry.getValue(), isReversal);
+            }
+        }
+
+        /**
+         * Charge Refunds (and their reversals) have an extra refund related 
pair of journal entries in addition to
+         * those related to the repayment above
+         ***/
+        if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
+            if (loanTransactionDTO.getTransactionType().isChargeRefund()) {
+                Integer incomeAccount = 
this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
+                this.helper.createJournalEntriesAndReversalsForLoan(office, 
currencyCode, incomeAccount,
+                        CashAccountsForLoan.FUND_SOURCE.getValue(), 
loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
+                        totalDebitAmount, isReversal);
+            }
+        }
+    }
+
+    private void createJournalEntriesForLoanRepayments(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
         // loan properties
         final Long loanProductId = loanDTO.getLoanProductId();
         final Long loanId = loanDTO.getLoanId();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
index e7514deb7..fc2c49615 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java
@@ -263,12 +263,26 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
                         changes);
                 mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
                         CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
CashAccountsForLoan.INCOME_FROM_RECOVERY.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
+                        loanProductId, 
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
+                        loanProductId, 
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
+                        loanProductId, 
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.toString(), changes);
 
                 // expenses
                 mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
                         CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), 
CashAccountsForLoan.LOSSES_WRITTEN_OFF.toString(), changes);
                 mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
                         CashAccountsForLoan.GOODWILL_CREDIT.getValue(), 
CashAccountsForLoan.GOODWILL_CREDIT.toString(), changes);
+                mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
+                        CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
CashAccountsForLoan.CHARGE_OFF_EXPENSE.toString(), changes);
+                mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
+                        loanProductId, 
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
+                        
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.toString(), changes);
 
                 // liabilities
                 mergeLoanToLiabilityAccountMappingChanges(element, 
LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId,
@@ -307,6 +321,15 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
                 mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
                         
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), 
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.toString(),
                         changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
+                        loanProductId, 
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
+                        loanProductId, 
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.toString(), changes);
+                mergeLoanToIncomeAccountMappingChanges(element, 
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
+                        loanProductId, 
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.toString(), changes);
 
                 // expenses
                 mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
@@ -314,6 +337,12 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
                         changes);
                 mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
                         AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), 
AccrualAccountsForLoan.GOODWILL_CREDIT.toString(), changes);
+                mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
+                        AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.toString(),
+                        changes);
+                mergeLoanToExpenseAccountMappingChanges(element, 
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
+                        loanProductId, 
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
+                        
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.toString(), changes);
 
                 // liabilities
                 mergeLoanToLiabilityAccountMappingChanges(element, 
LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
index 3b594ba56..a9acb2bbb 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
@@ -84,6 +84,11 @@ public class ProductToGLAccountMappingHelper {
             if (accountMapping == null) {
                 ArrayList<String> optionalProductToGLAccountMappingEntries = 
new ArrayList<String>();
                 
optionalProductToGLAccountMappingEntries.add("goodwillCreditAccountId");
+                
optionalProductToGLAccountMappingEntries.add("incomeFromChargeOffInterestAccountId");
+                
optionalProductToGLAccountMappingEntries.add("incomeFromChargeOffFeesAccountId");
+                
optionalProductToGLAccountMappingEntries.add("chargeOffAccountId");
+                
optionalProductToGLAccountMappingEntries.add("chargeOffFraudAccountId");
+                
optionalProductToGLAccountMappingEntries.add("incomeFromChargeOffPenaltyAccountId");
                 if 
(optionalProductToGLAccountMappingEntries.contains(paramName)) {
                     saveProductToAccountMapping(element, paramName, productId, 
accountTypeId, expectedAccountType, portfolioProductType);
                 } else {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
index 5cf3f735d..c34e27c87 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
@@ -138,6 +138,16 @@ public class 
ProductToGLAccountMappingReadPlatformServiceImpl implements Product
                     
accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(),
 gLAccountData);
                 } else if 
(glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_RECOVERY)) {
                     
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_EXPENSE)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
 gLAccountData);
                 }
             }
         } else if 
(AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
@@ -178,6 +188,16 @@ public class 
ProductToGLAccountMappingReadPlatformServiceImpl implements Product
                     
accountMappingDetails.put(LoanProductAccountingDataParams.PENALTIES_RECEIVABLE.getValue(),
 gLAccountData);
                 } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_RECOVERY)) {
                     
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST))
 {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_EXPENSE)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
 gLAccountData);
+                } else if 
(glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY))
 {
+                    
accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
 gLAccountData);
                 }
             }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
index 41d94d28b..c09d8ec64 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
@@ -86,6 +86,15 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
                         
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
                         CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
loanProductId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
loanProductId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
loanProductId,
+                        
CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue());
 
                 // expenses
                 
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
@@ -94,6 +103,12 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
                         
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
                         CashAccountsForLoan.GOODWILL_CREDIT.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
+                        CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId,
+                        
CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue());
 
                 // liabilities
                 
this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
@@ -140,6 +155,15 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
                         
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), loanProductId,
                         
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), 
loanProductId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), 
loanProductId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
+                        
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
loanProductId,
+                        
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue());
 
                 // expenses
                 
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
@@ -148,6 +172,12 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
                         
LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId,
                         AccrualAccountsForLoan.GOODWILL_CREDIT.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), loanProductId,
+                        AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue());
+                
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
+                        
LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId,
+                        
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue());
 
                 // liabilities
                 
this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
index e86183190..47ed70bf7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -53,6 +53,7 @@ public class LoanTransactionEnumData {
     private final boolean creditBalanceRefund;
     private final boolean chargeAdjustment;
     private final boolean chargeback;
+    private final boolean chargeoff;
 
     public LoanTransactionEnumData(final Long id, final String code, final 
String value) {
         this.id = id;
@@ -81,6 +82,7 @@ public class LoanTransactionEnumData {
         this.creditBalanceRefund = Long.valueOf(20).equals(this.id);
         this.chargeback = Long.valueOf(25).equals(this.id);
         this.chargeAdjustment = Long.valueOf(26).equals(this.id);
+        this.chargeoff = Long.valueOf(27).equals(this.id);
     }
 
     public boolean isRepaymentType() {
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 4de3ed85c..d66428e6b 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
@@ -4526,6 +4526,8 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", 
isUpfrontAccrualAccountingEnabledOnLoanProduct());
         accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", 
isPeriodicAccrualAccountingEnabledOnLoanProduct());
         accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
+        accountingBridgeData.put("isChargeOff", isChargedOff());
+        accountingBridgeData.put("isFraud", isFraud());
 
         final List<Map<String, Object>> newLoanTransactions = new 
ArrayList<>();
         for (final LoanTransaction transaction : this.loanTransactions) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
index 26090f0bb..f0c46d89c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -277,7 +277,7 @@ public class LoanAccrualWritePlatformServiceImpl implements 
LoanAccrualWritePlat
         
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
     }
 
-    public Map<String, Object> deriveAccountingBridgeData(final 
LoanScheduleAccrualData loanScheduleAccrualData,
+    private Map<String, Object> deriveAccountingBridgeData(final 
LoanScheduleAccrualData loanScheduleAccrualData,
             final Map<String, Object> transactionMap) {
 
         final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
@@ -289,6 +289,8 @@ public class LoanAccrualWritePlatformServiceImpl implements 
LoanAccrualWritePlat
         accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", 
false);
         accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", 
true);
         accountingBridgeData.put("isAccountTransfer", false);
+        accountingBridgeData.put("isChargeOff", false);
+        accountingBridgeData.put("isFraud", false);
 
         final List<Map<String, Object>> newLoanTransactions = new 
ArrayList<>();
         newLoanTransactions.add(transactionMap);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 53400439a..aba8b4476 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -2675,8 +2675,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             loan.markAsChargedOff(transactionDate, currentUser, null);
         }
 
+        final List<Long> existingTransactionIds = 
loan.findExistingTransactionIds();
+        final List<Long> existingReversedTransactionIds = 
loan.findExistingReversedTransactionIds();
+
         LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, 
transactionDate, txnExternalId);
-        loanTransactionRepository.saveAndFlush(chargeOffTransaction);
+        loan.addLoanTransaction(chargeOffTransaction);
+        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
         String noteText = 
command.stringValueOfParameterNamed(LoanApiConstants.noteParameterName);
         if (StringUtils.isNotBlank(noteText)) {
@@ -2685,7 +2689,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             this.noteRepository.save(note);
         }
 
-        // TODO: add accounting
+        postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
         businessEventNotifierService.notifyPostBusinessEvent(new 
LoanChargeOffPostBusinessEvent(chargeOffTransaction));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index bcdd51a9d..0fe3ac997 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -119,7 +119,11 @@ public final class LoanProductDataValidator {
             LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), 
LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
             
LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
             LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), 
LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(),
-            
LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), 
LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME,
+            
LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
+            LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), 
LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(),
+            LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
+            
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
+            
LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), 
LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME,
             
LoanProductConstants.PRINCIPAL_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME,
             
LoanProductConstants.INTEREST_RATE_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME,
             
LoanProductConstants.NUMBER_OF_REPAYMENT_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME,
 LoanProductConstants.SHORT_NAME,
@@ -639,6 +643,31 @@ public final class LoanProductDataValidator {
             
baseDataValidator.reset().parameter(LoanProductAccountingParams.OVERPAYMENT.getValue()).value(overpaymentAccountId).notNull()
                     .integerGreaterThanZero();
 
+            final Long incomeFromChargeOffInterestAccountId = 
this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
 element);
+            
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue())
+                    
.value(incomeFromChargeOffInterestAccountId).ignoreIfNull().integerGreaterThanZero();
+
+            final Long incomeFromChargeOffFeesAccountId = 
this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
 element);
+            
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue())
+                    
.value(incomeFromChargeOffFeesAccountId).ignoreIfNull().integerGreaterThanZero();
+
+            final Long incomeFromChargeOffPenaltyAccountId = 
this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
 element);
+            
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue())
+                    
.value(incomeFromChargeOffPenaltyAccountId).ignoreIfNull().integerGreaterThanZero();
+
+            final Long chargeOffExpenseAccountId = this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), 
element);
+            
baseDataValidator.reset().parameter(LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue()).value(chargeOffExpenseAccountId)
+                    .ignoreIfNull().integerGreaterThanZero();
+
+            final Long chargeOffFraudExpenseAccountId = this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
 element);
+            
baseDataValidator.reset().parameter(LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue())
+                    
.value(chargeOffFraudExpenseAccountId).ignoreIfNull().integerGreaterThanZero();
+
             validatePaymentChannelFundSourceMappings(baseDataValidator, 
element);
             validateChargeToIncomeAccountMappings(baseDataValidator, element);
 
@@ -1484,6 +1513,31 @@ public final class LoanProductDataValidator {
         
baseDataValidator.reset().parameter(LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue()).value(receivablePenaltyAccountId)
                 .ignoreIfNull().integerGreaterThanZero();
 
+        final Long incomeFromChargeOffInterestAccountId = 
this.fromApiJsonHelper
+                
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(),
 element);
+        
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue())
+                
.value(incomeFromChargeOffInterestAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromChargeOffFeesAccountId = this.fromApiJsonHelper
+                
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
 element);
+        
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_FEES.getValue())
+                
.value(incomeFromChargeOffFeesAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long incomeFromChargeOffPenaltyAccountId = this.fromApiJsonHelper
+                
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(),
 element);
+        
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue())
+                
.value(incomeFromChargeOffPenaltyAccountId).ignoreIfNull().integerGreaterThanZero();
+
+        final Long chargeOffExpenseAccountId = this.fromApiJsonHelper
+                
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue(), 
element);
+        
baseDataValidator.reset().parameter(LoanProductAccountingParams.CHARGE_OFF_EXPENSE.getValue()).value(chargeOffExpenseAccountId)
+                .ignoreIfNull().integerGreaterThanZero();
+
+        final Long chargeOffFraudExpenseAccountId = this.fromApiJsonHelper
+                
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(),
 element);
+        
baseDataValidator.reset().parameter(LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue())
+                
.value(chargeOffFraudExpenseAccountId).ignoreIfNull().integerGreaterThanZero();
+
         validatePaymentChannelFundSourceMappings(baseDataValidator, element);
         validateChargeToIncomeAccountMappings(baseDataValidator, element);
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java
new file mode 100644
index 000000000..2ee0379a2
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java
@@ -0,0 +1,450 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.UUID;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
+import 
org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PutLoansLoanIdResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+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.JournalEntry;
+import 
org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class LoanChargeOffAccountingTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpec403;
+    private RequestSpecification requestSpec;
+    private ClientHelper clientHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanTransactionHelper loanTransactionHelperValidationError;
+    private JournalEntryHelper journalEntryHelper;
+    private AccountHelper accountHelper;
+    private Account assetAccount;
+    private Account incomeAccount;
+    private Account expenseAccount;
+    private Account overpaymentAccount;
+    private DateTimeFormatter dateFormatter = new 
DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpec403 = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        this.loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.loanTransactionHelperValidationError = new 
LoanTransactionHelper(this.requestSpec, new ResponseSpecBuilder().build());
+        this.accountHelper = new AccountHelper(this.requestSpec, 
this.responseSpec);
+        this.assetAccount = this.accountHelper.createAssetAccount();
+        this.incomeAccount = this.accountHelper.createIncomeAccount();
+        this.expenseAccount = this.accountHelper.createExpenseAccount();
+        this.overpaymentAccount = this.accountHelper.createLiabilityAccount();
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
+        this.clientHelper = new ClientHelper(this.requestSpec, 
this.responseSpec);
+    }
+
+    @Test
+    public void 
loanChargeOffAccountingTreatmentTestForPeriodicAccrualAccounting() {
+        // Loan ExternalId
+        String loanExternalIdStr = UUID.randomUUID().toString();
+
+        // Product to GL account mapping for test
+        // ASSET
+        // 
-fundSourceAccountId,loanPortfolioAccountId,transfersInSuspenseAccountId,receivableFeeAccountId,receivablePenaltyAccountId,receivableInterestAccountId
+        // 
INCOME-interestOnLoanAccountId,incomeFromFeeAccountId,incomeFromPenaltyAccountId,incomeFromRecoveryAccountId,incomeFromChargeOffInterestAccountId,incomeFromChargeOffFeesAccountId,incomeFromChargeOffPenaltyAccountId
+        // 
EXPENSE-writeOffAccountId,goodwillCreditAccountId,chargeOffExpenseAccountId,chargeOffFraudExpenseAccountId
+        // LIABILITY-overpaymentLiabilityAccountId
+
+        final Integer loanProductID = 
createLoanProductWithPeriodicAccrualAccounting(assetAccount, incomeAccount, 
expenseAccount,
+                overpaymentAccount);
+        final Integer clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+        final Integer loanId = createLoanAccount(clientId, loanProductID, 
loanExternalIdStr);
+
+        // apply charges
+        Integer feeCharge = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", false));
+
+        LocalDate targetDate = LocalDate.of(2022, 9, 5);
+        final String feeCharge1AddedDate = dateFormatter.format(targetDate);
+        Integer feeLoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanId,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge),
 feeCharge1AddedDate, "10"));
+
+        // apply penalty
+        Integer penalty = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", true));
+
+        final String penaltyCharge1AddedDate = 
dateFormatter.format(targetDate);
+
+        Integer penalty1LoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanId,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty),
 penaltyCharge1AddedDate, "10"));
+
+        // set loan as chargeoff
+        String randomText = Utils.randomStringGenerator("en", 5) + 
Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5);
+        Integer chargeOffReasonId = 
CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
+        String transactionExternalId = UUID.randomUUID().toString();
+        this.loanTransactionHelper.chargeOffLoan((long) loanId, new 
PostLoansLoanIdTransactionsRequest().transactionDate("6 September 2022")
+                .locale("en").dateFormat("dd MMMM 
yyyy").externalId(transactionExternalId).chargeOffReasonId((long) 
chargeOffReasonId));
+
+        GetLoansLoanIdResponse loanDetails = 
this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries For ChargeOff Transaction
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"6 September 2022",
+                new JournalEntry(1020, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "6 
September 2022",
+                new JournalEntry(1000, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "6 
September 2022",
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "6 
September 2022",
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+
+        // make Repayment
+        final PostLoansLoanIdTransactionsResponse repaymentTransaction = 
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("7 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Repayment transaction
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "7 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"7 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Merchant Refund
+        final PostLoansLoanIdTransactionsResponse merchantIssuedRefund_1 = 
loanTransactionHelper.makeMerchantIssuedRefund((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("8 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Merchant Refund
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "8 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"8 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Payout Refund
+        final PostLoansLoanIdTransactionsResponse payoutRefund_1 = 
loanTransactionHelper.makePayoutRefund((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("9 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Payout Refund
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "9 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"9 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Goodwill Credit
+        final PostLoansLoanIdTransactionsResponse goodwillCredit_1 = 
loanTransactionHelper.makeGoodwillCredit((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("10 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Goodwill Credit
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "10 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "10 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+
+        // make overpaid repayment
+        final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = 
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("11 September 2022").locale("en")
+                        .transactionAmount(720.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getOverpaid());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal entries for overpaid repayment
+        
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount,
 "11 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "11 
September 2022",
+                new JournalEntry(620, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"11 September 2022",
+                new JournalEntry(720, JournalEntry.TransactionType.DEBIT));
+
+        // CBR for making loan active again
+        final PostLoansLoanIdTransactionsResponse cbr_transaction = 
loanTransactionHelper.makeCreditBalanceRefund(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("12 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        // Charge Adjustment making loan overpaid
+        final PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResult = 
loanTransactionHelper.chargeAdjustment((long) loanId,
+                (long) feeLoanChargeId, new 
PostLoansLoanIdChargesChargeIdRequest().amount(10.0).locale("en"));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getOverpaid());
+
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        String transactionDate = Utils.dateFormatter.format(todaysDate);
+
+        // verify Journal entries for Charge Adjustment
+        
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount,
 transactionDate,
+                new JournalEntry(10, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, 
transactionDate,
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+    }
+
+    @Test
+    public void 
loanChargeOffFraudAccountingTreatmentTestForCashBasedAccounting() {
+        // Loan ExternalId
+        String loanExternalIdStr = UUID.randomUUID().toString();
+
+        // Product to GL account mapping for test
+        // ASSET
+        // 
-fundSourceAccountId,loanPortfolioAccountId,transfersInSuspenseAccountId
+        // 
INCOME-interestOnLoanAccountId,incomeFromFeeAccountId,incomeFromPenaltyAccountId,incomeFromRecoveryAccountId,incomeFromChargeOffInterestAccountId,incomeFromChargeOffFeesAccountId,incomeFromChargeOffPenaltyAccountId
+        // 
EXPENSE-writeOffAccountId,goodwillCreditAccountId,chargeOffExpenseAccountId,chargeOffFraudExpenseAccountId
+        // LIABILITY-overpaymentLiabilityAccountId
+
+        final Integer loanProductID = 
createLoanProductWithCashBasedAccounting(assetAccount, incomeAccount, 
expenseAccount,
+                overpaymentAccount);
+        final Integer clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+        final Integer loanId = createLoanAccount(clientId, loanProductID, 
loanExternalIdStr);
+
+        // apply charges
+        Integer feeCharge = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", false));
+
+        LocalDate targetDate = LocalDate.of(2022, 9, 5);
+        final String feeCharge1AddedDate = dateFormatter.format(targetDate);
+        Integer feeLoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanId,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge),
 feeCharge1AddedDate, "10"));
+
+        // apply penalty
+        Integer penalty = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", true));
+
+        final String penaltyCharge1AddedDate = 
dateFormatter.format(targetDate);
+
+        Integer penalty1LoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanId,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty),
 penaltyCharge1AddedDate, "10"));
+
+        // set loan as fraud
+        final String command = "markAsFraud";
+        String payload = 
loanTransactionHelper.getLoanFraudPayloadAsJSON("fraud", "true");
+        PutLoansLoanIdResponse putLoansLoanIdResponse = 
loanTransactionHelper.modifyLoanCommand(loanId, command, payload,
+                this.responseSpec);
+
+        GetLoansLoanIdResponse loanDetails = 
this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+
+        // set loan as chargeoff
+        String randomText = Utils.randomStringGenerator("en", 5) + 
Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5);
+        Integer chargeOffReasonId = 
CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
+        String transactionExternalId = UUID.randomUUID().toString();
+        this.loanTransactionHelper.chargeOffLoan((long) loanId, new 
PostLoansLoanIdTransactionsRequest().transactionDate("6 September 2022")
+                .locale("en").dateFormat("dd MMMM 
yyyy").externalId(transactionExternalId).chargeOffReasonId((long) 
chargeOffReasonId));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries For ChargeOff Transaction
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"6 September 2022",
+                new JournalEntry(1000, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "6 
September 2022",
+                new JournalEntry(20, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "6 
September 2022",
+                new JournalEntry(1000, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "6 
September 2022",
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "6 
September 2022",
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+
+        // make Repayment
+        final PostLoansLoanIdTransactionsResponse repaymentTransaction = 
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("7 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Repayment transaction
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "7 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"7 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Merchant Refund
+        final PostLoansLoanIdTransactionsResponse merchantIssuedRefund_1 = 
loanTransactionHelper.makeMerchantIssuedRefund((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("8 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Merchant Refund
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "8 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"8 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Payout Refund
+        final PostLoansLoanIdTransactionsResponse payoutRefund_1 = 
loanTransactionHelper.makePayoutRefund((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("9 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Payout Refund
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "9 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"9 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+
+        // Goodwill Credit
+        final PostLoansLoanIdTransactionsResponse goodwillCredit_1 = 
loanTransactionHelper.makeGoodwillCredit((long) loanId,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("10 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getActive());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal Entries for Goodwill Credit
+        
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "10 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.DEBIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "10 
September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+
+        // make overpaid repayment
+        final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = 
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("11 September 2022").locale("en")
+                        .transactionAmount(720.0));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getOverpaid());
+        assertTrue(loanDetails.getFraud());
+        assertTrue(loanDetails.getChargedOff());
+
+        // verify Journal entries for overpaid repayment
+        
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount,
 "11 September 2022",
+                new JournalEntry(100, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, "11 
September 2022",
+                new JournalEntry(620, JournalEntry.TransactionType.CREDIT));
+        this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"11 September 2022",
+                new JournalEntry(720, JournalEntry.TransactionType.DEBIT));
+
+        // CBR for making loan active again
+        final PostLoansLoanIdTransactionsResponse cbr_transaction = 
loanTransactionHelper.makeCreditBalanceRefund(loanExternalIdStr,
+                new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate("12 September 2022").locale("en")
+                        .transactionAmount(100.0));
+
+        // Charge Adjustment making loan overpaid
+        final PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResult = 
loanTransactionHelper.chargeAdjustment((long) loanId,
+                (long) feeLoanChargeId, new 
PostLoansLoanIdChargesChargeIdRequest().amount(10.0).locale("en"));
+
+        loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+        assertTrue(loanDetails.getStatus().getOverpaid());
+
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        String transactionDate = Utils.dateFormatter.format(todaysDate);
+
+        // verify Journal entries for Charge Adjustment
+        
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount,
 transactionDate,
+                new JournalEntry(10, JournalEntry.TransactionType.CREDIT));
+        
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, 
transactionDate,
+                new JournalEntry(10, JournalEntry.TransactionType.DEBIT));
+    }
+
+    private Integer createLoanAccount(final Integer clientID, final Integer 
loanProductID, final String externalId) {
+
+        String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
+                
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
+                
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+                .withExpectedDisbursementDate("03 September 
2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
+                .withExternalId(externalId).build(clientID.toString(), 
loanProductID.toString(), null);
+
+        final Integer loanId = 
loanTransactionHelper.getLoanId(loanApplicationJSON);
+        loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, 
null);
+        loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 
2022", loanId, "1000");
+        return loanId;
+    }
+
+    private Integer createLoanProductWithPeriodicAccrualAccounting(final 
Account... accounts) {
+
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("1000").withRepaymentAfterEvery("1")
+                
.withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
+                
.withAccountingRulePeriodicAccrual(accounts).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0",
 "0")
+                .build(null);
+
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer createLoanProductWithCashBasedAccounting(final Account... 
accounts) {
+
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("1000").withRepaymentAfterEvery("1")
+                
.withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
+                
.withAccountingRuleAsCashBased(accounts).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0",
 "0").build(null);
+
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index adb4700d1..7f0eb32cc 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -443,11 +443,16 @@ public class LoanProductTestBuilder {
                 map.put("incomeFromFeeAccountId", ID);
                 map.put("incomeFromPenaltyAccountId", ID);
                 map.put("incomeFromRecoveryAccountId", ID);
+                map.put("incomeFromChargeOffInterestAccountId", ID);
+                map.put("incomeFromChargeOffFeesAccountId", ID);
+                map.put("incomeFromChargeOffPenaltyAccountId", ID);
             }
             if 
(this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
                 final String ID = 
this.accountList[i].getAccountID().toString();
                 map.put("writeOffAccountId", ID);
                 map.put("goodwillCreditAccountId", ID);
+                map.put("chargeOffExpenseAccountId", ID);
+                map.put("chargeOffFraudExpenseAccountId", ID);
             }
             if 
(this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
                 final String ID = 
this.accountList[i].getAccountID().toString();
@@ -481,11 +486,16 @@ public class LoanProductTestBuilder {
                 map.put("incomeFromFeeAccountId", ID);
                 map.put("incomeFromPenaltyAccountId", ID);
                 map.put("incomeFromRecoveryAccountId", ID);
+                map.put("incomeFromChargeOffInterestAccountId", ID);
+                map.put("incomeFromChargeOffFeesAccountId", ID);
+                map.put("incomeFromChargeOffPenaltyAccountId", ID);
             }
             if 
(this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
                 final String ID = 
this.accountList[i].getAccountID().toString();
                 map.put("writeOffAccountId", ID);
                 map.put("goodwillCreditAccountId", ID);
+                map.put("chargeOffExpenseAccountId", ID);
+                map.put("chargeOffFraudExpenseAccountId", ID);
             }
             if 
(this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
                 final String ID = 
this.accountList[i].getAccountID().toString();

Reply via email to