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.");
|
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
|
@Override
|
||||||
public CommandProcessingResult updateNote(JsonCommand command) {
|
public CommandProcessingResult updateNote(JsonCommand command) {
|
||||||
throw new UnsupportedOperationException("updateNote() is not yet implemented.");
|
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);
|
CommandProcessingResult createNote(JsonCommand command);
|
||||||
|
|
||||||
|
void createLoanNote(Long loanId, String note);
|
||||||
|
|
||||||
CommandProcessingResult updateNote(JsonCommand command);
|
CommandProcessingResult updateNote(JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult deleteNote(JsonCommand command);
|
CommandProcessingResult deleteNote(JsonCommand command);
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@ public enum TransactionType {
|
|||||||
CHARGE_OFF("chargeOff"), //
|
CHARGE_OFF("chargeOff"), //
|
||||||
CHARGE_ADJUSTMENT("chargeAdjustment"), //
|
CHARGE_ADJUSTMENT("chargeAdjustment"), //
|
||||||
INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), //
|
INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), //
|
||||||
REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"); //
|
REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"), //
|
||||||
|
CAPITALIZED_INCOME("capitalizedIncome"),;
|
||||||
|
|
||||||
public final String value;
|
public final String value;
|
||||||
|
|
||||||
|
|||||||
+22
-10
@@ -21,19 +21,31 @@ package org.apache.fineract.test.data.accounttype;
|
|||||||
public enum DefaultAccountType implements AccountType {
|
public enum DefaultAccountType implements AccountType {
|
||||||
|
|
||||||
// Asset
|
// Asset
|
||||||
LOANS_RECEIVABLE("Loans Receivable"), INTEREST_FEE_RECEIVABLE("Interest/Fee Receivable"), OTHER_RECEIVABLES(
|
LOANS_RECEIVABLE("Loans Receivable"), //
|
||||||
"Other Receivables"), UNC_RECEIVABLE("UNC Receivable"), FUND_RECEIVABLES(
|
INTEREST_FEE_RECEIVABLE("Interest/Fee Receivable"), //
|
||||||
"Fund Receivables"), TRANSFER_IN_SUSPENSE_ACCOUNT("Transfer in suspense account"), ASSET_TRANSFER("Asset transfer"),
|
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
|
// Income
|
||||||
DEFERRED_INTEREST_REVENUE("Deferred Interest Revenue"), RETAINED_EARNINGS_PRIOR_YEAR("Retained Earnings Prior Year"), INTEREST_INCOME(
|
DEFERRED_INTEREST_REVENUE("Deferred Interest Revenue"), //
|
||||||
"Interest Income"), FEE_INCOME("Fee Income"), FEE_CHARGE_OFF(
|
RETAINED_EARNINGS_PRIOR_YEAR("Retained Earnings Prior Year"), //
|
||||||
"Fee Charge Off"), RECOVERIES("Recoveries"), INTEREST_INCOME_CHARGE_OFF("Interest Income Charge Off"),
|
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
|
// Liability
|
||||||
AA_SUSPENSE_BALANCE("AA Suspense Balance"), SUSPENSE_CLEARING_ACCOUNT("Suspense/Clearing account"), OVERPAYMENT_ACCOUNT(
|
AA_SUSPENSE_BALANCE("AA Suspense Balance"), //
|
||||||
"Overpayment account"),
|
SUSPENSE_CLEARING_ACCOUNT("Suspense/Clearing account"), //
|
||||||
|
OVERPAYMENT_ACCOUNT("Overpayment account"), //
|
||||||
|
DEFERRED_CAPITALIZED_INCOME("Deferred Capitalized Income"), //
|
||||||
// Expense
|
// Expense
|
||||||
CREDIT_LOSS_BAD_DEBT("Credit Loss/Bad Debt"), CREDIT_LOSS_BAD_DEBT_FRAUD("Credit Loss/Bad Debt-Fraud"), GOODWILL_EXPENSE_ACCOUNT(
|
CREDIT_LOSS_BAD_DEBT("Credit Loss/Bad Debt"), //
|
||||||
"Goodwill Expense Account"), WRITTEN_OFF("Written off");
|
CREDIT_LOSS_BAD_DEBT_FRAUD("Credit Loss/Bad Debt-Fraud"), //
|
||||||
|
GOODWILL_EXPENSE_ACCOUNT("Goodwill Expense Account"), //
|
||||||
|
WRITTEN_OFF("Written off"); //
|
||||||
|
|
||||||
private final String customName;
|
private final String customName;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1613,7 +1613,7 @@ public class LoanProductsRequestFactory {
|
|||||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
|
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
|
||||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
|
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
|
||||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST)
|
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST)
|
||||||
.deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))
|
.deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.DEFERRED_CAPITALIZED_INCOME))
|
||||||
.incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))
|
.incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))
|
||||||
.enableIncomeCapitalization(true);//
|
.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_19 = "Goodwill Expense Account";
|
||||||
public static final String GLA_NAME_20 = "Interest Income Charge Off";
|
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_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_1 = "112601";
|
||||||
public static final String GLA_GL_CODE_2 = "112603";
|
public static final String GLA_GL_CODE_2 = "112603";
|
||||||
public static final String GLA_GL_CODE_3 = "145800";
|
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_19 = "744003";
|
||||||
public static final String GLA_GL_CODE_20 = "404001";
|
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_21 = "146000";
|
||||||
|
public static final String GLA_GL_CODE_22 = "145024";
|
||||||
|
|
||||||
private final GeneralLedgerAccountApi glaApi;
|
private final GeneralLedgerAccountApi glaApi;
|
||||||
|
|
||||||
@@ -149,5 +151,8 @@ public class GLGlobalInitializerStep implements FineractGlobalInitializerStep {
|
|||||||
PostGLAccountsRequest postGLAccountsRequest21 = GLAccountRequestFactory.defaultGLAccountRequest(GLA_NAME_21, GLA_GL_CODE_21,
|
PostGLAccountsRequest postGLAccountsRequest21 = GLAccountRequestFactory.defaultGLAccountRequest(GLA_NAME_21, GLA_GL_CODE_21,
|
||||||
GLA_TYPE_ASSET, GLA_USAGE_DETAIL, true);
|
GLA_TYPE_ASSET, GLA_USAGE_DETAIL, true);
|
||||||
glaApi.createGLAccount1(postGLAccountsRequest21).execute();
|
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,
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION,
|
||||||
responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculation);
|
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 +
|
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement + downpayment +
|
||||||
// interest recalculation
|
// interest recalculation
|
||||||
// 25%, auto disabled
|
// 25%, auto disabled
|
||||||
@@ -2421,6 +2393,34 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
.createLoanProduct(loanProductsRequestLP2ProgressiveAdvPaymentCapitalizedIncome).execute();
|
.createLoanProduct(loanProductsRequestLP2ProgressiveAdvPaymentCapitalizedIncome).execute();
|
||||||
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_CAPITALIZED_INCOME,
|
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_CAPITALIZED_INCOME,
|
||||||
responseLoanProductsRequestLP2ProgressiveAdvPaymentCapitalizedIncome);
|
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,
|
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@CapitalizedIncomeFeature
|
@CapitalizedIncomeFeature
|
||||||
Feature: Capitalized Income
|
Feature: Capitalized Income
|
||||||
|
|
||||||
|
@TestRailId:C3635
|
||||||
Scenario: As a user I want to add capitalized income to a progressive loan after disbursement
|
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"
|
When Admin sets the business date to "1 January 2024"
|
||||||
And Admin creates a client with random data
|
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 |
|
| 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 |
|
| 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:
|
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 |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||||
| LIABILITY | 145024 | Deferred Income | | 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.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import lombok.AllArgsConstructor;
|
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.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
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.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
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,
|
private void validateInterestPauseDates(Loan loan, LocalDate startDate, LocalDate endDate, String dateFormat, String locale,
|
||||||
Long currentVariationId) {
|
Long currentVariationId) {
|
||||||
|
|
||||||
validateOrThrow(baseDataValidator -> {
|
Validator.validateOrThrow("InterestPause", baseDataValidator -> {
|
||||||
baseDataValidator.reset().parameter("startDate").value(startDate).notBlank();
|
baseDataValidator.reset().parameter("startDate").value(startDate).notBlank();
|
||||||
baseDataValidator.reset().parameter("endDate").value(endDate).notBlank();
|
baseDataValidator.reset().parameter("endDate").value(endDate).notBlank();
|
||||||
baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
|
baseDataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
|
||||||
@@ -223,16 +220,4 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
|
|||||||
e.getMessage(), e);
|
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() {
|
public BigDecimal getDisbursedAmount() {
|
||||||
BigDecimal principal = BigDecimal.ZERO;
|
BigDecimal principal = BigDecimal.ZERO;
|
||||||
for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) {
|
if (isMultiDisburmentLoan()) {
|
||||||
if (disbursementDetail.actualDisbursementDate() != null) {
|
for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) {
|
||||||
principal = principal.add(disbursementDetail.principal());
|
if (disbursementDetail.actualDisbursementDate() != null) {
|
||||||
|
principal = principal.add(disbursementDetail.principal());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return principal;
|
||||||
|
} else {
|
||||||
|
return getNetDisbursalAmount();
|
||||||
}
|
}
|
||||||
return principal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeDisbursementDetail() {
|
public void removeDisbursementDetail() {
|
||||||
@@ -2066,7 +2070,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
Money outstanding = Money.zero(getCurrency());
|
Money outstanding = Money.zero(getCurrency());
|
||||||
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
|
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
|
||||||
for (LoanTransaction loanTransaction : loanTransactions) {
|
for (LoanTransaction loanTransaction : loanTransactions) {
|
||||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting() || loanTransaction.isCapitalizedIncome()) {
|
||||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
||||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||||
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
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,
|
public static LoanTransaction capitalizedIncome(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
|
||||||
final LocalDate transactionDate, final ExternalId externalId) {
|
final LocalDate transactionDate, final ExternalId externalId) {
|
||||||
return new LoanTransaction(null, loan.getOffice(), LoanTransactionType.CAPITALIZED_INCOME, paymentDetail, amount.getAmount(),
|
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CAPITALIZED_INCOME, transactionDate, amount.getAmount(),
|
||||||
transactionDate, externalId);
|
amount.getAmount(), null, null, null, null, false, paymentDetail, externalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanTransaction copyTransactionPropertiesAndMappings() {
|
public LoanTransaction copyTransactionPropertiesAndMappings() {
|
||||||
@@ -632,6 +632,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
|
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCapitalizedIncome() {
|
||||||
|
return LoanTransactionType.CAPITALIZED_INCOME.equals(getTypeOf()) && isNotReversed();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isWaiver() {
|
public boolean isWaiver() {
|
||||||
return isInterestWaiver() || isChargesWaiver();
|
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 undoChargeOff(JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, 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.DataIntegrityErrorHandler;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
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.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -35,7 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@CommandType(entity = "LOAN", action = "CAPITALIZEDINCOME")
|
@CommandType(entity = "LOAN", action = "CAPITALIZEDINCOME")
|
||||||
public class AddCapitalizedIncomeCommandHandler implements NewCommandSourceHandler {
|
public class AddCapitalizedIncomeCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final LoanWritePlatformService writePlatformService;
|
private final CapitalizedIncomePlatformService capitalizedIncomePlatformService;
|
||||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -43,7 +43,7 @@ public class AddCapitalizedIncomeCommandHandler implements NewCommandSourceHandl
|
|||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return this.writePlatformService.addCapitalizedIncome(command.getLoanId(), command);
|
return this.capitalizedIncomePlatformService.addCapitalizedIncome(command.getLoanId(), command);
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.capitalized.income",
|
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.capitalized.income",
|
||||||
"Capitalized Income");
|
"Capitalized Income");
|
||||||
+2
-1
@@ -16,9 +16,10 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.repository;
|
||||||
|
|
||||||
import java.util.List;
|
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.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
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.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.collateralmanagement.service.LoanCollateralAssembler;
|
||||||
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
|
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
|
||||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
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.Group;
|
||||||
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
|
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;
|
final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;
|
||||||
|
|
||||||
validateClientOrGroup(client, group, productId);
|
validateClientOrGroup(client, group, productId);
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
|
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
|
||||||
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
|
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
|
||||||
|
|
||||||
@@ -798,7 +798,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fixedLengthValidations(final JsonElement element) {
|
private void fixedLengthValidations(final JsonElement element) {
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
final String transactionProcessingStrategy = this.fromApiJsonHelper
|
final String transactionProcessingStrategy = this.fromApiJsonHelper
|
||||||
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
|
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
|
||||||
final Integer numberOfRepayments = this.fromApiJsonHelper
|
final Integer numberOfRepayments = this.fromApiJsonHelper
|
||||||
@@ -896,7 +896,7 @@ public final class LoanApplicationValidator {
|
|||||||
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
|
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
boolean atLeastOneParameterPassedForUpdate = false;
|
boolean atLeastOneParameterPassedForUpdate = false;
|
||||||
|
|
||||||
@@ -1487,7 +1487,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateClientOrGroup(Client client, Group group, Long productId) {
|
private void validateClientOrGroup(Client client, Group group, Long productId) {
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
if (client == null && group == null) {
|
if (client == null && group == null) {
|
||||||
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(client).notNull();
|
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(client).notNull();
|
||||||
} else {
|
} else {
|
||||||
@@ -1546,7 +1546,7 @@ public final class LoanApplicationValidator {
|
|||||||
}.getType();
|
}.getType();
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
|
||||||
|
|
||||||
validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
Validator.validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
final String note = "note";
|
final String note = "note";
|
||||||
@@ -1558,7 +1558,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
|
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
|
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
|
||||||
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
|
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
|
||||||
final String principalParameterName = LoanApiConstants.principalParameterName;
|
final String principalParameterName = LoanApiConstants.principalParameterName;
|
||||||
@@ -1668,7 +1668,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
|
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
|
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1742,7 +1742,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
|
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
|
||||||
validateOrThrow("loan", baseDataValidator -> {
|
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||||
if (loan.getProposedPrincipal().compareTo(total) >= 0) {
|
if (loan.getProposedPrincipal().compareTo(total) >= 0) {
|
||||||
String errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
|
String errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
|
||||||
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
|
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
|
||||||
@@ -1996,7 +1996,7 @@ public final class LoanApplicationValidator {
|
|||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
||||||
|
|
||||||
validateOrThrow("loanapplication", baseDataValidator -> {
|
Validator.validateOrThrow("loanapplication", baseDataValidator -> {
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
final BigDecimal principal = this.fromApiJsonHelper
|
final BigDecimal principal = this.fromApiJsonHelper
|
||||||
@@ -2174,18 +2174,6 @@ public final class LoanApplicationValidator {
|
|||||||
&& loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
|
&& 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) {
|
public Long resolveOfficeId(Client client, Group group) {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
return client.getOffice().getId();
|
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.domain.Client;
|
||||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||||
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
|
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.domain.Group;
|
||||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.LoanCollateralManagement;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
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.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component("loanTransactionValidator")
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public final class LoanTransactionValidator {
|
public final class LoanTransactionValidatorImpl implements LoanTransactionValidator {
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final LoanApplicationValidator fromApiJsonDeserializer;
|
private final LoanApplicationValidator fromApiJsonDeserializer;
|
||||||
@@ -107,7 +106,6 @@ public final class LoanTransactionValidator {
|
|||||||
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
|
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
|
||||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||||
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
|
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
|
||||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
@@ -116,6 +114,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||||
String json = command.json();
|
String json = command.json();
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -125,7 +124,7 @@ public final class LoanTransactionValidator {
|
|||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getDisbursementParameters(isAccountTransfer));
|
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 JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
||||||
baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
|
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 JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
|
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) {
|
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
|
||||||
if (null != calendarInstance) {
|
if (null != calendarInstance) {
|
||||||
final Calendar calendar = calendarInstance.getCalendar();
|
final Calendar calendar = calendarInstance.getCalendar();
|
||||||
@@ -299,6 +298,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateUndoChargeOff(final String json) {
|
public void validateUndoChargeOff(final String json) {
|
||||||
if (!StringUtils.isBlank(json)) {
|
if (!StringUtils.isBlank(json)) {
|
||||||
final Set<String> transactionParameters = new HashSet<>(Arrays.asList(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME));
|
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) {
|
public void validateTransaction(final String json) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -355,6 +356,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateChargebackTransaction(final String json) {
|
public void validateChargebackTransaction(final String json) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -385,10 +387,12 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateNewRepaymentTransaction(final String json) {
|
public void validateNewRepaymentTransaction(final String json) {
|
||||||
validatePaymentTransaction(json);
|
validatePaymentTransaction(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateTransactionWithNoAmount(final String json) {
|
public void validateTransactionWithNoAmount(final String json) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
throw new InvalidJsonException();
|
throw new InvalidJsonException();
|
||||||
@@ -416,6 +420,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateChargeOffTransaction(final String json) {
|
public void validateChargeOffTransaction(final String json) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
throw new InvalidJsonException();
|
throw new InvalidJsonException();
|
||||||
@@ -446,6 +451,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateUpdateOfLoanOfficer(final String json) {
|
public void validateUpdateOfLoanOfficer(final String json) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
throw new InvalidJsonException();
|
throw new InvalidJsonException();
|
||||||
@@ -476,6 +482,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateForBulkLoanReassignment(final String json) {
|
public void validateForBulkLoanReassignment(final String json) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
throw new InvalidJsonException();
|
throw new InvalidJsonException();
|
||||||
@@ -504,6 +511,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateMarkAsFraudLoan(final String json) {
|
public void validateMarkAsFraudLoan(final String json) {
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
return;
|
return;
|
||||||
@@ -525,6 +533,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
|
public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -564,6 +573,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateNewRefundTransaction(final String json) {
|
public void validateNewRefundTransaction(final String json) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -596,6 +606,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateLoanForeclosure(final String json) {
|
public void validateLoanForeclosure(final String json) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
@@ -625,6 +636,7 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateLoanClientIsActive(final Loan loan) {
|
public void validateLoanClientIsActive(final Loan loan) {
|
||||||
final Client client = loan.client();
|
final Client client = loan.client();
|
||||||
if (client != null && client.isNotActive()) {
|
if (client != null && client.isNotActive()) {
|
||||||
@@ -632,6 +644,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateLoanGroupIsActive(final Loan loan) {
|
public void validateLoanGroupIsActive(final Loan loan) {
|
||||||
final Group group = loan.group();
|
final Group group = loan.group();
|
||||||
if (group != null && group.isNotActive()) {
|
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) {
|
String reversedOrCreated) {
|
||||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||||
if (txn.isChargeRefund() && DateUtils.isBefore(transactionDate, txn.getTransactionDate())) {
|
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())) {
|
if (DateUtils.isBefore(transactionDate, loan.getDisbursementDate())) {
|
||||||
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
||||||
+ loan.getDisbursementDate().toString();
|
+ 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)) {
|
if (DateUtils.isDateInTheFuture(transactionDate)) {
|
||||||
final String errorMessage = "The transaction date cannot be in the future.";
|
final String errorMessage = "The transaction date cannot be in the future.";
|
||||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, transactionDate);
|
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();
|
MonetaryCurrency currency = loan.getCurrency();
|
||||||
final ApplicationCurrency defaultApplicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
|
final ApplicationCurrency defaultApplicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
|
||||||
if (defaultApplicationCurrency == null) {
|
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) {
|
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||||
if (DateUtils.isBefore(transactionDate, clientOfficeJoiningDate)) {
|
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) {
|
public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate, final LoanEvent event) {
|
||||||
if (!(loan.isInterestBearingAndInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())
|
if (!(loan.isInterestBearingAndInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())
|
||||||
|| !loan.getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
|
|| !loan.getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
|
||||||
@@ -720,6 +734,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||||
final boolean allowTransactionsOnNonWorkingDay) {
|
final boolean allowTransactionsOnNonWorkingDay) {
|
||||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||||
@@ -728,6 +743,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
|
public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
|
||||||
final List<Holiday> holidays) {
|
final List<Holiday> holidays) {
|
||||||
if (!allowTransactionsOnHoliday && HolidayUtil.isHoliday(repaymentDate, 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()) {
|
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||||
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||||
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||||
@@ -748,6 +764,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
||||||
final Long loanId = command.getLoanId();
|
final Long loanId = command.getLoanId();
|
||||||
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||||
@@ -770,6 +787,7 @@ public final class LoanTransactionValidator {
|
|||||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
||||||
// Payment allocation calculates the new total principal repaid, and it should be validated after recalculation
|
// Payment allocation calculates the new total principal repaid, and it should be validated after recalculation
|
||||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||||
@@ -784,10 +802,12 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateRefund(String json) {
|
public void validateRefund(String json) {
|
||||||
validatePaymentTransaction(json);
|
validatePaymentTransaction(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
|
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
|
||||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
@@ -803,7 +823,7 @@ public final class LoanTransactionValidator {
|
|||||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
private void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
||||||
final LocalDate transactionDate) {
|
final LocalDate transactionDate) {
|
||||||
if (loanTransactionType.isRepaymentType() && !loanTransactionType.isChargeRefund()) {
|
if (loanTransactionType.isRepaymentType() && !loanTransactionType.isChargeRefund()) {
|
||||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||||
@@ -816,6 +836,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateRefundDateIsAfterLastRepayment(final Loan loan, final LocalDate refundTransactionDate) {
|
public void validateRefundDateIsAfterLastRepayment(final Loan loan, final LocalDate refundTransactionDate) {
|
||||||
final LocalDate possibleNextRefundDate = loan.possibleNextRefundDate();
|
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) {
|
public void validateActivityNotBeforeClientOrGroupTransferDate(final Loan loan, final LoanEvent event, final LocalDate activityDate) {
|
||||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||||
@@ -947,7 +969,8 @@ public final class LoanTransactionValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
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
|
// Validate all string payment detail fields for max length
|
||||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||||
|
|
||||||
@@ -1016,6 +1039,7 @@ public final class LoanTransactionValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void validateIfTransactionIsChargeback(final LoanTransaction chargebackTransaction) {
|
public void validateIfTransactionIsChargeback(final LoanTransaction chargebackTransaction) {
|
||||||
if (!chargebackTransaction.isChargeback()) {
|
if (!chargebackTransaction.isChargeback()) {
|
||||||
final String errorMessage = "A transaction of type chargeback was expected but not received.";
|
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) {
|
public void validateLoanRescheduleDate(final Loan loan) {
|
||||||
if (DateUtils.isBefore(loan.getRescheduledOnDate(), loan.getDisbursementDate())) {
|
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: "
|
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) {
|
@Override
|
||||||
final String json = command.json();
|
public void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||||
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) {
|
|
||||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||||
if (StringUtils.isNotBlank(note)) {
|
if (StringUtils.isNotBlank(note)) {
|
||||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
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);
|
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||||
if (StringUtils.isNotBlank(externalId)) {
|
if (StringUtils.isNotBlank(externalId)) {
|
||||||
baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
|
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
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanJournalEntryPoster {
|
public class LoanJournalEntryPosterImpl implements LoanJournalEntryPoster {
|
||||||
|
|
||||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
public void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
||||||
final List<Long> existingReversedTransactionIds) {
|
final List<Long> existingReversedTransactionIds) {
|
||||||
final MonetaryCurrency currency = loan.getCurrency();
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
-47
@@ -2996,53 +2996,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.build();
|
.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) {
|
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
|
||||||
loanTransactionValidator.validateIfTransactionIsChargeback(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) {
|
private String getResourceUrlFromCommand(JsonCommand command) {
|
||||||
|
|
||||||
final String resourceUrl;
|
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.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
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.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -354,7 +355,10 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
|
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
|
||||||
delinquencyEffectivePauseHelper, configurationDomainService);
|
delinquencyEffectivePauseHelper, configurationDomainService);
|
||||||
|
|
||||||
|
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||||
|
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
|
||||||
Loan loan = Mockito.spy(Loan.class);
|
Loan loan = Mockito.spy(Loan.class);
|
||||||
|
ReflectionTestUtils.setField(loan, "loanProduct", loanProduct);
|
||||||
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
||||||
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
||||||
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user