FINERACT-2347: Introduce classification field on loan transactions

This commit is contained in:
Jose Alberto Hernandez
2025-09-03 11:43:06 +02:00
committed by Adam Saghy
parent 9ba17bf79a
commit 65538495aa
27 changed files with 442 additions and 21 deletions
@@ -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";
}
@@ -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")
@@ -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,
@@ -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;
}
@@ -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);
}
@@ -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);
}
@@ -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>
@@ -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>
@@ -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