Merge pull request #1161 from pramodn02/develop

MIFOSX-1709 : guarantor on hold funds functionality
This commit is contained in:
Vishwas Babu A J
2014-12-03 22:52:11 -08:00
43 changed files with 1487 additions and 123 deletions
@@ -290,6 +290,10 @@ public class CommandWrapper {
return this.entityName.equalsIgnoreCase("GUARANTOR");
}
public boolean isGuaranteeRecovery() {
return this.actionName.equalsIgnoreCase("RECOVERGUARANTEES");
}
public boolean isGLAccountResource() {
return this.entityName.equalsIgnoreCase("GLACCOUNT");
}
@@ -186,7 +186,16 @@ public class CommandWrapperBuilder {
this.entityName = "GUARANTOR";
this.entityId = null;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/guarantors/template";
this.href = "/loans/" + loanId + "/guarantors";
return this;
}
public CommandWrapperBuilder recoverFromGuarantor(final Long loanId) {
this.actionName = "RECOVERGUARANTEES";
this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId + "?command=recoverGuarantees";
return this;
}
@@ -388,6 +388,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("loanApplicationDeletionCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUndoLoanWriteOff()) {
handler = this.applicationContext.getBean("undoWriteOffLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isGuaranteeRecovery()) {
handler = this.applicationContext.getBean("recoverFromGuarantorCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -40,6 +40,7 @@ public class AccountTransferDTO {
private final SavingsAccount toSavingsAccount;
private final SavingsAccount fromSavingsAccount;
private final Boolean isRegularTransaction;
private final Boolean isExceptionForBalanceCheck;
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
@@ -47,7 +48,7 @@ public class AccountTransferDTO {
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId,
Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails,
final String noteText, final String txnExternalId, final Loan loan, SavingsAccount toSavingsAccount,
final SavingsAccount fromSavingsAccount,final Boolean isRegularTransaction) {
final SavingsAccount fromSavingsAccount, final Boolean isRegularTransaction, Boolean isExceptionForBalanceCheck) {
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.fromAccountType = fromAccountType;
@@ -70,6 +71,7 @@ public class AccountTransferDTO {
this.toSavingsAccount = toSavingsAccount;
this.fromSavingsAccount = fromSavingsAccount;
this.isRegularTransaction = isRegularTransaction;
this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
}
public LocalDate getTransactionDate() {
@@ -156,9 +158,12 @@ public class AccountTransferDTO {
return this.fromSavingsAccount;
}
public Boolean isRegularTransaction() {
return this.isRegularTransaction;
}
public Boolean isExceptionForBalanceCheck() {
return this.isExceptionForBalanceCheck;
}
}
@@ -240,7 +240,6 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
public Long transferFunds(final AccountTransferDTO accountTransferDTO) {
Long transferTransactionId = null;
final boolean isAccountTransfer = true;
final boolean isWithdrawBalance = false;
final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction();
AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
@@ -248,8 +247,19 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
SavingsAccount fromSavingsAccount = null;
Loan toLoanAccount = null;
if (accountTransferDetails == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
} else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
}
if (accountTransferDTO.getLoan() == null) {
toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
} else {
toLoanAccount = accountTransferDTO.getLoan();
this.loanAccountAssembler.setHelpers(toLoanAccount);
}
} else {
fromSavingsAccount = accountTransferDetails.fromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
@@ -259,7 +269,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), AccountTransferType.fromInt(
accountTransferDTO.getTransferType()).isInterestTransfer(), isWithdrawBalance);
accountTransferDTO.getTransferType()).isInterestTransfer(), accountTransferDTO.isExceptionForBalanceCheck());
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
@@ -293,11 +303,13 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
} else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
}
if (accountTransferDTO.getToSavingsAccount() == null) {
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
} else {
toSavingsAccount = accountTransferDTO.getToSavingsAccount();
this.savingsAccountAssembler.setHelpers(toSavingsAccount);
}
} else {
fromSavingsAccount = accountTransferDetails.fromSavingsAccount();
@@ -308,7 +320,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), AccountTransferType.fromInt(
accountTransferDTO.getTransferType()).isInterestTransfer(), isWithdrawBalance);
accountTransferDTO.getTransferType()).isInterestTransfer(), accountTransferDTO.isExceptionForBalanceCheck());
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
@@ -332,6 +344,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
} else {
fromLoanAccount = accountTransferDTO.getLoan();
this.loanAccountAssembler.setHelpers(fromLoanAccount);
}
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
} else {
@@ -221,12 +221,13 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailRepository.findOne(data.accountDetailId());
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
accountTransferDetails.accountTransferStandingInstruction().updateLatsRunDate(transactionDate.toDate());
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
data.toAccountType(), data.fromAccount().accountId(), data.toAccount().accountId(), data.name()
+ " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data
.transferType().getValue(), accountTransferDetails, null, null, null, null, fromSavingsAccount,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
transferAmount(sb, accountTransferDTO, data.getId());
}
}
@@ -0,0 +1,39 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.common;
import java.util.HashSet;
import java.util.Set;
public class BusinessEventNotificationConstants {
public static enum BUSINESS_EVENTS {
LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_TRANSACTION(
"loan_undo_transaction"), LOAN_MAKE_REPAYMENT("loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF(
"loan_undo_writtenoff");
private final String value;
private BUSINESS_EVENTS(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final BUSINESS_EVENTS type : BUSINESS_EVENTS.values()) {
values.add(type.value);
}
}
public static Set<String> getAllValues() {
return values;
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,16 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.common.service;
import org.springframework.data.jpa.domain.AbstractPersistable;
public interface BusinessEventListner {
public void businessEventToBeExecuted(AbstractPersistable<Long> businessEventEntity);
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity);
}
@@ -0,0 +1,21 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.common.service;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.springframework.data.jpa.domain.AbstractPersistable;
public interface BusinessEventNotifierService {
public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity);
public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity);
public void addBusinessEventPreListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner);
public void addBusinessEventPostListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner);
}
@@ -0,0 +1,63 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.common.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.stereotype.Service;
@Service
public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierService {
private final Map<BUSINESS_EVENTS, List<BusinessEventListner>> preListners = new HashMap<>(5);
private final Map<BUSINESS_EVENTS, List<BusinessEventListner>> postListners = new HashMap<>(5);
@Override
public void notifyBusinessEventToBeExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity) {
List<BusinessEventListner> businessEventListners = this.preListners.get(businessEvent);
if (businessEventListners != null) {
for (BusinessEventListner eventListner : businessEventListners) {
eventListner.businessEventToBeExecuted(businessEventEntity);
}
}
}
@Override
public void notifyBusinessEventWasExecuted(BUSINESS_EVENTS businessEvent, AbstractPersistable<Long> businessEventEntity) {
List<BusinessEventListner> businessEventListners = this.postListners.get(businessEvent);
if (businessEventListners != null) {
for (BusinessEventListner eventListner : businessEventListners) {
eventListner.businessEventWasExecuted(businessEventEntity);
}
}
}
@Override
public void addBusinessEventPreListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner) {
addBusinessEventListners(businessEvent, businessEventListner, preListners);
}
@Override
public void addBusinessEventPostListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner) {
addBusinessEventListners(businessEvent, businessEventListner, postListners);
}
private void addBusinessEventListners(BUSINESS_EVENTS businessEvent, BusinessEventListner businessEventListner,
final Map<BUSINESS_EVENTS, List<BusinessEventListner>> businessEventListnerMap) {
List<BusinessEventListner> businessEventListners = businessEventListnerMap.get(businessEvent);
if (businessEventListners == null) {
businessEventListners = new ArrayList<>();
businessEventListnerMap.put(businessEvent, businessEventListners);
}
businessEventListners.add(businessEventListner);
}
}
@@ -638,6 +638,9 @@ public class LoansApiResource {
} else if (is(commandParam, "unassignloanofficer")) {
final CommandWrapper commandRequest = builder.unassignLoanOfficer(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "recoverGuarantees")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().recoverFromGuarantor(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
@@ -345,6 +345,9 @@ public class Loan extends AbstractPersistable<Long> {
@Column(name = "create_standing_instruction_at_disbursement", nullable = true)
private Boolean createStandingInstructionAtDisbursement;
@Column(name = "guarantee_amount_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal guaranteeAmountDerived;
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -586,7 +589,7 @@ public class Loan extends AbstractPersistable<Long> {
* @return
*/
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate,
final AppUser currentUser) {
final AppUser currentUser) {
final Money chargeAmount = loanCharge.getAmount(getCurrency());
Money feeCharges = chargeAmount;
Money penaltyCharges = Money.zero(loanCurrency());
@@ -905,7 +908,8 @@ public class Loan extends AbstractPersistable<Long> {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived,
transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser );
transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(),
currentUser);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction
.getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
@@ -1040,8 +1044,7 @@ public class Loan extends AbstractPersistable<Long> {
this.collateral.addAll(associateWithThisLoan(loanCollateral));
}
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule,
AppUser currentUser) {
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, AppUser currentUser) {
this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
@@ -1101,8 +1104,7 @@ public class Loan extends AbstractPersistable<Long> {
}
}
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals,
final AppUser currentUser) {
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals, final AppUser currentUser) {
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
for (LoanTransaction loanTransaction : accruals) {
@@ -2256,8 +2258,7 @@ public class Loan extends AbstractPersistable<Long> {
* Ability to regenerate the repayment schedule based on the loans current
* details/state.
*/
public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO,
AppUser currentUser) {
public void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO, AppUser currentUser) {
final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO);
@@ -2489,15 +2490,13 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final AppUser currentUser) {
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, waiveInterestTransaction.getTransactionDate());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
@@ -2523,7 +2522,7 @@ public class Loan extends AbstractPersistable<Long> {
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateAccountStatus(event);
validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(event, repaymentTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDate(event, repaymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
@@ -2544,8 +2543,7 @@ public class Loan extends AbstractPersistable<Long> {
validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_CHARGE_PAYMENT,
paymentTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
@@ -2936,8 +2934,7 @@ public class Loan extends AbstractPersistable<Long> {
return loanRepaymentScheduleInstallment;
}
public LoanTransaction deriveDefaultInterestWaiverTransaction(
final LocalDateTime createdDate, final AppUser currentUser) {
public LoanTransaction deriveDefaultInterestWaiverTransaction(final LocalDateTime createdDate, final AppUser currentUser) {
final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
@@ -2968,6 +2965,7 @@ public class Loan extends AbstractPersistable<Long> {
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate());
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
@@ -3009,8 +3007,7 @@ public class Loan extends AbstractPersistable<Long> {
}
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final AppUser currentUser) {
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds());
@@ -3031,7 +3028,7 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail;
}
private LoanTransaction findWriteOffTransaction() {
public LoanTransaction findWriteOffTransaction() {
LoanTransaction writeOff = null;
for (final LoanTransaction transaction : this.loanTransactions) {
@@ -3132,8 +3129,7 @@ public class Loan extends AbstractPersistable<Long> {
}
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId,
createdDate, currentUser);
loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId, createdDate, currentUser);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions);
if (!isLastTransaction) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
@@ -3155,8 +3151,7 @@ public class Loan extends AbstractPersistable<Long> {
}
private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final AppUser currentUser) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final AppUser currentUser) {
ChangedTransactionDetail changedTransactionDetail = null;
if (isDisbursementAllowed() && atleastOnceDisbursed()) {
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
@@ -3236,7 +3231,7 @@ public class Loan extends AbstractPersistable<Long> {
}
this.closedOnDate = closureDate.toDate();
loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId,
DateUtils.getLocalDateTimeOfTenant(), currentUser);
DateUtils.getLocalDateTimeOfTenant(), currentUser);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions);
if (!isLastTransaction) {
final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
@@ -3330,7 +3325,7 @@ public class Loan extends AbstractPersistable<Long> {
return status().isSubmittedAndPendingApproval();
}
private boolean isApproved() {
public boolean isApproved() {
return status().isApproved();
}
@@ -4102,8 +4097,8 @@ public class Loan extends AbstractPersistable<Long> {
}
}
private void validateActivityNotBeforeLastTransactionDateForInterestRecalculation(final LoanEvent event, final LocalDate activityDate) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; }
private void validateActivityNotBeforeLastTransactionDate(final LoanEvent event, final LocalDate activityDate) {
if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled() || this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; }
LocalDate lastTransactionDate = getLastUserTransactionDate();
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
if (lastTransactionDate.isAfter(activityDate)) {
@@ -4332,8 +4327,7 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails,
final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final AppUser currentUser) {
final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate());
@@ -4426,8 +4420,7 @@ public class Loan extends AbstractPersistable<Long> {
}
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final AppUser currentUser) {
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final AppUser currentUser) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
/*
@@ -4448,7 +4441,7 @@ public class Loan extends AbstractPersistable<Long> {
}
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO,
final AppUser currentUser) {
final AppUser currentUser) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
return processTransactions();
@@ -4478,8 +4471,7 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail;
}
private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO,
final AppUser currentUser) {
private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) {
LocalDate lastTransactionDate = getLastUserTransactionDate();
generatorDTO.setLastTransactionDate(lastTransactionDate);
@@ -4897,4 +4889,16 @@ public class Loan extends AbstractPersistable<Long> {
return loanCharges;
}
public void setGuaranteeAmount(BigDecimal guaranteeAmountDerived) {
this.guaranteeAmountDerived = guaranteeAmountDerived;
}
public void updateGuaranteeAmount(BigDecimal guaranteeAmount) {
this.guaranteeAmountDerived = getGuaranteeAmount().add(guaranteeAmount);
}
public BigDecimal getGuaranteeAmount() {
return this.guaranteeAmountDerived == null ? BigDecimal.ZERO : this.guaranteeAmountDerived;
}
}
@@ -44,7 +44,9 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
@@ -83,6 +85,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
private final LoanAccrualWritePlatformService accrualWritePlatformService;
private final PlatformSecurityContext context;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -95,7 +98,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanAccrualWritePlatformService accrualWritePlatformService, final PlatformSecurityContext context) {
final LoanAccrualWritePlatformService accrualWritePlatformService, final PlatformSecurityContext context,
final BusinessEventNotifierService businessEventNotifierService) {
this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
@@ -112,6 +116,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
this.accrualWritePlatformService = accrualWritePlatformService;
this.context = context;
this.businessEventNotifierService = businessEventNotifierService;
}
@Transactional
@@ -215,6 +220,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, newRepaymentTransaction);
builderResult.withEntityId(newRepaymentTransaction.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
@@ -100,13 +100,13 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false)
private final Date createdDate;
@ManyToOne
@JoinColumn(name="appuser_id", nullable = true)
@JoinColumn(name = "appuser_id", nullable = true)
private final AppUser appUser;
@LazyCollection(LazyCollectionOption.FALSE)
@@ -137,7 +137,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId,
createdDate, appUser);
createdDate, appUser);
}
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
@@ -148,9 +148,9 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType,
final LocalDateTime createdDate, final AppUser appUser){
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId,
createdDate, appUser);
final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId, createdDate,
appUser);
}
public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
@@ -183,7 +183,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final LocalDateTime createdDate, final AppUser appUser) {
final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
.toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
@@ -191,7 +191,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final LocalDateTime createdDate, final AppUser appUser) {
final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
.toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
@@ -199,7 +199,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final LocalDateTime createdDate, final AppUser appUser) {
final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate.toDateTimeAtStartOfDay()
.toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
@@ -209,15 +209,15 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId,
createdDate, appUser);
createdDate, appUser);
}
public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId,
new LocalDateTime(loanTransaction.createdDate), loanTransaction.appUser);
loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId, new LocalDateTime(
loanTransaction.createdDate), loanTransaction.appUser);
}
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
@@ -274,7 +274,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId,
final LocalDateTime createdDate, final AppUser appUser) {
final LocalDateTime createdDate, final AppUser appUser) {
return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId, createdDate, appUser);
}
@@ -621,4 +621,26 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public void manuallyAdjustedOrReversed() {
this.manuallyAdjustedOrReversed = true;
}
private LocalDate getCreatedDate() {
return new LocalDate(this.createdDate);
}
public boolean isLastTransaction(final LoanTransaction loanTransaction) {
boolean isLatest = false;
if (loanTransaction != null) {
isLatest = this.getTransactionDate().isBefore(loanTransaction.getTransactionDate())
|| (this.getTransactionDate().isEqual(loanTransaction.getTransactionDate()) && this.getCreatedDate().isBefore(
loanTransaction.getCreatedDate()));
}
return isLatest;
}
public boolean isLatestTransaction(final LoanTransaction loanTransaction) {
boolean isLatest = false;
if (loanTransaction != null) {
isLatest = this.getCreatedDate().isBefore(loanTransaction.getCreatedDate());
}
return isLatest;
}
}
@@ -47,4 +47,11 @@ public class GuarantorConstants {
return this.value;
}
}
public static final String GUARANTOR_SELF_GUARANTEE_ERROR = "min.self.guarantee.required";
public static final String GUARANTOR_EXTERNAL_GUARANTEE_ERROR = "min.external.guarantee.required";
public static final String GUARANTOR_MANDATORY_GUARANTEE_ERROR = "mandated.guarantee.required";
public static final String GUARANTOR_INSUFFICIENT_BALANCE_ERROR = "insufficient.balance";
public static final String GUARANTOR_NOT_ACTIVE_ERROR = "not.active";
}
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.data;
import java.math.BigDecimal;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
public class GuarantorDTO {
private BigDecimal paidAmount;
private Loan loan;
}
@@ -345,4 +345,12 @@ public class Guarantor extends AbstractPersistable<Long> {
}
return hasGuarantee;
}
public boolean isSelfGuarantee() {
boolean isSelf = false;
if (isExistingCustomer() && getEntityId().equals(getClientId())) {
isSelf = true;
}
return isSelf;
}
}
@@ -20,6 +20,7 @@ import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.mifosplatform.portfolio.account.domain.AccountAssociations;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -46,7 +47,7 @@ public class GuarantorFundingDetails extends AbstractPersistable<Long> {
@Column(name = "amount_remaining_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountRemaining;
@Column(name = "amount_transfered_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountTransfered;
@@ -70,12 +71,62 @@ public class GuarantorFundingDetails extends AbstractPersistable<Long> {
public void updateStatus(final GuarantorFundStatusType guarantorFundStatusType) {
this.status = guarantorFundStatusType.getValue();
}
public GuarantorFundStatusType getStatus(){
public GuarantorFundStatusType getStatus() {
return GuarantorFundStatusType.fromInt(this.status);
}
public SavingsAccount getLinkedSavingsAccount(){
public SavingsAccount getLinkedSavingsAccount() {
return accountAssociations.linkedSavingsAccount();
}
public Loan getLoanAccount() {
return this.guarantor.getLoan();
}
public BigDecimal getAmount() {
return this.amount;
}
public BigDecimal getAmountReleased() {
return this.amountReleased == null ? BigDecimal.ZERO : this.amountReleased;
}
public BigDecimal getAmountRemaining() {
return this.amountRemaining == null ? BigDecimal.ZERO : this.amountRemaining;
}
public BigDecimal getAmountTransfered() {
return this.amountTransfered == null ? BigDecimal.ZERO : this.amountTransfered;
}
public void releaseFunds(final BigDecimal amount) {
this.amountReleased = getAmountReleased().add(amount);
this.amountRemaining = getAmountRemaining().subtract(amount);
if (this.amountRemaining.compareTo(BigDecimal.ZERO) == 0) {
this.updateStatus(GuarantorFundStatusType.COMPLETED);
}
}
public void undoReleaseFunds(final BigDecimal amount) {
this.amountReleased = getAmountReleased().subtract(amount);
this.amountRemaining = getAmountRemaining().add(amount);
if (getStatus().isCompleted() && this.amountRemaining.compareTo(BigDecimal.ZERO) == 1) {
this.updateStatus(GuarantorFundStatusType.ACTIVE);
}
}
public void withdrawFunds(final BigDecimal amount) {
this.amountTransfered = amount;
}
public void addGuarantorFundingTransactions(final GuarantorFundingTransaction guarantorFundingTransaction) {
this.guarantorFundingTransactions.add(guarantorFundingTransaction);
}
public void undoAllTransactions() {
for (GuarantorFundingTransaction fundingTransaction : this.guarantorFundingTransactions) {
fundingTransaction.reverseTransaction();
}
}
}
@@ -0,0 +1,12 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface GuarantorFundingRepository extends JpaRepository<GuarantorFundingDetails, Long>,
JpaSpecificationExecutor<GuarantorFundingDetails> {}
@@ -5,10 +5,13 @@
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.domain;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -24,14 +27,35 @@ public class GuarantorFundingTransaction extends AbstractPersistable<Long> {
private GuarantorFundingDetails guarantorFundingDetails;
@ManyToOne
@JoinColumn(name = "loan_transaction_id", nullable = false)
@JoinColumn(name = "loan_transaction_id", nullable = true)
private LoanTransaction loanTransaction;
@ManyToOne
@OneToOne
@JoinColumn(name = "deposit_on_hold_transaction_id", nullable = false)
private DepositAccountOnHoldTransaction depositAccountOnHoldTransaction;
@Column(name = "is_reversed", nullable = false)
private boolean reversed;
protected GuarantorFundingTransaction() {}
public GuarantorFundingTransaction(final GuarantorFundingDetails guarantorFundingDetails, final LoanTransaction loanTransaction,
final DepositAccountOnHoldTransaction depositAccountOnHoldTransaction) {
this.depositAccountOnHoldTransaction = depositAccountOnHoldTransaction;
this.guarantorFundingDetails = guarantorFundingDetails;
this.loanTransaction = loanTransaction;
this.reversed = false;
}
public void reverseTransaction() {
if (!this.reversed) {
this.reversed = true;
BigDecimal amountForReverse = this.depositAccountOnHoldTransaction.getAmount();
this.depositAccountOnHoldTransaction.reverseTransaction();
if (this.depositAccountOnHoldTransaction.getTransactionType().isRelease()) {
this.guarantorFundingDetails.undoReleaseFunds(amountForReverse);
}
}
}
}
@@ -0,0 +1,21 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GuarantorFundingTransactionRepository extends JpaRepository<GuarantorFundingTransaction, Long>,
JpaSpecificationExecutor<GuarantorFundingTransaction> {
@Query("from GuarantorFundingTransaction ft where ft.loanTransaction.id in (:loanTransactions)")
List<GuarantorFundingTransaction> fetchGuarantorFundingTransactions(@Param("loanTransactions") List<Long> loanTransactions);
}
@@ -0,0 +1,22 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.service;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
public interface GuarantorDomainService {
void releaseGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate);
void validateGuarantorBusinessRules(Loan loan);
void assignGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate);
void transaferFundsFromGuarantor(Loan loan);
}
@@ -0,0 +1,558 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.guarantor.service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.account.domain.AccountTransferDetails;
import org.mifosplatform.portfolio.account.domain.AccountTransferType;
import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.service.BusinessEventListner;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.guarantor.GuarantorConstants;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.Guarantor;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductGuaranteeDetails;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.savings.domain.DepositAccountOnHoldTransaction;
import org.mifosplatform.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.stereotype.Service;
@Service
public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private final GuarantorRepository guarantorRepository;
private final GuarantorFundingRepository guarantorFundingRepository;
private final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository;
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final BusinessEventNotifierService businessEventNotifierService;
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
private final Map<Long, Long> releaseLoanIds = new HashMap<>(2);
private final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
@Autowired
public GuarantorDomainServiceImpl(final GuarantorRepository guarantorRepository,
final GuarantorFundingRepository guarantorFundingRepository,
final GuarantorFundingTransactionRepository guarantorFundingTransactionRepository,
final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
final BusinessEventNotifierService businessEventNotifierService,
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
this.guarantorRepository = guarantorRepository;
this.guarantorFundingRepository = guarantorFundingRepository;
this.guarantorFundingTransactionRepository = guarantorFundingTransactionRepository;
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
this.businessEventNotifierService = businessEventNotifierService;
this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
}
@PostConstruct
public void addListners() {
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED, new ValidateOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED, new HoldFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, new UndoAllFundTransactions());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL,
new ReverseAllFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_TRANSACTION,
new ReverseFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
new ReleaseFundsOnBusinessEvent());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, new ReleaseAllFunds());
this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF,
new ReverseFundsOnBusinessEvent());
}
@Override
public void validateGuarantorBusinessRules(Loan loan) {
LoanProduct loanProduct = loan.loanProduct();
BigDecimal principal = loan.getPrincpal().getAmount();
if (loanProduct.isHoldGuaranteeFundsEnabled()) {
LoanProductGuaranteeDetails guaranteeData = loanProduct.getLoanProductGuaranteeDetails();
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
BigDecimal mandatoryAmount = principal.multiply(guaranteeData.getMandatoryGuarantee()).divide(BigDecimal.valueOf(100));
BigDecimal minSelfAmount = principal.multiply(guaranteeData.getMinimumGuaranteeFromOwnFunds()).divide(BigDecimal.valueOf(100));
BigDecimal minExtGuarantee = principal.multiply(guaranteeData.getMinimumGuaranteeFromGuarantor()).divide(
BigDecimal.valueOf(100));
BigDecimal actualAmount = BigDecimal.ZERO;
BigDecimal actualSelfAmount = BigDecimal.ZERO;
BigDecimal actualExtGuarantee = BigDecimal.ZERO;
for (Guarantor guarantor : existGuarantorList) {
List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
if (guarantorFundingDetails.getStatus().isActive() || guarantorFundingDetails.getStatus().isWithdrawn()
|| guarantorFundingDetails.getStatus().isCompleted()) {
if (guarantor.isSelfGuarantee()) {
actualSelfAmount = actualSelfAmount.add(guarantorFundingDetails.getAmount()).subtract(
guarantorFundingDetails.getAmountTransfered());
} else {
actualExtGuarantee = actualExtGuarantee.add(guarantorFundingDetails.getAmount()).subtract(
guarantorFundingDetails.getAmountTransfered());
}
}
}
}
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
if (actualSelfAmount.compareTo(minSelfAmount) == -1) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_SELF_GUARANTEE_ERROR,
minSelfAmount);
}
if (actualExtGuarantee.compareTo(minExtGuarantee) == -1) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_EXTERNAL_GUARANTEE_ERROR,
minExtGuarantee);
}
actualAmount = actualAmount.add(actualExtGuarantee).add(actualSelfAmount);
if (actualAmount.compareTo(mandatoryAmount) == -1) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_MANDATORY_GUARANTEE_ERROR,
mandatoryAmount);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@Override
public void assignGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) {
if (guarantorFundingDetails.getStatus().isActive()) {
SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
savingsAccount.holdFunds(guarantorFundingDetails.getAmount());
if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
savingsAccount.getId());
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount,
guarantorFundingDetails.getAmount(), transactionDate);
GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null,
onHoldTransaction);
guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
this.depositAccountOnHoldTransactionRepository.save(onHoldTransaction);
}
}
@Override
public void releaseGuarantor(final GuarantorFundingDetails guarantorFundingDetails, final LocalDate transactionDate) {
BigDecimal amoutForWithdraw = guarantorFundingDetails.getAmountRemaining();
if (amoutForWithdraw.compareTo(BigDecimal.ZERO) == 1 && (guarantorFundingDetails.getStatus().isActive())) {
SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
savingsAccount.releaseFunds(amoutForWithdraw);
DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, amoutForWithdraw,
transactionDate);
GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails, null,
onHoldTransaction);
guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
guarantorFundingDetails.releaseFunds(amoutForWithdraw);
guarantorFundingDetails.withdrawFunds(amoutForWithdraw);
guarantorFundingDetails.getLoanAccount().updateGuaranteeAmount(amoutForWithdraw.negate());
this.depositAccountOnHoldTransactionRepository.save(onHoldTransaction);
this.guarantorFundingRepository.save(guarantorFundingDetails);
}
}
@Override
public void transaferFundsFromGuarantor(final Loan loan) {
if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) != 1) { return; }
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = true;
LocalDate transactionDate = LocalDate.now();
PortfolioAccountType fromAccountType = PortfolioAccountType.SAVINGS;
PortfolioAccountType toAccountType = PortfolioAccountType.LOAN;
final Long toAccountId = loan.getId();
final String description = "Payment from guarantor savings";
final Locale locale = null;
final DateTimeFormatter fmt = null;
final PaymentDetail paymentDetail = null;
final Integer fromTransferType = null;
final Integer toTransferType = null;
final Long chargeId = null;
final Integer loanInstallmentNumber = null;
final Integer transferType = AccountTransferType.LOAN_REPAYMENT.getValue();
final AccountTransferDetails accountTransferDetails = null;
final String noteText = null;
final String txnExternalId = null;
final SavingsAccount toSavingsAccount = null;
Long loanId = loan.getId();
for (Guarantor guarantor : existGuarantorList) {
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
if (guarantorFundingDetails.getStatus().isActive()) {
final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
final Long fromAccountId = fromSavingsAccount.getId();
releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
try {
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate,
guarantorFundingDetails.getAmountRemaining(), fromAccountType, toAccountType, fromAccountId, toAccountId,
description, locale, fmt, paymentDetail, fromTransferType, toTransferType, chargeId, loanInstallmentNumber,
transferType, accountTransferDetails, noteText, txnExternalId, loan, toSavingsAccount, fromSavingsAccount,
isRegularTransaction, isExceptionForBalanceCheck);
transferAmount(accountTransferDTO);
} finally {
releaseLoanIds.remove(loanId);
}
}
}
}
}
/**
* @param accountTransferDTO
*/
private void transferAmount(final AccountTransferDTO accountTransferDTO) {
try {
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} catch (final InsufficientAccountBalanceException e) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
accountTransferDTO.getFromAccountId(), accountTransferDTO.getToAccountId());
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
private void reverseAllFundTransaction(final Loan loan) {
if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
List<GuarantorFundingDetails> guarantorFundingDetailList = new ArrayList<>();
for (Guarantor guarantor : existGuarantorList) {
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
guarantorFundingDetails.undoAllTransactions();
guarantorFundingDetailList.add(guarantorFundingDetails);
}
}
if (!guarantorFundingDetailList.isEmpty()) {
loan.setGuaranteeAmount(null);
this.guarantorFundingRepository.save(guarantorFundingDetailList);
}
}
}
private void holdGuarantorFunds(final Loan loan) {
if (loan.loanProduct().isHoldGuaranteeFundsEnabled()) {
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
List<GuarantorFundingDetails> guarantorFundingDetailList = new ArrayList<>();
List<DepositAccountOnHoldTransaction> onHoldTransactions = new ArrayList<>();
BigDecimal totalGuarantee = BigDecimal.ZERO;
List<Long> insufficientBalanceIds = new ArrayList<>();
for (Guarantor guarantor : existGuarantorList) {
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
if (guarantorFundingDetails.getStatus().isActive()) {
SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
savingsAccount.holdFunds(guarantorFundingDetails.getAmount());
totalGuarantee = totalGuarantee.add(guarantorFundingDetails.getAmount());
DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.hold(savingsAccount,
guarantorFundingDetails.getAmount(), loan.getApprovedOnDate());
onHoldTransactions.add(onHoldTransaction);
GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails,
null, onHoldTransaction);
guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
guarantorFundingDetailList.add(guarantorFundingDetails);
if (savingsAccount.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
insufficientBalanceIds.add(savingsAccount.getId());
}
}
}
}
if (!insufficientBalanceIds.isEmpty()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_INSUFFICIENT_BALANCE_ERROR,
insufficientBalanceIds);
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
loan.setGuaranteeAmount(totalGuarantee);
if (!guarantorFundingDetailList.isEmpty()) {
this.depositAccountOnHoldTransactionRepository.save(onHoldTransactions);
this.guarantorFundingRepository.save(guarantorFundingDetailList);
}
}
}
private void releaseGuarantorFunds(final LoanTransaction loanTransaction) {
final Loan loan = loanTransaction.getLoan();
if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
List<GuarantorFundingDetails> externalGuarantorList = new ArrayList<>();
List<GuarantorFundingDetails> selfGuarantorList = new ArrayList<>();
BigDecimal selfGuarantee = BigDecimal.ZERO;
BigDecimal guarantorGuarantee = BigDecimal.ZERO;
for (Guarantor guarantor : existGuarantorList) {
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
if (guarantorFundingDetails.getStatus().isActive()) {
if (guarantor.isSelfGuarantee()) {
selfGuarantorList.add(guarantorFundingDetails);
selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining());
} else if (guarantor.isExistingCustomer()) {
externalGuarantorList.add(guarantorFundingDetails);
guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining());
}
}
}
}
BigDecimal amountForRelease = loanTransaction.getPrincipalPortion();
BigDecimal totalGuaranteeAmount = loan.getGuaranteeAmount();
BigDecimal principal = loan.getPrincpal().getAmount();
amountForRelease = amountForRelease.multiply(totalGuaranteeAmount).divide(principal);
List<DepositAccountOnHoldTransaction> accountOnHoldTransactions = new ArrayList<>();
BigDecimal amountLeft = calculateAndRelaseGuarantorFunds(externalGuarantorList, guarantorGuarantee, amountForRelease,
loanTransaction, accountOnHoldTransactions);
if (amountLeft.compareTo(BigDecimal.ZERO) == 1) {
calculateAndRelaseGuarantorFunds(selfGuarantorList, selfGuarantee, amountLeft, loanTransaction, accountOnHoldTransactions);
externalGuarantorList.addAll(selfGuarantorList);
}
if (!externalGuarantorList.isEmpty()) {
this.depositAccountOnHoldTransactionRepository.save(accountOnHoldTransactions);
this.guarantorFundingRepository.save(externalGuarantorList);
}
}
}
private void releaseAllGuarantors(final LoanTransaction loanTransaction) {
Loan loan = loanTransaction.getLoan();
if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) == 1) {
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
List<GuarantorFundingDetails> saveGuarantorFundingDetails = new ArrayList<>();
List<DepositAccountOnHoldTransaction> onHoldTransactions = new ArrayList<>();
for (Guarantor guarantor : existGuarantorList) {
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
BigDecimal amoutForRelease = guarantorFundingDetails.getAmountRemaining();
if (amoutForRelease.compareTo(BigDecimal.ZERO) == 1 && (guarantorFundingDetails.getStatus().isActive())) {
SavingsAccount savingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
savingsAccount.releaseFunds(amoutForRelease);
DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount,
amoutForRelease, loanTransaction.getTransactionDate());
onHoldTransactions.add(onHoldTransaction);
GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(guarantorFundingDetails,
loanTransaction, onHoldTransaction);
guarantorFundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
guarantorFundingDetails.releaseFunds(amoutForRelease);
saveGuarantorFundingDetails.add(guarantorFundingDetails);
}
}
}
if (!saveGuarantorFundingDetails.isEmpty()) {
this.depositAccountOnHoldTransactionRepository.save(onHoldTransactions);
this.guarantorFundingRepository.save(saveGuarantorFundingDetails);
}
}
}
private void completeGuarantorFund(final LoanTransaction loanTransaction) {
Loan loan = loanTransaction.getLoan();
GuarantorFundingDetails guarantorFundingDetails = this.guarantorFundingRepository.findOne(releaseLoanIds.get(loan.getId()));
if (guarantorFundingDetails != null) {
BigDecimal amountForRelease = loanTransaction.getAmount(loan.getCurrency()).getAmount();
BigDecimal guarantorGuarantee = amountForRelease;
List<GuarantorFundingDetails> guarantorList = Arrays.asList(guarantorFundingDetails);
final List<DepositAccountOnHoldTransaction> accountOnHoldTransactions = new ArrayList<>();
calculateAndRelaseGuarantorFunds(guarantorList, guarantorGuarantee, amountForRelease, loanTransaction,
accountOnHoldTransactions);
this.depositAccountOnHoldTransactionRepository.save(accountOnHoldTransactions);
this.guarantorFundingRepository.save(guarantorFundingDetails);
}
}
private BigDecimal calculateAndRelaseGuarantorFunds(List<GuarantorFundingDetails> guarantorList, BigDecimal totalGuaranteeAmount,
BigDecimal amountForRelease, LoanTransaction loanTransaction,
final List<DepositAccountOnHoldTransaction> accountOnHoldTransactions) {
BigDecimal amountLeft = amountForRelease;
for (GuarantorFundingDetails fundingDetails : guarantorList) {
BigDecimal guarantorAmount = amountForRelease.multiply(fundingDetails.getAmountRemaining()).divide(totalGuaranteeAmount,
roundingMode);
if (fundingDetails.getAmountRemaining().compareTo(guarantorAmount) < 1) {
guarantorAmount = fundingDetails.getAmountRemaining();
}
fundingDetails.releaseFunds(guarantorAmount);
SavingsAccount savingsAccount = fundingDetails.getLinkedSavingsAccount();
savingsAccount.releaseFunds(guarantorAmount);
DepositAccountOnHoldTransaction onHoldTransaction = DepositAccountOnHoldTransaction.release(savingsAccount, guarantorAmount,
loanTransaction.getTransactionDate());
accountOnHoldTransactions.add(onHoldTransaction);
GuarantorFundingTransaction guarantorFundingTransaction = new GuarantorFundingTransaction(fundingDetails, loanTransaction,
onHoldTransaction);
fundingDetails.addGuarantorFundingTransactions(guarantorFundingTransaction);
amountLeft = amountLeft.subtract(guarantorAmount);
}
return amountLeft;
}
private void reverseTransaction(final List<Long> loanTransactionIds) {
List<GuarantorFundingTransaction> fundingTransactions = this.guarantorFundingTransactionRepository
.fetchGuarantorFundingTransactions(loanTransactionIds);
for (GuarantorFundingTransaction fundingTransaction : fundingTransactions) {
fundingTransaction.reverseTransaction();
}
if (!fundingTransactions.isEmpty()) {
this.guarantorFundingTransactionRepository.save(fundingTransactions);
}
}
private class ValidateOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof Loan) {
Loan loan = (Loan) businessEventEntity;
validateGuarantorBusinessRules(loan);
}
}
}
private class HoldFundsOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof Loan) {
Loan loan = (Loan) businessEventEntity;
holdGuarantorFunds(loan);
}
}
}
private class ReleaseFundsOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
if (releaseLoanIds.containsKey(loanTransaction.getLoan().getId())) {
completeGuarantorFund(loanTransaction);
} else {
releaseGuarantorFunds(loanTransaction);
}
}
}
}
private class ReverseFundsOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
List<Long> reersedTransactions = new ArrayList<>(1);
reersedTransactions.add(loanTransaction.getId());
reverseTransaction(reersedTransactions);
}
}
}
private class ReverseAllFundsOnBusinessEvent implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof Loan) {
Loan loan = (Loan) businessEventEntity;
List<Long> reersedTransactions = new ArrayList<>(1);
reersedTransactions.addAll(loan.findExistingTransactionIds());
reverseTransaction(reersedTransactions);
}
}
}
private class UndoAllFundTransactions implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof Loan) {
Loan loan = (Loan) businessEventEntity;
reverseAllFundTransaction(loan);
}
}
}
private class ReleaseAllFunds implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(@SuppressWarnings("unused") AbstractPersistable<Long> businessEventEntity) {}
@Override
public void businessEventWasExecuted(AbstractPersistable<Long> businessEventEntity) {
if (businessEventEntity instanceof LoanTransaction) {
LoanTransaction loanTransaction = (LoanTransaction) businessEventEntity;
releaseAllGuarantors(loanTransaction);
}
}
}
}
@@ -10,11 +10,15 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.account.domain.AccountAssociationType;
@@ -57,13 +61,14 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
private final GuarantorDomainService guarantorDomainService;
@Autowired
public GuarantorWritePlatformServiceJpaRepositoryIImpl(final LoanRepositoryWrapper loanRepositoryWrapper,
final GuarantorRepository guarantorRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
final StaffRepositoryWrapper staffRepositoryWrapper, final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
final AccountAssociationsRepository accountAssociationsRepository) {
final AccountAssociationsRepository accountAssociationsRepository, final GuarantorDomainService guarantorDomainService) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.clientRepositoryWrapper = clientRepositoryWrapper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
@@ -72,6 +77,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.guarantorDomainService = guarantorDomainService;
}
@Override
@@ -87,6 +93,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
final Collection<Guarantor> existGuarantorList) {
try {
guarantorCommand.validateForCreate();
validateLoanStatus(loan);
final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>();
AccountAssociations accountAssociations = null;
if (guarantorCommand.getSavingsId() != null) {
@@ -97,6 +104,11 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations,
GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount());
guarantorFundingDetails.add(fundingDetails);
if (loan.isDisbursed() || loan.isApproved()
|| (loan.getGuaranteeAmount() != null || loan.loanProduct().isHoldGuaranteeFundsEnabled())) {
this.guarantorDomainService.assignGuarantor(fundingDetails, LocalDate.now());
loan.updateGuaranteeAmount(fundingDetails.getAmount());
}
}
final Long clientRelationshipId = guarantorCommand.getClientRelationshipTypeId();
@@ -162,6 +174,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
guarantorCommand.validateForUpdate();
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
validateLoanStatus(loan);
final Guarantor guarantorForUpdate = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
if (guarantorForUpdate == null) { throw new GuarantorNotFoundException(loanId, guarantorId); }
@@ -214,27 +227,42 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
@Transactional
public CommandProcessingResult removeGuarantor(final Long loanId, final Long guarantorId, final Long guarantorFundingId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
validateLoanStatus(loan);
final Guarantor guarantorForDelete = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
if (guarantorForDelete == null || (guarantorFundingId == null && !guarantorForDelete.getGuarantorFundDetails().isEmpty())) { throw new GuarantorNotFoundException(
loanId, guarantorId, guarantorFundingId); }
return removeGuarantor(guarantorForDelete, loanId, guarantorFundingId);
CommandProcessingResult commandProcessingResult = removeGuarantor(guarantorForDelete, loanId, guarantorFundingId);
this.guarantorDomainService.validateGuarantorBusinessRules(loan);
return commandProcessingResult;
}
private CommandProcessingResult removeGuarantor(final Guarantor guarantorForDelete, final Long loanId, final Long guarantorFundingId) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("Guarantor");
if (guarantorFundingId == null) {
if (!guarantorForDelete.isActive()) {
baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
}
guarantorForDelete.updateStatus(false);
} else {
GuarantorFundingDetails guarantorFundingDetails = guarantorForDelete.getGuarantorFundingDetail(guarantorFundingId);
if (guarantorFundingDetails == null) { throw new GuarantorNotFoundException(loanId, guarantorForDelete.getId(),
guarantorFundingId); }
if (!guarantorFundingDetails.getStatus().isActive()) {
baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
}
GuarantorFundStatusType fundStatusType = GuarantorFundStatusType.DELETED;
if (guarantorForDelete.getLoan().isDisbursed()) {
if (guarantorForDelete.getLoan().isDisbursed() || guarantorForDelete.getLoan().isApproved()) {
fundStatusType = GuarantorFundStatusType.WITHDRAWN;
this.guarantorDomainService.releaseGuarantor(guarantorFundingDetails, LocalDate.now());
}
guarantorForDelete.updateStatus(guarantorFundingDetails, fundStatusType);
}
this.guarantorRepository.save(guarantorForDelete);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
this.guarantorRepository.saveAndFlush(guarantorForDelete);
CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder()
.withEntityId(guarantorForDelete.getId()).withLoanId(guarantorForDelete.getLoanId())
.withOfficeId(guarantorForDelete.getOfficeId());
@@ -244,7 +272,6 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
return commandProcessingResultBuilder.build();
}
private void validateGuarantorBusinessRules(final Guarantor guarantor) {
// validate guarantor conditions
if (guarantor.isExistingCustomer()) {
@@ -266,11 +293,20 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
}
}
private void validateLoanStatus(Loan loan) {
if (!loan.status().isActiveOrAwaitingApprovalOrDisbursal()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.closed");
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
private void handleGuarantorDataIntegrityIssues(final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.guarantor.unknown.data.integrity.issue",
"Unknown data integrity issue with resource Guarantor: " + realCause.getMessage());
}
}
@@ -0,0 +1,30 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RecoverFromGuarantorCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public RecoverFromGuarantorCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return this.writePlatformService.recoverFromGuarantor(command.getLoanId());
}
}
@@ -47,6 +47,8 @@ import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
import org.mifosplatform.portfolio.collateral.service.CollateralAssembler;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
@@ -128,6 +130,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
private final LoanAccountDomainService loanAccountDomainService;
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -144,7 +147,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanReadPlatformService loanReadPlatformService, final LoanAccountDomainService loanAccountDomainService,
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) {
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
final BusinessEventNotifierService businessEventNotifierService) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -171,7 +175,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.loanReadPlatformService = loanReadPlatformService;
this.loanAccountDomainService = loanAccountDomainService;
this.accountNumberFormatRepository = accountNumberFormatRepository;
this.businessEventNotifierService = businessEventNotifierService;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -751,6 +755,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
changes.put("note", noteText);
this.noteRepository.save(note);
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_APPROVED, loan);
}
return new CommandProcessingResultBuilder() //
@@ -794,6 +800,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_APPROVAL, loan);
}
return new CommandProcessingResultBuilder() //
@@ -81,4 +81,6 @@ public interface LoanWritePlatformService {
void recalculateInterest();
CommandProcessingResult recoverFromGuarantor(Long loanId);
}
@@ -92,7 +92,9 @@ import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDi
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -124,6 +126,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.mifosplatform.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
@@ -193,6 +196,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
private final AccountAssociationsRepository accountAssociationRepository;
private final AccountTransferDetailRepository accountTransferDetailRepository;
private final BusinessEventNotifierService businessEventNotifierService;
private final GuarantorDomainService guarantorDomainService;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -217,7 +222,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper,
final AccountAssociationsRepository accountAssociationRepository,
final AccountTransferDetailRepository accountTransferDetailRepository) {
final AccountTransferDetailRepository accountTransferDetailRepository,
final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -250,6 +256,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanApplicationCommandFromApiJsonHelper = loanApplicationCommandFromApiJsonHelper;
this.accountAssociationRepository = accountAssociationRepository;
this.accountTransferDetailRepository = accountTransferDetailRepository;
this.businessEventNotifierService = businessEventNotifierService;
this.guarantorDomainService = guarantorDomainService;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -383,10 +391,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction);
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
@@ -625,11 +635,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.retriveLoanLinkedAssociation(loan.getId());
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loan.getId(),
"Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(),
entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null,
fromSavingsAccount, isRegularTransaction);
fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
updateRecurringCalendarDatesForInterestRecalculation(loan);
@@ -689,6 +700,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_DISBURSAL, loan);
}
return new CommandProcessingResultBuilder() //
@@ -899,6 +911,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_TRANSACTION, transactionToAdjust);
if (newTransactionDetail.isRepayment() && newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, newTransactionDetail);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -1083,6 +1099,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_WRITTEN_OFF, writeoff);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(writeoff.getId()) //
@@ -1643,10 +1660,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction);
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return new CommandProcessingResultBuilder() //
@@ -1673,12 +1692,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, loan.getId());
}
final SavingsAccount fromSavingsAccount = null;
final boolean isExceptionForBalanceCheck = false;
final boolean isRegularTransaction = true;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount.getAmount(),
PortfolioAccountType.LOAN, PortfolioAccountType.SAVINGS, loan.getId(), portfolioAccountData.accountId(),
"Loan Disbursement", locale, fmt, paymentDetail, LoanTransactionType.DISBURSEMENT.getValue(), null, null, null,
AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, txnExternalId, loan, null, fromSavingsAccount,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
@@ -1703,12 +1723,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.getLoanId());
}
final SavingsAccount fromSavingsAccount = null;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null,
null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null,
null, null, null, null, fromSavingsAccount, isRegularTransaction);
null, null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
transferFeeCharge(sb, accountTransferDTO);
}
}
@@ -1716,12 +1737,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanLinkedAssociation(chargeData.getLoanId());
final SavingsAccount fromSavingsAccount = null;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
transferFeeCharge(sb, accountTransferDTO);
}
}
@@ -2377,7 +2399,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
LoanTransaction writeOffTransaction = loan.findWriteOffTransaction();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -2412,6 +2434,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan);
if (writeOffTransaction != null) {
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF, writeOffTransaction);
}
return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
@@ -2543,10 +2568,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction);
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return transaction;
@@ -2612,6 +2639,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.recalculateAccruals(loan);
}
@Override
public CommandProcessingResult recoverFromGuarantor(final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
this.guarantorDomainService.transaferFundsFromGuarantor(loan);
return new CommandProcessingResultBuilder().withLoanId(loanId).build();
}
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) {
@@ -101,6 +101,7 @@ public class SavingsApiConstants {
public static final String enforceMinRequiredBalanceParamName = "enforceMinRequiredBalance";
public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation";
public static final String withdrawBalanceParamName = "withdrawBalance";
public static final String onHoldFundsParamName = "onHoldFunds";
// transaction parameters
public static final String transactionDateParamName = "transactionDate";
@@ -138,6 +139,11 @@ public class SavingsApiConstants {
public static final String charges = "charges";
public static final String linkedAccount = "linkedAccount";
// Savings on hold transaction
public static final String onHoldTransactionTypeParamName = "transactionType";
public static final String onHoldTransactionDateParamName = "transactionDate";
public static final String onHoldReversedParamName = "reversed";
public static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName,
@@ -195,7 +201,7 @@ public class SavingsApiConstants {
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName,
feeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions",
"interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee"));
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee", onHoldFundsParamName));
public static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
@@ -224,4 +230,7 @@ public class SavingsApiConstants {
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
public static final Set<String> SAVINGS_ACCOUNT_ON_HOLD_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
amountParamName, onHoldTransactionTypeParamName, onHoldTransactionDateParamName, onHoldReversedParamName));
}
@@ -11,16 +11,16 @@ public class SavingsTransactionBooleanValues {
private final boolean isRegularTransaction;
private final boolean isApplyWithdrawFee;
private final boolean isInterestTransfer;
private final boolean isWithdrawBalance;
private final boolean isExceptionForBalanceCheck;
public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction,
final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isWithdrawBalance) {
final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) {
this.isAccountTransfer = isAccountTransfer;
this.isRegularTransaction = isRegularTransaction;
this.isApplyWithdrawFee = isApplyWithdrawFee;
this.isInterestTransfer = isInterestTransfer;
this.isWithdrawBalance = isWithdrawBalance;
this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
}
public boolean isAccountTransfer() {
@@ -39,8 +39,8 @@ public class SavingsTransactionBooleanValues {
return this.isInterestTransfer;
}
public boolean isWithdrawBalance() {
return this.isWithdrawBalance;
public boolean isExceptionForBalanceCheck() {
return this.isExceptionForBalanceCheck;
}
}
@@ -0,0 +1,72 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.DepositAccountOnHoldTransactionData;
import org.mifosplatform.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/savingsaccounts/{savingsId}/onholdtransactions")
@Component
@Scope("singleton")
public class DepositAccountOnHoldFundTransactionsApiResource {
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService;
@Autowired
public DepositAccountOnHoldFundTransactionsApiResource(final PlatformSecurityContext context,
final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper,
final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService) {
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.depositAccountOnHoldTransactionReadPlatformService = depositAccountOnHoldTransactionReadPlatformService;
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAll(@PathParam("savingsId") final Long savingsId,
@QueryParam("guarantorFundingId") final Long guarantorFundingId, @Context final UriInfo uriInfo,
@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder);
final Page<DepositAccountOnHoldTransactionData> transfers = this.depositAccountOnHoldTransactionReadPlatformService.retriveAll(
savingsId, guarantorFundingId, searchParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer
.serialize(settings, transfers, SavingsApiConstants.SAVINGS_ACCOUNT_ON_HOLD_RESPONSE_DATA_PARAMETERS);
}
}
@@ -50,6 +50,7 @@ public class SavingsAccountData {
private final BigDecimal minRequiredBalance;
private final boolean enforceMinRequiredBalance;
private final BigDecimal minBalanceForInterestCalculation;
private final BigDecimal onHoldFunds;
// associations
private final SavingsAccountSummaryData summary;
@@ -83,7 +84,8 @@ public class SavingsAccountData {
final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
final SavingsAccountSummaryData summary, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation) {
final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds) {
final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null;
@@ -105,7 +107,7 @@ public class SavingsAccountData {
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, minBalanceForInterestCalculation);
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds);
}
public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) {
@@ -141,6 +143,7 @@ public class SavingsAccountData {
final BigDecimal minRequiredBalance = null;
final boolean enforceMinRequiredBalance = false;
final BigDecimal minBalanceForInterestCalculation = null;
final BigDecimal onHoldFunds = null;
final SavingsAccountSummaryData summary = null;
final Collection<SavingsAccountTransactionData> transactions = null;
@@ -164,7 +167,7 @@ public class SavingsAccountData {
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, minBalanceForInterestCalculation);
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds);
}
public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo,
@@ -198,6 +201,7 @@ public class SavingsAccountData {
final BigDecimal minRequiredBalance = null;
final boolean enforceMinRequiredBalance = false;
final BigDecimal minBalanceForInterestCalculation = null;
final BigDecimal onHoldFunds = null;
final SavingsAccountSummaryData summary = null;
final Collection<SavingsAccountTransactionData> transactions = null;
@@ -221,7 +225,7 @@ public class SavingsAccountData {
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, minBalanceForInterestCalculation);
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
@@ -253,7 +257,8 @@ public class SavingsAccountData {
template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions,
template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions,
template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft, account.overdraftLimit,
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation);
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation,
account.onHoldFunds);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -275,7 +280,8 @@ public class SavingsAccountData {
transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft, account.overdraftLimit,
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation);
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation,
account.onHoldFunds);
}
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -311,6 +317,7 @@ public class SavingsAccountData {
final BigDecimal minRequiredBalance = null;
final boolean enforceMinRequiredBalance = false;
final BigDecimal minBalanceForInterestCalculation = null;
final BigDecimal onHoldFunds = null;
final SavingsAccountSummaryData summary = null;
final Collection<SavingsAccountTransactionData> transactions = null;
@@ -334,7 +341,7 @@ public class SavingsAccountData {
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, minBalanceForInterestCalculation);
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds);
}
private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId,
@@ -354,7 +361,7 @@ public class SavingsAccountData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions, final boolean allowOverdraft,
final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
final BigDecimal minBalanceForInterestCalculation) {
final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds) {
this.id = id;
this.accountNo = accountNo;
this.depositType = depositType;
@@ -409,6 +416,7 @@ public class SavingsAccountData {
this.minRequiredBalance = minRequiredBalance;
this.enforceMinRequiredBalance = enforceMinRequiredBalance;
this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
this.onHoldFunds = onHoldFunds;
}
private SavingsAccountChargeData getWithdrawalFee() {
@@ -176,11 +176,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
DepositAccountType.SAVINGS_DEPOSIT);
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
isAccountTransfer);
isAccountTransfer, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
@@ -254,11 +254,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
DepositAccountType.SAVINGS_DEPOSIT);
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, transactionAmount,
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
@@ -315,7 +315,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
DepositAccountOnClosureType closureType = DepositAccountOnClosureType.fromInt(closureTypeValue);
if (closureType.isTransferToSavings()) {
final boolean isExceptionForBalanceCheck = false;
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
@@ -323,7 +323,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
@@ -371,6 +371,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
DepositAccountOnClosureType closureType = DepositAccountOnClosureType.fromInt(closureTypeValue);
if (closureType.isTransferToSavings()) {
final boolean isExceptionForBalanceCheck = false;
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
@@ -378,7 +379,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
@@ -8,14 +8,21 @@ package org.mifosplatform.portfolio.savings.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransaction;
import org.mifosplatform.portfolio.savings.DepositAccountOnHoldTransactionType;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@@ -36,8 +43,59 @@ public class DepositAccountOnHoldTransaction extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
@Column(name = "transaction_date", nullable = false)
private Date transactionDate;
@Column(name = "is_reversed", nullable = false)
private boolean reversed;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false)
private Date createdDate;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "depositAccountOnHoldTransaction", optional = true, orphanRemoval = true)
private GuarantorFundingTransaction guarantorFundingTransaction;
protected DepositAccountOnHoldTransaction() {}
private DepositAccountOnHoldTransaction(final SavingsAccount savingsAccount, final BigDecimal amount,
final DepositAccountOnHoldTransactionType transactionType, final LocalDate transactionDate, final boolean reversed) {
this.savingsAccount = savingsAccount;
this.amount = amount;
this.transactionType = transactionType.getValue();
this.transactionDate = transactionDate.toDate();
this.createdDate = new Date();
this.reversed = reversed;
}
public static DepositAccountOnHoldTransaction hold(final SavingsAccount savingsAccount, final BigDecimal amount,
final LocalDate transactionDate) {
boolean reversed = false;
return new DepositAccountOnHoldTransaction(savingsAccount, amount, DepositAccountOnHoldTransactionType.HOLD, transactionDate,
reversed);
}
public static DepositAccountOnHoldTransaction release(final SavingsAccount savingsAccount, final BigDecimal amount,
final LocalDate transactionDate) {
boolean reversed = false;
return new DepositAccountOnHoldTransaction(savingsAccount, amount, DepositAccountOnHoldTransactionType.RELEASE, transactionDate,
reversed);
}
public BigDecimal getAmount() {
return this.amount;
}
public void reverseTransaction() {
this.reversed = true;
if (this.getTransactionType().isHold()) {
this.savingsAccount.releaseFunds(this.amount);
} else {
this.savingsAccount.holdFunds(this.amount);
}
}
public DepositAccountOnHoldTransactionType getTransactionType() {
return DepositAccountOnHoldTransactionType.fromInt(this.transactionType);
}
}
@@ -0,0 +1,15 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DepositAccountOnHoldTransactionRepository extends JpaRepository<DepositAccountOnHoldTransaction ,Long>,
JpaSpecificationExecutor<DepositAccountOnHoldTransaction>{
}
@@ -858,7 +858,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return transactionBeforeLastInterestPosting;
}
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isWithdrawBalance) {
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
@@ -874,7 +874,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final BigDecimal withdrawalFee = null;
// deal with potential minRequiredBalance and
// enforceMinRequiredBalance
if (!isWithdrawBalance && transaction.canProcessBalanceCheck()) {
if (!isException && transaction.canProcessBalanceCheck()) {
if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException(
"transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); }
}
@@ -2445,4 +2445,20 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
return minReqBalance;
}
public BigDecimal getOnHoldFunds() {
return this.onHoldFunds == null ? BigDecimal.ZERO : this.onHoldFunds;
}
public void holdFunds(BigDecimal onHoldFunds) {
this.onHoldFunds = getOnHoldFunds().add(onHoldFunds);
}
public void releaseFunds(BigDecimal onHoldFunds) {
this.onHoldFunds = getOnHoldFunds().subtract(onHoldFunds);
}
public BigDecimal getWithdrawableBalance() {
return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount());
}
}
@@ -84,7 +84,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
}
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isWithdrawBalance());
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck());
saveTransactionToGenerateTransactionId(withdrawal);
this.savingsAccountRepository.save(account);
@@ -0,0 +1,17 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings.service;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.data.DepositAccountOnHoldTransactionData;
public interface DepositAccountOnHoldTransactionReadPlatformService {
public Page<DepositAccountOnHoldTransactionData> retriveAll(final Long savingsId, final Long guarantorFundingId,
final SearchParameters searchParameters);
}
@@ -0,0 +1,109 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.PaginationHelper;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.data.DepositAccountOnHoldTransactionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class DepositAccountOnHoldTransactionReadPlatformServiceImpl implements DepositAccountOnHoldTransactionReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PaginationHelper<DepositAccountOnHoldTransactionData> paginationHelper = new PaginationHelper<>();
private final DepositAccountOnHoldTransactionsMapper mapper;
@Autowired
public DepositAccountOnHoldTransactionReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
mapper = new DepositAccountOnHoldTransactionsMapper();
}
@Override
public Page<DepositAccountOnHoldTransactionData> retriveAll(Long savingsId, Long guarantorFundingId, SearchParameters searchParameters) {
final StringBuilder sqlBuilder = new StringBuilder(200);
List<Long> paramObj = new ArrayList<>(2);
sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
sqlBuilder.append(this.mapper.schema());
sqlBuilder.append(" where tr.savings_account_id = ? ");
paramObj.add(savingsId);
if (guarantorFundingId != null) {
sqlBuilder.append(" and gt.guarantor_fund_detail_id = ? ");
paramObj.add(guarantorFundingId);
}
if (searchParameters.isOrderByRequested()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
final String sqlCountRows = "SELECT FOUND_ROWS()";
final Object[] finalObjectArray = paramObj.toArray();
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, this.mapper);
}
private static final class DepositAccountOnHoldTransactionsMapper implements RowMapper<DepositAccountOnHoldTransactionData> {
private final String schemaSql;
public DepositAccountOnHoldTransactionsMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
sqlBuilder.append("tr.is_reversed as reversed ");
sqlBuilder.append("from m_deposit_account_on_hold_transaction tr ");
sqlBuilder.append(" left join m_guarantor_transaction gt on gt.deposit_on_hold_transaction_id = tr.id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public DepositAccountOnHoldTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
throws SQLException {
final Long id = rs.getLong("transactionId");
final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
final EnumOptionData transactionType = SavingsEnumerations.onHoldTransactionType(transactionTypeInt);
final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
final boolean reversed = rs.getBoolean("reversed");
return DepositAccountOnHoldTransactionData.instance(id, amount, transactionType, date, reversed);
}
}
}
@@ -350,8 +350,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final Collection<SavingsAccountChargeData> charges = fromChargesToSavingsCharges(productCharges);
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService
.retrieveSavingsApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsApplicableCharges(feeChargesOnly);
Collection<StaffData> fieldOfficerOptions = null;
@@ -427,8 +426,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final Collection<SavingsAccountChargeData> charges = null;
final boolean feeChargesOnly = true;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService
.retrieveSavingsApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsApplicableCharges(feeChargesOnly);
if (depositAccountType.isFixedDeposit()) {
@@ -1398,10 +1396,12 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final Long toAccountId = rs.getLong("toAcc");
final BigDecimal transactionAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount");
final boolean isRegularTransaction = false;
final boolean isExceptionForBalanceCheck = false;
final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate");
return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
fromAccountId, toAccountId, "trasfer interest to savings", null, null, null, null, null, null, null,
AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, null, null, null, null, isRegularTransaction);
AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, null, null, null, null, isRegularTransaction,
isExceptionForBalanceCheck);
}
}
@@ -205,11 +205,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
} else {
final SavingsAccount fromSavingsAccount = null;
boolean isRegularTransaction = false;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
portfolioAccountData.accountId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null,
null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
final boolean isInterestTransfer = false;
@@ -279,12 +280,13 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(),
amountForDeposit.getAmount(), null, isRegularTransaction);
} else {
final boolean isExceptionForBalanceCheck = false;
final SavingsAccount fromSavingsAccount = null;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
portfolioAccountData.accountId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null,
null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount,
isRegularTransaction);
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -252,7 +252,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
sqlBuilder.append("sa.min_required_balance as minRequiredBalance, ");
sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance ");
sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, ");
sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds ");
sqlBuilder.append("from m_savings_account sa ");
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
@@ -414,6 +415,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
"minBalanceForInterestCalculation");
final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds");
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
@@ -423,7 +425,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary,
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation);
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation,
onHoldFunds);
}
}
@@ -517,8 +520,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Collection<SavingsAccountChargeData> charges = fromChargesToSavingsCharges(productCharges);
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService
.retrieveSavingsApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsApplicableCharges(feeChargesOnly);
Collection<StaffData> fieldOfficerOptions = null;
@@ -576,8 +578,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Collection<SavingsAccountChargeData> charges = null;
final boolean feeChargesOnly = true;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService
.retrieveSavingsApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsApplicableCharges(feeChargesOnly);
template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
@@ -743,7 +744,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
}
return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency,
amount, runningBalance, reversed, transfer,submittedOnDate);
amount, runningBalance, reversed, transfer, submittedOnDate);
}
}
@@ -927,6 +928,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final SavingsAccountStatusEnumData status = null;
// final LocalDate annualFeeNextDueDate = null;
final SavingsAccountSummaryData summary = null;
final BigDecimal onHoldFunds = null;
final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault();
final EnumOptionData depositType = null;
@@ -934,7 +936,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary,
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation);
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation,
onHoldFunds);
}
}
@@ -0,0 +1,14 @@
ALTER TABLE `m_deposit_account_on_hold_transaction`
ADD COLUMN `created_date` DATETIME NOT NULL;
ALTER TABLE `m_guarantor_transaction`
ALTER `loan_transaction_id` DROP DEFAULT;
ALTER TABLE `m_guarantor_transaction`
CHANGE COLUMN `loan_transaction_id` `loan_transaction_id` BIGINT(20) NULL ;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'RECOVERGUARANTEES_LOAN', 'LOAN', 'RECOVERGUARANTEES', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'RECOVERGUARANTEES_LOAN_CHECKER', 'LOAN', 'RECOVERGUARANTEES_CHECKER', 0);
ALTER TABLE `m_loan`
DROP COLUMN `guarantee_outstanding_amount_derived`;