Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kjozsa committed Jul 8, 2024
1 parent a84b168 commit 2a1dc0f
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
Expand All @@ -57,21 +61,26 @@
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
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.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.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

Expand All @@ -82,8 +91,10 @@ public final class LoanTransactionValidator {
private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationValidator fromApiJsonDeserializer;
private final LoanRepository loanRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final LoanUtilService loanUtilService;
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;

private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
Expand All @@ -98,8 +109,7 @@ public void validateDisbursement(JsonCommand command, boolean isAccountTransfer,
throw new InvalidJsonException();
}

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));

LoanApplicationValidator.validateOrThrow("loan.disbursement", baseDataValidator -> {
Expand All @@ -120,20 +130,73 @@ public void validateDisbursement(JsonCommand command, boolean isAccountTransfer,
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
.ignoreIfNull().positiveAmount();

final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
.notGreaterThanMax(principal);
final BigDecimal emiAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull()
.positiveAmount().notGreaterThanMax(principal);

validatePaymentDetails(baseDataValidator, element);

if (command.parameterExists("postDatedChecks")) {
// validate with post dated checks for the disbursement
this.validateDisbursementWithPostDatedChecks(command.json(), loanId);
}

final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
validateLoanClientIsActive(loan);
validateLoanGroupIsActive(loan);

if (loan.isChargedOff() && DateUtils.isBefore(actualDisbursementDate, loan.getChargedOffOnDate())) {
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
+ loan.getId()
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
loan.getId());
}

if ((!(loan.isApproved() && loan.isNotDisbursed()) && !loan.getLoanProduct().isMultiDisburseLoan())
|| (loan.getLoanProduct().isMultiDisburseLoan() && !loan.isAllTranchesNotDisbursed())) {
final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
baseDataValidator.getDataValidationErrors().add(error);
}

final BigDecimal disbursedAmount = loan.getDisbursedAmount();
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();

if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
BigDecimal totalCollateral = collectTotalCollateral(loanCollateralManagements);

// Validate the loan collateral value against the disbursedAmount
if (disbursedAmount.compareTo(totalCollateral) > 0) {
throw new LoanCollateralAmountNotSufficientException(disbursedAmount);
}
}

// validate ActualDisbursement Date Against Expected Disbursement Date
LoanProduct loanProduct = loan.loanProduct();
if (loanProduct.isSyncExpectedWithDisbursementDate()) {
if (!loan.getExpectedDisbursedOnLocalDate().equals(actualDisbursementDate)) {
throw new DateMismatchException(actualDisbursementDate, loan.getExpectedDisbursedOnLocalDate());
}
}

entityDatatableChecksWritePlatformService.runTheCheckForProduct(loan.getId(), EntityTables.LOAN.getName(),
StatusEnum.DISBURSE.getValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
});
}

private static @NotNull BigDecimal collectTotalCollateral(Set<LoanCollateralManagement> loanCollateralManagements) {
BigDecimal totalCollateral = BigDecimal.ZERO;

for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
BigDecimal quantity = loanCollateralManagement.getQuantity();
BigDecimal pctToBase = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getPctToBase();
BigDecimal basePrice = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getBasePrice();
totalCollateral = totalCollateral.add(quantity.multiply(basePrice).multiply(pctToBase).divide(BigDecimal.valueOf(100)));
}
return totalCollateral;
}

private static @NotNull Set<String> getDisbursementParameters(boolean isAccountTransfer) {
Set<String> disbursementParameters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand;
Expand Down Expand Up @@ -291,78 +290,32 @@ public CommandProcessingResult disburseLoan(Long loanId, JsonCommand command, Bo
@Override
public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command, Boolean isAccountTransfer,
Boolean isWithoutAutoPayment) {
this.loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId);
loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId);

Loan loan = this.loanAssembler.assembleFrom(loanId);
// Fail fast if client/group is not active or actual loan status disallows disbursal
checkClientOrGroupActive(loan);

final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");

if (loan.isChargedOff() && DateUtils.isBefore(actualDisbursementDate, loan.getChargedOffOnDate())) {
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
+ loanId
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
loanId);
}
Loan loan = loanAssembler.assembleFrom(loanId);

if (loan.loanProduct().isDisallowExpectedDisbursements()) {
List<LoanDisbursementDetails> filteredList = loan.getDisbursementDetails().stream()
.filter(disbursementDetails -> disbursementDetails.actualDisbursementDate() == null).toList();
// Check whether a new LoanDisbursementDetails is required
if (filteredList.isEmpty()) {
// create artificial 'tranche/expected disbursal' as current disburse code expects it for
// multi-disbursal
// products
// multi-disbursal products
final LocalDate artificialExpectedDate = loan.getExpectedDisbursedOnLocalDate();
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
loan.getDisbursedAmount(), null, false);
disbursementDetail.updateLoan(loan);
loan.getAllDisbursementDetails().add(disbursementDetail);
}
}
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);

// Get disbursedAmount
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();

// Get relevant loan collateral modules
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
BigDecimal totalCollateral = BigDecimal.valueOf(0);

for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
BigDecimal quantity = loanCollateralManagement.getQuantity();
BigDecimal pctToBase = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getPctToBase();
BigDecimal basePrice = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getBasePrice();
totalCollateral = totalCollateral.add(quantity.multiply(basePrice).multiply(pctToBase).divide(BigDecimal.valueOf(100)));
}

// Validate the loan collateral value against the disbursedAmount
if (disbursedAmount.compareTo(totalCollateral) > 0) {
throw new LoanCollateralAmountNotSufficientException(disbursedAmount);
}
}

// validate ActualDisbursement Date Against Expected Disbursement Date
LoanProduct loanProduct = loan.loanProduct();
if (loanProduct.isSyncExpectedWithDisbursementDate()) {
syncExpectedDateWithActualDisbursementDate(loan, actualDisbursementDate);
}

final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
final LocalDate rescheduledRepaymentDate = command.localDateValueOfParameterNamed("adjustRepaymentDate");

entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(), StatusEnum.DISBURSE.getValue(),
EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());

LocalDate recalculateFrom = null;
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
if (!loan.isMultiDisburmentLoan()) {
loan.setActualDisbursementDate(actualDisbursementDate);
}
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);

// validate actual disbursement date against meeting date
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
if (loan.isSyncDisbursementWithMeeting()) {
Expand All @@ -389,12 +342,10 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand
// Recalculate first repayment date based in actual disbursement date.
updateLoanCounters(loan, actualDisbursementDate);
Money amountBeforeAdjust = loan.getPrincipal();
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
ChangedTransactionDetail changedTransactionDetail = null;
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
if (canDisburse) {

if (loan.canDisburse(actualDisbursementDate)) {
// Get netDisbursalAmount from disbursal screen field.
final BigDecimal netDisbursalAmount = command
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
Expand Down Expand Up @@ -430,9 +381,9 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand
}

amountToDisburse = disburseAmount.minus(loanOutstanding);

disburseLoanToLoan(loan, command, loanOutstanding);
}

LoanTransaction disbursementTransaction = null;
if (isAccountTransfer) {
disburseLoanToSavings(loan, command, amountToDisburse, paymentDetail);
Expand All @@ -452,12 +403,17 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand
*/
recalculateSchedule = true;
}

final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
final LocalDate rescheduledRepaymentDate = command.localDateValueOfParameterNamed("adjustRepaymentDate");
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
rescheduledRepaymentDate);
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
}

ChangedTransactionDetail changedTransactionDetail;
if (isPaymentTypeApplicableForDisbursementCharge) {
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
} else {
Expand Down Expand Up @@ -2000,7 +1956,6 @@ public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonC
}

private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) {

final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);

Expand All @@ -2015,7 +1970,6 @@ private void disburseLoanToLoan(final Loan loan, final JsonCommand command, fina
}

private void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {

final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);

Expand All @@ -2035,7 +1989,6 @@ private void disburseLoanToSavings(final Loan loan, final JsonCommand command, f
LoanTransactionType.DISBURSEMENT.getValue(), null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null,
txnExternalId, loan, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);

}

@Transactional
Expand Down

0 comments on commit 2a1dc0f

Please sign in to comment.