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 65538495aa FINERACT-2347: Introduce classification field on loan
transactions
65538495aa is described below
commit 65538495aa301246d305451c6fd9d5254b0cd424
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Fri Aug 22 16:07:50 2025 -0500
FINERACT-2347: Introduce classification field on loan transactions
---
.../main/avro/loan/v1/LoanTransactionDataV1.avsc | 8 ++
.../apache/fineract/test/helper/CodeHelper.java | 4 +
.../fineract/test/stepdef/loan/LoanStepDef.java | 112 +++++++++++++++++++++
.../resources/features/LoanBuyDownFees.feature | 36 +++++++
.../features/LoanCapitalizedIncome.feature | 37 +++++++
.../api/LoanTransactionApiConstants.java | 4 +
.../api/LoanTransactionsApiResourceSwagger.java | 19 ++++
.../loanaccount/data/LoanTransactionData.java | 6 +-
.../loanaccount/domain/LoanTransaction.java | 7 ++
.../loanaccount/mapper/LoanTransactionMapper.java | 2 +
.../serialization/LoanTransactionValidator.java | 2 +
.../tenant/module/loan/module-changelog-master.xml | 1 +
...1032_add_classification_to_loan_transaction.xml | 41 ++++++++
.../loanaccount/data/LoanTransactionDataTest.java | 4 +-
.../BuyDownFeeWritePlatformServiceImpl.java | 16 +++
.../CapitalizedIncomeWritePlatformServiceImpl.java | 14 +++
.../ProgressiveLoanTransactionValidatorImpl.java | 24 ++++-
.../ProgressiveLoanAccountConfiguration.java | 7 +-
.../infrastructure/codes/api/CodesApiResource.java | 17 ++++
.../api/LoanTransactionsApiResource.java | 2 +-
.../LoanTransactionValidatorImpl.java | 18 ++++
.../service/LoanReadPlatformServiceImpl.java | 29 ++++--
.../starter/LoanAccountConfiguration.java | 6 +-
.../LoanCapitalizedIncomeTest.java | 24 ++++-
.../integrationtests/LoanTransactionTest.java | 13 +++
.../common/loans/LoanTransactionHelper.java | 6 ++
.../integrationtests/common/system/CodeHelper.java | 4 +
27 files changed, 442 insertions(+), 21 deletions(-)
diff --git
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
index d9df8b18c4..2b59fd23a9 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
@@ -263,6 +263,14 @@
"null",
"boolean"
]
+ },
+ {
+ "default": null,
+ "name": "classification",
+ "type": [
+ "null",
+ "org.apache.fineract.avro.generic.v1.CodeValueDataV1"
+ ]
}
]
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
index 8d6203cc26..d0a75faf7b 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
@@ -60,4 +60,8 @@ public class CodeHelper {
return codesApi.retrieveCodes().execute().body().stream().filter(r ->
name.equals(r.getName())).findAny()
.orElseThrow(() -> new IllegalArgumentException("Code with
name " + name + " has not been found"));
}
+
+ public Response<PostCodeValueDataResponse> createCodeValue(Long codeId,
PostCodeValuesDataRequest request) throws IOException {
+ return codeValuesApi.createCodeValue(codeId, request).execute();
+ }
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 9193440837..6273fac69e 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -67,6 +67,8 @@ import
org.apache.fineract.client.models.BuyDownFeeAmortizationDetails;
import org.apache.fineract.client.models.CapitalizedIncomeDetails;
import org.apache.fineract.client.models.DeleteLoansLoanIdResponse;
import org.apache.fineract.client.models.DisbursementDetail;
+import org.apache.fineract.client.models.GetCodeValuesDataResponse;
+import org.apache.fineract.client.models.GetCodesResponse;
import org.apache.fineract.client.models.GetLoanProductsChargeOffReasonOptions;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoanProductsResponse;
@@ -90,6 +92,8 @@ import
org.apache.fineract.client.models.OldestCOBProcessedLoanDTO;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import
org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCodeValueDataResponse;
+import org.apache.fineract.client.models.PostCodeValuesDataRequest;
import org.apache.fineract.client.models.PostLoansDisbursementData;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
@@ -108,6 +112,7 @@ import
org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResp
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.services.BusinessDateManagementApi;
+import org.apache.fineract.client.services.CodeValuesApi;
import org.apache.fineract.client.services.LoanBuyDownFeesApi;
import org.apache.fineract.client.services.LoanCapitalizedIncomeApi;
import org.apache.fineract.client.services.LoanCobCatchUpApi;
@@ -236,6 +241,9 @@ public class LoanStepDef extends AbstractStepDef {
@Autowired
private CodeHelper codeHelper;
+ @Autowired
+ private CodeValuesApi codeValuesApi;
+
@Autowired
private LoanInterestPauseApi loanInterestPauseApi;
@@ -4771,6 +4779,35 @@ public class LoanStepDef extends AbstractStepDef {
ErrorHelper.checkSuccessfulApiCall(capitalizedIncomeResponse);
}
+ @And("Admin adds capitalized income with {string} payment type to the loan
on {string} with {string} EUR transaction amount and {string} classification")
+ public void adminAddsCapitalizedIncomeWithClassification(final String
transactionPaymentType, final String transactionDate,
+ final String amount, final String classificationCodeName) throws
IOException {
+ final Response<PostLoansLoanIdTransactionsResponse>
capitalizedIncomeResponse = addCapitalizedIncomeWithClassification(
+ transactionPaymentType, transactionDate, amount,
classificationCodeName);
+ testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_RESPONSE,
capitalizedIncomeResponse);
+ ErrorHelper.checkSuccessfulApiCall(capitalizedIncomeResponse);
+ }
+
+ public Response<PostLoansLoanIdTransactionsResponse>
addCapitalizedIncomeWithClassification(final String transactionPaymentType,
+ final String transactionDate, final String amount, final String
classificationCodeName) throws IOException {
+ final Response<PostLoansResponse> loanResponse =
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ final long loanId = loanResponse.body().getLoanId();
+
+ final DefaultPaymentType paymentType =
DefaultPaymentType.valueOf(transactionPaymentType);
+ final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
+
+ // Get classification code value
+ final Long classificationId =
getClassificationCodeValueId(classificationCodeName);
+
+ final PostLoansLoanIdTransactionsRequest capitalizedIncomeRequest =
LoanRequestFactory.defaultCapitalizedIncomeRequest()
+
.transactionDate(transactionDate).transactionAmount(Double.valueOf(amount)).paymentTypeId(paymentTypeValue)
+ .externalId("EXT-CAP-INC-" +
UUID.randomUUID()).classificationId(classificationId);
+
+ final Response<PostLoansLoanIdTransactionsResponse>
capitalizedIncomeResponse = loanTransactionsApi
+ .executeLoanTransaction(loanId, capitalizedIncomeRequest,
"capitalizedIncome").execute();
+ return capitalizedIncomeResponse;
+ }
+
public Response<PostLoansLoanIdTransactionsResponse>
adjustCapitalizedIncome(final String transactionPaymentType,
final String transactionDate, final String amount, final Long
transactionId) throws IOException {
@@ -5167,6 +5204,35 @@ public class LoanStepDef extends AbstractStepDef {
ErrorHelper.checkSuccessfulApiCall(buyDownFeesIncomeResponse);
}
+ @When("Admin adds buy down fee with {string} payment type to the loan on
{string} with {string} EUR transaction amount and {string} classification")
+ public void adminAddsBuyDownFeeWithClassification(final String
transactionPaymentType, final String transactionDate,
+ final String amount, final String classificationCodeName) throws
IOException {
+ final Response<PostLoansLoanIdTransactionsResponse>
buyDownFeesIncomeResponse = addBuyDownFeeWithClassification(
+ transactionPaymentType, transactionDate, amount,
classificationCodeName);
+ testContext().set(TestContextKey.LOAN_BUY_DOWN_FEE_RESPONSE,
buyDownFeesIncomeResponse);
+ ErrorHelper.checkSuccessfulApiCall(buyDownFeesIncomeResponse);
+ }
+
+ public Response<PostLoansLoanIdTransactionsResponse>
addBuyDownFeeWithClassification(final String transactionPaymentType,
+ final String transactionDate, final String amount, final String
classificationCodeName) throws IOException {
+ final Response<PostLoansResponse> loanResponse =
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ final long loanId = loanResponse.body().getLoanId();
+
+ final DefaultPaymentType paymentType =
DefaultPaymentType.valueOf(transactionPaymentType);
+ final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
+
+ // Get classification code value
+ final Long classificationId =
getClassificationCodeValueId(classificationCodeName);
+
+ final PostLoansLoanIdTransactionsRequest buyDownFeeRequest =
LoanRequestFactory.defaultBuyDownFeeIncomeRequest()
+
.transactionDate(transactionDate).transactionAmount(Double.valueOf(amount)).paymentTypeId(paymentTypeValue)
+ .externalId("EXT-BUY-DOWN-FEE" +
UUID.randomUUID()).classificationId(classificationId);
+
+ final Response<PostLoansLoanIdTransactionsResponse> buyDownFeeResponse
= loanTransactionsApi
+ .executeLoanTransaction(loanId, buyDownFeeRequest,
"buyDownFee").execute();
+ return buyDownFeeResponse;
+ }
+
@And("Admin adds buy down fee adjustment with {string} payment type to the
loan on {string} with {string} EUR transaction amount")
public void adminAddsBuyDownFeesAdjustmentToTheLoan(final String
transactionPaymentType, final String transactionDate,
final String amount) throws IOException {
@@ -5553,4 +5619,50 @@ public class LoanStepDef extends AbstractStepDef {
eventAssertion.assertEventRaised(LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.class,
buyDownFeeAmortizationAdjustmentTransactionId);
}
+
+ @And("Loan Transactions tab has a {string} transaction with date {string}
which has classification code value {string}")
+ public void loanTransactionHasClassification(String transactionType,
String expectedDate, String expectedClassification)
+ throws IOException {
+ Response<PostLoansResponse> loanCreateResponse =
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ long loanId = loanCreateResponse.body().getLoanId();
+
+ Response<GetLoansLoanIdResponse> loanDetailsResponse =
loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
+ ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+
+ List<GetLoansLoanIdTransactions> transactions =
loanDetailsResponse.body().getTransactions();
+ GetLoansLoanIdTransactions transaction = transactions.stream()
+ .filter(t -> transactionType.equals(t.getType().getValue()) &&
expectedDate.equals(FORMATTER.format(t.getDate())))
+ .findFirst().orElseThrow(
+ () -> new IllegalStateException(String.format("No %s
transaction found on %s", transactionType, expectedDate)));
+
+ // Get detailed transaction information including classification
+ Response<GetLoansLoanIdTransactionsTransactionIdResponse>
transactionDetailsResponse = loanTransactionsApi
+ .retrieveTransaction(loanId, transaction.getId(),
null).execute();
+ ErrorHelper.checkSuccessfulApiCall(transactionDetailsResponse);
+
+ GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails =
transactionDetailsResponse.body();
+
assertThat(transactionDetails.getClassification()).as(String.format("%s
transaction should have classification", transactionType))
+ .isNotNull();
+
assertThat(transactionDetails.getClassification().getName()).as("Classification
name should match expected value")
+ .isEqualTo(expectedClassification);
+ }
+
+ private Long getClassificationCodeValueId(String classificationName)
throws IOException {
+ final GetCodesResponse code =
codeHelper.retrieveCodeByName(classificationName);
+
+ // Delete all existing code values for this classification
+ List<GetCodeValuesDataResponse> existingCodeValues =
codeValuesApi.retrieveAllCodeValues(code.getId()).execute().body();
+ for (GetCodeValuesDataResponse codeValue : existingCodeValues) {
+ codeValuesApi.deleteCodeValue(code.getId(),
codeValue.getId()).execute();
+ }
+
+ // Create a new code value under the classification code
+ PostCodeValuesDataRequest codeValueRequest = new
PostCodeValuesDataRequest().name(classificationName + "_value").isActive(true)
+ .position(1);
+
+ Response<PostCodeValueDataResponse> response =
codeHelper.createCodeValue(code.getId(), codeValueRequest);
+ ErrorHelper.checkSuccessfulApiCall(response);
+
+ return response.body().getSubResourceId();
+ }
}
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
index bc2442f803..2c469309b4 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
@@ -3204,3 +3204,39 @@ Feature:Feature: Buy Down Fees
| Date | Fee Amount | Amortized Amount | Not Yet Amortized
Amount | Adjusted Amount | Charged Off Amount |
| 01 January 2024 | 50.0 | 50.0 | 0.0
| 0.0 | 0.0 |
And LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent is created
on "01 April 2024"
+
+ @TestRailId:C4004
+ Scenario: Verify buy down fee transaction creation with classification field
set
+ When Admin sets the business date to "01 January 2024"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct | submitted
on date | with Principal | ANNUAL interest rate % | interest type |
interest calculation period | amortization type | loanTermFrequency |
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType |
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment |
interest free period | Payment strategy |
+ | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | 01 January
2024 | 100 | 7 | DECLINING_BALANCE | DAILY
| EQUAL_INSTALLMENTS | 3 | MONTHS
| 1 | MONTHS | 3 | 0
| 0 | 0 |
ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2024" with "100"
amount and expected disbursement date on "01 January 2024"
+ And Admin successfully disburse the loan on "01 January 2024" with "100"
EUR transaction amount
+ Then Loan status will be "ACTIVE"
+ When Admin adds buy down fee with "AUTOPAY" payment type to the loan on
"01 January 2024" with "50" EUR transaction amount and
"buydown_fee_transaction_classification" classification
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan | Principal
due | Interest | Fees | Penalties | Due | Paid | In advance | Late |
Outstanding |
+ | | | 01 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 01 February 2024 | | 66.86 | 33.14
| 0.58 | 0.0 | 0.0 | 33.72 | 0.0 | 0.0 | 0.0 | 33.72
|
+ | 2 | 29 | 01 March 2024 | | 33.53 | 33.33
| 0.39 | 0.0 | 0.0 | 33.72 | 0.0 | 0.0 | 0.0 | 33.72
|
+ | 3 | 31 | 01 April 2024 | | 0.0 | 33.53
| 0.2 | 0.0 | 0.0 | 33.73 | 0.0 | 0.0 | 0.0 | 33.73
|
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 100.0 | 1.17 | 0.0 | 0.0 | 101.17 | 0.0 | 0.0
| 0.0 | 101.17 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest |
Fees | Penalties | Loan Balance | Reverted |
+ | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 |
0.0 | 0.0 | 100.0 | false |
+ | 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 |
0.0 | 0.0 | 0.0 | false |
+ And Loan Transactions tab has a "BUY_DOWN_FEE" transaction with date "01
January 2024" which has the following Journal entries:
+ | Type | Account code | Account name | Debit |
Credit |
+ | EXPENSE | 450280 | Buy Down Expense | 50.0 |
|
+ | LIABILITY | 145024 | Deferred Capitalized Income | | 50.0
|
+ And Loan Transactions tab has a "Buy Down Fee" transaction with date "01
January 2024" which has classification code value
"buydown_fee_transaction_classification_value"
+ And Buy down fee contains the following data:
+ | Date | Fee Amount | Amortized Amount | Not Yet Amortized
Amount | Adjusted Amount | Charged Off Amount |
+ | 01 January 2024 | 50.0 | 0.0 | 50.0
| 0.0 | 0.0 |
+ And LoanBuyDownFeeTransactionCreatedBusinessEvent is created on "01
January 2024"
+ And Admin adds buy down fee adjustment with "AUTOPAY" payment type to the
loan on "01 January 2024" with "25" EUR transaction amount
+ And Loan Transactions tab has a "Buy Down Fee Adjustment" transaction with
date "01 January 2024" which has classification code value
"buydown_fee_transaction_classification_value"
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
index b7951e3951..0350b40e5c 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
@@ -7529,3 +7529,40 @@ Feature: Capitalized Income
When Admin successfully disburse the loan on "2 January 2024" with "300"
EUR transaction amount
# Available amount = 1200 - 900 - 300 - 0 = 0
Then Loan's available disbursement amount is "0.0"
+
+ @TestRailId:C4005
+ Scenario: Verify capitalized income transaction creation with classification
field set
+ When Admin sets the business date to "1 January 2024"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct |
submitted on date | with Principal | ANNUAL interest rate % | interest type |
interest calculation period | amortization type | loanTermFrequency |
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType |
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment |
interest free period | Payment strategy |
+ | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME | 1
January 2024 | 1000.0 | 0 | FLAT |
SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS
| 30 | DAYS | 1 | 0
| 0 | 0 |
ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "1 January 2024" with "1000"
amount and expected disbursement date on "1 January 2024"
+ And Admin successfully disburse the loan on "1 January 2024" with "900"
EUR transaction amount
+ Then Loan status will be "ACTIVE"
+ When Admin sets the business date to "2 January 2024"
+ And Admin adds capitalized income with "AUTOPAY" payment type to the loan
on "2 January 2024" with "100" EUR transaction amount and
"capitalized_income_transaction_classification" classification
+ Then Loan Repayment schedule has 2 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 01 January 2024 | | 900.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 01 January 2024 | 01 January 2024 | 675.0 |
225.0 | 0.0 | 0.0 | 0.0 | 225.0 | 225.0 | 0.0 | 0.0
| 0.0 |
+ | | | 02 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 2 | 30 | 31 January 2024 | | 0.0 |
775.0 | 0.0 | 0.0 | 0.0 | 775.0 | 0.0 | 0.0 | 0.0
| 775.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 225.0 | 0.0
| 0.0 | 775.0 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest
| Fees | Penalties | Loan Balance | Reverted |
+ | 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0
| 0.0 | 0.0 | 900.0 | false |
+ | 01 January 2024 | Down Payment | 225.0 | 225.0 | 0.0
| 0.0 | 0.0 | 675.0 | false |
+ | 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 775.0 | false |
+ And Loan Transactions tab has a "CAPITALIZED_INCOME" transaction with date
"02 January 2024" which has the following Journal entries:
+ | Type | Account code | Account name | Debit |
Credit |
+ | ASSET | 112601 | Loans Receivable | 100.0 |
|
+ | LIABILITY | 145024 | Deferred Capitalized Income | |
100.0 |
+ And Loan Transactions tab has a "Capitalized Income" transaction with date
"02 January 2024" which has classification code value
"capitalized_income_transaction_classification_value"
+ And Deferred Capitalized Income contains the following data:
+ | Amount | Amortized Amount | Unrecognized Amount | Adjusted Amount |
Charged Off Amount |
+ | 100.0 | 0.0 | 100.0 | 0.0 |
0.0 |
+ And Admin adds capitalized income adjustment with "AUTOPAY" payment type
to the loan on "02 January 2024" with "50" EUR transaction amount
+ And Loan Transactions tab has a "Capitalized Income Adjustment"
transaction with date "02 January 2024" which has classification code value
"capitalized_income_transaction_classification_value"
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
index 6617def508..258635d957 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
@@ -60,4 +60,8 @@ public interface LoanTransactionApiConstants {
buyDownFeeAmortization, //
buyDownFeeAmortizationAdjustment, //
}
+
+ String TRANSACTION_CLASSIFICATIONID_PARAMNAME = "classificationId";
+ String CAPITALIZED_INCOME_CLASSIFICATION_CODE =
"capitalized_income_transaction_classification";
+ String BUY_DOWN_FEE_CLASSIFICATION_CODE =
"buydown_fee_transaction_classification";
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index 8929d7b430..dd0bb22c1b 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -32,6 +32,20 @@ final class LoanTransactionsApiResourceSwagger {
private LoanTransactionsApiResourceSwagger() {}
+ static final class GetCodeValuesDataResponse {
+
+ private GetCodeValuesDataResponse() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "Passport")
+ public String name;
+ @Schema(example = "Passport information")
+ public String description;
+ @Schema(example = "0")
+ public Integer position;
+ }
+
@Schema(description = "GetLoansLoanIdTransactionsTemplateResponse")
public static final class GetLoansLoanIdTransactionsTemplateResponse {
@@ -110,6 +124,8 @@ final class LoanTransactionsApiResourceSwagger {
public List<GetPaymentTypeOptions> paymentTypeOptions;
@Schema(example = "200.000000")
public Double netDisbursalAmount;
+
+ public List<GetCodeValuesDataResponse> classificationOptions;
}
public static final class GetLoanCurrency {
@@ -263,6 +279,7 @@ final class LoanTransactionsApiResourceSwagger {
public Set<GetLoanTransactionRelation> transactionRelations;
public Set<GetLoansLoanIdLoanChargePaidByData> loanChargePaidByList;
public PaymentDetailData paymentDetailData;
+ public GetCodeValuesDataResponse classification;
static final class PaymentDetailData {
@@ -354,6 +371,8 @@ final class LoanTransactionsApiResourceSwagger {
// command=reAge END
@Schema(description = "Optional. Controls whether Interest Refund
transaction should be created for this refund. If not provided, loan product
config is used.", example = "false")
public Boolean interestRefundCalculation;
+ @Schema(example = "1")
+ public Long classificationId;
}
@Schema(description = "PostLoansLoanIdTransactionsResponse")
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index 0e669052f1..c0a4ab510a 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -113,6 +113,8 @@ public class LoanTransactionData implements Serializable {
private List<LoanTransactionRelationData> transactionRelations;
private Collection<CodeValueData> chargeOffReasonOptions = null;
+ private Collection<CodeValueData> classificationOptions = null;
+ private CodeValueData classification;
public static LoanTransactionData importInstance(BigDecimal
repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
Integer rowIndex, String locale, String dateFormat) {
@@ -162,10 +164,10 @@ public class LoanTransactionData implements Serializable {
public static LoanTransactionData
loanTransactionDataForCreditTemplate(final LoanTransactionEnumData
transactionType,
final LocalDate transactionDate, final BigDecimal
transactionAmount, final Collection<PaymentTypeData> paymentOptions,
- final CurrencyData currency) {
+ final CurrencyData currency, List<CodeValueData>
classificationOptions) {
return
builder().type(transactionType).date(transactionDate).amount(transactionAmount).paymentTypeOptions(paymentOptions)
.currency(currency).externalLoanId(ExternalId.empty()).externalId(ExternalId.empty()).reversalExternalId(ExternalId.empty())
- .manuallyReversed(false).build();
+
.manuallyReversed(false).classificationOptions(classificationOptions).build();
}
public static LoanTransactionData
loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData
transactionType,
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 327e491ac9..230d3f3dde 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -39,6 +39,7 @@ import java.util.Set;
import java.util.function.Predicate;
import lombok.Getter;
import lombok.Setter;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -144,6 +145,11 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom<Long
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch =
FetchType.LAZY, mappedBy = "loanTransaction")
private LoanReAgeParameter loanReAgeParameter;
+ @Setter
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "classification_cv_id")
+ private CodeValue classification;
+
protected LoanTransaction() {}
public static LoanTransaction incomePosting(final Loan loan, final Office
office, final LocalDate dateOf, final BigDecimal amount,
@@ -312,6 +318,7 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom<Long
if (LoanTransactionType.REAGE.equals(loanTransaction.getTypeOf())) {
newTransaction.setLoanReAgeParameter(loanTransaction.getLoanReAgeParameter().getCopy(newTransaction));
}
+ newTransaction.setClassification(loanTransaction.getClassification());
return newTransaction;
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
index be938e810d..57eb2498fc 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
@@ -33,6 +33,7 @@ public interface LoanTransactionMapper {
@Mapping(target = "loanRepaymentScheduleInstallments", ignore = true)
@Mapping(target = "writeOffReasonOptions", ignore = true)
@Mapping(target = "chargeOffReasonOptions", ignore = true)
+ @Mapping(target = "classificationOptions", ignore = true)
@Mapping(target = "paymentTypeOptions", ignore = true)
@Mapping(target = "overpaymentPortion", ignore = true)
@Mapping(target = "transfer", ignore = true)
@@ -63,5 +64,6 @@ public interface LoanTransactionMapper {
@Mapping(target = "bankNumber", ignore = true)
@Mapping(target = "accountId", ignore = true)
@Mapping(target = "transactionAmount", ignore = true)
+ @Mapping(target = "classification", expression =
"java(loanTransaction.getClassification() != null ?
loanTransaction.getClassification().toData() : null)")
LoanTransactionData mapLoanTransaction(LoanTransaction loanTransaction);
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
index d3592f8a0d..43527f9a33 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
@@ -96,4 +96,6 @@ public interface LoanTransactionValidator {
void validateReversalExternalId(DataValidatorBuilder baseDataValidator,
JsonElement element);
void validateManualInterestRefundTransaction(String json);
+
+ void validateClassificationCodeValue(String codeName, Long
transactionClassificationId, DataValidatorBuilder baseDataValidator);
}
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index b805e53b7f..97127b644c 100644
---
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -54,4 +54,5 @@
<include relativeToChangelogFile="true"
file="parts/1029_add_installment_amount_in_multiples_of_to_loan.xml"/>
<include relativeToChangelogFile="true"
file="parts/1030_add_loan_undo_contract_termination_event.xml"/>
<include relativeToChangelogFile="true"
file="parts/1031_loan_merchant_buy_down_fee.xml"/>
+ <include relativeToChangelogFile="true"
file="parts/1032_add_classification_to_loan_transaction.xml"/>
</databaseChangeLog>
diff --git
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
new file mode 100644
index 0000000000..2b4e8e91b7
--- /dev/null
+++
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
@@ -0,0 +1,41 @@
+<?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 author="fineract" id="1">
+ <addColumn tableName="m_loan_transaction">
+ <column defaultValueComputed="NULL" name="classification_cv_id"
type="BIGINT"/>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <insert tableName="m_code">
+ <column name="code_name"
value="capitalized_income_transaction_classification"/>
+ <column name="is_system_defined" valueBoolean="true"/>
+ </insert>
+ <insert tableName="m_code">
+ <column name="code_name"
value="buydown_fee_transaction_classification"/>
+ <column name="is_system_defined" valueBoolean="true"/>
+ </insert>
+ </changeSet>
+</databaseChangeLog>
diff --git
a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
index 9b690341f2..4ee6a17f0f 100644
---
a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
+++
b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
@@ -280,10 +280,11 @@ public class LoanTransactionDataTest {
BigDecimal transactionAmount = new BigDecimal("3000.00");
Collection<PaymentTypeData> paymentOptions = mock(Collection.class);
CurrencyData currency = new CurrencyData("USD", "US Dollar", 2, 0,
"$", "USD");
+ List<CodeValueData> classificationOptions = mock(List.class);
// When
LoanTransactionData result =
LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType,
transactionDate,
- transactionAmount, paymentOptions, currency);
+ transactionAmount, paymentOptions, currency,
classificationOptions);
// Then
assertEquals(transactionType, result.getType());
@@ -295,6 +296,7 @@ public class LoanTransactionDataTest {
assertEquals(ExternalId.empty(), result.getExternalId());
assertEquals(ExternalId.empty(), result.getExternalLoanId());
assertEquals(ExternalId.empty(), result.getReversalExternalId());
+ assertEquals(classificationOptions, result.getClassificationOptions());
}
@Test
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
index 2a623ae506..35c850fcd9 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -40,6 +41,7 @@ import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -65,6 +67,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements
BuyDownFeePlatformSer
private final ExternalIdFactory externalIdFactory;
private final LoanBuyDownFeeBalanceRepository
loanBuyDownFeeBalanceRepository;
private final BusinessEventNotifierService businessEventNotifierService;
+ private final CodeValueRepository codeValueRepository;
@Transactional
@Override
@@ -93,6 +96,9 @@ public class BuyDownFeeWritePlatformServiceImpl implements
BuyDownFeePlatformSer
// Add to loan (NO schedule recalculation as per requirements)
loan.addLoanTransaction(buyDownFeeTransaction);
+ // Add Loan Transaction classification
+ addClassificationCodeToTransaction(command,
LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE,
buyDownFeeTransaction);
+
// Save transaction
loanTransactionRepository.saveAndFlush(buyDownFeeTransaction);
@@ -150,6 +156,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements
BuyDownFeePlatformSer
buyDownFeeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(buyDownFeeAdjustment,
originalBuyDownFee.get(),
LoanTransactionRelationTypeEnum.ADJUSTMENT));
+ // Inherit from the target transaction the classification
+
buyDownFeeAdjustment.setClassification(originalBuyDownFee.get().getClassification());
// Add transaction to loan
loan.addLoanTransaction(buyDownFeeAdjustment);
@@ -203,4 +211,12 @@ public class BuyDownFeeWritePlatformServiceImpl implements
BuyDownFeePlatformSer
buyDownFeeBalance.setUnrecognizedAmount(buyDownFeeTransaction.getAmount());
loanBuyDownFeeBalanceRepository.saveAndFlush(buyDownFeeBalance);
}
+
+ private void addClassificationCodeToTransaction(final JsonCommand command,
final String codeName, LoanTransaction loanTransaction) {
+ final Long transactionClassificationId = command
+
.longValueOfParameterNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME);
+ if (transactionClassificationId != null) {
+
loanTransaction.setClassification(codeValueRepository.findByCodeNameAndId(codeName,
transactionClassificationId));
+ }
+ }
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
index fe4112a1c3..431f01b196 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -37,6 +38,7 @@ import
org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeTransactionCreatedBusinessEvent;
import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.monetary.domain.Money;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
@@ -66,6 +68,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl
implements CapitalizedInc
private final LoanBalanceService loanBalanceService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
private final BusinessEventNotifierService businessEventNotifierService;
+ private final CodeValueRepository codeValueRepository;
@Transactional
@Override
@@ -86,6 +89,9 @@ public class CapitalizedIncomeWritePlatformServiceImpl
implements CapitalizedInc
transactionDate, txnExternalId);
// Update loan with capitalized income
loan.addLoanTransaction(capitalizedIncomeTransaction);
+ // Add Loan Transaction classification
+ addClassificationCodeToTransaction(command,
LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE,
+ capitalizedIncomeTransaction);
// Recalculate loan transactions
recalculateLoanTransactions(loan, transactionDate,
capitalizedIncomeTransaction);
// Save and flush (PK is set)
@@ -137,6 +143,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl
implements CapitalizedInc
Money.of(loan.getCurrency(), transactionAmount),
paymentDetail, transactionDate, txnExternalId);
capitalizedIncomeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(capitalizedIncomeAdjustment,
capitalizedIncome.get(),
LoanTransactionRelationTypeEnum.ADJUSTMENT));
+
capitalizedIncomeAdjustment.setClassification(capitalizedIncome.get().getClassification());
loan.addLoanTransaction(capitalizedIncomeAdjustment);
recalculateLoanTransactions(loan, transactionDate,
capitalizedIncomeAdjustment);
LoanTransaction savedCapitalizedIncomeAdjustment =
loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment);
@@ -205,4 +212,11 @@ public class CapitalizedIncomeWritePlatformServiceImpl
implements CapitalizedInc
capitalizedIncomeBalanceRepository.saveAndFlush(capitalizedIncomeBalance);
}
+ private void addClassificationCodeToTransaction(final JsonCommand command,
final String codeName, LoanTransaction loanTransaction) {
+ final Long transactionClassificationId = command
+
.longValueOfParameterNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME);
+ if (transactionClassificationId != null) {
+
loanTransaction.setClassification(codeValueRepository.findByCodeNameAndId(codeName,
transactionClassificationId));
+ }
+ }
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
index 51688ae080..085904d2cd 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
@@ -45,6 +45,7 @@ import
org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.common.service.Validator;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
@@ -149,6 +150,11 @@ public class ProgressiveLoanTransactionValidatorImpl
implements ProgressiveLoanT
}
}
+ final Long transactionClassificationId = fromApiJsonHelper
+
.extractLongNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME,
element);
+
loanTransactionValidator.validateClassificationCodeValue(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE,
+ transactionClassificationId, baseDataValidator);
+
validatePaymentDetails(baseDataValidator, element);
validateNote(baseDataValidator, element);
validateExternalId(baseDataValidator, element);
@@ -276,7 +282,8 @@ public class ProgressiveLoanTransactionValidatorImpl
implements ProgressiveLoanT
}
private static final List<String>
BUY_DOWN_FEE_TRANSACTION_SUPPORTED_PARAMETERS = List
- .of(new String[] { "transactionDate", "dateFormat", "locale",
"transactionAmount", "paymentTypeId", "note", "externalId" });
+ .of(new String[] { "transactionDate", "dateFormat", "locale",
"transactionAmount", "paymentTypeId", "note", "externalId",
+
LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME });
@Override
public void validateBuyDownFee(JsonCommand command, Long loanId) {
@@ -313,6 +320,11 @@ public class ProgressiveLoanTransactionValidatorImpl
implements ProgressiveLoanT
final BigDecimal transactionAmount =
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount",
element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+ final Long transactionClassificationId = fromApiJsonHelper
+
.extractLongNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME,
element);
+
loanTransactionValidator.validateClassificationCodeValue(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE,
+ transactionClassificationId, baseDataValidator);
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -576,9 +588,15 @@ public class ProgressiveLoanTransactionValidatorImpl
implements ProgressiveLoanT
loanTransactionValidator.validateManualInterestRefundTransaction(json);
}
+ @Override
+ public void validateClassificationCodeValue(final String codeName, final
Long transactionClassificationId,
+ DataValidatorBuilder baseDataValidator) {
+ loanTransactionValidator.validateClassificationCodeValue(codeName,
transactionClassificationId, baseDataValidator);
+ }
+
private Set<String> getCapitalizedIncomeParameters() {
- return new HashSet<>(
- Arrays.asList("transactionDate", "dateFormat", "locale",
"transactionAmount", "paymentTypeId", "note", "externalId"));
+ return new HashSet<>(Arrays.asList("transactionDate", "dateFormat",
"locale", "transactionAmount", "paymentTypeId", "note",
+ "externalId",
LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME));
}
private Set<String> getCapitalizedIncomeAdjustmentParameters() {
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
index 291b02b8f5..eb70e364ed 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.starter;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -57,11 +58,12 @@ public class ProgressiveLoanAccountConfiguration {
NoteWritePlatformService noteWritePlatformService,
ExternalIdFactory externalIdFactory,
LoanCapitalizedIncomeBalanceRepository
capitalizedIncomeBalanceRepository,
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanBalanceService loanBalanceService,
- LoanLifecycleStateMachine loanLifecycleStateMachine,
BusinessEventNotifierService businessEventNotifierService) {
+ LoanLifecycleStateMachine loanLifecycleStateMachine,
BusinessEventNotifierService businessEventNotifierService,
+ CodeValueRepository codeValueRepository) {
return new
CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator,
loanAssembler, loanTransactionRepository,
paymentDetailWritePlatformService, journalEntryPoster,
noteWritePlatformService, externalIdFactory,
capitalizedIncomeBalanceRepository,
reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
- businessEventNotifierService);
+ businessEventNotifierService, codeValueRepository);
}
@Bean
@@ -89,4 +91,5 @@ public class ProgressiveLoanAccountConfiguration {
LoanCapitalizedIncomeBalanceRepository
loanCapitalizedIncomeBalanceRepository) {
return new CapitalizedIncomeBalanceReadServiceImpl(loanRepository,
loanCapitalizedIncomeBalanceRepository);
}
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
index 9fc63d8683..409bf3bf10 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
@@ -123,6 +123,23 @@ public class CodesApiResource {
return this.toApiJsonSerializer.serialize(settings, code,
RESPONSE_DATA_PARAMETERS);
}
+ @GET
+ @Path("name/{codeName}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Code", description = "Returns the details
of a Code.\n" + "\n" + "Example Requests:\n" + "\n"
+ + "codes/1")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
CodesApiResourceSwagger.GetCodesResponse.class))) })
+ public String retrieveCodeByName(@PathParam("codeName")
@Parameter(description = "codeName") final String codeName,
+ @Context final UriInfo uriInfo) {
+
+ final CodeData code = this.readPlatformService.retriveCode(codeName);
+
+ final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, code,
RESPONSE_DATA_PARAMETERS);
+ }
+
@PUT
@Path("{codeId}")
@Consumes({ MediaType.APPLICATION_JSON })
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 22ed1e9110..f8c45fcc53 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -93,7 +93,7 @@ public class LoanTransactionsApiResource {
public static final String CAPITALIZED_INCOME = "capitalizedIncome";
public static final String INTEREST_REFUND_COMMAND_VALUE =
"interest-refund";
private final Set<String> responseDataParameters = new
HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount",
"externalId",
- LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME,
LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
+ LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME,
LoanApiConstants.REVERSED_ON_DATE_PARAMNAME, "classification"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
index f75e69daf4..42ff8fefcd 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
+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.data.DataValidatorBuilder;
@@ -67,6 +69,7 @@ import org.apache.fineract.portfolio.common.service.Validator;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -107,6 +110,7 @@ public final class LoanTransactionValidatorImpl implements
LoanTransactionValida
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanDownPaymentTransactionValidator
loanDownPaymentTransactionValidator;
private final LoanDisbursementValidator loanDisbursementValidator;
+ private final CodeValueRepository codeValueRepository;
private void throwExceptionIfValidationWarningsExist(final
List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
@@ -1120,4 +1124,18 @@ public final class LoanTransactionValidatorImpl
implements LoanTransactionValida
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
+
+ @Override
+ public void validateClassificationCodeValue(final String codeName, final
Long transactionClassificationId,
+ DataValidatorBuilder baseDataValidator) {
+
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
+
.value(transactionClassificationId).ignoreIfNull().positiveAmount();
+ if (transactionClassificationId != null) {
+ final CodeValue codeValue =
codeValueRepository.findByCodeNameAndId(codeName, transactionClassificationId);
+ if (codeValue == null) {
+
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
+ .failWithCode("code.value.classification.not.exists",
"Code value does not exists in the code " + codeName);
+ }
+ }
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index f9e609b251..44a6badd49 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -94,6 +94,7 @@ import
org.apache.fineract.portfolio.group.data.GroupGeneralData;
import org.apache.fineract.portfolio.group.data.GroupRoleData;
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import
org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
@@ -485,6 +486,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
LoanTransactionData loanTransactionData = null;
Collection<PaymentTypeData> paymentOptions = null;
+ List<CodeValueData> classificationOptions = null;
BigDecimal transactionAmount = BigDecimal.ZERO;
switch (transactionType) {
case CAPITALIZED_INCOME:
@@ -499,15 +501,19 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
: loan.getApprovedPrincipal();
transactionAmount =
transactionAmount.subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
paymentOptions =
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+ classificationOptions = this.codeValueReadPlatformService
+
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
- paymentOptions, retriveLoanCurrencyData(loanId));
+ paymentOptions, retriveLoanCurrencyData(loanId),
classificationOptions);
break;
case BUY_DOWN_FEE:
paymentOptions =
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+ classificationOptions = this.codeValueReadPlatformService
+
.retrieveCodeValuesByCode(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
- paymentOptions, retriveLoanCurrencyData(loanId));
+ paymentOptions, retriveLoanCurrencyData(loanId),
classificationOptions);
break;
case CAPITALIZED_INCOME_ADJUSTMENT:
final LoanCapitalizedIncomeBalance
loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
@@ -518,7 +524,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
.subtract(MathUtil.nullToZero(loanCapitalizedIncomeBalance.getAmountAdjustment()));
loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
- paymentOptions, retriveLoanCurrencyData(loanId));
+ paymentOptions, retriveLoanCurrencyData(loanId),
classificationOptions);
break;
case BUY_DOWN_FEE_ADJUSTMENT:
final LoanBuyDownFeeBalance loanBuyDownFeeBalance =
loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
@@ -528,7 +534,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
:
loanBuyDownFeeBalance.getAmount().subtract(MathUtil.nullToZero(loanBuyDownFeeBalance.getAmountAdjustment()));
loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
- paymentOptions, retriveLoanCurrencyData(loanId));
+ paymentOptions, retriveLoanCurrencyData(loanId),
classificationOptions);
break;
default:
loanTransactionData =
LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId),
@@ -1667,13 +1673,15 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
+ " fromtran.transaction_date as fromTransferDate,
fromtran.amount as fromTransferAmount,"
+ " fromtran.description as fromTransferDescription, "
+ " totran.id as toTransferId, totran.is_reversed as
toTransferReversed, "
- + " totran.transaction_date as toTransferDate,
totran.amount as toTransferAmount,"
+ + " totran.transaction_date as toTransferDate,
totran.amount as toTransferAmount, "
+ + " clcv.id as classCodeId, clcv.code_value as
classCodeValue, "
+ " totran.description as toTransferDescription from
m_loan l join m_loan_transaction tr on tr.loan_id = l.id "
+ " join m_currency rc on rc." +
sqlGenerator.escape("code") + " = l.currency_code "
+ " left JOIN m_payment_detail pd ON tr.payment_detail_id
= pd.id"
+ " left join m_payment_type pt on pd.payment_type_id =
pt.id left join m_office office on office.id=tr.office_id"
+ " left join m_account_transfer_transaction fromtran on
fromtran.from_loan_transaction_id = tr.id "
- + " left join m_account_transfer_transaction totran on
totran.to_loan_transaction_id = tr.id ";
+ + " left join m_account_transfer_transaction totran on
totran.to_loan_transaction_id = tr.id "
+ + " left join m_code_value clcv on clcv.id =
tr.classification_cv_id ";
}
@Override
@@ -1700,6 +1708,8 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
PaymentDetailData paymentDetailData = null;
+ final CodeValueData classificationData =
CodeValueData.instance(rs.getLong("classCodeId"),
rs.getString("classCodeValue"));
+
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
if (paymentTypeId != null) {
final String typeName = rs.getString("paymentTypeName");
@@ -1759,7 +1769,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
.overpaymentPortion(overPaymentPortion).unrecognizedIncomePortion(unrecognizedIncomePortion).externalId(externalId)
.transfer(transfer).outstandingLoanBalance(outstandingLoanBalance).submittedOnDate(submittedOnDate)
.manuallyReversed(manuallyReversed).reversalExternalId(reversalExternalId).reversedOnDate(reversedOnDate).loanId(loanId)
- .externalLoanId(externalLoanId).build();
+
.externalLoanId(externalLoanId).classification(classificationData).build();
}
}
@@ -2491,8 +2501,9 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
final Collection<PaymentTypeData> paymentTypeOptions =
paymentTypeReadPlatformService.retrieveAllPaymentTypes();
final LoanTransactionEnumData transactionType =
LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
- return
LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType,
targetTxn.getTransactionDate(),
- interestRefundAmount, paymentTypeOptions,
loan.getCurrency().toData());
+ return
LoanTransactionData.builder().transactionType(LoanTransactionType.INTEREST_REFUND.name()).type(transactionType)
+
.date(targetTxn.getTransactionDate()).amount(interestRefundAmount).paymentTypeOptions(paymentTypeOptions)
+ .currency(loan.getCurrency().toData()).build();
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 7a1e2f2637..c55a94cfdc 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.starter;
import org.apache.fineract.cob.service.LoanAccountLockService;
import
org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import
org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import
org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -391,10 +392,11 @@ public class LoanAccountConfiguration {
LoanAssembler loanAssembler, LoanTransactionRepository
loanTransactionRepository,
PaymentDetailWritePlatformService
paymentDetailWritePlatformService, LoanJournalEntryPoster
loanJournalEntryPoster,
NoteWritePlatformService noteWritePlatformService,
ExternalIdFactory externalIdFactory,
- LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
BusinessEventNotifierService businessEventNotifierService) {
+ LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
BusinessEventNotifierService businessEventNotifierService,
+ CodeValueRepository codeValueRepository) {
return new
BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler,
loanTransactionRepository,
paymentDetailWritePlatformService, loanJournalEntryPoster,
noteWritePlatformService, externalIdFactory,
- loanBuyDownFeeBalanceRepository, businessEventNotifierService);
+ loanBuyDownFeeBalanceRepository, businessEventNotifierService,
codeValueRepository);
}
@Bean
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
index 9c95110d24..e0e1ef1ee0 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
@@ -29,10 +29,14 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.fineract.client.models.CapitalizedIncomeDetails;
+import org.apache.fineract.client.models.GetCodesResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.LoanCapitalizedIncomeData;
import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCodeValueDataResponse;
+import org.apache.fineract.client.models.PostCodeValuesDataRequest;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
@@ -45,6 +49,7 @@ import org.apache.fineract.integrationtests.common.Utils;
import
org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent;
import
org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent;
import
org.apache.fineract.integrationtests.common.externalevents.LoanTransactionBusinessEvent;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -185,6 +190,11 @@ public class LoanCapitalizedIncomeTest extends
BaseLoanIntegrationTest {
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
+ final GetCodesResponse code =
codeHelper.retrieveCodeByName(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
+ final PostCodeValueDataResponse classificationCode =
codeHelper.createCodeValue(code.getId(),
+ new
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_",
6)).isActive(true).position(10));
+ final Long classificationId = classificationCode.getSubResourceId();
+
runAt("1 April 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(),
loanProductsResponse.getResourceId(), "1 January 2024",
500.0, 7.0, 3, null);
@@ -192,9 +202,15 @@ public class LoanCapitalizedIncomeTest extends
BaseLoanIntegrationTest {
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
PostLoansLoanIdTransactionsResponse capitalizedIncomeResponse =
loanTransactionHelper.addCapitalizedIncome(loanId,
- "1 January 2024", 50.0);
+ "1 January 2024", 50.0, classificationId);
capitalizedIncomeIdRef.set(capitalizedIncomeResponse.getResourceId());
+ // Validate Loan Transaction classification set value
+ GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails
= loanTransactionHelper.getLoanTransactionDetails(loanId,
+ capitalizedIncomeIdRef.get());
+ assertNotNull(transactionDetails.getClassification());
+ assertEquals(classificationId,
transactionDetails.getClassification().getId());
+
PostLoansLoanIdTransactionsResponse
capitalizedIncomeAdjustmentResponse = loanTransactionHelper
.capitalizedIncomeAdjustment(loanId,
capitalizedIncomeIdRef.get(), "1 April 2024", 50.0);
assertNotNull(capitalizedIncomeAdjustmentResponse.getLoanId());
@@ -222,6 +238,12 @@ public class LoanCapitalizedIncomeTest extends
BaseLoanIntegrationTest {
journalEntry(49.71, loansReceivableAccount, "CREDIT"), //
journalEntry(0.29, interestReceivableAccount, "CREDIT") //
);
+
+ // Validate Loan Transaction classification Inherit
+ transactionDetails =
loanTransactionHelper.getLoanTransactionDetails(loanId,
+ capitalizedIncomeAdjustmentResponse.getResourceId());
+ assertNotNull(transactionDetails.getClassification());
+ assertEquals(classificationId,
transactionDetails.getClassification().getId());
});
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
index 7e24d12c5e..df505ccc2c 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
@@ -26,15 +26,20 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetCodesResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
import
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCodeValueDataResponse;
+import org.apache.fineract.client.models.PostCodeValuesDataRequest;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.TransactionType;
import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -103,6 +108,9 @@ public class LoanTransactionTest extends
BaseLoanIntegrationTest {
@Test
public void
testGetLoanTransactionTemplateForCapitalizedIncomeWithOverAppliedAmount() {
final PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+ final GetCodesResponse code =
codeHelper.retrieveCodeByName(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
+ final PostCodeValueDataResponse classificationCode =
codeHelper.createCodeValue(code.getId(),
+ new
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_",
6)).isActive(true).position(10));
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
@@ -127,6 +135,7 @@ public class LoanTransactionTest extends
BaseLoanIntegrationTest {
assertEquals("loanTransactionType." + capitalizedIncomeCommand,
transactionTemplate.getType().getCode());
assertEquals(transactionTemplate.getAmount(), 415);
assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
+ assertThat(transactionTemplate.getClassificationOptions().size() >
0);
});
}
@@ -242,6 +251,9 @@ public class LoanTransactionTest extends
BaseLoanIntegrationTest {
@Test
public void testGetLoanTransactionTemplateForBuyDownFee() {
final PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+ final GetCodesResponse code =
codeHelper.retrieveCodeByName(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
+ final PostCodeValueDataResponse classificationCode =
codeHelper.createCodeValue(code.getId(),
+ new
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_",
6)).isActive(true).position(10));
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
@@ -266,6 +278,7 @@ public class LoanTransactionTest extends
BaseLoanIntegrationTest {
assertEquals("loanTransactionType." + buyDownFeeCommand,
transactionTemplate.getType().getCode());
assertEquals(transactionTemplate.getAmount(), 0);
assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
+ assertThat(transactionTemplate.getClassificationOptions().size() >
0);
});
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 39a75fcce7..276713ad53 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -930,6 +930,12 @@ public class LoanTransactionHelper {
.transactionDate(transactionDate).dateFormat("dd MMMM
yyyy").locale("en"));
}
+ public PostLoansLoanIdTransactionsResponse addCapitalizedIncome(final Long
loanId, final String transactionDate, final double amount,
+ final Long classificationId) {
+ return addCapitalizedIncome(loanId, new
PostLoansLoanIdTransactionsRequest().transactionAmount(amount)
+ .transactionDate(transactionDate).dateFormat("dd MMMM
yyyy").locale("en").classificationId(classificationId));
+ }
+
public Response<CommandProcessingResult> createInterestPause(Long loanId,
String startDate, String endDate) {
log.info("Creating interest pause for Loan {} from {} to {}", loanId,
startDate, endDate);
return
Calls.executeU(FineractClientHelper.getFineractClient().loanInterestPauseApi.createInterestPause(loanId,
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 a7951c169c..fe063ec80f 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
@@ -388,4 +388,8 @@ public final class CodeHelper {
public List<GetCodesResponse> retrieveCodes() {
return
Calls.ok(FineractClientHelper.getFineractClient().codes.retrieveCodes());
}
+
+ public GetCodesResponse retrieveCodeByName(final String codeName) {
+ return
Calls.ok(FineractClientHelper.getFineractClient().codes.retrieveCodeByName(codeName));
+ }
}