FINERACT-1692: Allow disbursement transaction when the loan is fully paid/overpaid
This commit is contained in:
committed by
Arnold Gálovics
parent
9d8b2d38c5
commit
d2e56edba6
+20
-10
@@ -26,6 +26,7 @@ import java.security.InvalidParameterException;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -44,6 +45,7 @@ import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||
import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepository;
|
||||
import org.apache.fineract.organisation.office.exception.OfficeNotFoundException;
|
||||
import org.apache.fineract.portfolio.businessevent.BusinessEventListener;
|
||||
import org.apache.fineract.portfolio.businessevent.domain.client.ClientActivateBusinessEvent;
|
||||
import org.apache.fineract.portfolio.businessevent.domain.client.ClientRejectBusinessEvent;
|
||||
@@ -58,6 +60,7 @@ import org.apache.fineract.portfolio.businessevent.service.BusinessEventNotifier
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepository;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException;
|
||||
@@ -169,13 +172,16 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
throw new InvalidLoanTypeException("Loan Type cannot be Invalid for the Triggered Sms Campaign");
|
||||
}
|
||||
if (loan.isGroupLoan()) {
|
||||
Group group = this.groupRepository.findById(loan.getGroupId()).orElse(null);
|
||||
Group group = this.groupRepository.findById(loan.getGroupId())
|
||||
.orElseThrow(() -> new GroupNotFoundException(loan.getGroupId()));
|
||||
groupClients.addAll(group.getClientMembers());
|
||||
} else {
|
||||
groupClients.add(loan.client());
|
||||
}
|
||||
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
||||
new TypeReference<HashMap<String, String>>() {});
|
||||
new TypeReference<>() {
|
||||
|
||||
});
|
||||
|
||||
if (groupClients.size() > 0) {
|
||||
for (Client client : groupClients) {
|
||||
@@ -189,7 +195,9 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
}
|
||||
if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) {
|
||||
if (key.equals("officeId")) {
|
||||
Office campaignOffice = this.officeRepository.findById(Long.valueOf(value)).orElse(null);
|
||||
Long officeId = Long.valueOf(value);
|
||||
Office campaignOffice = this.officeRepository.findById(Long.valueOf(value))
|
||||
.orElseThrow(() -> new OfficeNotFoundException(officeId));
|
||||
if (campaignOffice.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
|
||||
throw new SmsRuntimeException("error.msg.no.office", "Office not found for the id");
|
||||
}
|
||||
@@ -208,10 +216,8 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
}
|
||||
SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, mobileNumber, smsCampaign,
|
||||
smsCampaign.isNotification());
|
||||
Collection<SmsMessage> messages = new ArrayList<>();
|
||||
messages.add(smsMessage);
|
||||
Map<SmsCampaign, Collection<SmsMessage>> smsDataMap = new HashMap<>();
|
||||
smsDataMap.put(smsCampaign, messages);
|
||||
smsDataMap.put(smsCampaign, Collections.singletonList(smsMessage));
|
||||
this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +240,9 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
final SavingsAccount savingsAccount = savingsTransaction.getSavingsAccount();
|
||||
final Client client = savingsAccount.getClient();
|
||||
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
||||
new TypeReference<HashMap<String, String>>() {});
|
||||
new TypeReference<>() {
|
||||
|
||||
});
|
||||
HashMap<String, Object> smsParams = processSavingsTransactionDataForSms(savingsTransaction, client);
|
||||
for (String key : campaignParams.keySet()) {
|
||||
String value = campaignParams.get(key);
|
||||
@@ -245,7 +253,9 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
}
|
||||
if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) {
|
||||
if (key.equals("officeId")) {
|
||||
Office campaignOffice = this.officeRepository.findById(Long.valueOf(value)).orElse(null);
|
||||
Long officeId = Long.valueOf(value);
|
||||
Office campaignOffice = this.officeRepository.findById(officeId)
|
||||
.orElseThrow(() -> new OfficeNotFoundException(officeId));
|
||||
if (campaignOffice.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
|
||||
throw new SmsRuntimeException("error.msg.no.office", "Office not found for the id");
|
||||
}
|
||||
@@ -308,7 +318,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
smsParams.put("lastname", client.getLastname());
|
||||
smsParams.put("FullName", client.getDisplayName());
|
||||
smsParams.put("mobileNo", client.mobileNo());
|
||||
smsParams.put("LoanAmount", loan.getPrincpal());
|
||||
smsParams.put("LoanAmount", loan.getPrincipal());
|
||||
smsParams.put("LoanOutstanding", loanTransaction.getOutstandingLoanBalance());
|
||||
smsParams.put("loanId", loan.getId());
|
||||
smsParams.put("LoanAccountId", loan.getAccountNumber());
|
||||
@@ -340,7 +350,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
|
||||
// {{balance}}
|
||||
|
||||
// transactionDate
|
||||
HashMap<String, Object> smsParams = new HashMap<String, Object>();
|
||||
HashMap<String, Object> smsParams = new HashMap<>();
|
||||
SavingsAccount savingsAccount = savingsAccountTransaction.getSavingsAccount();
|
||||
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MMM:d:yyyy");
|
||||
smsParams.put("clientId", client.getId());
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ public interface ConfigurationDomainService {
|
||||
|
||||
LocalDate retrieveOrganisationStartDate();
|
||||
|
||||
boolean isPaymnetypeApplicableforDisbursementCharge();
|
||||
boolean isPaymentTypeApplicableForDisbursementCharge();
|
||||
|
||||
boolean isInterestChargedFromDateSameAsDisbursementDate();
|
||||
|
||||
|
||||
+3
-3
@@ -245,7 +245,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaymnetypeApplicableforDisbursementCharge() {
|
||||
public boolean isPaymentTypeApplicableForDisbursementCharge() {
|
||||
final String propertyName = "paymenttype-applicable-for-disbursement-charges";
|
||||
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
|
||||
return property.isEnabled();
|
||||
@@ -372,13 +372,13 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
final String propertyName = "allow-backdated-transaction-before-interest-posting-date-for-days";
|
||||
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
|
||||
if (property.getValue() == null) {
|
||||
return Long.valueOf(0);
|
||||
return 0L;
|
||||
}
|
||||
return property.getValue();
|
||||
}
|
||||
|
||||
@Cacheable(value = "configByName", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)")
|
||||
private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
|
||||
public GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
|
||||
String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
|
||||
String key = identifier + "_" + propertyName;
|
||||
if (!configurations.containsKey(key)) {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
||||
}
|
||||
break;
|
||||
case LOAN_DISBURSED:
|
||||
if (from.hasStateOf(LoanStatus.APPROVED)) {
|
||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.APPROVED, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
|
||||
newState = stateOf(LoanStatus.ACTIVE, this.allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
|
||||
+11
-10
@@ -902,7 +902,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (isMultiDisburmentLoan() && loanCharge.isDisbursementCharge()) {
|
||||
amount = getTotalAllTrancheDisbursementAmount().getAmount().add(totalInterestCharged);
|
||||
} else {
|
||||
amount = getPrincpal().getAmount().add(totalInterestCharged);
|
||||
amount = getPrincipal().getAmount().add(totalInterestCharged);
|
||||
}
|
||||
break;
|
||||
case PERCENT_OF_INTEREST:
|
||||
@@ -912,7 +912,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (loanCharge.getTrancheDisbursementCharge() != null) {
|
||||
amount = loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().principal();
|
||||
} else {
|
||||
amount = getPrincpal().getAmount();
|
||||
amount = getPrincipal().getAmount();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1306,7 +1306,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) {
|
||||
if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
loanTransaction.reverse();
|
||||
final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
|
||||
getDisbursementDate());
|
||||
addLoanTransaction(interestAppliedTransaction);
|
||||
@@ -2537,7 +2536,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
|
||||
boolean isMultiTrancheDisburse = false;
|
||||
if (LoanStatus.fromInt(this.loanStatus).isActive() && isAllTranchesNotDisbursed()) {
|
||||
LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus);
|
||||
if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid())
|
||||
&& isAllTranchesNotDisbursed()) {
|
||||
LoanDisbursementDetails details = fetchLastDisburseDetail();
|
||||
|
||||
if (details != null) {
|
||||
@@ -2550,7 +2551,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
isMultiTrancheDisburse = true;
|
||||
}
|
||||
return !statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus)) || isMultiTrancheDisburse;
|
||||
return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse;
|
||||
}
|
||||
|
||||
public Money adjustDisburseAmount(final JsonCommand command, final LocalDate actualDisbursementDate) {
|
||||
@@ -4021,9 +4022,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private boolean isAllTranchesNotDisbursed() {
|
||||
return this.loanProduct.isMultiDisburseLoan()
|
||||
&& (LoanStatus.fromInt(this.loanStatus).isActive() || LoanStatus.fromInt(this.loanStatus).isApproved())
|
||||
&& isDisbursementAllowed();
|
||||
LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus);
|
||||
return this.loanProduct.isMultiDisburseLoan() && (actualLoanStatus.isActive() || actualLoanStatus.isApproved()
|
||||
|| actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) && isDisbursementAllowed();
|
||||
}
|
||||
|
||||
private boolean hasDisbursementTransaction() {
|
||||
@@ -4193,7 +4194,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return this.interestChargedFromDate;
|
||||
}
|
||||
|
||||
public Money getPrincpal() {
|
||||
public Money getPrincipal() {
|
||||
return this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
}
|
||||
|
||||
@@ -6399,7 +6400,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amount = getPrincpal().getAmount();
|
||||
amount = getPrincipal().getAmount();
|
||||
}
|
||||
}
|
||||
return amount;
|
||||
|
||||
+4
-4
@@ -158,7 +158,7 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
if (command.hasParameter("principal")) {
|
||||
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
|
||||
} else {
|
||||
amountPercentageAppliedTo = loan.getPrincpal().getAmount();
|
||||
amountPercentageAppliedTo = loan.getPrincipal().getAmount();
|
||||
}
|
||||
break;
|
||||
case PERCENT_OF_AMOUNT_AND_INTEREST:
|
||||
@@ -166,7 +166,7 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal")
|
||||
.add(command.bigDecimalValueOfParameterNamed("interest"));
|
||||
} else {
|
||||
amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest());
|
||||
amountPercentageAppliedTo = loan.getPrincipal().getAmount().add(loan.getTotalInterest());
|
||||
}
|
||||
break;
|
||||
case PERCENT_OF_INTEREST:
|
||||
@@ -448,11 +448,11 @@ public class LoanCharge extends AbstractPersistableCustom {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amountPercentageAppliedTo = this.loan.getPrincpal().getAmount();
|
||||
amountPercentageAppliedTo = this.loan.getPrincipal().getAmount();
|
||||
}
|
||||
break;
|
||||
case PERCENT_OF_AMOUNT_AND_INTEREST:
|
||||
amountPercentageAppliedTo = this.loan.getPrincpal().getAmount().add(this.loan.getTotalInterest());
|
||||
amountPercentageAppliedTo = this.loan.getPrincipal().getAmount().add(this.loan.getTotalInterest());
|
||||
break;
|
||||
case PERCENT_OF_INTEREST:
|
||||
amountPercentageAppliedTo = this.loan.getTotalInterest();
|
||||
|
||||
+4
-4
@@ -99,7 +99,7 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
@Override
|
||||
public void validateGuarantorBusinessRules(Loan loan) {
|
||||
LoanProduct loanProduct = loan.loanProduct();
|
||||
BigDecimal principal = loan.getPrincpal().getAmount();
|
||||
BigDecimal principal = loan.getPrincipal().getAmount();
|
||||
if (loanProduct.isHoldGuaranteeFundsEnabled()) {
|
||||
LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails();
|
||||
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
|
||||
@@ -241,8 +241,8 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
|
||||
try {
|
||||
BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
|
||||
if (loan.getGuaranteeAmount().compareTo(loan.getPrincpal().getAmount()) > 0) {
|
||||
remainingAmount = remainingAmount.multiply(loan.getPrincpal().getAmount()).divide(loan.getGuaranteeAmount(),
|
||||
if (loan.getGuaranteeAmount().compareTo(loan.getPrincipal().getAmount()) > 0) {
|
||||
remainingAmount = remainingAmount.multiply(loan.getPrincipal().getAmount()).divide(loan.getGuaranteeAmount(),
|
||||
MoneyHelper.getRoundingMode());
|
||||
}
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
|
||||
@@ -393,7 +393,7 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
|
||||
BigDecimal amountForRelease = loanTransaction.getPrincipalPortion();
|
||||
BigDecimal totalGuaranteeAmount = loan.getGuaranteeAmount();
|
||||
BigDecimal principal = loan.getPrincpal().getAmount();
|
||||
BigDecimal principal = loan.getPrincipal().getAmount();
|
||||
if ((amountForRelease != null) && (totalGuaranteeAmount != null)) {
|
||||
amountForRelease = amountForRelease.multiply(totalGuaranteeAmount).divide(principal, MoneyHelper.getRoundingMode());
|
||||
List<DepositAccountOnHoldTransaction> accountOnHoldTransactions = new ArrayList<>();
|
||||
|
||||
+1
-1
@@ -218,7 +218,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
Collection<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
final List<LoanSchedulePeriodData> installmentData = new ArrayList<>();
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
Money outstanding = loan.getPrincpal();
|
||||
Money outstanding = loan.getPrincipal();
|
||||
|
||||
List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
|
||||
if (loan.isMultiDisburmentLoan()) {
|
||||
|
||||
+56
-36
@@ -166,6 +166,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtio
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
@@ -266,6 +267,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
|
||||
private final PostDatedChecksRepository postDatedChecksRepository;
|
||||
|
||||
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
|
||||
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
|
||||
@@ -307,16 +310,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanEventApiJsonValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId);
|
||||
}
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
// Fail fast if client/group is not active or actual loan status disallows disbursal
|
||||
checkClientOrGroupActive(loan);
|
||||
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
||||
// create artificial 'tranche/expected disbursal' as current disburse code expects it for multi-disbursal
|
||||
// products
|
||||
final LocalDate artificialExpectedDate = loan.getExpectedDisbursedOnLocalDate();
|
||||
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
|
||||
loan.getDisbursedAmount(), null);
|
||||
disbursementDetail.updateLoan(loan);
|
||||
loan.getDisbursementDetails().add(disbursementDetail);
|
||||
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
|
||||
final LocalDate artificialExpectedDate = loan.getExpectedDisbursedOnLocalDate();
|
||||
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
|
||||
loan.getDisbursedAmount(), null);
|
||||
disbursementDetail.updateLoan(loan);
|
||||
loan.getDisbursementDetails().add(disbursementDetail);
|
||||
}
|
||||
}
|
||||
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
|
||||
// Get disbursedAmount
|
||||
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
||||
@@ -348,7 +360,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loanProduct.syncExpectedWithDisbursementDate()) {
|
||||
syncExpectedDateWithActualDisbursementDate(loan, actualDisbursementDate);
|
||||
}
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
|
||||
final LocalDate rescheduledRepaymentDate = command.localDateValueOfParameterNamed("adjustRepaymentDate");
|
||||
@@ -382,13 +393,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, loan.getCurrencyCode(), transactionAmount);
|
||||
}
|
||||
final Boolean isPaymnetypeApplicableforDisbursementCharge = configurationDomainService
|
||||
.isPaymnetypeApplicableforDisbursementCharge();
|
||||
final boolean isPaymentTypeApplicableForDisbursementCharge = configurationDomainService
|
||||
.isPaymentTypeApplicableForDisbursementCharge();
|
||||
|
||||
// Recalculate first repayment date based in actual disbursement date.
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
Money amountBeforeAdjust = loan.getPrincpal();
|
||||
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
Money amountBeforeAdjust = loan.getPrincipal();
|
||||
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
if (canDisburse) {
|
||||
@@ -401,7 +411,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
|
||||
Money amountToDisburse = disburseAmount.copy();
|
||||
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
|
||||
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
|
||||
if (loan.isTopup() && loan.getClientId() != null) {
|
||||
@@ -456,7 +466,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (isPaymnetypeApplicableforDisbursementCharge) {
|
||||
if (isPaymentTypeApplicableForDisbursementCharge) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
@@ -465,7 +475,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
@@ -609,9 +620,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
}
|
||||
|
||||
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
|
||||
private Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
|
||||
/*
|
||||
* Due to the "saveAndFlushLoanWithDataIntegrityViolationChecks" method the loan is saved and flushed in the
|
||||
* middle of the transaction. EclipseLink is in some situations are saving inconsistently the newly created
|
||||
* associations, like the newly created repayment schedule installments. The save and flush cannot be removed
|
||||
* safely till any native queries are used as part of this transaction either. See:
|
||||
* this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
*/
|
||||
try {
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
loanRepaymentScheduleInstallmentRepository.saveAll(loan.getRepaymentScheduleInstallments());
|
||||
return this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException e) {
|
||||
final Throwable realCause = e.getCause();
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
@@ -623,6 +642,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors, e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,7 +684,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LocalDate rescheduledRepaymentDate = null;
|
||||
|
||||
for (final SingleDisbursalCommand singleLoanDisbursalCommand : disbursalCommand) {
|
||||
final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
|
||||
Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
|
||||
// validate ActualDisbursement Date Against Expected Disbursement
|
||||
@@ -692,9 +712,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
if (canDisburse) {
|
||||
Money amountBeforeAdjust = loan.getPrincpal();
|
||||
Money amountBeforeAdjust = loan.getPrincipal();
|
||||
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
|
||||
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
|
||||
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
if (isAccountTransfer) {
|
||||
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
|
||||
@@ -716,7 +736,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (configurationDomainService.isPaymnetypeApplicableforDisbursementCharge()) {
|
||||
if (configurationDomainService.isPaymentTypeApplicableForDisbursementCharge()) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
@@ -724,7 +744,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
@@ -800,7 +820,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult undoLoanDisbursal(final Long loanId, final JsonCommand command) {
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoDisbursalBusinessEvent(loan));
|
||||
removeLoanCycle(loan);
|
||||
@@ -830,7 +850,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
BigDecimal netDisbursalAmount = loan.getApprovedPrincipal().subtract(loanOutstanding);
|
||||
loan.adjustNetDisbursalAmount(netDisbursalAmount);
|
||||
}
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
|
||||
String noteText = null;
|
||||
if (command.hasParameter("note")) {
|
||||
@@ -1010,7 +1030,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
if (loan.status().isClosed() && loan.getLoanSubStatus() != null
|
||||
&& loan.getLoanSubStatus().equals(LoanSubStatus.FORECLOSED.getValue())) {
|
||||
final String defaultUserMessage = "The loan cannot reopend as it is foreclosed.";
|
||||
@@ -1076,7 +1096,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
|
||||
scheduleGeneratorDTO, reversalExternalId);
|
||||
|
||||
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
|
||||
if (newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) {
|
||||
if (paymentDetail != null) {
|
||||
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
|
||||
}
|
||||
@@ -1088,7 +1108,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
* time being, not a major issue for now as this loop is entered only in edge cases (when a adjustment is made
|
||||
* before the latest payment recorded against the loan)
|
||||
***/
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
@@ -1105,7 +1125,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
/**
|
||||
* If a new transaction is not created, associate note with the transaction to be adjusted
|
||||
**/
|
||||
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
|
||||
if (newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) {
|
||||
note = Note.loanTransactionNote(loan, newTransactionDetail, noteText);
|
||||
} else {
|
||||
note = Note.loanTransactionNote(loan, transactionToAdjust, noteText);
|
||||
@@ -1131,7 +1151,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust);
|
||||
if (newTransactionDetail.isRepaymentType() && newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
|
||||
if (newTransactionDetail.isRepaymentType() && newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) {
|
||||
eventData.setNewTransactionDetail(newTransactionDetail);
|
||||
}
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
|
||||
@@ -1157,7 +1177,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
@@ -1192,7 +1212,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
* time being, not a major issue for now as this loop is entered only in edge cases (when a waiver is made
|
||||
* before the latest payment recorded against the loan)
|
||||
***/
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
@@ -2961,7 +2981,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
}
|
||||
|
||||
private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command,
|
||||
private CommandProcessingResult processLoanDisbursementDetail(Loan loan, Long loanId, JsonCommand command,
|
||||
LoanDisbursementDetails loanDisbursementDetails) {
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
@@ -2998,7 +3018,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
}
|
||||
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
if (command.entityId() != null && changedTransactionDetail != null) {
|
||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
@@ -3275,7 +3295,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Transactional
|
||||
public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) {
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
|
||||
validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan);
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -3291,7 +3311,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds,
|
||||
existingReversedTransactionIds, loan);
|
||||
if (!changes.isEmpty()) {
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
String noteText = null;
|
||||
if (command.hasParameter("note")) {
|
||||
noteText = command.stringValueOfParameterNamed("note");
|
||||
|
||||
Reference in New Issue
Block a user