FINERACT-2311: Add buydown fees to loan
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.BuyDownFeePlatformService;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@CommandType(entity = "LOAN", action = "BUYDOWNFEE")
|
||||
public class AddBuyDownFeeCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final BuyDownFeePlatformService buyDownFeePlatformService;
|
||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
return this.buyDownFeePlatformService.makeLoanBuyDownFee(command.getLoanId(), command);
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.buy.down.fee",
|
||||
"Buy Down Fee");
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 BuyDownFeePlatformService {
|
||||
|
||||
@Transactional
|
||||
CommandProcessingResult makeLoanBuyDownFee(Long loanId, JsonCommand command);
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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.commons.lang3.StringUtils;
|
||||
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.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
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 BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformService {
|
||||
|
||||
private final ProgressiveLoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanAssembler loanAssembler;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
private final LoanJournalEntryPoster loanJournalEntryPoster;
|
||||
private final NoteWritePlatformService noteWritePlatformService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult makeLoanBuyDownFee(final Long loanId, final JsonCommand command) {
|
||||
|
||||
this.loanTransactionValidator.validateBuyDownFee(command, loanId);
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
|
||||
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 buy down fee transaction
|
||||
final Money buyDownFeeAmount = Money.of(loan.getCurrency(), transactionAmount); // FLAT calculation
|
||||
final LoanTransaction buyDownFeeTransaction = LoanTransaction.buyDownFee(loan, buyDownFeeAmount, paymentDetail, transactionDate,
|
||||
txnExternalId);
|
||||
|
||||
// Add to loan (NO schedule recalculation as per requirements)
|
||||
loan.addLoanTransaction(buyDownFeeTransaction);
|
||||
|
||||
// Save transaction
|
||||
loanTransactionRepository.saveAndFlush(buyDownFeeTransaction);
|
||||
|
||||
// Update loan derived fields
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
// Add note if provided
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
noteWritePlatformService.createLoanTransactionNote(buyDownFeeTransaction.getId(), noteText);
|
||||
}
|
||||
|
||||
// Post journal entries
|
||||
loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
.withEntityId(buyDownFeeTransaction.getId()) //
|
||||
.withEntityExternalId(buyDownFeeTransaction.getExternalId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private void checkClientOrGroupActive(final Loan loan) {
|
||||
final Client client = loan.client();
|
||||
if (client != null && client.isNotActive()) {
|
||||
throw new ClientNotActiveException(client.getId());
|
||||
}
|
||||
final Group group = loan.group();
|
||||
if (group != null && group.isNotActive()) {
|
||||
throw new GroupNotActiveException(group.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -28,4 +28,6 @@ public interface ProgressiveLoanTransactionValidator extends LoanTransactionVali
|
||||
void validateCapitalizedIncomeAdjustment(JsonCommand command, Long loanId, Long capitalizedIncomeTransactionId);
|
||||
|
||||
void validateContractTerminationUndo(JsonCommand command, Long loanId);
|
||||
|
||||
void validateBuyDownFee(JsonCommand command, Long loanId);
|
||||
}
|
||||
|
||||
+58
@@ -23,6 +23,7 @@ import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -33,9 +34,11 @@ 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.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.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
@@ -53,6 +56,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingException;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
@@ -269,6 +273,60 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
|
||||
});
|
||||
}
|
||||
|
||||
private static final List<String> BUY_DOWN_FEE_TRANSACTION_SUPPORTED_PARAMETERS = List
|
||||
.of(new String[] { "transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId" });
|
||||
|
||||
@Override
|
||||
public void validateBuyDownFee(JsonCommand command, Long loanId) {
|
||||
final String json = command.json();
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, BUY_DOWN_FEE_TRANSACTION_SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource("loan.transaction.buyDownFee");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("buy.down.fee.not.enabled",
|
||||
"Buy down fee is not enabled for this loan product");
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
validateBuyDownFeeEligibility(loan);
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
// Validate transaction date is on or after first disbursement
|
||||
if (transactionDate != null) {
|
||||
final LocalDate firstDisbursementDate = loan.getDisbursementDate();
|
||||
if (firstDisbursementDate != null && transactionDate.isBefore(firstDisbursementDate)) {
|
||||
baseDataValidator.reset().parameter("transactionDate").failWithCode("cannot.be.before.first.disbursement.date");
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateBuyDownFeeEligibility(Loan loan) {
|
||||
if (!loan.getStatus().isActive()) {
|
||||
throw new LoanTransactionProcessingException("Buy Down fees can only be added to active loans");
|
||||
}
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
// Delegates
|
||||
@Override
|
||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||
|
||||
Reference in New Issue
Block a user