FINERACT-2113: Advanced Charge-off Expense Accounting - Add new "Advanced Accounting Rule"
This commit is contained in:
committed by
Adam Saghy
parent
75fa740ff6
commit
4ccb33a95e
+4
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment,repaymentStartDateType\n\n"
|
||||
+ "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, chargeOffReasonsToExpenseMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment,repaymentStartDateType\n\n"
|
||||
+ "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());
|
||||
|
||||
+62
-1
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
+40
@@ -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>
|
||||
Reference in New Issue
Block a user