FINERACT-2232: Capitalized income - Add Capitalized income to Loan
This commit is contained in:
+10
-22
@@ -34,7 +34,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -86,6 +85,7 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManag
|
||||
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.common.service.Validator;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
|
||||
@@ -265,7 +265,7 @@ public final class LoanApplicationValidator {
|
||||
final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;
|
||||
|
||||
validateClientOrGroup(client, group, productId);
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
|
||||
|
||||
@@ -798,7 +798,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
private void fixedLengthValidations(final JsonElement element) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final String transactionProcessingStrategy = this.fromApiJsonHelper
|
||||
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
|
||||
final Integer numberOfRepayments = this.fromApiJsonHelper
|
||||
@@ -896,7 +896,7 @@ public final class LoanApplicationValidator {
|
||||
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
|
||||
}
|
||||
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
boolean atLeastOneParameterPassedForUpdate = false;
|
||||
|
||||
@@ -1487,7 +1487,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
private void validateClientOrGroup(Client client, Group group, Long productId) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
if (client == null && group == null) {
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(client).notNull();
|
||||
} else {
|
||||
@@ -1546,7 +1546,7 @@ public final class LoanApplicationValidator {
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
|
||||
|
||||
validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
||||
Validator.validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final String note = "note";
|
||||
@@ -1558,7 +1558,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
|
||||
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
|
||||
final String principalParameterName = LoanApiConstants.principalParameterName;
|
||||
@@ -1668,7 +1668,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
|
||||
});
|
||||
}
|
||||
@@ -1742,7 +1742,7 @@ public final class LoanApplicationValidator {
|
||||
}
|
||||
|
||||
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
|
||||
validateOrThrow("loan", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan", baseDataValidator -> {
|
||||
if (loan.getProposedPrincipal().compareTo(total) >= 0) {
|
||||
String errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
|
||||
@@ -1996,7 +1996,7 @@ public final class LoanApplicationValidator {
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
||||
|
||||
validateOrThrow("loanapplication", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loanapplication", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final BigDecimal principal = this.fromApiJsonHelper
|
||||
@@ -2174,18 +2174,6 @@ public final class LoanApplicationValidator {
|
||||
&& loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
|
||||
}
|
||||
|
||||
public static void validateOrThrow(String resource, Consumer<DataValidatorBuilder> baseDataValidator) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(resource);
|
||||
|
||||
baseDataValidator.accept(dataValidatorBuilder);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Long resolveOfficeId(Client client, Group group) {
|
||||
if (client != null) {
|
||||
return client.getOffice().getId();
|
||||
|
||||
+45
-91
@@ -63,14 +63,13 @@ import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateExc
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
|
||||
import org.apache.fineract.portfolio.common.service.Validator;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
||||
@@ -94,9 +93,9 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Component("loanTransactionValidator")
|
||||
@AllArgsConstructor
|
||||
public final class LoanTransactionValidator {
|
||||
public final class LoanTransactionValidatorImpl implements LoanTransactionValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanApplicationValidator fromApiJsonDeserializer;
|
||||
@@ -107,7 +106,6 @@ public final class LoanTransactionValidator {
|
||||
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
@@ -116,6 +114,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||
String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -125,7 +124,7 @@ public final class LoanTransactionValidator {
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getDisbursementParameters(isAccountTransfer));
|
||||
|
||||
LoanApplicationValidator.validateOrThrow("loan.disbursement", baseDataValidator -> {
|
||||
Validator.validateOrThrow("loan.disbursement", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
||||
baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
|
||||
@@ -239,7 +238,7 @@ public final class LoanTransactionValidator {
|
||||
});
|
||||
}
|
||||
|
||||
public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
|
||||
private void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
|
||||
final JsonElement jsonElement = this.fromApiJsonHelper.parse(json);
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
|
||||
@@ -285,7 +284,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
|
||||
private void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
|
||||
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
|
||||
if (null != calendarInstance) {
|
||||
final Calendar calendar = calendarInstance.getCalendar();
|
||||
@@ -299,6 +298,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUndoChargeOff(final String json) {
|
||||
if (!StringUtils.isBlank(json)) {
|
||||
final Set<String> transactionParameters = new HashSet<>(Arrays.asList(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME));
|
||||
@@ -320,6 +320,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -355,6 +356,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargebackTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -385,10 +387,12 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRepaymentTransaction(final String json) {
|
||||
validatePaymentTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTransactionWithNoAmount(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -416,6 +420,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChargeOffTransaction(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -446,6 +451,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateOfLoanOfficer(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -476,6 +482,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateForBulkLoanReassignment(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
@@ -504,6 +511,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateMarkAsFraudLoan(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return;
|
||||
@@ -525,6 +533,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -564,6 +573,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateNewRefundTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -596,6 +606,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanForeclosure(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
@@ -625,6 +636,7 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanClientIsActive(final Loan loan) {
|
||||
final Client client = loan.client();
|
||||
if (client != null && client.isNotActive()) {
|
||||
@@ -632,6 +644,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanGroupIsActive(final Loan loan) {
|
||||
final Group group = loan.group();
|
||||
if (group != null && group.isNotActive()) {
|
||||
@@ -639,7 +652,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
|
||||
private void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
|
||||
String reversedOrCreated) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
if (txn.isChargeRefund() && DateUtils.isBefore(transactionDate, txn.getTransactionDate())) {
|
||||
@@ -651,7 +664,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanDisbursementIsBeforeTransactionDate(final Loan loan, final LocalDate transactionDate) {
|
||||
private void validateLoanDisbursementIsBeforeTransactionDate(final Loan loan, final LocalDate transactionDate) {
|
||||
if (DateUtils.isBefore(transactionDate, loan.getDisbursementDate())) {
|
||||
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
||||
+ loan.getDisbursementDate().toString();
|
||||
@@ -660,14 +673,14 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionShouldNotBeInTheFuture(final LocalDate transactionDate) {
|
||||
private void validateTransactionShouldNotBeInTheFuture(final LocalDate transactionDate) {
|
||||
if (DateUtils.isDateInTheFuture(transactionDate)) {
|
||||
final String errorMessage = "The transaction date cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, transactionDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLoanHasCurrency(final Loan loan) {
|
||||
private void validateLoanHasCurrency(final Loan loan) {
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency defaultApplicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
|
||||
if (defaultApplicationCurrency == null) {
|
||||
@@ -675,7 +688,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, LocalDate transactionDate) {
|
||||
private void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, LocalDate transactionDate) {
|
||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||
if (DateUtils.isBefore(transactionDate, clientOfficeJoiningDate)) {
|
||||
@@ -687,6 +700,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate, final LoanEvent event) {
|
||||
if (!(loan.isInterestBearingAndInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())
|
||||
|| !loan.getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
|
||||
@@ -720,6 +734,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||
final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||
@@ -728,6 +743,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean allowTransactionsOnHoliday,
|
||||
final List<Holiday> holidays) {
|
||||
if (!allowTransactionsOnHoliday && HolidayUtil.isHoliday(repaymentDate, holidays)) {
|
||||
@@ -736,7 +752,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(Loan loan) {
|
||||
private void validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(Loan loan) {
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
@@ -748,6 +764,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
|
||||
final Long loanId = command.getLoanId();
|
||||
Loan loan = this.loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||
@@ -770,6 +787,7 @@ public final class LoanTransactionValidator {
|
||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanTransactionInterestPaymentWaiverAfterRecalculation(Loan loan) {
|
||||
// Payment allocation calculates the new total principal repaid, and it should be validated after recalculation
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
@@ -784,10 +802,12 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(String json) {
|
||||
validatePaymentTransaction(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -803,7 +823,7 @@ public final class LoanTransactionValidator {
|
||||
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
|
||||
}
|
||||
|
||||
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
||||
private void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
|
||||
final LocalDate transactionDate) {
|
||||
if (loanTransactionType.isRepaymentType() && !loanTransactionType.isChargeRefund()) {
|
||||
for (LoanTransaction txn : loan.getLoanTransactions()) {
|
||||
@@ -816,6 +836,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRefundDateIsAfterLastRepayment(final Loan loan, final LocalDate refundTransactionDate) {
|
||||
final LocalDate possibleNextRefundDate = loan.possibleNextRefundDate();
|
||||
|
||||
@@ -824,6 +845,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActivityNotBeforeClientOrGroupTransferDate(final Loan loan, final LoanEvent event, final LocalDate activityDate) {
|
||||
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
|
||||
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
|
||||
@@ -947,7 +969,8 @@ public final class LoanTransactionValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||
|
||||
@@ -1016,6 +1039,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateIfTransactionIsChargeback(final LoanTransaction chargebackTransaction) {
|
||||
if (!chargebackTransaction.isChargeback()) {
|
||||
final String errorMessage = "A transaction of type chargeback was expected but not received.";
|
||||
@@ -1023,6 +1047,7 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateLoanRescheduleDate(final Loan loan) {
|
||||
if (DateUtils.isBefore(loan.getRescheduledOnDate(), loan.getDisbursementDate())) {
|
||||
final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: "
|
||||
@@ -1038,90 +1063,19 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateCapitalizedIncome(final JsonCommand command, final Long loanId) {
|
||||
final String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getCapitalizedIncomeParameters());
|
||||
|
||||
LoanApplicationValidator.validateOrThrow("loan.capitalized.income", baseDataValidator -> {
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
validateLoanClientIsActive(loan);
|
||||
validateLoanGroupIsActive(loan);
|
||||
|
||||
// Validate that loan is disbursed
|
||||
if (!loan.isDisbursed()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("capitalized.income.only.after.disbursement",
|
||||
"Capitalized income can be added to the loan only after Disbursement");
|
||||
}
|
||||
|
||||
// Validate loan is progressive
|
||||
if (!loan.isProgressiveSchedule()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.progressive.loan");
|
||||
}
|
||||
|
||||
// Validate income capitalization is enabled
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("income.capitalization.not.enabled");
|
||||
}
|
||||
|
||||
// Validate loan is active
|
||||
if (!loan.getStatus().isActive()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.active");
|
||||
}
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
// Validate transaction date is not before disbursement date
|
||||
if (transactionDate != null && loan.getDisbursementDate() != null && transactionDate.isBefore(loan.getDisbursementDate())) {
|
||||
baseDataValidator.reset().parameter("transactionDate").failWithCode("before.disbursement.date",
|
||||
"Transaction date cannot be before disbursement date");
|
||||
}
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate total disbursement + capitalized income <= approved amount
|
||||
if (transactionAmount != null) {
|
||||
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
final BigDecimal existingCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
|
||||
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
final BigDecimal approvedAmount = loan.getApprovedPrincipal();
|
||||
final BigDecimal newTotal = totalDisbursed.add(existingCapitalizedIncomeBalance).add(transactionAmount);
|
||||
|
||||
if (newTotal.compareTo(approvedAmount) > 0) {
|
||||
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
|
||||
"Sum of disbursed amount and capitalized income cannot exceed approved amount");
|
||||
}
|
||||
}
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
validateNote(baseDataValidator, element);
|
||||
validateExternalId(baseDataValidator, element);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||
if (StringUtils.isNotBlank(note)) {
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExternalId(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
@Override
|
||||
public void validateExternalId(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||
if (StringUtils.isNotBlank(externalId)) {
|
||||
baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getCapitalizedIncomeParameters() {
|
||||
return new HashSet<>(
|
||||
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -29,11 +29,12 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LoanJournalEntryPoster {
|
||||
public class LoanJournalEntryPosterImpl implements LoanJournalEntryPoster {
|
||||
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||
|
||||
@Override
|
||||
public void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* 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.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlatformService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, new LoanStatusChangedListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class,
|
||||
new LoanAccrualActivityPostingLoanStatusChangedListener());
|
||||
}
|
||||
|
||||
private static 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 (loan.isOpen()) {
|
||||
loan.handleMaturityDateActivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanAccrualActivityPostingLoanStatusChangedListener
|
||||
implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
LoanStatus oldStatus = event.getOldStatus();
|
||||
LoanStatus newStatus = loan.getStatus();
|
||||
if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -2996,53 +2996,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult addCapitalizedIncome(final Long loanId, final JsonCommand command) {
|
||||
loanTransactionValidator.validateCapitalizedIncome(command, loanId);
|
||||
|
||||
final Loan loan = loanAssembler.assembleFrom(loanId);
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
|
||||
// Create payment details
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
|
||||
// Validate cash payment if applicable
|
||||
if (paymentDetail != null && paymentDetail.getPaymentType() != null && paymentDetail.getPaymentType().getIsCashPayment()) {
|
||||
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, loan.getCurrencyCode(), transactionAmount);
|
||||
}
|
||||
|
||||
// Extract transaction details
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
|
||||
|
||||
// Create capitalized income transaction
|
||||
final Money capitalizedIncomeAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail,
|
||||
transactionDate, txnExternalId);
|
||||
|
||||
// Update loan with capitalized income
|
||||
capitalizedIncomeTransaction.updateLoan(loan);
|
||||
loan.addLoanTransaction(capitalizedIncomeTransaction);
|
||||
loanTransactionRepository.saveAndFlush(capitalizedIncomeTransaction);
|
||||
|
||||
// Update loan counters and save
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
// Create note if provided
|
||||
createNote(loan, command, changes);
|
||||
|
||||
// Post journal entries
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(loan.getId()).withEntityExternalId(loan.getExternalId()).build();
|
||||
}
|
||||
|
||||
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
|
||||
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
|
||||
|
||||
|
||||
+8
@@ -203,6 +203,14 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLoanNote(final Long loanId, final String note) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final Note newNote = Note.loanNote(loan, note);
|
||||
|
||||
this.noteRepository.save(newNote);
|
||||
}
|
||||
|
||||
private String getResourceUrlFromCommand(JsonCommand command) {
|
||||
|
||||
final String resourceUrl;
|
||||
|
||||
+4
@@ -93,6 +93,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -354,7 +355,10 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
||||
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
|
||||
delinquencyEffectivePauseHelper, configurationDomainService);
|
||||
|
||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
|
||||
Loan loan = Mockito.spy(Loan.class);
|
||||
ReflectionTestUtils.setField(loan, "loanProduct", loanProduct);
|
||||
ReflectionTestUtils.setField(loan, "loanStatus", LoanStatus.ACTIVE);
|
||||
LoanTransaction transaction1 = Mockito.mock(LoanTransaction.class);
|
||||
LoanTransaction transaction2 = Mockito.mock(LoanTransaction.class);
|
||||
|
||||
Reference in New Issue
Block a user