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 4ccb33a95 FINERACT-2113: Advanced Charge-off Expense Accounting - Add 
new "Advanced Accounting Rule"
4ccb33a95 is described below

commit 4ccb33a95e8bd8a7934bcdacbfd146664080db3b
Author: Andrii Kulminskyi <[email protected]>
AuthorDate: Tue Nov 5 11:54:55 2024 +0200

    FINERACT-2113: Advanced Charge-off Expense Accounting - Add new "Advanced 
Accounting Rule"
---
 .../domain/ProductToGLAccountMapping.java          |   7 +-
 .../ProductToGLAccountMappingRepository.java       |   6 +-
 .../service/ProductToGLAccountMappingHelper.java   | 139 +++++++++++
 ...oductToGLAccountMappingReadPlatformService.java |   1 -
 .../SavingsProductToGLAccountMappingHelper.java    |   5 +-
 .../ShareProductToGLAccountMappingHelper.java      |   5 +-
 .../accounting/common/AccountingConstants.java     |   5 +-
 .../LoanProductToGLAccountMappingHelper.java       |  16 +-
 .../api/LoanProductsApiResourceSwagger.java        |  13 +
 .../loanproduct/data/LoanProductData.java          |   3 +
 ...ToGLAccountMappingWritePlatformServiceImpl.java |   4 +
 .../loanproduct/api/LoanProductsApiResource.java   |   3 +-
 .../serialization/LoanProductDataValidator.java    |  63 ++++-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...charge_off_reason_id_to_acc_product_mapping.xml |  40 +++
 .../LoanProductChargeOffReasonMappingsTest.java    | 272 +++++++++++++++++++++
 .../common/loans/LoanProductTestBuilder.java       |  16 ++
 .../integrationtests/common/system/CodeHelper.java |  36 +++
 18 files changed, 621 insertions(+), 14 deletions(-)

diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
index 8dd35f891..43680cb5b 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
@@ -63,10 +63,13 @@ public class ProductToGLAccountMapping extends 
AbstractPersistableCustom<Long> {
     @Column(name = "financial_account_type", nullable = true)
     private int financialAccountType;
 
+    @Column(name = "charge_off_reason_id", nullable = true)
+    private Long chargeOffReasonId;
+
     public static ProductToGLAccountMapping createNew(final GLAccount 
glAccount, final Long productId, final int productType,
-            final int financialAccountType) {
+            final int financialAccountType, final Long chargeOffReasonId) {
 
         return new 
ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId).setProductType(productType)
-                .setFinancialAccountType(financialAccountType);
+                
.setFinancialAccountType(financialAccountType).setChargeOffReasonId(chargeOffReasonId);
     }
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
index 1a7ecc8df..fa953838f 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
@@ -35,7 +35,7 @@ public interface ProductToGLAccountMappingRepository
             @Param("productType") int productType, 
@Param("financialAccountType") int financialAccountType,
             @Param("chargeId") Long ChargeId);
 
-    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.financialAccountType=:financialAccountType and mapping.paymentType is 
NULL and mapping.charge is NULL")
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.financialAccountType=:financialAccountType and mapping.paymentType is 
NULL and mapping.charge is NULL and mapping.chargeOffReasonId is NULL")
     ProductToGLAccountMapping 
findCoreProductToFinAccountMapping(@Param("productId") Long productId, 
@Param("productType") int productType,
             @Param("financialAccountType") int financialAccountType);
 
@@ -61,4 +61,8 @@ public interface ProductToGLAccountMappingRepository
             @Param("productType") int productType);
 
     List<ProductToGLAccountMapping> findByProductIdAndProductType(Long 
productId, int productType);
+
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.chargeOffReasonId is not NULL")
+    List<ProductToGLAccountMapping> 
findAllChargesOffReasonsMappings(@Param("productId") Long productId,
+            @Param("productType") int productType);
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
index f51ad5c57..5c01b1670 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
@@ -27,6 +27,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
@@ -39,7 +40,11 @@ import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGL
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
 import 
org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
 import 
org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.portfolio.PortfolioProductType;
 import org.apache.fineract.portfolio.charge.domain.Charge;
@@ -53,6 +58,7 @@ import org.springframework.stereotype.Component;
 public class ProductToGLAccountMappingHelper {
 
     protected static final List<GLAccountType> ASSET_LIABILITY_TYPES = 
List.of(GLAccountType.ASSET, GLAccountType.LIABILITY);
+    private static final Integer GL_ACCOUNT_EXPENSE_TYPE = 5;
 
     protected final GLAccountRepository accountRepository;
     protected final ProductToGLAccountMappingRepository 
accountMappingRepository;
@@ -60,6 +66,7 @@ public class ProductToGLAccountMappingHelper {
     private final ChargeRepositoryWrapper chargeRepositoryWrapper;
     protected final GLAccountRepositoryWrapper accountRepositoryWrapper;
     private final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper;
+    private final CodeValueRepository codeValueRepository;
 
     public void saveProductToAccountMapping(final JsonElement element, final 
String paramName, final Long productId,
             final int placeHolderTypeId, final GLAccountType 
expectedAccountType, final PortfolioProductType portfolioProductType) {
@@ -194,6 +201,27 @@ public class ProductToGLAccountMappingHelper {
         }
     }
 
+    public void saveChargeOffReasonToGLAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType 
portfolioProductType) {
+
+        final String arrayName = 
LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue();
+        final JsonArray chargeOffReasonToExpenseAccountMappingArray = 
this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
+
+        if (chargeOffReasonToExpenseAccountMappingArray != null) {
+            if (changes != null) {
+                changes.put(arrayName, command.jsonFragment(arrayName));
+            }
+
+            for (int i = 0; i < 
chargeOffReasonToExpenseAccountMappingArray.size(); i++) {
+                final JsonObject jsonObject = 
chargeOffReasonToExpenseAccountMappingArray.get(i).getAsJsonObject();
+                final Long reasonId = 
jsonObject.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
+                final Long expenseAccountId = 
jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
+
+                saveChargeOffReasonToExpenseMapping(productId, reasonId, 
expenseAccountId, portfolioProductType);
+            }
+        }
+    }
+
     /**
      * @param command
      * @param element
@@ -356,6 +384,65 @@ public class ProductToGLAccountMappingHelper {
         }
     }
 
+    public void updateChargeOffReasonToGLAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType 
portfolioProductType) {
+
+        final List<ProductToGLAccountMapping> 
existingChargeOffReasonToGLAccountMappings = this.accountMappingRepository
+                .findAllChargesOffReasonsMappings(productId, 
portfolioProductType.getValue());
+        final JsonArray chargeOffReasonToGLAccountMappingArray = 
this.fromApiJsonHelper
+                
.extractJsonArrayNamed(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(),
 element);
+
+        final Map<Long, Long> inputChargeOffReasonToGLAccountMap = new 
HashMap<>();
+
+        final Set<Long> existingChargeOffReasons = new HashSet<>();
+        if (chargeOffReasonToGLAccountMappingArray != null) {
+            if (changes != null) {
+                
changes.put(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(),
+                        
command.jsonFragment(LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue()));
+            }
+
+            for (int i = 0; i < chargeOffReasonToGLAccountMappingArray.size(); 
i++) {
+                final JsonObject jsonObject = 
chargeOffReasonToGLAccountMappingArray.get(i).getAsJsonObject();
+                final Long expenseGlAccountId = 
jsonObject.get(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()).getAsLong();
+                final Long chargeOffReasonCodeValueId = jsonObject
+                        
.get(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue()).getAsLong();
+                
inputChargeOffReasonToGLAccountMap.put(chargeOffReasonCodeValueId, 
expenseGlAccountId);
+            }
+
+            // If input map is empty, delete all existing mappings
+            if (inputChargeOffReasonToGLAccountMap.isEmpty()) {
+                
this.accountMappingRepository.deleteAllInBatch(existingChargeOffReasonToGLAccountMappings);
+            } else {
+                for (final ProductToGLAccountMapping 
existingChargeOffReasonToGLAccountMapping : 
existingChargeOffReasonToGLAccountMappings) {
+                    final Long currentChargeOffReasonId = 
existingChargeOffReasonToGLAccountMapping.getChargeOffReasonId();
+                    if (currentChargeOffReasonId != null) {
+                        existingChargeOffReasons.add(currentChargeOffReasonId);
+                        // update existing mappings (if required)
+                        if 
(inputChargeOffReasonToGLAccountMap.containsKey(currentChargeOffReasonId)) {
+                            final Long newGLAccountId = 
inputChargeOffReasonToGLAccountMap.get(currentChargeOffReasonId);
+                            if 
(!newGLAccountId.equals(existingChargeOffReasonToGLAccountMapping.getGlAccount().getId()))
 {
+                                final Optional<GLAccount> glAccount = 
accountRepository.findById(newGLAccountId);
+                                if (glAccount.isPresent()) {
+                                    
existingChargeOffReasonToGLAccountMapping.setGlAccount(glAccount.get());
+                                    
this.accountMappingRepository.saveAndFlush(existingChargeOffReasonToGLAccountMapping);
+                                }
+                            }
+                        } // deleted payment type
+                        else {
+                            
this.accountMappingRepository.delete(existingChargeOffReasonToGLAccountMapping);
+                        }
+                    }
+                }
+
+                // only the newly added
+                for (Map.Entry<Long, Long> entry : 
inputChargeOffReasonToGLAccountMap.entrySet().stream()
+                        .filter(e -> 
!existingChargeOffReasons.contains(e.getKey())).toList()) {
+                    saveChargeOffReasonToExpenseMapping(productId, 
entry.getKey(), entry.getValue(), portfolioProductType);
+                }
+            }
+        }
+    }
+
     /**
      * @param productId
      *
@@ -402,6 +489,24 @@ public class ProductToGLAccountMappingHelper {
         this.accountMappingRepository.saveAndFlush(accountMapping);
     }
 
+    private void saveChargeOffReasonToExpenseMapping(final Long productId, 
final Long reasonId, final Long expenseAccountId,
+            final PortfolioProductType portfolioProductType) {
+
+        final Optional<GLAccount> glAccount = 
accountRepository.findById(expenseAccountId);
+
+        final boolean reasonMappingExists = this.accountMappingRepository
+                .findAllChargesOffReasonsMappings(productId, 
portfolioProductType.getValue()).stream()
+                .anyMatch(mapping -> 
mapping.getChargeOffReasonId().equals(reasonId));
+
+        if (glAccount.isPresent() && !reasonMappingExists) {
+            final ProductToGLAccountMapping accountMapping = new 
ProductToGLAccountMapping().setGlAccount(glAccount.get())
+                    
.setProductId(productId).setProductType(portfolioProductType.getValue())
+                    
.setFinancialAccountType(CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue()).setChargeOffReasonId(reasonId);
+
+            this.accountMappingRepository.saveAndFlush(accountMapping);
+        }
+    }
+
     private List<GLAccountType> getAllowedAccountTypesForFeeMapping() {
         List<GLAccountType> allowedAccountTypes = new ArrayList<>();
         allowedAccountTypes.add(GLAccountType.INCOME);
@@ -455,4 +560,38 @@ public class ProductToGLAccountMappingHelper {
             
this.accountMappingRepository.deleteAllInBatch(productToGLAccountMappings);
         }
     }
+
+    public void validateChargeOffMappingsInDatabase(final List<JsonObject> 
mappings) {
+        final List<ApiParameterError> validationErrors = new ArrayList<>();
+
+        for (JsonObject jsonObject : mappings) {
+            final Long expenseGlAccountId = this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), 
jsonObject);
+            final Long chargeOffReasonCodeValueId = this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(),
 jsonObject);
+
+            // Validation: chargeOffReasonCodeValueId must exist in the 
database
+            CodeValue codeValue = 
this.codeValueRepository.findByCodeNameAndId("ChargeOffReasons", 
chargeOffReasonCodeValueId);
+            if (codeValue == null) {
+                
validationErrors.add(ApiParameterError.parameterError("validation.msg.chargeoffreason.invalid",
+                        "Charge-off reason with ID " + 
chargeOffReasonCodeValueId + " does not exist",
+                        
LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue()));
+            }
+
+            // Validation: expenseGLAccountId must exist as a valid Expense GL 
account
+            final Optional<GLAccount> glAccount = 
accountRepository.findById(expenseGlAccountId);
+
+            if (glAccount.isEmpty() || 
!glAccount.get().getType().equals(GL_ACCOUNT_EXPENSE_TYPE)) {
+                
validationErrors.add(ApiParameterError.parameterError("validation.msg.glaccount.not.found",
+                        "GL Account with ID " + expenseGlAccountId + " does 
not exist or is not an Expense GL account",
+                        
LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue()));
+
+            }
+        }
+
+        // Throw all collected validation errors, if any
+        if (!validationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(validationErrors);
+        }
+    }
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
index 47df148a8..612c23c12 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
@@ -46,5 +46,4 @@ public interface ProductToGLAccountMappingReadPlatformService 
{
     List<PaymentTypeToGLAccountMapper> 
fetchPaymentTypeToFundSourceMappingsForShareProduct(Long productId);
 
     List<ChargeToGLAccountMapper> 
fetchFeeToIncomeAccountMappingsForShareProduct(Long productId);
-
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
index 18b7da24e..903ae2025 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
@@ -29,6 +29,7 @@ import 
org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
 import 
org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
 import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.portfolio.PortfolioProductType;
@@ -42,9 +43,9 @@ public class SavingsProductToGLAccountMappingHelper extends 
ProductToGLAccountMa
     public SavingsProductToGLAccountMappingHelper(final GLAccountRepository 
glAccountRepository,
             final ProductToGLAccountMappingRepository 
glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
             final ChargeRepositoryWrapper chargeRepositoryWrapper, final 
GLAccountRepositoryWrapper accountRepositoryWrapper,
-            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper, 
final CodeValueRepository codeValueRepository) {
         super(glAccountRepository, glAccountMappingRepository, 
fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
-                paymentTypeRepositoryWrapper);
+                paymentTypeRepositoryWrapper, codeValueRepository);
     }
 
     /***
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java
index b026b428b..b7314f693 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ShareProductToGLAccountMappingHelper.java
@@ -28,6 +28,7 @@ import 
org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
 import 
org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
 import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.portfolio.PortfolioProductType;
@@ -41,9 +42,9 @@ public class ShareProductToGLAccountMappingHelper extends 
ProductToGLAccountMapp
     public ShareProductToGLAccountMappingHelper(final GLAccountRepository 
glAccountRepository,
             final ProductToGLAccountMappingRepository 
glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
             final ChargeRepositoryWrapper chargeRepositoryWrapper, final 
GLAccountRepositoryWrapper accountRepositoryWrapper,
-            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper, 
final CodeValueRepository codeValueRepository) {
         super(glAccountRepository, glAccountMappingRepository, 
fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
-                paymentTypeRepositoryWrapper);
+                paymentTypeRepositoryWrapper, codeValueRepository);
     }
 
     /***
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
 
b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
index 669ad59dc..ce7a2ff9f 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
@@ -172,7 +172,10 @@ public final class AccountingConstants {
         INCOME_FROM_CHARGE_OFF_PENALTY("incomeFromChargeOffPenaltyAccountId"), 
//
         
INCOME_FROM_GOODWILL_CREDIT_INTEREST("incomeFromGoodwillCreditInterestAccountId"),
 //
         
INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccountId"), //
-        
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId");
 //
+        
INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"),
 //
+        CHARGE_OFF_REASONS_TO_EXPENSE("chargeOffReasonsToExpenseMappings"), //
+        EXPENSE_GL_ACCOUNT_ID("expenseGLAccountId"), //
+        CHARGE_OFF_REASON_CODE_VALUE_ID("chargeOffReasonCodeValueId"); //
 
         private final String value;
 
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
index b40a343e0..461404419 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
@@ -33,6 +33,7 @@ import 
org.apache.fineract.accounting.glaccount.domain.GLAccountType;
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
 import 
org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException;
 import 
org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingHelper;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.portfolio.PortfolioProductType;
@@ -46,9 +47,9 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
     public LoanProductToGLAccountMappingHelper(final GLAccountRepository 
glAccountRepository,
             final ProductToGLAccountMappingRepository 
glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
             final ChargeRepositoryWrapper chargeRepositoryWrapper, final 
GLAccountRepositoryWrapper accountRepositoryWrapper,
-            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
+            final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper, 
final CodeValueRepository codeValueRepository) {
         super(glAccountRepository, glAccountMappingRepository, 
fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
-                paymentTypeRepositoryWrapper);
+                paymentTypeRepositoryWrapper, codeValueRepository);
     }
 
     /***
@@ -138,6 +139,16 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
         saveChargesToGLAccountMappings(command, element, productId, changes, 
PortfolioProductType.LOAN, false);
     }
 
+    public void saveChargeOffReasonToExpenseAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        saveChargeOffReasonToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN);
+    }
+
+    public void updateChargeOffReasonToExpenseAccountMappings(final 
JsonCommand command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes) {
+        updateChargeOffReasonToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN);
+    }
+
     public void updateChargesToIncomeAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
             final Map<String, Object> changes) {
         // update both fee and penalty charges
@@ -387,5 +398,4 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
         }
         return gLAccountType;
     }
-
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 5ecfb6cff..e83786baa 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -246,6 +246,7 @@ final class LoanProductsApiResourceSwagger {
         public Long incomeFromGoodwillCreditPenaltyAccountId;
         public 
List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
 paymentChannelToFundSourceMappings;
         public 
List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> 
feeToIncomeAccountMappings;
+        public 
List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> 
chargeOffReasonsToExpenseMappings;
         public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
 
         // Multi Disburse
@@ -1209,6 +1210,16 @@ final class LoanProductsApiResourceSwagger {
             public Long fundSourceAccountId;
         }
 
+        static final class GetChargeOffReasonsToExpenseMappings {
+
+            private GetChargeOffReasonsToExpenseMappings() {}
+
+            @Schema(example = "1")
+            public Long chargeOffReasonCodeValueId;
+            @Schema(example = "12")
+            public Long expenseGLAccountId;
+        }
+
         static final class GetLoanFeeToIncomeAccountMappings {
 
             private GetLoanFeeToIncomeAccountMappings() {}
@@ -1319,6 +1330,7 @@ final class LoanProductsApiResourceSwagger {
         public GetLoanAccountingMappings accountingMappings;
         public Set<GetLoanPaymentChannelToFundSourceMappings> 
paymentChannelToFundSourceMappings;
         public Set<GetLoanFeeToIncomeAccountMappings> 
feeToIncomeAccountMappings;
+        public Set<GetChargeOffReasonsToExpenseMappings> 
chargeOffReasonsToExpenseMappings;
         @Schema(example = "false")
         public Boolean isRatesEnabled;
         @Schema(example = "true")
@@ -1577,6 +1589,7 @@ final class LoanProductsApiResourceSwagger {
         public Long incomeFromChargeOffPenaltyAccountId;
         public 
List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
 paymentChannelToFundSourceMappings;
         public 
List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> 
feeToIncomeAccountMappings;
+        public 
List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> 
chargeOffReasonsToExpenseMappings;
         public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
         @Schema(example = "false")
         public Boolean enableAccrualActivityPosting;
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index c32997cb6..dd1273338 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -151,6 +151,7 @@ public class LoanProductData implements Serializable {
     private Collection<PaymentTypeToGLAccountMapper> 
paymentChannelToFundSourceMappings;
     private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
     private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
+    private List<ChargeToGLAccountMapper> chargeOffReasonsToExpenseMappings;
     private final boolean enableAccrualActivityPosting;
 
     // rates
@@ -853,6 +854,7 @@ public class LoanProductData implements Serializable {
         this.paymentChannelToFundSourceMappings = null;
         this.feeToIncomeAccountMappings = null;
         this.penaltyToIncomeAccountMappings = null;
+        this.chargeOffReasonsToExpenseMappings = null;
         this.valueConditionTypeOptions = null;
         this.principalVariationsForBorrowerCycle = principalVariations;
         this.interestRateVariationsForBorrowerCycle = interestRateVariations;
@@ -992,6 +994,7 @@ public class LoanProductData implements Serializable {
         this.paymentChannelToFundSourceMappings = 
productData.paymentChannelToFundSourceMappings;
         this.feeToIncomeAccountMappings = 
productData.feeToIncomeAccountMappings;
         this.penaltyToIncomeAccountMappings = 
productData.penaltyToIncomeAccountMappings;
+        this.chargeOffReasonsToExpenseMappings = 
productData.chargeOffReasonsToExpenseMappings;
 
         this.chargeOptions = chargeOptions;
         this.penaltyOptions = penaltyOptions;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
index 53496003a..05d8a530c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
@@ -130,6 +130,7 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 // advanced accounting mappings
                 
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command,
 element, loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId, null);
             break;
             case ACCRUAL_UPFRONT:
                 // Fall Through
@@ -208,6 +209,7 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 // advanced accounting mappings
                 
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command,
 element, loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId, null);
             break;
         }
     }
@@ -379,6 +381,8 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                     accountingRuleType);
             
this.loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command,
 element, loanProductId, changes);
             
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command,
 element, loanProductId, changes);
+            
this.loanProductToGLAccountMappingHelper.updateChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId,
+                    changes);
         }
         return changes;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
index 7adcbdefd..b2ac25942 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -160,7 +160,7 @@ public class LoanProductsApiResource {
     @Operation(summary = "Create a Loan Product", description = "Depending of 
the Accounting Rule (accountingRule) selected, additional fields with details 
of the appropriate Ledger Account identifiers would need to be passed in.\n"
             + "\n" + "Refer MifosX Accounting Specs Draft for more details 
regarding the significance of the selected accounting rule\n\n"
             + "Mandatory Fields: name, shortName, currencyCode, 
digitsAfterDecimal, inMultiplesOf, principal, numberOfRepayments, 
repaymentEvery, repaymentFrequencyType, interestRatePerPeriod, 
interestRateFrequencyType, amortizationType, interestType, 
interestCalculationPeriodType, transactionProcessingStrategyCode, 
accountingRule, isInterestRecalculationEnabled, daysInYearType, 
daysInMonthType\n\n"
-            + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, 
graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, 
paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, 
penaltyToIncomeAccountMappings, includeInBorrowerCycle, 
useBorrowerCycle,principalVariationsForBorrowerCycle, 
numberOfRepaymentVariationsForBorrowerCycle, 
interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, 
outstandingLoanBalance,overdueDaysForNPA,h [...]
+            + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, 
graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, 
paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, 
penaltyToIncomeAccountMappings, chargeOffReasonsToExpenseMappings, 
includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, 
numberOfRepaymentVariationsForBorrowerCycle, 
interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, 
outstan [...]
             + "Additional Mandatory Fields for Cash(2) based accounting: 
fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId, 
incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, 
transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
             + "Additional Mandatory Fields for periodic (3) and upfront 
(4)accrual accounting: fundSourceAccountId, loanPortfolioAccountId, 
interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId, 
writeOffAccountId, receivableInterestAccountId, receivableFeeAccountId, 
receivablePenaltyAccountId, transfersInSuspenseAccountId, 
overpaymentLiabilityAccountId\n\n"
             + "Additional Mandatory Fields if interest recalculation is 
enabled(true): interestRecalculationCompoundingMethod, 
rescheduleStrategyMethod, recalculationRestFrequencyType\n\n"
@@ -336,6 +336,7 @@ public class LoanProductsApiResource {
         Collection<PaymentTypeToGLAccountMapper> 
paymentChannelToFundSourceMappings;
         Collection<ChargeToGLAccountMapper> feeToGLAccountMappings;
         Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings;
+        List<ChargeToGLAccountMapper> chargeOffReasonsToExpenseMappings;
         if (loanProduct.hasAccountingEnabled()) {
             accountingMappings = 
this.accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId,
                     loanProduct.getAccountingRule().getId().intValue());
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 3758e4e20..e0d45318e 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
@@ -27,6 +27,7 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -36,6 +37,7 @@ import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
 import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import org.apache.fineract.accounting.common.AccountingValidations;
+import 
org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingHelper;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -141,7 +143,9 @@ public final class LoanProductDataValidator {
             
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(),
             
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
             
LoanProductAccountingParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(),
-            LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME,
+            
LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue(),
+            LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(),
+            
LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.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,
@@ -195,6 +199,7 @@ public final class LoanProductDataValidator {
     private final LoanRepaymentScheduleTransactionProcessorFactory 
loanRepaymentScheduleTransactionProcessorFactory;
     private final AdvancedPaymentAllocationsJsonParser 
advancedPaymentAllocationsJsonParser;
     private final AdvancedPaymentAllocationsValidator 
advancedPaymentAllocationsValidator;
+    private final ProductToGLAccountMappingHelper 
productToGLAccountMappingHelper;
 
     public void validateForCreate(final JsonCommand command) {
         String json = command.json();
@@ -718,6 +723,7 @@ public final class LoanProductDataValidator {
 
             validatePaymentChannelFundSourceMappings(baseDataValidator, 
element);
             validateChargeToIncomeAccountMappings(baseDataValidator, element);
+            validateChargeOffToExpenseMappings(baseDataValidator, element);
 
         }
 
@@ -1791,6 +1797,7 @@ public final class LoanProductDataValidator {
 
         validatePaymentChannelFundSourceMappings(baseDataValidator, element);
         validateChargeToIncomeAccountMappings(baseDataValidator, element);
+        validateChargeOffToExpenseMappings(baseDataValidator, element);
 
         validateMinMaxConstraints(element, baseDataValidator, loanProduct);
 
@@ -1964,6 +1971,60 @@ public final class LoanProductDataValidator {
         }
     }
 
+    private void validateChargeOffToExpenseMappings(final DataValidatorBuilder 
baseDataValidator, final JsonElement element) {
+        String parameterName = 
LoanProductAccountingParams.CHARGE_OFF_REASONS_TO_EXPENSE.getValue();
+
+        if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
+            final JsonArray chargeOffToExpenseMappingArray = 
this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+            if (chargeOffToExpenseMappingArray != null && 
chargeOffToExpenseMappingArray.size() > 0) {
+                Map<Long, Set<Long>> chargeOffReasonToAccounts = new 
HashMap<>();
+                List<JsonObject> processedMappings = new ArrayList<>(); // 
Collect processed mappings for the new method
+
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = 
chargeOffToExpenseMappingArray.get(i).getAsJsonObject();
+                    final Long expenseGlAccountId = this.fromApiJsonHelper
+                            
.extractLongNamed(LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), 
jsonObject);
+                    final Long chargeOffReasonCodeValueId = 
this.fromApiJsonHelper
+                            
.extractLongNamed(LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(),
 jsonObject);
+
+                    // Validate parameters locally
+                    baseDataValidator.reset()
+                            .parameter(parameterName + OPENING_SQUARE_BRACKET 
+ i + CLOSING_SQUARE_BRACKET + DOT
+                                    + 
LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue())
+                            
.value(expenseGlAccountId).notNull().integerGreaterThanZero();
+                    baseDataValidator.reset()
+                            .parameter(parameterName + OPENING_SQUARE_BRACKET 
+ i + CLOSING_SQUARE_BRACKET + DOT
+                                    + 
LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue())
+                            
.value(chargeOffReasonCodeValueId).notNull().integerGreaterThanZero();
+
+                    // Handle duplicate charge-off reason and GL Account 
validation
+                    
chargeOffReasonToAccounts.putIfAbsent(chargeOffReasonCodeValueId, new 
HashSet<>());
+                    Set<Long> associatedAccounts = 
chargeOffReasonToAccounts.get(chargeOffReasonCodeValueId);
+
+                    if (associatedAccounts.contains(expenseGlAccountId)) {
+                        baseDataValidator.reset().parameter(parameterName + 
OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
+                                
.failWithCode("duplicate.chargeOffReason.and.glAccount");
+                    }
+                    associatedAccounts.add(expenseGlAccountId);
+
+                    if (associatedAccounts.size() > 1) {
+                        baseDataValidator.reset().parameter(parameterName + 
OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
+                                
.failWithCode("multiple.glAccounts.for.chargeOffReason");
+                    }
+
+                    // Collect mapping for additional validations
+                    processedMappings.add(jsonObject);
+
+                    i++;
+                } while (i < chargeOffToExpenseMappingArray.size());
+
+                // Call the new validation method for additional checks
+                
productToGLAccountMappingHelper.validateChargeOffMappingsInDatabase(processedMappings);
+            }
+        }
+    }
+
     public void validateMinMaxConstraints(final JsonElement element, final 
DataValidatorBuilder baseDataValidator,
             final LoanProduct loanProduct) {
         validatePrincipalMinMaxConstraint(element, loanProduct, 
baseDataValidator);
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 7b79a6754..2a0d2d0c3 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -171,4 +171,5 @@
     <include 
file="parts/0150_transaction_summary_with_asset_owner_report_interest_waiver_interest_refund_added.xml"
 relativeToChangelogFile="true" />
     <include file="parts/0151_interest_refund_business_events.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0152_update_password_validation_policy.xml" 
relativeToChangelogFile="true" />
+    <include 
file="parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml
new file mode 100644
index 000000000..5002a38bb
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet id="1" author="fineract">
+        <addColumn tableName="acc_product_mapping">
+            <column name="charge_off_reason_id" type="INT" 
defaultValueNumeric="NULL">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+        <addForeignKeyConstraint
+                baseTableName="acc_product_mapping"
+                baseColumnNames="charge_off_reason_id"
+                referencedTableName="m_code_value"
+                referencedColumnNames="id"
+                constraintName="fk_acc_product_mapping_charge_off_reason"/>
+    </changeSet>
+
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java
new file mode 100644
index 000000000..95d8a735c
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java
@@ -0,0 +1,272 @@
+/**
+ * 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.apache.fineract.integrationtests.common.funds.FundsResourceHandler.createFund;
+
+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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.fineract.client.models.AllowAttributeOverrides;
+import org.apache.fineract.client.models.ChargeData;
+import org.apache.fineract.client.models.ChargeToGLAccountMapper;
+import org.apache.fineract.client.models.GetChargeOffReasonsToExpenseMappings;
+import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings;
+import 
org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
+import org.apache.fineract.integrationtests.common.BusinessStepHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(LoanTestLifecycleExtension.class)
+public class LoanProductChargeOffReasonMappingsTest extends 
BaseLoanIntegrationTest {
+
+    private static final String CODE_VALUE_NAME = "ChargeOffReasons";
+
+    private static ResponseSpecification responseSpec;
+    private static RequestSpecification requestSpec;
+    private static LoanTransactionHelper loanTransactionHelper;
+
+    @BeforeAll
+    public static void setup() {
+        Utils.initializeRESTAssured();
+        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        requestSpec.header("Fineract-Platform-TenantId", "default");
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+        BusinessStepHelper businessStepHelper = new BusinessStepHelper();
+        // setup COB Business Steps to prevent test failing due other 
integration test configurations
+        businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", 
"APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
+                "CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", 
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+                "EXTERNAL_ASSET_OWNER_TRANSFER", "ACCRUAL_ACTIVITY_POSTING");
+    }
+
+    @Test
+    public void testCreateLoanProductWithValidChargeOffReason() {
+        final String creationBusinessDay = "15 January 2023";
+        runAt(creationBusinessDay, () -> {
+            Integer chargeOffReasons = createChargeOffReason();
+            Long localLoanProductId = 
loanTransactionHelper.createLoanProduct(loanProductsRequest(Long.valueOf(chargeOffReasons),
 15L))
+                    .getResourceId();
+
+            Assertions.assertNotNull(localLoanProductId);
+        });
+    }
+
+    @Test
+    public void testUpdateLoanProductWithValidChargeOffReason() {
+        final String creationBusinessDay = "15 January 2023";
+        runAt(creationBusinessDay, () -> {
+            Integer chargeOffReasons = createChargeOffReason();
+            List<GetChargeOffReasonsToExpenseMappings> 
chargeOffReasonsToExpenseMappings = new ArrayList<>();
+            GetChargeOffReasonsToExpenseMappings 
getChargeOffReasonsToExpenseMappings = new 
GetChargeOffReasonsToExpenseMappings();
+            
getChargeOffReasonsToExpenseMappings.setChargeOffReasonCodeValueId(Long.valueOf(chargeOffReasons));
+            getChargeOffReasonsToExpenseMappings.setExpenseGLAccountId(15L);
+            
chargeOffReasonsToExpenseMappings.add(getChargeOffReasonsToExpenseMappings);
+
+            Long localLoanProductId = 
loanTransactionHelper.updateLoanProduct(1L,
+                    new 
PutLoanProductsProductIdRequest().locale("en").chargeOffReasonsToExpenseMappings(chargeOffReasonsToExpenseMappings))
+                    .getResourceId();
+
+            Assertions.assertNotNull(localLoanProductId);
+        });
+    }
+
+    @Test
+    public void testCreateLoanProductWithInvalidGLAccount() {
+        final String creationBusinessDay = "15 January 2023";
+        runAt(creationBusinessDay, () -> {
+            try {
+                Integer chargeOffReasons = createChargeOffReason();
+                
loanTransactionHelper.createLoanProduct(loanProductsRequest(Long.valueOf(chargeOffReasons),
 9999L));
+            } catch (CallFailedRuntimeException e) {
+                
Assertions.assertTrue(e.getMessage().contains("validation.msg.glaccount.not.found"));
+            }
+        });
+    }
+
+    @Test
+    public void testCreateLoanProductWithInvalidChargeOffReason() {
+        final String creationBusinessDay = "15 January 2023";
+        runAt(creationBusinessDay, () -> {
+            try {
+                
loanTransactionHelper.createLoanProduct(loanProductsRequest(1L, 12L));
+            } catch (CallFailedRuntimeException e) {
+                
Assertions.assertTrue(e.getMessage().contains("validation.msg.chargeoffreason.invalid"));
+            }
+        });
+    }
+
+    private PostLoanProductsRequest loanProductsRequest(Long 
chargeOffReasonId, Long glAccountId) {
+        String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
+        String shortName = Utils.uniqueRandomStringGenerator("", 4);
+
+        List<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
+        List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new 
ArrayList<>();
+        List<Integer> interestRateVariationsForBorrowerCycle = new 
ArrayList<>();
+        List<ChargeData> charges = new ArrayList<>();
+        List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new 
ArrayList<>();
+        List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = 
new ArrayList<>();
+
+        List<GetChargeOffReasonsToExpenseMappings> 
chargeOffReasonsToExpenseMappings = new ArrayList<>();
+        GetChargeOffReasonsToExpenseMappings 
getChargeOffReasonsToExpenseMappings = new 
GetChargeOffReasonsToExpenseMappings();
+        
getChargeOffReasonsToExpenseMappings.setChargeOffReasonCodeValueId(chargeOffReasonId);
+        
getChargeOffReasonsToExpenseMappings.setExpenseGLAccountId(glAccountId);
+        
chargeOffReasonsToExpenseMappings.add(getChargeOffReasonsToExpenseMappings);
+
+        List<GetLoanPaymentChannelToFundSourceMappings> 
paymentChannelToFundSourceMappings = new ArrayList<>();
+        GetLoanPaymentChannelToFundSourceMappings 
loanPaymentChannelToFundSourceMappings = new 
GetLoanPaymentChannelToFundSourceMappings();
+        
loanPaymentChannelToFundSourceMappings.fundSourceAccountId(fundSource.getAccountID().longValue());
+        loanPaymentChannelToFundSourceMappings.paymentTypeId(1L);
+        
paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings);
+
+        final Integer fundId = createFund(requestSpec, responseSpec);
+        Assertions.assertNotNull(fundId);
+
+        final Integer delinquencyBucketId = 
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+        Assertions.assertNotNull(delinquencyBucketId);
+
+        return new PostLoanProductsRequest()//
+                .name(name)//
+                .enableAccrualActivityPosting(true)//
+                .shortName(shortName)//
+                .description(
+                        "LP1 with 12% DECLINING BALANCE interest, interest 
period: Daily, Interest recalculation-Daily, Compounding:none")//
+                .fundId(fundId.longValue())//
+                .startDate(null)//
+                .closeDate(null)//
+                .includeInBorrowerCycle(false)//
+                .currencyCode("EUR")//
+                .digitsAfterDecimal(2)//
+                .inMultiplesOf(1)//
+                .installmentAmountInMultiplesOf(1)//
+                .useBorrowerCycle(false)//
+                .minPrincipal(100.0)//
+                .principal(1000.0)//
+                .maxPrincipal(10000.0)//
+                .minNumberOfRepayments(1)//
+                .numberOfRepayments(1)//
+                .maxNumberOfRepayments(30)//
+                .isLinkedToFloatingInterestRates(false)//
+                .minInterestRatePerPeriod(0.0)//
+                .interestRatePerPeriod(12.0)//
+                .maxInterestRatePerPeriod(30.0)//
+                .interestRateFrequencyType(3)//
+                .repaymentEvery(30)//
+                .repaymentFrequencyType(0L)//
+                
.principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)//
+                
.numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)//
+                
.interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)//
+                .amortizationType(1)//
+                .interestType(0)//
+                .isEqualAmortization(false)//
+                .interestCalculationPeriodType(0)//
+                .transactionProcessingStrategyCode("mifos-standard-strategy")//
+                .daysInYearType(1)//
+                .daysInMonthType(1)//
+                .canDefineInstallmentAmount(true)//
+                .graceOnArrearsAgeing(3)//
+                .overdueDaysForNPA(179)//
+                .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
+                .principalThresholdForLastInstallment(50)//
+                .allowVariableInstallments(false)//
+                .canUseForTopup(false)//
+                .holdGuaranteeFunds(false)//
+                .multiDisburseLoan(false)//
+                .allowAttributeOverrides(new AllowAttributeOverrides()//
+                        .amortizationType(true)//
+                        .interestType(true)//
+                        .transactionProcessingStrategyCode(true)//
+                        .interestCalculationPeriodType(true)//
+                        .inArrearsTolerance(true)//
+                        .repaymentEvery(true)//
+                        .graceOnPrincipalAndInterestPayment(true)//
+                        .graceOnArrearsAgeing(true))
+                .outstandingLoanBalance(10000.0)//
+                .charges(charges)//
+                .accountingRule(3)//
+
+                
.fundSourceAccountId(suspenseAccount.getAccountID().longValue())//
+                
.loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())//
+                
.transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())//
+                
.interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())//
+                
.incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())//
+                
.incomeFromPenaltyAccountId(penaltyIncomeAccount.getAccountID().longValue())//
+                
.incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())//
+                
.writeOffAccountId(writtenOffAccount.getAccountID().longValue())//
+                
.overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())//
+                
.receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())//
+                
.receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())//
+                
.receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())//
+                
.goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())//
+                
.incomeFromGoodwillCreditInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
+                
.incomeFromGoodwillCreditFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
+                
.incomeFromGoodwillCreditPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue())//
+                
.incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
+                
.incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
+                
.chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())//
+                
.chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())//
+                
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())//
+
+                .dateFormat("dd MMMM yyyy")//
+                .locale("en")//
+                .disallowExpectedDisbursements(false)//
+                .allowApprovedDisbursedAmountsOverApplied(false)//
+                .delinquencyBucketId(delinquencyBucketId.longValue())//
+                
.paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)//
+                
.penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)//
+                
.chargeOffReasonsToExpenseMappings(chargeOffReasonsToExpenseMappings).feeToIncomeAccountMappings(feeToIncomeAccountMappings)//
+                .isInterestRecalculationEnabled(true)//
+                .preClosureInterestCalculationStrategy(1)//
+                .rescheduleStrategyMethod(3)//
+                .interestRecalculationCompoundingMethod(0)//
+                .recalculationRestFrequencyType(2)//
+                .recalculationRestFrequencyInterval(1)//
+                .allowPartialPeriodInterestCalcualtion(false);//
+    }
+
+    private Integer createChargeOffReason() {
+        Integer chargeOffReasonId;
+        HashMap<String, Object> codes = CodeHelper.getCodeByName(requestSpec, 
responseSpec, CODE_VALUE_NAME);
+        if (codes.isEmpty()) {
+            CodeHelper.createCode(requestSpec, responseSpec, CODE_VALUE_NAME, 
"");
+        }
+        codes = CodeHelper.getCodeByName(requestSpec, responseSpec, 
CODE_VALUE_NAME);
+        Integer codeId = (Integer) codes.get("id");
+        HashMap<String, Object> codeValues = 
CodeHelper.getOrCreateCodeValueByCodeIdAndCodeName(requestSpec, responseSpec, 
codeId,
+                CODE_VALUE_NAME, 1);
+        chargeOffReasonId = (Integer) codeValues.get("id");
+        return chargeOffReasonId;
+    }
+}
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 e9439c259..75624a8c5 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
@@ -107,6 +107,7 @@ public class LoanProductTestBuilder {
 
     private List<Map<String, Long>> feeToIncomeAccountMappings = null;
     private List<Map<String, Long>> penaltyToIncomeAccountMappings = null;
+    private List<Map<String, Long>> chargeOffReasonsToExpenseMappings = null;
     private Account feeAndPenaltyAssetAccount;
 
     private Boolean multiDisburseLoan = false;
@@ -303,6 +304,10 @@ public class LoanProductTestBuilder {
             map.put("penaltyToIncomeAccountMappings", 
this.penaltyToIncomeAccountMappings);
         }
 
+        if (this.chargeOffReasonsToExpenseMappings != null) {
+            map.put("chargeOffReasonsToExpenseMappings", 
this.chargeOffReasonsToExpenseMappings);
+        }
+
         if (this.dueDaysForRepaymentEvent != null) {
             map.put("dueDaysForRepaymentEvent", this.dueDaysForRepaymentEvent);
         }
@@ -796,6 +801,17 @@ public class LoanProductTestBuilder {
         return this;
     }
 
+    public LoanProductTestBuilder withChargeOffReasonsToExpenseMappings(final 
Long reasonId, final Long accountId) {
+        if (this.chargeOffReasonsToExpenseMappings == null) {
+            this.chargeOffReasonsToExpenseMappings = new ArrayList<>();
+        }
+        Map<String, Long> newMap = new HashMap<>();
+        newMap.put("chargeOffReasonCodeValueId", reasonId);
+        newMap.put("expenseGLAccountId", accountId);
+        this.chargeOffReasonsToExpenseMappings.add(newMap);
+        return this;
+    }
+
     public String getTransactionProcessingStrategyCode() {
         return transactionProcessingStrategyCode;
     }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
index 6eed20b57..2f0cce363 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
@@ -93,6 +93,35 @@ public final class CodeHelper {
         return code;
     }
 
+    public static HashMap<String, Object> 
getOrCreateCodeValueByCodeIdAndCodeName(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer codeId, 
final String codeName, final Integer position) {
+
+        ArrayList<HashMap<String, Object>> allCodeValues = 
CodeHelper.getAllCodeValuesByCodeId(requestSpec, responseSpec, codeId);
+        HashMap<String, Object> codesByName = filterCodesByName(allCodeValues, 
codeName);
+
+        if (codesByName.isEmpty()) {
+            CodeHelper.createCodeValue(requestSpec, responseSpec, codeId, 
codeName, position);
+            allCodeValues = CodeHelper.getAllCodeValuesByCodeId(requestSpec, 
responseSpec, codeId);
+        }
+
+        return filterCodesByName(allCodeValues, codeName);
+    }
+
+    private static HashMap<String, Object> 
filterCodesByName(ArrayList<HashMap<String, Object>> allCodeValues, String 
codeName) {
+        final HashMap<String, Object> codes = new HashMap<>();
+
+        for (HashMap<String, Object> map : allCodeValues) {
+            String name = (String) map.get("name");
+            if (name.equals(codeName)) {
+                codes.put("id", map.get("id"));
+                codes.put("name", map.get("name"));
+                break;
+            }
+        }
+
+        return codes;
+    }
+
     public static HashMap<String, Object> retrieveOrCreateCodeValue(Integer 
codeId, final RequestSpecification requestSpec,
             final ResponseSpecification responseSpec) {
         Integer codeValueId = null;
@@ -119,6 +148,13 @@ public final class CodeHelper {
 
     }
 
+    public static ArrayList<HashMap<String, Object>> 
getAllCodeValuesByCodeId(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer codeId) {
+
+        return Utils.performServerGet(requestSpec, responseSpec,
+                CODE_VALUE_URL.replace("[codeId]", codeId.toString()) + "?" + 
Utils.TENANT_IDENTIFIER, "");
+    }
+
     public static Object getSystemDefinedCodes(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec) {
 
         final String getResponse = 
given().spec(requestSpec).expect().spec(responseSpec).when()

Reply via email to