FINERACT-2232: Capitalized income - Add Capitalized income to Loan
This commit is contained in:
+5
@@ -42,6 +42,11 @@ public class AcmeNoteWritePlatformService implements NoteWritePlatformService, I
|
||||
throw new UnsupportedOperationException("createNote() is not yet implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLoanNote(Long loanId, String note) {
|
||||
throw new UnsupportedOperationException("createLoanNote() is not yet implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult updateNote(JsonCommand command) {
|
||||
throw new UnsupportedOperationException("updateNote() is not yet implemented.");
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.common.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
|
||||
public final class Validator {
|
||||
|
||||
private Validator() {}
|
||||
|
||||
public static void validateOrThrow(String resource, Consumer<DataValidatorBuilder> baseDataValidator) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(resource);
|
||||
|
||||
baseDataValidator.accept(dataValidatorBuilder);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -26,6 +26,8 @@ public interface NoteWritePlatformService {
|
||||
|
||||
CommandProcessingResult createNote(JsonCommand command);
|
||||
|
||||
void createLoanNote(Long loanId, String note);
|
||||
|
||||
CommandProcessingResult updateNote(JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteNote(JsonCommand command);
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ public enum TransactionType {
|
||||
CHARGE_OFF("chargeOff"), //
|
||||
CHARGE_ADJUSTMENT("chargeAdjustment"), //
|
||||
INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), //
|
||||
REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"); //
|
||||
REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"), //
|
||||
CAPITALIZED_INCOME("capitalizedIncome"),;
|
||||
|
||||
public final String value;
|
||||
|
||||
|
||||
+22
-10
@@ -21,19 +21,31 @@ package org.apache.fineract.test.data.accounttype;
|
||||
public enum DefaultAccountType implements AccountType {
|
||||
|
||||
// Asset
|
||||
LOANS_RECEIVABLE("Loans Receivable"), INTEREST_FEE_RECEIVABLE("Interest/Fee Receivable"), OTHER_RECEIVABLES(
|
||||
"Other Receivables"), UNC_RECEIVABLE("UNC Receivable"), FUND_RECEIVABLES(
|
||||
"Fund Receivables"), TRANSFER_IN_SUSPENSE_ACCOUNT("Transfer in suspense account"), ASSET_TRANSFER("Asset transfer"),
|
||||
LOANS_RECEIVABLE("Loans Receivable"), //
|
||||
INTEREST_FEE_RECEIVABLE("Interest/Fee Receivable"), //
|
||||
OTHER_RECEIVABLES("Other Receivables"), //
|
||||
UNC_RECEIVABLE("UNC Receivable"), //
|
||||
FUND_RECEIVABLES("Fund Receivables"), //
|
||||
TRANSFER_IN_SUSPENSE_ACCOUNT("Transfer in suspense account"), //
|
||||
ASSET_TRANSFER("Asset transfer"), //
|
||||
// Income
|
||||
DEFERRED_INTEREST_REVENUE("Deferred Interest Revenue"), RETAINED_EARNINGS_PRIOR_YEAR("Retained Earnings Prior Year"), INTEREST_INCOME(
|
||||
"Interest Income"), FEE_INCOME("Fee Income"), FEE_CHARGE_OFF(
|
||||
"Fee Charge Off"), RECOVERIES("Recoveries"), INTEREST_INCOME_CHARGE_OFF("Interest Income Charge Off"),
|
||||
DEFERRED_INTEREST_REVENUE("Deferred Interest Revenue"), //
|
||||
RETAINED_EARNINGS_PRIOR_YEAR("Retained Earnings Prior Year"), //
|
||||
INTEREST_INCOME("Interest Income"), //
|
||||
FEE_INCOME("Fee Income"), //
|
||||
FEE_CHARGE_OFF("Fee Charge Off"), //
|
||||
RECOVERIES("Recoveries"), //
|
||||
INTEREST_INCOME_CHARGE_OFF("Interest Income Charge Off"), //
|
||||
// Liability
|
||||
AA_SUSPENSE_BALANCE("AA Suspense Balance"), SUSPENSE_CLEARING_ACCOUNT("Suspense/Clearing account"), OVERPAYMENT_ACCOUNT(
|
||||
"Overpayment account"),
|
||||
AA_SUSPENSE_BALANCE("AA Suspense Balance"), //
|
||||
SUSPENSE_CLEARING_ACCOUNT("Suspense/Clearing account"), //
|
||||
OVERPAYMENT_ACCOUNT("Overpayment account"), //
|
||||
DEFERRED_CAPITALIZED_INCOME("Deferred Capitalized Income"), //
|
||||
// Expense
|
||||
CREDIT_LOSS_BAD_DEBT("Credit Loss/Bad Debt"), CREDIT_LOSS_BAD_DEBT_FRAUD("Credit Loss/Bad Debt-Fraud"), GOODWILL_EXPENSE_ACCOUNT(
|
||||
"Goodwill Expense Account"), WRITTEN_OFF("Written off");
|
||||
CREDIT_LOSS_BAD_DEBT("Credit Loss/Bad Debt"), //
|
||||
CREDIT_LOSS_BAD_DEBT_FRAUD("Credit Loss/Bad Debt-Fraud"), //
|
||||
GOODWILL_EXPENSE_ACCOUNT("Goodwill Expense Account"), //
|
||||
WRITTEN_OFF("Written off"); //
|
||||
|
||||
private final String customName;
|
||||
|
||||
|
||||
+1
-1
@@ -1613,7 +1613,7 @@ public class LoanProductsRequestFactory {
|
||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
|
||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
|
||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST)
|
||||
.deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))
|
||||
.deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.DEFERRED_CAPITALIZED_INCOME))
|
||||
.incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))
|
||||
.enableIncomeCapitalization(true);//
|
||||
}
|
||||
|
||||
+5
@@ -59,6 +59,7 @@ public class GLGlobalInitializerStep implements FineractGlobalInitializerStep {
|
||||
public static final String GLA_NAME_19 = "Goodwill Expense Account";
|
||||
public static final String GLA_NAME_20 = "Interest Income Charge Off";
|
||||
public static final String GLA_NAME_21 = "Asset transfer";
|
||||
public static final String GLA_NAME_22 = "Deferred Capitalized Income";
|
||||
public static final String GLA_GL_CODE_1 = "112601";
|
||||
public static final String GLA_GL_CODE_2 = "112603";
|
||||
public static final String GLA_GL_CODE_3 = "145800";
|
||||
@@ -80,6 +81,7 @@ public class GLGlobalInitializerStep implements FineractGlobalInitializerStep {
|
||||
public static final String GLA_GL_CODE_19 = "744003";
|
||||
public static final String GLA_GL_CODE_20 = "404001";
|
||||
public static final String GLA_GL_CODE_21 = "146000";
|
||||
public static final String GLA_GL_CODE_22 = "145024";
|
||||
|
||||
private final GeneralLedgerAccountApi glaApi;
|
||||
|
||||
@@ -149,5 +151,8 @@ public class GLGlobalInitializerStep implements FineractGlobalInitializerStep {
|
||||
PostGLAccountsRequest postGLAccountsRequest21 = GLAccountRequestFactory.defaultGLAccountRequest(GLA_NAME_21, GLA_GL_CODE_21,
|
||||
GLA_TYPE_ASSET, GLA_USAGE_DETAIL, true);
|
||||
glaApi.createGLAccount1(postGLAccountsRequest21).execute();
|
||||
PostGLAccountsRequest postGLAccountsRequest22 = GLAccountRequestFactory.defaultGLAccountRequest(GLA_NAME_22, GLA_GL_CODE_22,
|
||||
GLA_TYPE_LIABILITY, GLA_USAGE_DETAIL, true);
|
||||
glaApi.createGLAccount1(postGLAccountsRequest22).execute();
|
||||
}
|
||||
}
|
||||
|
||||
+28
-28
@@ -1024,34 +1024,6 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION,
|
||||
responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculation);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + interest refund with
|
||||
// Merchant issued and Payment refund + interest recalculation + Multidisbursement
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB)
|
||||
String name97 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB
|
||||
.getName();
|
||||
PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculationMultiDisb = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2Emi()//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.preClosureInterestCalculationStrategy(1)//
|
||||
.rescheduleStrategyMethod(4)//
|
||||
.interestRecalculationCompoundingMethod(0)//
|
||||
.recalculationRestFrequencyType(2)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.name(name97)//
|
||||
.multiDisburseLoan(true)//
|
||||
.disallowExpectedDisbursements(true)//
|
||||
.maxTrancheCount(10)//
|
||||
.outstandingLoanBalance(10000.0)//
|
||||
.supportedInterestRefundTypes(Arrays.asList("MERCHANT_ISSUED_REFUND", "PAYOUT_REFUND"))//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT")));//
|
||||
Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculationMultidisb = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculationMultiDisb)
|
||||
.execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB,
|
||||
responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculationMultidisb);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement + downpayment +
|
||||
// interest recalculation
|
||||
// 25%, auto disabled
|
||||
@@ -2421,6 +2393,34 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
||||
.createLoanProduct(loanProductsRequestLP2ProgressiveAdvPaymentCapitalizedIncome).execute();
|
||||
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_CAPITALIZED_INCOME,
|
||||
responseLoanProductsRequestLP2ProgressiveAdvPaymentCapitalizedIncome);
|
||||
|
||||
// LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + interest refund with
|
||||
// Merchant issued and Payment refund + interest recalculation + Multidisbursement
|
||||
// (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB)
|
||||
String name98 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB
|
||||
.getName();
|
||||
PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculationMultiDisb = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2Emi()//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.preClosureInterestCalculationStrategy(1)//
|
||||
.rescheduleStrategyMethod(4)//
|
||||
.interestRecalculationCompoundingMethod(0)//
|
||||
.recalculationRestFrequencyType(2)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.name(name98)//
|
||||
.multiDisburseLoan(true)//
|
||||
.disallowExpectedDisbursements(true)//
|
||||
.maxTrancheCount(10)//
|
||||
.outstandingLoanBalance(10000.0)//
|
||||
.supportedInterestRefundTypes(Arrays.asList("MERCHANT_ISSUED_REFUND", "PAYOUT_REFUND"))//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT")));//
|
||||
Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculationMultidisb = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculationMultiDisb)
|
||||
.execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION_MULTIDISB,
|
||||
responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculationMultidisb);
|
||||
}
|
||||
|
||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@CapitalizedIncomeFeature
|
||||
Feature: Capitalized Income
|
||||
|
||||
@TestRailId:C3635
|
||||
Scenario: As a user I want to add capitalized income to a progressive loan after disbursement
|
||||
When Admin sets the business date to "1 January 2024"
|
||||
And Admin creates a client with random data
|
||||
@@ -18,6 +19,6 @@ Feature: Capitalized Income
|
||||
| 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 |
|
||||
Then 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 Income | | 100.0 |
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | | 100.0 |
|
||||
|
||||
+2
-17
@@ -26,20 +26,17 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.common.service.Validator;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||
@@ -156,7 +153,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
private void validateInterestPauseDates(Loan loan, LocalDate startDate, LocalDate endDate, String dateFormat, String locale,
|
||||
Long currentVariationId) {
|
||||
|
||||
validateOrThrow(baseDataValidator -> {
|
||||
Validator.validateOrThrow("InterestPause", baseDataValidator -> {
|
||||
baseDataValidator.reset().parameter("startDate").value(startDate).notBlank();
|
||||
baseDataValidator.reset().parameter("endDate").value(endDate).notBlank();
|
||||
baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
|
||||
@@ -223,16 +220,4 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
||||
e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOrThrow(Consumer<DataValidatorBuilder> baseDataValidator) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource("InterestPause");
|
||||
|
||||
baseDataValidator.accept(dataValidatorBuilder);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-5
@@ -1124,12 +1124,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
public BigDecimal getDisbursedAmount() {
|
||||
BigDecimal principal = BigDecimal.ZERO;
|
||||
for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) {
|
||||
if (disbursementDetail.actualDisbursementDate() != null) {
|
||||
principal = principal.add(disbursementDetail.principal());
|
||||
if (isMultiDisburmentLoan()) {
|
||||
for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) {
|
||||
if (disbursementDetail.actualDisbursementDate() != null) {
|
||||
principal = principal.add(disbursementDetail.principal());
|
||||
}
|
||||
}
|
||||
return principal;
|
||||
} else {
|
||||
return getNetDisbursalAmount();
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
public void removeDisbursementDetail() {
|
||||
@@ -2066,7 +2070,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
Money outstanding = Money.zero(getCurrency());
|
||||
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
|
||||
for (LoanTransaction loanTransaction : loanTransactions) {
|
||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting() || loanTransaction.isCapitalizedIncome()) {
|
||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
||||
|
||||
+6
-2
@@ -308,8 +308,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
|
||||
public static LoanTransaction capitalizedIncome(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
|
||||
final LocalDate transactionDate, final ExternalId externalId) {
|
||||
return new LoanTransaction(null, loan.getOffice(), LoanTransactionType.CAPITALIZED_INCOME, paymentDetail, amount.getAmount(),
|
||||
transactionDate, externalId);
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CAPITALIZED_INCOME, transactionDate, amount.getAmount(),
|
||||
amount.getAmount(), null, null, null, null, false, paymentDetail, externalId);
|
||||
}
|
||||
|
||||
public LoanTransaction copyTransactionPropertiesAndMappings() {
|
||||
@@ -632,6 +632,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isCapitalizedIncome() {
|
||||
return LoanTransactionType.CAPITALIZED_INCOME.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isWaiver() {
|
||||
return isInterestWaiver() || isChargesWaiver();
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
public interface LoanTransactionValidator {
|
||||
|
||||
void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId);
|
||||
|
||||
void validateUndoChargeOff(String json);
|
||||
|
||||
void validateTransaction(String json);
|
||||
|
||||
void validateChargebackTransaction(String json);
|
||||
|
||||
void validateNewRepaymentTransaction(String json);
|
||||
|
||||
void validateTransactionWithNoAmount(String json);
|
||||
|
||||
void validateChargeOffTransaction(String json);
|
||||
|
||||
void validateUpdateOfLoanOfficer(String json);
|
||||
|
||||
void validateForBulkLoanReassignment(String json);
|
||||
|
||||
void validateMarkAsFraudLoan(String json);
|
||||
|
||||
void validateUpdateDisbursementDateAndAmount(String json, LoanDisbursementDetails loanDisbursementDetails);
|
||||
|
||||
void validateNewRefundTransaction(String json);
|
||||
|
||||
void validateLoanForeclosure(String json);
|
||||
|
||||
void validateLoanClientIsActive(Loan loan);
|
||||
|
||||
void validateLoanGroupIsActive(Loan loan);
|
||||
|
||||
void validateActivityNotBeforeLastTransactionDate(Loan loan, LocalDate activityDate, LoanEvent event);
|
||||
|
||||
void validateRepaymentDateIsOnNonWorkingDay(LocalDate repaymentDate, WorkingDays workingDays, boolean allowTransactionsOnNonWorkingDay);
|
||||
|
||||
void validateRepaymentDateIsOnHoliday(LocalDate repaymentDate, boolean allowTransactionsOnHoliday, List<Holiday> holidays);
|
||||
|
||||
void validateLoanTransactionInterestPaymentWaiver(JsonCommand command);
|
||||
|
||||
void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan);
|
||||
|
||||
void validateRefund(String json);
|
||||
|
||||
void validateRefund(Loan loan, LoanTransactionType loanTransactionType, LocalDate transactionDate,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO);
|
||||
|
||||
void validateRefundDateIsAfterLastRepayment(Loan loan, LocalDate refundTransactionDate);
|
||||
|
||||
void validateActivityNotBeforeClientOrGroupTransferDate(Loan loan, LoanEvent event, LocalDate activityDate);
|
||||
|
||||
void validatePaymentDetails(DataValidatorBuilder baseDataValidator, JsonElement element);
|
||||
|
||||
void validateIfTransactionIsChargeback(LoanTransaction chargebackTransaction);
|
||||
|
||||
void validateLoanRescheduleDate(Loan loan);
|
||||
|
||||
void validateNote(DataValidatorBuilder baseDataValidator, JsonElement element);
|
||||
|
||||
void validateExternalId(DataValidatorBuilder baseDataValidator, JsonElement element);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
public interface LoanJournalEntryPoster {
|
||||
|
||||
void postJournalEntries(Loan loan, List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds);
|
||||
}
|
||||
-2
@@ -125,6 +125,4 @@ public interface LoanWritePlatformService {
|
||||
CommandProcessingResult undoChargeOff(JsonCommand command);
|
||||
|
||||
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command);
|
||||
|
||||
CommandProcessingResult addCapitalizedIncome(Long loanId, JsonCommand command);
|
||||
}
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomePlatformService;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -35,7 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@CommandType(entity = "LOAN", action = "CAPITALIZEDINCOME")
|
||||
public class AddCapitalizedIncomeCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
private final CapitalizedIncomePlatformService capitalizedIncomePlatformService;
|
||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||
|
||||
@Transactional
|
||||
@@ -43,7 +43,7 @@ public class AddCapitalizedIncomeCommandHandler implements NewCommandSourceHandl
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
return this.writePlatformService.addCapitalizedIncome(command.getLoanId(), command);
|
||||
return this.capitalizedIncomePlatformService.addCapitalizedIncome(command.getLoanId(), command);
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.capitalized.income",
|
||||
"Capitalized Income");
|
||||
+2
-1
@@ -16,9 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||
package org.apache.fineract.portfolio.loanaccount.repository;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
public interface CapitalizedIncomePlatformService {
|
||||
|
||||
@Transactional
|
||||
CommandProcessingResult addCapitalizedIncome(Long loanId, JsonCommand command);
|
||||
|
||||
void resetBalance(Long loanId);
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedIncomePlatformService {
|
||||
|
||||
private final ProgressiveLoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanAssembler loanAssembler;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
private final LoanJournalEntryPoster journalEntryPoster;
|
||||
private final NoteWritePlatformService noteWritePlatformService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult addCapitalizedIncome(final Long loanId, final JsonCommand command) {
|
||||
loanTransactionValidator.validateCapitalizedIncome(command, loanId);
|
||||
final Loan loan = loanAssembler.assembleFrom(loanId);
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
// Create payment details
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
// Extract transaction details
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
|
||||
|
||||
// Create capitalized income transaction
|
||||
final Money capitalizedIncomeAmount = calculateCapitalizedIncomeAmount(loan, transactionAmount);
|
||||
final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail,
|
||||
transactionDate, txnExternalId);
|
||||
// TODO: Transaction reverse-replay + loan repayment schedule update (follow-up story)
|
||||
// Update loan with capitalized income
|
||||
loan.addLoanTransaction(capitalizedIncomeTransaction);
|
||||
// Create capitalized income balances
|
||||
createCapitalizedIncomeBalance(capitalizedIncomeTransaction);
|
||||
// Save and flush (PK is set)
|
||||
loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction);
|
||||
// Update loan counters and save
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
// Update outstanding loan balances
|
||||
loan.updateLoanOutstandingBalances();
|
||||
// Create a note if provided
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (noteText != null && !noteText.isEmpty()) {
|
||||
noteWritePlatformService.createLoanNote(loanId, noteText);
|
||||
}
|
||||
// Post journal entries
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(loan.getId()).withEntityExternalId(loan.getExternalId()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBalance(final Long loanId) {
|
||||
capitalizedIncomeBalanceRepository
|
||||
.delete((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("loan").get("id"), loanId));
|
||||
}
|
||||
|
||||
private Money calculateCapitalizedIncomeAmount(final Loan loan, final BigDecimal transactionAmount) {
|
||||
return switch (loan.getLoanProductRelatedDetail().getCapitalizedIncomeCalculationType()) {
|
||||
case FLAT -> Money.of(loan.getCurrency(), transactionAmount);
|
||||
};
|
||||
}
|
||||
|
||||
private void createCapitalizedIncomeBalance(final LoanTransaction capitalizedIncomeTransaction) {
|
||||
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = new LoanCapitalizedIncomeBalance();
|
||||
capitalizedIncomeBalance.setLoan(capitalizedIncomeTransaction.getLoan());
|
||||
capitalizedIncomeBalance.setLoanTransaction(capitalizedIncomeTransaction);
|
||||
capitalizedIncomeBalance.setDate(capitalizedIncomeTransaction.getTransactionDate());
|
||||
capitalizedIncomeBalance.setAmount(capitalizedIncomeTransaction.getAmount());
|
||||
capitalizedIncomeBalance.setUnrecognizedAmount(capitalizedIncomeTransaction.getAmount());
|
||||
capitalizedIncomeBalanceRepository.save(capitalizedIncomeBalance);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("progressiveLoanStatusChangePlatformService")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProgressiveLoanStatusChangePlatformServiceImpl {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final CapitalizedIncomePlatformService capitalizedIncomeWritePlatformService;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, new LoanStatusChangedListener());
|
||||
}
|
||||
|
||||
private final class LoanStatusChangedListener implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
log.debug("Loan Status change for loan {}", loan.getId());
|
||||
if (!event.getOldStatus().isSubmittedAndPendingApproval() && loan.isApproved()) {
|
||||
capitalizedIncomeWritePlatformService.resetBalance(loan.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
|
||||
public interface ProgressiveLoanTransactionValidator extends LoanTransactionValidator {
|
||||
|
||||
void validateCapitalizedIncome(JsonCommand command, Long loanId);
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
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.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanTransactionValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
|
||||
@Override
|
||||
public void validateCapitalizedIncome(final JsonCommand command, final Long loanId) {
|
||||
final String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getCapitalizedIncomeParameters());
|
||||
|
||||
Validator.validateOrThrow("loan.capitalized.income", baseDataValidator -> {
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
validateLoanClientIsActive(loan);
|
||||
validateLoanGroupIsActive(loan);
|
||||
|
||||
// Validate that loan is disbursed
|
||||
if (!loan.isDisbursed()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("capitalized.income.only.after.disbursement",
|
||||
"Capitalized income can be added to the loan only after Disbursement");
|
||||
}
|
||||
|
||||
// Validate loan is progressive
|
||||
if (!loan.isProgressiveSchedule()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.progressive.loan");
|
||||
}
|
||||
|
||||
// Validate income capitalization is enabled
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("income.capitalization.not.enabled");
|
||||
}
|
||||
|
||||
// Validate loan is active
|
||||
if (!loan.getStatus().isActive()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.active");
|
||||
}
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
// Validate transaction date is not before disbursement date
|
||||
if (transactionDate != null && loan.getDisbursementDate() != null && transactionDate.isBefore(loan.getDisbursementDate())) {
|
||||
baseDataValidator.reset().parameter("transactionDate").failWithCode("before.disbursement.date",
|
||||
"Transaction date cannot be before disbursement date");
|
||||
}
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate total disbursement + capitalized income <= approved amount
|
||||
if (transactionAmount != null) {
|
||||
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
final BigDecimal existingCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
|
||||
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
final BigDecimal approvedAmount = loan.getApprovedPrincipal();
|
||||
final BigDecimal newTotal = totalDisbursed.add(existingCapitalizedIncomeBalance).add(transactionAmount);
|
||||
|
||||
if (newTotal.compareTo(approvedAmount) > 0) {
|
||||
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
|
||||
"Sum of disbursed amount and capitalized income cannot exceed approved amount");
|
||||
}
|
||||
}
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
validateNote(baseDataValidator, element);
|
||||
validateExternalId(baseDataValidator, element);
|
||||
});
|
||||
}
|
||||
|
||||
// Delegates
|
||||
@Override
|
||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||
loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUndoChargeOff(String json) {
|
||||
loanTransactionValidator.validateUndoChargeOff(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransaction(String json) {
|
||||
loanTransactionValidator.validateTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargebackTransaction(String json) {
|
||||
loanTransactionValidator.validateChargebackTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRepaymentTransaction(String json) {
|
||||
loanTransactionValidator.validateNewRepaymentTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransactionWithNoAmount(String json) {
|
||||
loanTransactionValidator.validateTransactionWithNoAmount(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargeOffTransaction(String json) {
|
||||
loanTransactionValidator.validateChargeOffTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateOfLoanOfficer(String json) {
|
||||
loanTransactionValidator.validateUpdateOfLoanOfficer(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateForBulkLoanReassignment(String json) {
|
||||
loanTransactionValidator.validateForBulkLoanReassignment(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateMarkAsFraudLoan(String json) {
|
||||
loanTransactionValidator.validateMarkAsFraudLoan(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateDisbursementDateAndAmount(String json, LoanDisbursementDetails loanDisbursementDetails) {
|
||||
loanTransactionValidator.validateUpdateDisbursementDateAndAmount(json, loanDisbursementDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRefundTransaction(String json) {
|
||||
loanTransactionValidator.validateNewRefundTransaction(json);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanForeclosure(String json) {
|
||||
loanTransactionValidator.validateLoanForeclosure(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanClientIsActive(Loan loan) {
|
||||
loanTransactionValidator.validateLoanClientIsActive(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanGroupIsActive(Loan loan) {
|
||||
loanTransactionValidator.validateLoanGroupIsActive(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeLastTransactionDate(Loan loan, LocalDate activityDate, LoanEvent event) {
|
||||
loanTransactionValidator.validateActivityNotBeforeLastTransactionDate(loan, activityDate, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnNonWorkingDay(LocalDate repaymentDate, WorkingDays workingDays,
|
||||
boolean allowTransactionsOnNonWorkingDay) {
|
||||
loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(repaymentDate, workingDays, allowTransactionsOnNonWorkingDay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnHoliday(LocalDate repaymentDate, boolean allowTransactionsOnHoliday, List<Holiday> holidays) {
|
||||
loanTransactionValidator.validateRepaymentDateIsOnHoliday(repaymentDate, allowTransactionsOnHoliday, holidays);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiver(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiverAfterRecalculation(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(String json) {
|
||||
loanTransactionValidator.validateRefund(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(Loan loan, LoanTransactionType loanTransactionType, LocalDate transactionDate,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
loanTransactionValidator.validateRefund(loan, loanTransactionType, transactionDate, scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefundDateIsAfterLastRepayment(Loan loan, LocalDate refundTransactionDate) {
|
||||
loanTransactionValidator.validateRefundDateIsAfterLastRepayment(loan, refundTransactionDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeClientOrGroupTransferDate(Loan loan, LoanEvent event, LocalDate activityDate) {
|
||||
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, event, activityDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validatePaymentDetails(DataValidatorBuilder baseDataValidator, JsonElement element) {
|
||||
loanTransactionValidator.validatePaymentDetails(baseDataValidator, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateIfTransactionIsChargeback(LoanTransaction chargebackTransaction) {
|
||||
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanRescheduleDate(Loan loan) {
|
||||
loanTransactionValidator.validateLoanRescheduleDate(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNote(DataValidatorBuilder baseDataValidator, JsonElement element) {
|
||||
loanTransactionValidator.validateNote(baseDataValidator, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateExternalId(DataValidatorBuilder baseDataValidator, JsonElement element) {
|
||||
loanTransactionValidator.validateExternalId(baseDataValidator, element);
|
||||
}
|
||||
|
||||
private Set<String> getCapitalizedIncomeParameters() {
|
||||
return new HashSet<>(
|
||||
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl;
|
||||
import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ProgressiveLoanAccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CapitalizedIncomePlatformService.class)
|
||||
public CapitalizedIncomePlatformService capitalizedIncomePlatformService(ProgressiveLoanTransactionValidator loanTransactionValidator,
|
||||
LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository,
|
||||
PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster journalEntryPoster,
|
||||
NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory,
|
||||
LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository) {
|
||||
return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
|
||||
paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory,
|
||||
capitalizedIncomeBalanceRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ProgressiveLoanTransactionValidator.class)
|
||||
public ProgressiveLoanTransactionValidator progressiveLoanTransactionValidator(FromJsonHelper fromApiJsonHelper,
|
||||
LoanTransactionValidator loanTransactionValidator, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository) {
|
||||
return new ProgressiveLoanTransactionValidatorImpl(fromApiJsonHelper, loanTransactionValidator, loanRepositoryWrapper,
|
||||
loanCapitalizedIncomeBalanceRepository);
|
||||
}
|
||||
}
|
||||
+10
-22
@@ -34,7 +34,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -86,6 +85,7 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManag
|
||||
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.common.service.Validator;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
|
||||
@@ -265,7 +265,7 @@ public final class LoanApplicationValidator {
|
||||
final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;
|
||||
|
||||
validateClientOrGroup(client, group, productId);
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
|
||||
|
||||
@@ -798,7 +798,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
private void fixedLengthValidations(final JsonElement element) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final String transactionProcessingStrategy = this.fromApiJsonHelper
|
||||
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
|
||||
final Integer numberOfRepayments = this.fromApiJsonHelper
|
||||
@@ -896,7 +896,7 @@ public final class LoanApplicationValidator {
|
||||
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
|
||||
}
|
||||
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
boolean atLeastOneParameterPassedForUpdate = false;
|
||||
|
||||
@@ -1487,7 +1487,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
private void validateClientOrGroup(Client client, Group group, Long productId) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
if (client == null && group == null) {
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(client).notNull();
|
||||
} else {
|
||||
@@ -1546,7 +1546,7 @@ public final class LoanApplicationValidator {
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
|
||||
|
||||
validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
||||
Validator.validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final String note = "note";
|
||||
@@ -1558,7 +1558,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
|
||||
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
|
||||
final String principalParameterName = LoanApiConstants.principalParameterName;
|
||||
@@ -1668,7 +1668,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
|
||||
});
|
||||
}
|
||||
@@ -1742,7 +1742,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
if (loan.getProposedPrincipal().compareTo(total) >= 0) {
|
||||
String errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
|
||||
@@ -1996,7 +1996,7 @@ public final class LoanApplicationValidator {
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
||||
|
||||
validateOrThrow("loanapplication", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loanapplication", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final BigDecimal principal = this.fromApiJsonHelper
|
||||
@@ -2174,18 +2174,6 @@ public final class LoanApplicationValidator {
|
||||
&& loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
|
||||
}
|
||||
|
||||
public static void validateOrThrow(String resource, Consumer<DataValidatorBuilder> baseDataValidator) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(resource);
|
||||
|
||||
baseDataValidator.accept(dataValidatorBuilder);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Long resolveOfficeId(Client client, Group group) {
|
||||
if (client != null) {
|
||||
return client.getOffice().getId();
|
||||
|
||||
+45
-91
@@ -63,14 +63,13 @@ import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateExc
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
|
||||
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.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
||||
@@ -94,9 +93,9 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Component("loanTransactionValidator")
|
||||
@AllArgsConstructor
|
||||
public final class LoanTransactionValidator {
|
||||
public final class LoanTransactionValidatorImpl implements LoanTransactionValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanApplicationValidator fromApiJsonDeserializer;
|
||||
@@ -107,7 +106,6 @@ public final class LoanTransactionValidator {
|
||||
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
@@ -116,6 +114,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||
String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -125,7 +124,7 @@ public final class LoanTransactionValidator {
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getDisbursementParameters(isAccountTransfer));
|
||||
|
||||
LoanApplicationValidator.validateOrThrow("loan.disbursement", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan.disbursement", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
||||
baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
|
||||
@@ -239,7 +238,7 @@ public final class LoanTransactionValidator {
|
||||
});
|
||||
}
|
||||
|
||||
public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
|
||||
private void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
|
||||
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
|
||||
@@ -285,7 +284,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
|
||||
private void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
|
||||
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
|
||||
if (null != calendarInstance) {
|
||||
final Calendar calendar = calendarInstance.getCalendar();
|
||||
@@ -299,6 +298,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUndoChargeOff(final String json) {
|
||||
if (!StringUtils.isBlank(json)) {
|
||||
final Set<String> transactionParameters = new HashSet<>(Arrays.asList(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME));
|
||||
@@ -320,6 +320,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -355,6 +356,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargebackTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -385,10 +387,12 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRepaymentTransaction(final String json) {
|
||||
validatePaymentTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransactionWithNoAmount(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -416,6 +420,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargeOffTransaction(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -446,6 +451,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateOfLoanOfficer(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -476,6 +482,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateForBulkLoanReassignment(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -504,6 +511,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateMarkAsFraudLoan(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return;
|
||||
@@ -525,6 +533,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -564,6 +573,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRefundTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -596,6 +606,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanForeclosure(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -625,6 +636,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanClientIsActive(final Loan loan) {
|
||||
final Client client = loan.client();
|
||||
if (client != null && client.isNotActive()) {
|
||||
@@ -632,6 +644,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanGroupIsActive(final Loan loan) {
|
||||
final Group group = loan.group();
|
||||
if (group != null && group.isNotActive()) {
|
||||
@@ -639,7 +652,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
|
||||
private void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
|
||||
String reversedOrCreated) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
if (txn.isChargeRefund() && DateUtils.isBefore(transactionDate, txn.getTransactionDate())) {
|
||||
@@ -651,7 +664,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanDisbursementIsBeforeTransactionDate(final Loan loan, final LocalDate transactionDate) {
|
||||
private void validateLoanDisbursementIsBeforeTransactionDate(final Loan loan, final LocalDate transactionDate) {
|
||||
if (DateUtils.isBefore(transactionDate, loan.getDisbursementDate())) {
|
||||
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
||||
+ loan.getDisbursementDate().toString();
|
||||
@@ -660,14 +673,14 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionShouldNotBeInTheFuture(final LocalDate transactionDate) {
|
||||
private void validateTransactionShouldNotBeInTheFuture(final LocalDate transactionDate) {
|
||||
if (DateUtils.isDateInTheFuture(transactionDate)) {
|
||||
final String errorMessage = "The transaction date cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, transactionDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasCurrency(final Loan loan) {
|
||||
private void validateLoanHasCurrency(final Loan loan) {
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency defaultApplicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
|
||||
if (defaultApplicationCurrency == null) {
|
||||
@@ -675,7 +688,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, LocalDate transactionDate) {
|
||||
private void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, LocalDate transactionDate) {
|
||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||
if (DateUtils.isBefore(transactionDate, clientOfficeJoiningDate)) {
|
||||
@@ -687,6 +700,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate, final LoanEvent event) {
|
||||
if (!(loan.isInterestBearingAndInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())
|
||||
|| !loan.getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
|
||||
@@ -720,6 +734,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||
final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||
@@ -728,6 +743,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
|
||||
final List<Holiday> holidays) {
|
||||
if (!allowTransactionsOnHoliday && HolidayUtil.isHoliday(repaymentDate, holidays)) {
|
||||
@@ -736,7 +752,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(Loan loan) {
|
||||
private void validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(Loan loan) {
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
@@ -748,6 +764,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
||||
final Long loanId = command.getLoanId();
|
||||
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||
@@ -770,6 +787,7 @@ public final class LoanTransactionValidator {
|
||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
||||
// Payment allocation calculates the new total principal repaid, and it should be validated after recalculation
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
@@ -784,10 +802,12 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(String json) {
|
||||
validatePaymentTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -803,7 +823,7 @@ public final class LoanTransactionValidator {
|
||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||
}
|
||||
|
||||
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
||||
private void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
||||
final LocalDate transactionDate) {
|
||||
if (loanTransactionType.isRepaymentType() && !loanTransactionType.isChargeRefund()) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
@@ -816,6 +836,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefundDateIsAfterLastRepayment(final Loan loan, final LocalDate refundTransactionDate) {
|
||||
final LocalDate possibleNextRefundDate = loan.possibleNextRefundDate();
|
||||
|
||||
@@ -824,6 +845,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeClientOrGroupTransferDate(final Loan loan, final LoanEvent event, final LocalDate activityDate) {
|
||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||
@@ -947,7 +969,8 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||
|
||||
@@ -1016,6 +1039,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateIfTransactionIsChargeback(final LoanTransaction chargebackTransaction) {
|
||||
if (!chargebackTransaction.isChargeback()) {
|
||||
final String errorMessage = "A transaction of type chargeback was expected but not received.";
|
||||
@@ -1023,6 +1047,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanRescheduleDate(final Loan loan) {
|
||||
if (DateUtils.isBefore(loan.getRescheduledOnDate(), loan.getDisbursementDate())) {
|
||||
final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: "
|
||||
@@ -1038,90 +1063,19 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateCapitalizedIncome(final JsonCommand command, final Long loanId) {
|
||||
final String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getCapitalizedIncomeParameters());
|
||||
|
||||
LoanApplicationValidator.validateOrThrow("loan.capitalized.income", baseDataValidator -> {
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
validateLoanClientIsActive(loan);
|
||||
validateLoanGroupIsActive(loan);
|
||||
|
||||
// Validate that loan is disbursed
|
||||
if (!loan.isDisbursed()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("capitalized.income.only.after.disbursement",
|
||||
"Capitalized income can be added to the loan only after Disbursement");
|
||||
}
|
||||
|
||||
// Validate loan is progressive
|
||||
if (!loan.isProgressiveSchedule()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.progressive.loan");
|
||||
}
|
||||
|
||||
// Validate income capitalization is enabled
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("income.capitalization.not.enabled");
|
||||
}
|
||||
|
||||
// Validate loan is active
|
||||
if (!loan.getStatus().isActive()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.active");
|
||||
}
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
// Validate transaction date is not before disbursement date
|
||||
if (transactionDate != null && loan.getDisbursementDate() != null && transactionDate.isBefore(loan.getDisbursementDate())) {
|
||||
baseDataValidator.reset().parameter("transactionDate").failWithCode("before.disbursement.date",
|
||||
"Transaction date cannot be before disbursement date");
|
||||
}
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate total disbursement + capitalized income <= approved amount
|
||||
if (transactionAmount != null) {
|
||||
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
final BigDecimal existingCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
|
||||
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
final BigDecimal approvedAmount = loan.getApprovedPrincipal();
|
||||
final BigDecimal newTotal = totalDisbursed.add(existingCapitalizedIncomeBalance).add(transactionAmount);
|
||||
|
||||
if (newTotal.compareTo(approvedAmount) > 0) {
|
||||
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
|
||||
"Sum of disbursed amount and capitalized income cannot exceed approved amount");
|
||||
}
|
||||
}
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
validateNote(baseDataValidator, element);
|
||||
validateExternalId(baseDataValidator, element);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||
if (StringUtils.isNotBlank(note)) {
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExternalId(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validateExternalId(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||
if (StringUtils.isNotBlank(externalId)) {
|
||||
baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getCapitalizedIncomeParameters() {
|
||||
return new HashSet<>(
|
||||
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -29,11 +29,12 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LoanJournalEntryPoster {
|
||||
public class LoanJournalEntryPosterImpl implements LoanJournalEntryPoster {
|
||||
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||
|
||||
@Override
|
||||
public void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
-47
@@ -2996,53 +2996,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult addCapitalizedIncome(final Long loanId, final JsonCommand command) {
|
||||
loanTransactionValidator.validateCapitalizedIncome(command, loanId);
|
||||
|
||||
final Loan loan = loanAssembler.assembleFrom(loanId);
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
|
||||
// Create payment details
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
|
||||
// Validate cash payment if applicable
|
||||
if (paymentDetail != null && paymentDetail.getPaymentType() != null && paymentDetail.getPaymentType().getIsCashPayment()) {
|
||||
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, loan.getCurrencyCode(), transactionAmount);
|
||||
}
|
||||
|
||||
// Extract transaction details
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
|
||||
|
||||
// Create capitalized income transaction
|
||||
final Money capitalizedIncomeAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail,
|
||||
transactionDate, txnExternalId);
|
||||
|
||||
// Update loan with capitalized income
|
||||
capitalizedIncomeTransaction.updateLoan(loan);
|
||||
loan.addLoanTransaction(capitalizedIncomeTransaction);
|
||||
loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction);
|
||||
|
||||
// Update loan counters and save
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
// Create note if provided
|
||||
createNote(loan, command, changes);
|
||||
|
||||
// Post journal entries
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(loan.getId()).withEntityExternalId(loan.getExternalId()).build();
|
||||
}
|
||||
|
||||
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
|
||||
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
|
||||
|
||||
|
||||
+8
@@ -203,6 +203,14 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLoanNote(final Long loanId, final String note) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final Note newNote = Note.loanNote(loan, note);
|
||||
|
||||
this.noteRepository.save(newNote);
|
||||
}
|
||||
|
||||
private String getResourceUrlFromCommand(JsonCommand command) {
|
||||
|
||||
final String resourceUrl;
|
||||
|
||||
+4
@@ -93,6 +93,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -354,7 +355,10 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
||||
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
|
||||
delinquencyEffectivePauseHelper, configurationDomainService);
|
||||
|
||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
|
||||
Loan loan = Mockito.spy(Loan.class);
|
||||
ReflectionTestUtils.setField(loan, "loanProduct", loanProduct);
|
||||
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
||||
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
||||
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
||||
|
||||
Reference in New Issue
Block a user