payment of loan charges from savings

This commit is contained in:
Pramod Nuthakki
2013-09-18 15:50:14 +05:30
parent adc915d9ef
commit d57a8faf30
67 changed files with 1695 additions and 240 deletions
@@ -0,0 +1,27 @@
CREATE TABLE `m_portfolio_account_associations` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`loan_account_id` BIGINT NULL DEFAULT NULL,
`savings_account_id` BIGINT NULL DEFAULT NULL,
`linked_loan_account_id` BIGINT NULL DEFAULT NULL,
`linked_savings_account_id` BIGINT NULL DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `account_association_loan_fk` FOREIGN KEY (`loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `account_association_savings_fk` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`),
CONSTRAINT `linked_loan_fk` FOREIGN KEY (`linked_loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `linked_savings_fk` FOREIGN KEY (`linked_savings_account_id`) REFERENCES `m_savings_account` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
ALTER TABLE `m_charge`
ADD COLUMN `charge_payment_mode_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `charge_calculation_enum`;
ALTER TABLE `m_loan_charge`
ADD COLUMN `charge_payment_mode_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `charge_calculation_enum`;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'PAY_LOANCHARGE', 'LOANCHARGE', 'PAY', 0);
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
@@ -54,9 +54,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
* Repayments and Repayments at disbursement
***/
else if (loanTransactionDTO.getTransactionType().isRepayment()
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()) {
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement() || loanTransactionDTO.getTransactionType().isChargePayment()) {
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false, loanTransactionDTO
.getTransactionType().isRepaymentAtDisbursement());
.getTransactionType().isRepaymentAtDisbursement() || loanTransactionDTO.getTransactionType().isChargePayment());
}
/** Handle Write Offs, waivers and their reversals **/
@@ -170,7 +170,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
*
*/
private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office, final boolean writeOff, final boolean repaymentAtDisbursement) {
final Office office, final boolean writeOff, final boolean isIncomeFromFee) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
@@ -208,7 +208,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
ACCRUAL_ACCOUNTS_FOR_LOAN feeAccountToCredit = ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE;
if (repaymentAtDisbursement) {
if (isIncomeFromFee) {
feeAccountToCredit = ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES;
}
@@ -51,7 +51,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
* Repayments and Repayments at disbursement
***/
else if (loanTransactionDTO.getTransactionType().isRepayment()
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()) {
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
}
/***
@@ -385,6 +385,10 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("WAIVE") && this.entityName.equalsIgnoreCase("LOANCHARGE");
}
public boolean isPayLoanCharge() {
return this.actionName.equalsIgnoreCase("PAY") && this.entityName.equalsIgnoreCase("LOANCHARGE");
}
public boolean isUpdateLoanOfficer() {
return this.actionName.equalsIgnoreCase("UPDATELOANOFFICER") && this.entityName.equalsIgnoreCase("LOAN");
}
@@ -489,9 +489,21 @@ public class CommandWrapperBuilder {
this.loanId = loanId;
this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
return this;
}
public CommandWrapperBuilder payLoanCharge(final Long loanId, final Long loanChargeId) {
this.actionName = "PAY";
this.entityName = "LOANCHARGE";
this.entityId = loanChargeId;
this.loanId = loanId;
if(loanChargeId == null){
this.href = "/loans/" + loanId;
}else{
this.href = "/loans/" + loanId + "/charges/" + loanChargeId;
}
return this;
}
public CommandWrapperBuilder deleteLoanCharge(final Long loanId, final Long loanChargeId) {
this.actionName = "DELETE";
this.entityName = "LOANCHARGE";
@@ -348,6 +348,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("updateLoanChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isWaiveLoanCharge()) {
handler = this.applicationContext.getBean("waiveLoanChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isPayLoanCharge()) {
handler = this.applicationContext.getBean("payLoanChargeCommandHandler", NewCommandSourceHandler.class);
}
} else if (wrapper.isGLAccountResource()) {
if (wrapper.isCreate()) {
@@ -0,0 +1,9 @@
package org.mifosplatform.infrastructure.jobs.exception;
public class JobExecutionException extends Exception{
public JobExecutionException(String msg) {
super(msg);
}
}
@@ -4,7 +4,8 @@ public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings");
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS(
"Transfer Fee For Loans From Savings");
private final String name;
@@ -0,0 +1,18 @@
package org.mifosplatform.portfolio.account.data;
public class AccountAssociationsData {
private final Long id;
private final PortfolioAccountData account;
private final PortfolioAccountData linkedAccount;
public AccountAssociationsData(final Long id, final PortfolioAccountData account, final PortfolioAccountData linkedAccount) {
this.id = id;
this.account = account;
this.linkedAccount = linkedAccount;
}
public PortfolioAccountData linkedAccount() {
return this.linkedAccount;
}
}
@@ -0,0 +1,99 @@
package org.mifosplatform.portfolio.account.data;
import java.math.BigDecimal;
import java.util.Locale;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
public class AccountTransferDTO {
private LocalDate transactionDate;
private BigDecimal transactionAmount;
private PortfolioAccountType fromAccountType;
private PortfolioAccountType toAccountType;
private Long fromAccountId;
private Long toAccountId;
private String description;
private Locale locale;
private DateTimeFormatter fmt;
private PaymentDetail paymentDetail;
private Integer fromTransferType;
private Integer toTransferType;
private Long chargeId;
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId) {
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.fromAccountType = fromAccountType;
this.toAccountType = toAccountType;
this.fromAccountId = fromAccountId;
this.toAccountId = toAccountId;
this.description = description;
this.locale = locale;
this.fmt = fmt;
this.paymentDetail = paymentDetail;
this.fromTransferType = fromTransferType;
this.toTransferType = toTransferType;
this.chargeId = chargeId;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
public BigDecimal getTransactionAmount() {
return this.transactionAmount;
}
public PortfolioAccountType getFromAccountType() {
return this.fromAccountType;
}
public PortfolioAccountType getToAccountType() {
return this.toAccountType;
}
public Long getFromAccountId() {
return this.fromAccountId;
}
public Long getToAccountId() {
return this.toAccountId;
}
public String getDescription() {
return this.description;
}
public Locale getLocale() {
return this.locale;
}
public DateTimeFormatter getFmt() {
return this.fmt;
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
public Integer getFromTransferType() {
return this.fromTransferType;
}
public Integer getToTransferType() {
return this.toTransferType;
}
public Long getChargeId() {
return this.chargeId;
}
}
@@ -103,4 +103,8 @@ public class PortfolioAccountData {
public BigDecimal getAmtForTransfer() {
return this.amtForTransfer;
}
public Long accountId(){
return this.id;
}
}
@@ -0,0 +1,53 @@
package org.mifosplatform.portfolio.account.domain;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_portfolio_account_associations")
public class AccountAssociations extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "loan_account_id", nullable = true)
private Loan loanAccount;
@ManyToOne
@JoinColumn(name = "savings_account_id", nullable = true)
private SavingsAccount savingsAccount;
@ManyToOne
@JoinColumn(name = "linked_loan_account_id", nullable = true)
private Loan linkedLoanAccount;
@ManyToOne
@JoinColumn(name = "linked_savings_account_id", nullable = true)
private SavingsAccount linkedSavingsAccount;
protected AccountAssociations() {}
private AccountAssociations(final Loan loanAccount, final SavingsAccount savingsAccount, final Loan linkedLoanAccount,
final SavingsAccount linkedSavingsAccount) {
this.loanAccount = loanAccount;
this.savingsAccount = savingsAccount;
this.linkedLoanAccount = linkedLoanAccount;
this.linkedSavingsAccount = linkedSavingsAccount;
}
public static AccountAssociations associateSavingsAccount(Loan loan, SavingsAccount savingsAccount) {
return new AccountAssociations(loan, null, null, savingsAccount);
}
public SavingsAccount linkedSavingsAccount() {
return this.linkedSavingsAccount;
}
public void updateLinkedSavingsAccount(final SavingsAccount savingsAccount) {
this.linkedSavingsAccount = savingsAccount;
}
}
@@ -0,0 +1,14 @@
package org.mifosplatform.portfolio.account.domain;
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 AccountAssociationsRepository extends JpaRepository<AccountAssociations, Long>,
JpaSpecificationExecutor<AccountAssociations> {
@Query("from AccountAssociations aa where aa.loanAccount.id= :loanId")
AccountAssociations findByLoanId(@Param("loanId") Long loanId);
}
@@ -23,6 +23,7 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
@@ -139,4 +140,18 @@ public class AccountTransferAssembler {
deposit, loanRefundTransaction, transactionDate, transactionMonetaryAmount, description);
}
public AccountTransfer assembleSavingsToLoanTransfer(final AccountTransferDTO accountTransferDTO,
final SavingsAccount fromSavingsAccount, final Loan toLoanAccount, final SavingsAccountTransaction savingsAccountTransaction,
final LoanTransaction loanTransaction) {
final Office fromOffice = fromSavingsAccount.office();
final Client fromClient = fromSavingsAccount.getClient();
final Office toOffice = toLoanAccount.getOffice();
final Client toClient = toLoanAccount.client();
final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
return AccountTransfer.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
savingsAccountTransaction, loanTransaction, accountTransferDTO.getTransactionDate(), transactionMonetaryAmount,
accountTransferDTO.getDescription());
}
}
@@ -15,5 +15,8 @@ import org.springframework.data.repository.query.Param;
public interface AccountTransferRepository extends JpaRepository<AccountTransfer, Long>, JpaSpecificationExecutor<AccountTransfer> {
@Query("from AccountTransfer at where at.fromLoanAccount.id= :accountNumber and at.reversed=false")
List<AccountTransfer> findByLoanId(@Param("accountNumber") Long accountNumber);
List<AccountTransfer> findByFromLoanId(@Param("accountNumber") Long accountNumber);
@Query("from AccountTransfer at where (at.fromLoanAccount.id= :accountNumber or at.toLoanAccount.id=:accountNumber) and at.reversed=false")
List<AccountTransfer> findAllByLoanId(@Param("accountNumber") Long accountNumber);
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.account.service;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
public interface AccountAssociationsReadPlatformService {
public PortfolioAccountData retriveLoanAssociation(final Long loanId);
public boolean isLinkedWithAnyActiveLoan(final Long savingsId);
}
@@ -0,0 +1,124 @@
package org.mifosplatform.portfolio.account.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.account.data.AccountAssociationsData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class AccountAssociationsReadPlatformServiceImpl implements AccountAssociationsReadPlatformService {
private final static Logger logger = LoggerFactory.getLogger(AccountAssociationsReadPlatformServiceImpl.class);
private final JdbcTemplate jdbcTemplate;
@Autowired
public AccountAssociationsReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public PortfolioAccountData retriveLoanAssociation(final Long loanId) {
PortfolioAccountData linkedAccount = null;
AccountAssociationsMapper mapper = new AccountAssociationsMapper();
String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? ";
try {
AccountAssociationsData accountAssociationsData = jdbcTemplate.queryForObject(sql, mapper, loanId);
if (accountAssociationsData != null) {
linkedAccount = accountAssociationsData.linkedAccount();
}
} catch (EmptyResultDataAccessException e) {
logger.debug("Linking account is not configured");
}
return linkedAccount;
}
@Override
public boolean isLinkedWithAnyActiveLoan(final Long savingsId) {
boolean hasActiveLoan = false;
String sql = "select loanAccount.loan_status_id as status from m_portfolio_account_associations aa " +
"left join m_loan loanAccount on loanAccount.id = aa.loan_account_id " +
"where aa.linked_savings_account_id = ?";
List<Integer> statusList= this.jdbcTemplate.queryForList(sql,Integer.class, savingsId);
for(Integer status:statusList){
LoanStatus loanStatus= LoanStatus.fromInt(status);
if(loanStatus.isActiveOrAwaitingApprovalOrDisbursal() || loanStatus.isUnderTransfer()){
hasActiveLoan = true;
break;
}
}
return hasActiveLoan;
}
private static final class AccountAssociationsMapper implements RowMapper<AccountAssociationsData> {
private final String schemaSql;
public AccountAssociationsMapper() {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("aa.id as id,");
// sqlBuilder.append("savingsAccount.id as savingsAccountId, savingsAccount.account_no as savingsAccountNo,");
sqlBuilder.append("loanAccount.id as loanAccountId, loanAccount.account_no as loanAccountNo,");
// sqlBuilder.append("linkLoanAccount.id as linkLoanAccountId, linkLoanAccount.account_no as linkLoanAccountNo, ");
sqlBuilder.append("linkSavingsAccount.id as linkSavingsAccountId, linkSavingsAccount.account_no as linkSavingsAccountNo ");
sqlBuilder.append("from m_portfolio_account_associations aa ");
// sqlBuilder.append("left join m_savings_account savingsAccount on savingsAccount.id = aa.savings_account_id ");
sqlBuilder.append("left join m_loan loanAccount on loanAccount.id = aa.loan_account_id ");
sqlBuilder.append("left join m_savings_account linkSavingsAccount on linkSavingsAccount.id = aa.linked_savings_account_id ");
// sqlBuilder.append("left join m_loan linkLoanAccount on linkLoanAccount.id = aa.linked_loan_account_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public AccountAssociationsData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
final Long id = rs.getLong("id");
// final Long savingsAccountId = JdbcSupport.getLong(rs,
// "savingsAccountId");
// final String savingsAccountNo = rs.getString("savingsAccountNo");
final Long loanAccountId = JdbcSupport.getLong(rs, "loanAccountId");
final String loanAccountNo = rs.getString("loanAccountNo");
PortfolioAccountData account = PortfolioAccountData.lookup(loanAccountId, loanAccountNo);
;
/*
* if (savingsAccountId != null) { account =
* PortfolioAccountData.lookup(savingsAccountId, savingsAccountNo);
* } else if (loanAccountId != null) { account =
* PortfolioAccountData.lookup(loanAccountId, loanAccountNo); }
*/
final Long linkSavingsAccountId = JdbcSupport.getLong(rs, "linkSavingsAccountId");
final String linkSavingsAccountNo = rs.getString("linkSavingsAccountNo");
// final Long linkLoanAccountId = JdbcSupport.getLong(rs,
// "linkLoanAccountId");
// final String linkLoanAccountNo =
// rs.getString("linkLoanAccountNo");
PortfolioAccountData linkedAccount = PortfolioAccountData.lookup(linkSavingsAccountId, linkSavingsAccountNo);
/*
* if (linkSavingsAccountId != null) { linkedAccount =
* PortfolioAccountData.lookup(linkSavingsAccountId,
* linkSavingsAccountNo); } else if (linkLoanAccountId != null) {
* linkedAccount = PortfolioAccountData.lookup(linkLoanAccountId,
* linkLoanAccountNo); }
*/
return new AccountAssociationsData(id, account, linkedAccount);
}
}
}
@@ -261,7 +261,7 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
sqlBuilder.append("join m_client fromclient on fromclient.id = sat.from_client_id ");
sqlBuilder.append("join m_client toclient on toclient.id = sat.to_client_id ");
sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = sat.from_savings_account_id ");
sqlBuilder.append("left join m_savings_account fromloanacc on fromloanacc.id = sat.from_loan_account_id ");
sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = sat.from_loan_account_id ");
sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = sat.to_savings_account_id ");
sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = sat.to_loan_account_id ");
sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = sat.from_savings_transaction_id ");
@@ -8,10 +8,15 @@ package org.mifosplatform.portfolio.account.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
public interface AccountTransfersWritePlatformService {
CommandProcessingResult create(JsonCommand command);
void reverseTransfers(Long accountNumber, PortfolioAccountType accountTypeId);
void reverseTransfersWithFromAccountType(Long accountNumber, PortfolioAccountType accountTypeId);
Long transferFunds(AccountTransferDTO accountTransferDTO);
void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId);
}
@@ -23,6 +23,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.account.data.AccountTransfersDataValidator;
import org.mifosplatform.portfolio.account.domain.AccountTransfer;
import org.mifosplatform.portfolio.account.domain.AccountTransferAssembler;
@@ -120,7 +121,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccountId,
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount,
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
@@ -166,23 +167,79 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
@Override
@Transactional
public void reverseTransfers(Long accountNumber, PortfolioAccountType accountTypeId) {
public void reverseTransfersWithFromAccountType(Long accountNumber, PortfolioAccountType accountTypeId) {
List<AccountTransfer> acccountTransfers = null;
if (accountTypeId.isLoanAccount()) {
acccountTransfers = this.accountTransferRepository.findByLoanId(accountNumber);
acccountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber);
}
if (acccountTransfers != null && acccountTransfers.size() > 0) {
for (AccountTransfer accountTransfer : acccountTransfers) {
this.loanAccountDomainService.reverseTransfer(accountTransfer.getFromLoanTransaction());
this.savingsAccountWritePlatformService.undoTransaction(accountTransfer.getToSavingsAccount().getId(), accountTransfer
.getToSavingsTransaction().getId());
accountTransfer.reverse();
this.accountTransferRepository.save(accountTransfer);
}
undoTransactions(acccountTransfers);
}
}
@Override
@Transactional
public void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId) {
List<AccountTransfer> acccountTransfers = null;
if (accountTypeId.isLoanAccount()) {
acccountTransfers = this.accountTransferRepository.findAllByLoanId(accountId);
}
if (acccountTransfers != null && acccountTransfers.size() > 0) {
undoTransactions(acccountTransfers);
}
}
/**
* @param acccountTransfers
*/
private void undoTransactions(List<AccountTransfer> acccountTransfers) {
for (AccountTransfer accountTransfer : acccountTransfers) {
if (accountTransfer.getFromLoanTransaction() != null) {
this.loanAccountDomainService.reverseTransfer(accountTransfer.getFromLoanTransaction());
} else if (accountTransfer.getToLoanTransaction() != null) {
this.loanAccountDomainService.reverseTransfer(accountTransfer.getToLoanTransaction());
}
if (accountTransfer.getFromTransaction() != null) {
this.savingsAccountWritePlatformService.undoTransaction(accountTransfer.getFromAccount().getId(), accountTransfer
.getFromTransaction().getId(), true);
}
if (accountTransfer.getToSavingsTransaction() != null) {
this.savingsAccountWritePlatformService.undoTransaction(accountTransfer.getToSavingsAccount().getId(), accountTransfer
.getToSavingsTransaction().getId(), true);
}
accountTransfer.reverse();
this.accountTransferRepository.save(accountTransfer);
}
}
@Override
@Transactional
public Long transferFunds(AccountTransferDTO accountTransferDTO) {
Long transferTransactionId = null;
if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
//
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), fromSavingsAccount.isWithdrawalFeeApplicableForTransfer());
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount,
accountTransferDTO.getChargeId(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType());
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
this.accountTransferRepository.saveAndFlush(transferTransaction);
transferTransactionId = transferTransaction.getId();
}
return transferTransactionId;
}
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
}
@@ -28,30 +28,33 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final EnumOptionData chargeTimeType;
private final EnumOptionData chargeAppliesTo;
private final EnumOptionData chargeCalculationType;
private final EnumOptionData chargePaymentMode;
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;
private final List<EnumOptionData> chargeAppliesToOptions;
private final List<EnumOptionData> chargeTimeTypeOptions;
private final List<EnumOptionData> chargePaymetModeOptions;
public static ChargeData template(final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions) {
return new ChargeData(null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions,
chargeAppliesToOptions, chargeTimeTypeOptions);
final List<EnumOptionData> chargeTimeTypeOptions, List<EnumOptionData> chargePaymentModeOptions) {
return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions,
chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions);
}
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo,
charge.chargeCalculationType, charge.penalty, charge.active, template.currencyOptions,
template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions);
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active,
template.currencyOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
template.chargePaymetModeOptions);
}
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final boolean penalty, final boolean active) {
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, penalty, active, null,
null, null, null);
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active) {
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active,
null, null, null, null, null);
}
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
@@ -60,21 +63,23 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
EnumOptionData chargeTimeType = null;
EnumOptionData chargeAppliesTo = null;
EnumOptionData chargeCalculationType = null;
EnumOptionData chargePaymentMode = null;
Boolean penalty = isPenalty;
Boolean active = false;
Collection<CurrencyData> currencyOptions = null;
List<EnumOptionData> chargeCalculationTypeOptions = null;
List<EnumOptionData> chargeAppliesToOptions = null;
List<EnumOptionData> chargeTimeTypeOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, penalty, active,
currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions);
List<EnumOptionData> chargePaymentModeOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty,
active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions);
}
private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final boolean penalty, final boolean active, final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions) {
EnumOptionData chargePaymentMode, final boolean penalty, final boolean active,
final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions, List<EnumOptionData> chargePaymentModeOptions) {
this.id = id;
this.name = name;
this.amount = amount;
@@ -82,12 +87,14 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.chargeTimeType = chargeTimeType;
this.chargeAppliesTo = chargeAppliesTo;
this.chargeCalculationType = chargeCalculationType;
this.chargePaymentMode = chargePaymentMode;
this.penalty = penalty;
this.active = active;
this.currencyOptions = currencyOptions;
this.chargeCalculationTypeOptions = chargeCalculationTypeOptions;
this.chargeAppliesToOptions = chargeAppliesToOptions;
this.chargeTimeTypeOptions = chargeTimeTypeOptions;
this.chargePaymetModeOptions = chargePaymentModeOptions;
}
@Override
@@ -116,6 +123,6 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
}
return LoanChargeData.newLoanChargeDetails(this.id, this.name, this.currency, this.amount, percentage, this.chargeTimeType,
this.chargeCalculationType, this.penalty);
this.chargeCalculationType, this.penalty,this.chargePaymentMode);
}
}
@@ -46,6 +46,9 @@ public class Charge extends AbstractPersistable<Long> {
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@Column(name = "charge_payment_mode_enum")
private Integer chargePaymentMode;
@Column(name = "is_penalty", nullable = false)
private boolean penalty;
@@ -66,11 +69,12 @@ public class Charge extends AbstractPersistable<Long> {
final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.integerValueOfParameterNamed("chargeTimeType"));
final ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command
.integerValueOfParameterNamed("chargeCalculationType"));
final ChargePaymentMode paymentMode = ChargePaymentMode.fromInt(command.integerValueOfParameterNamed("chargePaymentMode"));
final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty");
final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active);
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode);
}
protected Charge() {
@@ -78,7 +82,8 @@ public class Charge extends AbstractPersistable<Long> {
}
private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active) {
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active,
final ChargePaymentMode paymentMode) {
this.name = name;
this.amount = amount;
this.currencyCode = currencyCode;
@@ -87,6 +92,7 @@ public class Charge extends AbstractPersistable<Long> {
this.chargeCalculation = chargeCalculationType.getValue();
this.penalty = penalty;
this.active = active;
this.chargePaymentMode = paymentMode.getValue();
if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
}
@@ -191,6 +197,15 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput);
this.chargeCalculation = ChargeCalculationType.fromInt(newValue).getValue();
}
final String paymentModeParamName = "chargePaymentMode";
if (command.isChangeInIntegerParameterNamed(paymentModeParamName, this.chargePaymentMode)) {
final Integer newValue = command.integerValueOfParameterNamed(paymentModeParamName);
actualChanges.put(paymentModeParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue();
}
final String penaltyParamName = "penalty";
if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
@@ -228,9 +243,14 @@ public class Charge extends AbstractPersistable<Long> {
EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTime);
EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo);
EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation);
EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(this.getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
penalty, active);
chargePaymentmode, penalty, active);
}
public Integer getChargePaymentMode() {
return this.chargePaymentMode;
}
}
@@ -0,0 +1,40 @@
package org.mifosplatform.portfolio.charge.domain;
public enum ChargePaymentMode {
REGULAR(0, "chargepaymentmode.regular"), //
ACCOUNT_TRANSFER(1, "chargepaymentmode.accounttransfer");
private final Integer value;
private final String code;
private ChargePaymentMode(Integer value, String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return value;
}
public String getCode() {
return code;
}
public static ChargePaymentMode fromInt(final Integer paymentMode) {
ChargePaymentMode chargeAppliesToType = ChargePaymentMode.REGULAR;
switch (paymentMode) {
case 1:
chargeAppliesToType = ACCOUNT_TRANSFER;
break;
default:
chargeAppliesToType = REGULAR;
break;
}
return chargeAppliesToType;
}
public boolean isPaymentModeAccountTransfer(){
return this.value.equals(ChargePaymentMode.ACCOUNT_TRANSFER.getValue());
}
}
@@ -0,0 +1,47 @@
/**
* 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.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRuleException {
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_PAYED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "This loan charge can be payed as the loan associated with it is currently inactive";
}else if(name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "This loan charge can be payed as the charge payment mode is not account transfer";
}
return name().toString();
}
public String errorCode() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "error.msg.loan.charge.associated.loan.inactive";
}else if(name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "error.msg.loan.charge.payment.mode.not.account.transfer";
}
return name().toString();
}
}
public LoanChargeCannotBePayedException(final LOAN_CHARGE_CANNOT_BE_PAYED_REASON reason, final Long loanChargeId) {
super(reason.errorCode(), reason.errorMessage(), loanChargeId);
}
}
@@ -35,7 +35,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("name", "amount", "locale", "currencyCode",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active"));
"active","chargePaymentMode"));
private final FromJsonHelper fromApiJsonHelper;
@@ -73,6 +73,9 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4);
final Integer chargePaymentMode = fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode).notNull().inMinMaxRange(0, 1);
if (fromApiJsonHelper.parameterExists("penalty", element)) {
final Boolean penalty = fromApiJsonHelper.extractBooleanNamed("penalty", element);
baseDataValidator.reset().parameter("penalty").value(penalty).notNull();
@@ -126,6 +129,11 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
Locale.getDefault());
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4);
}
if (fromApiJsonHelper.parameterExists("chargePaymentMode", element)) {
final Integer chargePaymentMode = fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode).notNull().inMinMaxRange(0, 1);
}
if (fromApiJsonHelper.parameterExists("penalty", element)) {
final Boolean penalty = fromApiJsonHelper.extractBooleanNamed("penalty", element);
@@ -16,4 +16,6 @@ public interface ChargeDropdownReadPlatformService {
List<EnumOptionData> retrieveApplicableToTypes();
List<EnumOptionData> retrieveCollectionTimeTypes();
List<EnumOptionData> retrivePaymentModes();
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.charge.service;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeAppliesTo;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode;
import java.util.Arrays;
import java.util.List;
@@ -15,6 +16,7 @@ import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.springframework.stereotype.Service;
@@ -40,4 +42,9 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrieveCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
}
@Override
public List<EnumOptionData> retrivePaymentModes() {
return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER));
}
}
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.charge.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
public class ChargeEnumerations {
@@ -82,4 +83,25 @@ public class ChargeEnumerations {
}
return optionData;
}
public static EnumOptionData chargePaymentMode(final int id) {
return chargePaymentMode(ChargePaymentMode.fromInt(id));
}
public static EnumOptionData chargePaymentMode(final ChargePaymentMode type) {
EnumOptionData optionData = null;
switch (type) {
case ACCOUNT_TRANSFER:
optionData = new EnumOptionData(ChargePaymentMode.ACCOUNT_TRANSFER.getValue().longValue(),
ChargePaymentMode.ACCOUNT_TRANSFER.getCode(), ChargePaymentMode.ACCOUNT_TRANSFER.getCode());
break;
default:
optionData = new EnumOptionData(ChargePaymentMode.REGULAR.getValue().longValue(), ChargePaymentMode.REGULAR.getCode(),
ChargePaymentMode.REGULAR.getCode());
break;
}
return optionData;
}
}
@@ -81,9 +81,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
final List<EnumOptionData> allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
final List<EnumOptionData> chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes();
return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions,
allowedChargeTimeOptions);
allowedChargeTimeOptions, chargePaymentOptions);
}
@Override
@@ -130,6 +131,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
public String chargeSchema() {
return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, "
+ "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, "
+"c.charge_payment_mode_enum as chargePaymentMode, "
+ "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, c.is_active as active, oc.name as currencyName, "
+ "oc.decimal_places as currencyDecimalPlaces,oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c "
@@ -164,11 +166,16 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final int paymentMode = rs.getInt("chargePaymentMode");
final EnumOptionData chargePaymentMode = ChargeEnumerations.chargePaymentMode(paymentMode);
final boolean penalty = rs.getBoolean("penalty");
final boolean active = rs.getBoolean("active");
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, penalty,
active);
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, chargePaymentMode,
penalty, active);
}
}
@@ -124,11 +124,19 @@ public class LoanChargesApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String addLoanCharge(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
public String executeLoanCharge(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanCharge(loanId).withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
CommandProcessingResult result = null;
if (is(commandParam, "pay")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().payLoanCharge(loanId, null).withJson(apiRequestBodyAsJson)
.build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanCharge(loanId).withJson(apiRequestBodyAsJson)
.build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
return this.toApiJsonSerializer.serialize(result);
}
@@ -152,21 +160,22 @@ public class LoanChargesApiResource {
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String waiveLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@QueryParam("command") final String commandParam) {
public String executeLoanCharge(@PathParam("loanId") final Long loanId, @PathParam("chargeId") final Long loanChargeId,
@QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) {
String json = "";
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandProcessingResult result = null;
if (is(commandParam, "waive")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().waiveLoanCharge(loanId, loanChargeId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
json = this.toApiJsonSerializer.serialize(result);
final CommandWrapper commandRequest = builder.waiveLoanCharge(loanId, loanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "pay")) {
final CommandWrapper commandRequest = builder.payLoanCharge(loanId, loanChargeId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return json;
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@@ -41,7 +41,12 @@ import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSeria
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.PortfolioAccountReadPlatformService;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.charge.data.ChargeData;
@@ -73,6 +78,7 @@ import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformSe
import org.mifosplatform.portfolio.note.data.NoteData;
import org.mifosplatform.portfolio.note.domain.NoteType;
import org.mifosplatform.portfolio.note.service.NoteReadPlatformServiceImpl;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -89,14 +95,15 @@ public class LoansApiResource {
"clientId", "group", "loanProductId", "loanProductName", "loanProductDescription", "fundId", "fundName", "loanPurposeId",
"loanPurposeName", "loanOfficerId", "loanOfficerName", "currency", "principal", "totalOverpaid", "inArrearsTolerance",
"termFrequency", "termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod",
"annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName", "interestRateFrequencyType",
"amortizationType", "interestType", "interestCalculationPeriodType", "expectedFirstRepaymentOnDate", "graceOnPrincipalPayment",
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "timeline", "totalFeeChargesAtDisbursement",
"summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors", "meeting", "productOptions",
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions",
"termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting",
"loanCounter", "loanProductCounter", "notes"));
"annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName",
"interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
"expectedFirstRepaymentOnDate", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged",
"interestChargedFromDate", "timeline", "totalFeeChargesAtDisbursement", "summary", "repaymentSchedule", "transactions",
"charges", "collateral", "guarantors", "meeting", "productOptions", "amortizationTypeOptions", "interestTypeOptions",
"interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions", "termFrequencyTypeOptions",
"interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions",
"loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting",
"loanCounter", "loanProductCounter", "notes","accountLinkingOptions","linkedAccount"));
private final String resourceNameForPermissions = "LOAN";
@@ -119,6 +126,8 @@ public class LoansApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final NoteReadPlatformServiceImpl noteReadPlatformService;
private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
@Autowired
public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
@@ -133,7 +142,9 @@ public class LoansApiResource {
final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService) {
final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService,
final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) {
this.context = context;
this.loanReadPlatformService = loanReadPlatformService;
this.loanProductReadPlatformService = loanProductReadPlatformService;
@@ -153,6 +164,8 @@ public class LoansApiResource {
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.noteReadPlatformService = noteReadPlatformService;
this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformServiceImpl;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
}
@GET
@@ -225,10 +238,23 @@ public class LoansApiResource {
}
allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(officeId, staffInSelectedOfficeOnly);
Collection<PortfolioAccountData> accountLinkingOptions = null;
if(clientId != null){
CurrencyData currencyData = newLoanAccount.currency();
String currencyCode = null;
if (currencyData != null) {
currencyCode = currencyData.code();
}
long[] accountStatus = { SavingsAccountStatusType.ACTIVE.getValue() };
accountLinkingOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(
PortfolioAccountType.SAVINGS.getValue(), clientId, currencyCode, accountStatus);
}
// add product options, allowed loan officers and calendar options
// (calendar options will be null in individual loan)
newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions);
newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions,accountLinkingOptions);
}
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, newLoanAccount, LOAN_DATA_PARAMETERS);
@@ -253,13 +279,15 @@ public class LoansApiResource {
Collection<CollateralData> collateral = null;
CalendarData meeting = null;
Collection<NoteData> notes = null;
PortfolioAccountData linkedAccount = null;
final Set<String> mandatoryResponseParameters = new HashSet<String>();
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "charges", "guarantors", "collateral","notes"));
associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "charges", "guarantors", "collateral",
"notes", "linkedAccount"));
}
if (associationParameters.contains("guarantors")) {
@@ -305,7 +333,7 @@ public class LoansApiResource {
mandatoryResponseParameters.add("meeting");
meeting = this.calendarReadPlatformService.retrieveLoanCalendar(loanId);
}
if (associationParameters.contains("notes")) {
mandatoryResponseParameters.add("notes");
notes = this.noteReadPlatformService.retrieveNotesByResource(loanId, NoteType.LOAN.getValue());
@@ -313,6 +341,11 @@ public class LoansApiResource {
notes = null;
}
}
if (associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
}
}
Collection<LoanProductData> productOptions = null;
@@ -330,6 +363,9 @@ public class LoansApiResource {
Collection<CodeValueData> loanPurposeOptions = null;
Collection<CodeValueData> loanCollateralOptions = null;
Collection<CalendarData> calendarOptions = null;
Collection<PortfolioAccountData> accountLinkingOptions = null;
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
if (template) {
@@ -348,11 +384,24 @@ public class LoansApiResource {
chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate();
allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(), staffInSelectedOfficeOnly);
allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(),
staffInSelectedOfficeOnly);
loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
loanCollateralOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanCollateral");
CurrencyData currencyData = loanBasicDetails.currency();
String currencyCode = null;
if (currencyData != null) {
currencyCode = currencyData.code();
}
long[] accountStatus = { SavingsAccountStatusType.ACTIVE.getValue() };
accountLinkingOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(
PortfolioAccountType.SAVINGS.getValue(), loanBasicDetails.clientId(), currencyCode, accountStatus);
if (!associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
}
if (loanBasicDetails.groupId() != null) {
calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId());
}
@@ -363,7 +412,7 @@ public class LoansApiResource {
charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes);
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
@@ -129,6 +130,10 @@ public class LoanAccountData {
//loanCycle
private final Integer loanCounter;
private final Integer loanProductCounter;
//linkable account details
private final PortfolioAccountData linkedAccount;
private final Collection<PortfolioAccountData> accountLinkingOptions;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
@@ -203,7 +208,8 @@ public class LoanAccountData {
final Boolean syncDisbursementWithMeeting = null;
final Integer loancounter = null;
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
@@ -214,7 +220,7 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes);
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,accountLinkingOptions, linkedAccount);
}
/**
@@ -290,6 +296,8 @@ public class LoanAccountData {
final Boolean syncDisbursementWithMeeting = null;
final Integer loancounter = null;
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -301,7 +309,7 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes);
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,accountLinkingOptions, linkedAccount);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -319,7 +327,7 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting,
acc.loanCounter, acc.loanProductCounter, acc.notes);
acc.loanCounter, acc.loanProductCounter, acc.notes,acc.accountLinkingOptions, acc.linkedAccount);
}
/**
@@ -396,6 +404,8 @@ public class LoanAccountData {
final Boolean syncDisbursementWithMeeting = null;
final Integer loancounter = null;
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -407,7 +417,7 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes);
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,accountLinkingOptions, linkedAccount);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -425,9 +435,9 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes);
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes,acc.accountLinkingOptions, acc.linkedAccount);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
final Collection<EnumOptionData> termFrequencyTypeOptions, final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions,
@@ -495,6 +505,8 @@ public class LoanAccountData {
final Integer loancounter = null;
final Integer loanProductCounter = null;
final PortfolioAccountData linkedAccount = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
@@ -508,7 +520,8 @@ public class LoanAccountData {
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes);
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
accountLinkingOptions, linkedAccount);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -556,7 +569,7 @@ public class LoanAccountData {
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes);
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,acc.accountLinkingOptions, acc.linkedAccount);
}
/*
@@ -600,6 +613,8 @@ public class LoanAccountData {
final Collection<CodeValueData> loanPurposeOptions = null;
final Collection<CodeValueData> loanCollateralOptions = null;
final Collection<CalendarData> calendarOptions = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -611,7 +626,7 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes);
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,accountLinkingOptions, linkedAccount);
}
/*
@@ -628,7 +643,8 @@ public class LoanAccountData {
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions, final Collection<NoteData> notes) {
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions, final Collection<NoteData> notes,
final Collection<PortfolioAccountData> accountLinkingOptions, PortfolioAccountData linkedAccount) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
@@ -641,16 +657,16 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes);
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,accountLinkingOptions, linkedAccount);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions) {
final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions,final Collection<PortfolioAccountData> accountLinkingOptions) {
return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes);
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions, acc.linkedAccount);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
@@ -668,7 +684,7 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes);
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes,acc.accountLinkingOptions, acc.linkedAccount);
}
private LoanAccountData(
@@ -717,7 +733,8 @@ public class LoanAccountData {
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions, final ChargeData chargeTemplate,
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter, final Collection<NoteData> notes) {
final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter, final Collection<NoteData> notes,
final Collection<PortfolioAccountData> accountLinkingOptions, PortfolioAccountData linkedAccount) {
this.id = id;
this.accountNo = accountNo;
this.status = status;
@@ -827,6 +844,9 @@ public class LoanAccountData {
this.loanCounter = loanCounter;
this.loanProductCounter = loanProductCounter;
this.linkedAccount = linkedAccount;
this.accountLinkingOptions = accountLinkingOptions;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -857,4 +877,13 @@ public class LoanAccountData {
public Long groupId() {
return group == null ? null : group.getId();
}
public CurrencyData currency() {
return this.currency;
}
public Long clientId(){
return this.clientId;
}
}
@@ -12,6 +12,7 @@ import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
/**
* Immutable data object for loan charge data.
@@ -66,8 +67,18 @@ public class LoanChargeData {
@SuppressWarnings("unused")
private final boolean penalty;
private final EnumOptionData chargePaymentMode;
private final boolean paid;
private final boolean waived;
private final boolean chargePayable;
private final Long loanId;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false);
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null);
}
/**
@@ -76,15 +87,16 @@ public class LoanChargeData {
*/
public static LoanChargeData newLoanChargeDetails(final Long chargeId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal percentage, final EnumOptionData chargeTimeType,
final EnumOptionData chargeCalculationType, final boolean penalty) {
return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty);
final EnumOptionData chargeCalculationType, final boolean penalty, final EnumOptionData chargePaymentMode) {
return new LoanChargeData(null, chargeId, name, currency, amount, percentage, chargeTimeType, chargeCalculationType, null, penalty,
chargePaymentMode, false, false, null);
}
public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final boolean penalty) {
final boolean penalty, EnumOptionData chargePaymentMode, boolean paid, boolean waived, Long loanId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -100,6 +112,9 @@ public class LoanChargeData {
this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
this.penalty = penalty;
this.chargePaymentMode = chargePaymentMode;
this.paid = paid;
this.waived = waived;
if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
this.amountOrPercentage = this.percentage;
@@ -108,11 +123,14 @@ public class LoanChargeData {
}
this.chargeOptions = null;
this.chargePayable = isChargePayable();
this.loanId = loanId;
}
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
final BigDecimal pecentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
final Collection<ChargeData> chargeOptions, final boolean penalty) {
final Collection<ChargeData> chargeOptions, final boolean penalty, EnumOptionData chargePaymentMode, boolean paid,
boolean waived, Long loanId) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -128,6 +146,9 @@ public class LoanChargeData {
this.percentage = pecentage;
this.amountPercentageAppliedTo = null;
this.penalty = penalty;
this.chargePaymentMode = chargePaymentMode;
this.paid = paid;
this.waived = waived;
if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) {
this.amountOrPercentage = this.percentage;
@@ -136,5 +157,57 @@ public class LoanChargeData {
}
this.chargeOptions = chargeOptions;
this.chargePayable = isChargePayable();
this.loanId = loanId;
}
public LoanChargeData(final Long id,final LocalDate dueAsOfDate,final BigDecimal amountOutstanding,final Long loanId) {
this.id = id;
this.chargeId = null;
this.name = null;
this.currency = null;
this.amount = null;
this.amountPaid = null;
this.amountWaived = null;
this.amountWrittenOff = null;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = null;
this.dueDate = dueAsOfDate;
this.chargeCalculationType = null;
this.percentage = null;
this.amountPercentageAppliedTo = null;
this.penalty = false;
this.chargePaymentMode = null;
this.paid = false;
this.waived = false;
this.amountOrPercentage = null;
this.chargeOptions = null;
this.chargePayable = false;
this.loanId = loanId;
}
public boolean isChargePayable() {
boolean isAccountTransfer = false;
if(this.chargePaymentMode!=null){
isAccountTransfer = ChargePaymentMode.fromInt(this.chargePaymentMode.getId().intValue()).isPaymentModeAccountTransfer();
}
return isAccountTransfer && !this.paid && !this.waived;
}
public Long getId() {
return this.id;
}
public LocalDate getDueDate() {
return this.dueDate;
}
public Long getLoanId() {
return this.loanId;
}
public BigDecimal getAmountOutstanding() {
return this.amountOutstanding;
}
}
@@ -29,6 +29,8 @@ public class LoanTransactionEnumData {
private final boolean approveTransfer;
private final boolean withdrawTransfer;
private final boolean rejectTransfer;
private final boolean chargePayment;
private final boolean refund;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
@@ -48,6 +50,8 @@ public class LoanTransactionEnumData {
this.approveTransfer = Long.valueOf(13).equals(this.id);
this.withdrawTransfer = Long.valueOf(14).equals(this.id);
this.rejectTransfer = Long.valueOf(15).equals(this.id);
this.refund = Long.valueOf(16).equals(this.id);
this.chargePayment = Long.valueOf(17).equals(this.id);
}
public Long id() {
@@ -117,4 +121,14 @@ public class LoanTransactionEnumData {
return this.rejectTransfer;
}
public boolean isChargePayment() {
return this.chargePayment;
}
public boolean isRefund() {
return this.refund;
}
}
@@ -56,6 +56,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
newState = stateOf(LoanStatus.APPROVED, allowedLoanStatuses);
}
break;
case LOAN_CHARGE_PAYMENT:
case LOAN_REPAYMENT_OR_WAIVER:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
newState = stateOf(LoanStatus.ACTIVE, allowedLoanStatuses);
@@ -501,6 +501,32 @@ public class Loan extends AbstractPersistable<Long> {
return applyLoanChargeTransaction;
}
private void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT,
LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
LocalDate startDate = getDisbursementDate();
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
if (charge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
chargePaymentInstallments.add(installment);
break;
}
startDate = installment.getDueDate();
}
Set<LoanCharge> loanCharges = new HashSet<LoanCharge>(1);
loanCharges.add(charge);
loanRepaymentScheduleTransactionProcessor.handleTransaction(chargesPayment, getCurrency(), chargePaymentInstallments, loanCharges);
updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
}
private void validateLoanIsNotClosed(final LoanCharge loanCharge) {
if (isClosed()) {
final String defaultUserMessage = "This charge cannot be added as the loan is already closed.";
@@ -1420,13 +1446,6 @@ public class Loan extends AbstractPersistable<Long> {
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero()) {
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(this.getOffice(),
totalFeeChargesDueAtDisbursement, null, disbursedOn, txnExternalId);
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponents(zero, zero, totalFeeChargesDueAtDisbursement, zero);
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
/**
* all Charges repaid at disbursal is marked as repaid and
* "APPLY Charge" transactions are created for all other fees (
@@ -1434,10 +1453,19 @@ public class Loan extends AbstractPersistable<Long> {
**/
for (final LoanCharge charge : setOfLoanCharges()) {
if (charge.isDueAtDisbursement()) {
charge.markAsFullyPaid();
// Add "Loan Charge Paid By" details to this transaction
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount());
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
if (!charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(this.getOffice(),
charge.getAmount(getCurrency()), null, disbursedOn, txnExternalId);
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponents(zero, zero, totalFeeChargesDueAtDisbursement, zero);
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
charge.markAsFullyPaid();
// Add "Loan Charge Paid By" details to this transaction
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount());
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
}
} else {
handleChargeAppliedTransaction(charge, disbursedOn);
}
@@ -1462,6 +1490,24 @@ public class Loan extends AbstractPersistable<Long> {
return disbursementTransaction;
}
public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment) {
LoanCharge charge = null;
for (LoanCharge loanCharge : this.charges) {
if (chargeId.equals(loanCharge.getId())) {
charge = loanCharge;
}
}
@SuppressWarnings("null")
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount());
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponents(zero, zero, charge.getAmount(getCurrency()), zero);
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
charge.markAsFullyPaid();
return chargesPayment;
}
public Map<String, Object> undoDisbursal(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -1547,6 +1593,31 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail;
}
public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay, LoanTransaction paymentTransaction) {
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) {
final String errorMessage = "The date on which a loan charge paid cannot be in the future.";
throw new InvalidLoanStateTransitionException("charge.payment", "cannot.be.a.future.date", errorMessage,
paymentTransaction.getTransactionDate());
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
LoanCharge charge = null;
for (LoanCharge loanCharge : this.charges) {
if (chargeId.equals(loanCharge.getId())) {
charge = loanCharge;
}
}
handleChargePaidTransaction(charge, paymentTransaction, loanLifecycleStateMachine);
}
public void makeRefund(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
@@ -1571,16 +1642,13 @@ public class Loan extends AbstractPersistable<Long> {
final String errorMessage = "Transfer funds is allowed only for loan accounts with overpaid status ";
throw new InvalidLoanStateTransitionException("transaction", "is.not.a.overpaid.loan", errorMessage);
}
this.totalOverpaid = this.totalOverpaid.subtract(loanTransaction.getAmount(getCurrency()).getAmount());
if (this.totalOverpaid.compareTo(BigDecimal.ZERO) == 0) {
handleLoanRepaymentInFull(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
}
loanTransaction.updateLoan(this);
if (loanTransaction.isNotZero(loanCurrency())) {
this.loanTransactions.add(loanTransaction);
}
updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
}
private ChangedTransactionDetail handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction,
@@ -1676,11 +1744,25 @@ public class Loan extends AbstractPersistable<Long> {
private void handleLoanRepaymentInFull(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
boolean isAllChargesPaid = true;
for (LoanCharge loanCharge : this.charges) {
if (!(loanCharge.isPaid() || loanCharge.isWaived())) {
isAllChargesPaid = false;
break;
}
}
if (isAllChargesPaid) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL,
LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
this.closedOnDate = transactionDate.toDate();
this.actualMaturityDate = transactionDate.toDate();
this.closedOnDate = transactionDate.toDate();
this.actualMaturityDate = transactionDate.toDate();
} else if (LoanStatus.fromInt(this.loanStatus).isOverpaid()) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
}
}
private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
@@ -2194,7 +2276,7 @@ public class Loan extends AbstractPersistable<Long> {
Money cumulativePaid = Money.zero(loanCurrency());
for (final LoanTransaction repayment : this.loanTransactions) {
if (repayment.isRepayment()) {
if (repayment.isRepayment() && !repayment.isReversed()) {
cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
}
}
@@ -2731,6 +2813,11 @@ public class Loan extends AbstractPersistable<Long> {
action = "close";
postfix = "cannot.be.undone.before.client.transfer.date";
break;
case LOAN_CHARGE_PAYMENT:
errorMessage = "The date on which a charge payment is made cannot be earlier than client's transfer date to this office";
action = "charge.payment";
postfix = "cannot.be.made.before.client.transfer.date";
break;
default:
break;
}
@@ -2738,4 +2825,8 @@ public class Loan extends AbstractPersistable<Long> {
}
}
}
public Set<LoanCharge> charges() {
return this.charges;
}
}
@@ -13,11 +13,14 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
public interface LoanAccountDomainService {
LoanTransaction makeRepayment(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
void reverseTransfer(LoanTransaction loanTransaction);
LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType);
}
@@ -70,11 +70,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Transactional
@Override
public LoanTransaction makeRepayment(final Long accountId, final CommandProcessingResultBuilder builderResult,
public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final String txnExternalId) {
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan);
// TODO: Is it required to validate transaction date with meeting dates
@@ -137,6 +136,45 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return newRepaymentTransaction;
}
@Override
@Transactional
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId,
final Integer transactionType) {
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Money paymentAmout = Money.of(loan.getCurrency(), transactionAmount);
LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType);
final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail,
transactionDate, txnExternalId, loanTransactionType);
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository
.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
if(loanTransactionType.isRepaymentAtDisbursement()){
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction);
}else{
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction);
}
this.loanTransactionRepository.save(newPaymentTransaction);
this.loanRepository.save(loan);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText);
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return newPaymentTransaction;
}
private void postJournalEntries(final Loan loanAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
@@ -28,6 +28,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
@@ -54,6 +55,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@Column(name = "charge_payment_mode_enum")
private Integer chargePaymentMode;
@Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
private BigDecimal percentage;
@@ -92,16 +96,17 @@ public class LoanCharge extends AbstractPersistable<Long> {
ChargeTimeType chargeTime = null;
ChargeCalculationType chargeCalculation = null;
ChargePaymentMode chargePaymentMode = null;
return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation, dueDate);
return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode);
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate);
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, ChargePaymentMode chargePaymentMode) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode);
}
protected LoanCharge() {
@@ -109,7 +114,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, ChargePaymentMode chargePaymentMode) {
this.loan = loan;
this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty();
@@ -141,6 +146,11 @@ public class LoanCharge extends AbstractPersistable<Long> {
if (amount != null) {
chargeAmount = amount;
}
this.chargePaymentMode = chargeDefinition.getChargePaymentMode();
if(chargePaymentMode != null){
this.chargePaymentMode = chargePaymentMode.getValue();
}
populateDerivedFields(loanPrincipal, chargeAmount);
this.paid = determineIfFullyPaid();
@@ -388,6 +398,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
public BigDecimal amount() {
return this.amount;
}
public BigDecimal amountOutstanding(){
return this.amountOutstanding;
}
public boolean hasNotLoanIdentifiedBy(final Long loanId) {
return !hasLoanIdentifiedBy(loanId);
@@ -513,4 +527,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
.append(this.amount).append(this.getDueLocalDate()) //
.toHashCode();
}
public ChargePaymentMode getChargePaymentMode() {
return ChargePaymentMode.fromInt(this.chargePaymentMode);
}
}
@@ -58,5 +58,17 @@ public class LoanChargePaidBy extends AbstractPersistable<Long> {
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
@Override
public boolean equals(Object obj) {
boolean isEqual = false;
LoanChargePaidBy paidBy = (LoanChargePaidBy)obj;
if(this.loanTransaction.getId() == paidBy.loanTransaction.getId()
&& this.loanCharge.getId() == paidBy.loanCharge.getId()
&& this.amount == paidBy.amount){
isEqual = true;
}
return isEqual;
}
}
@@ -22,5 +22,6 @@ public enum LoanEvent {
WRITE_OFF_OUTSTANDING, //
LOAN_RESCHEDULE, //
INTERST_REBATE_OWED, //
LOAN_OVERPAYMENT;
LOAN_OVERPAYMENT,
LOAN_CHARGE_PAYMENT;
}
@@ -108,6 +108,12 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
}
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){
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate,
externalId);
}
public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) {
@@ -355,6 +361,21 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public boolean isNotWaiver() {
return !isInterestWaiver() && !isChargesWaiver();
}
public boolean isChargePayment(){
return getTypeOf().isChargePayment() && isNotReversed();
}
public boolean isPenaltyPayment(){
boolean isPenalty = false;
if(isChargePayment()){
for(LoanChargePaidBy chargePaidBy:this.loanChargesPaid){
isPenalty = chargePaidBy.getLoanCharge().isPenaltyCharge();
break;
}
}
return isPenalty;
}
public boolean isWriteOff() {
return getTypeOf().isWriteOff() && isNotReversed();
@@ -34,7 +34,8 @@ public enum LoanTransactionType {
WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), //
REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"),
REFUND(16,"loanTransactionType.refund");
REFUND(16,"loanTransactionType.refund"),
CHARGE_PAYMENT(17,"loanTransactionType.chargePayment");
private final Integer value;
private final String code;
@@ -106,6 +107,9 @@ public enum LoanTransactionType {
case 16:
loanTransactionType = LoanTransactionType.REFUND;
break;
case 17:
loanTransactionType = LoanTransactionType.CHARGE_PAYMENT;
break;
default:
loanTransactionType = LoanTransactionType.INVALID;
break;
@@ -144,4 +148,8 @@ public enum LoanTransactionType {
public boolean isWriteOff() {
return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
}
public boolean isChargePayment(){
return this.value.equals(LoanTransactionType.CHARGE_PAYMENT.getValue());
}
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -64,7 +65,33 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
wrapper.reprocess(currency, disbursementDate, installments, charges);
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
List<LoanTransaction> transactionstoBeProcessed = new ArrayList<LoanTransaction>();
for (LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (loanTransaction.isChargePayment()){
Set<LoanChargePaidBy> chargePaidBies =loanTransaction.getLoanChargesPaid();
Set<LoanCharge> transferCharges = new HashSet<LoanCharge>();
for(LoanChargePaidBy chargePaidBy: chargePaidBies){
transferCharges.add(chargePaidBy.getLoanCharge());
}
List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
LocalDate startDate = disbursementDate;
for(LoanRepaymentScheduleInstallment installment:installments){
for(LoanCharge loanCharge :transferCharges){
if(loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())){
chargePaymentInstallments.add(installment);
break;
}
}
startDate = installment.getDueDate();
}
loanTransaction.resetDerivedComponents();
handleTransaction(loanTransaction, currency, chargePaymentInstallments, transferCharges);
}else{
transactionstoBeProcessed.add(loanTransaction);
}
}
for (LoanTransaction loanTransaction : transactionstoBeProcessed) {
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) {
// pass through for new transactions
@@ -97,7 +97,15 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
}else {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
@@ -81,7 +81,15 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
}else {
if (currentInstallment.isPrincipalNotCompleted(currency)) {
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
@@ -141,7 +149,15 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
}else {
// 1. pay of principal before interest.
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
@@ -71,7 +71,16 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}else {
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -84,6 +84,15 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
@@ -71,7 +71,16 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}else {
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
@@ -80,7 +80,18 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestWaivedPortion, feeChargesPortion, penaltyChargesPortion);
} else {
} else if(loanTransaction.isChargePayment()){
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}else {
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(
loanTransaction.getTransactionDate(), installments);
@@ -161,7 +172,15 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
} else {
} else if(loanTransaction.isChargePayment()){
if(loanTransaction.isPenaltyPayment()){
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
}else{
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
}else {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
@@ -0,0 +1,26 @@
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;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PayLoanChargeCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public PayLoanChargeCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.payLoanCharge(command.getLoanId(), command.entityId(), command, command.entityId() == null);
}
}
@@ -42,7 +42,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting"));
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting","linkAccountId"));
private final FromJsonHelper fromApiJsonHelper;
@@ -23,7 +23,9 @@ import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -48,7 +50,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
"loanPurposeId", "inArrearsTolerance", "charges", "collateral", // optional
"transactionProcessingStrategyId", // settings
"calendarId", // optional
"syncDisbursementWithMeeting"//optional
"syncDisbursementWithMeeting",//optional
"linkAccountId"
));
private final FromJsonHelper fromApiJsonHelper;
@@ -233,6 +236,14 @@ public final class LoanApplicationCommandFromApiJsonHelper {
element);
baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
.integerGreaterThanZero();
final String linkAccountIdParameterName = "linkAccountId";
if (fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) {
final Long linkAccountId = fromApiJsonHelper.extractLongNamed(linkAccountIdParameterName, element);
baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).ignoreIfNull()
.longGreaterThanZero();
}
// charges
final String chargesParameterName = "charges";
@@ -533,6 +544,14 @@ public final class LoanApplicationCommandFromApiJsonHelper {
baseDataValidator.reset().parameter(submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
.notExceedingLengthOf(500);
}
final String linkAccountIdParameterName = "linkAccountId";
if (fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long linkAccountId = fromApiJsonHelper.extractLongNamed(linkAccountIdParameterName, element);
baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).ignoreIfNull()
.longGreaterThanZero();
}
// charges
final String chargesParameterName = "charges";
@@ -677,5 +696,25 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validatelinkedSavingsAccount(final SavingsAccount savingsAccount,final Loan loanApplication) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
if(savingsAccount.isNotActive()){
ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.loan.linked.savings.account.is.not.active",
"Linked Savings account with id:"+savingsAccount.getId()+ " is not in active state", "linkAccountId",
savingsAccount.getId());
dataValidationErrors.add(error);
}else if(loanApplication.getClientId() != savingsAccount.clientId()){
ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.loan.linked.savings.account.not.belongs.to.same.client",
"Linked Savings account with id:"+savingsAccount.getId()+ " is not belongs to the same client", "linkAccountId",
savingsAccount.getId());
dataValidationErrors.add(error);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -50,8 +50,8 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale",
"dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
@@ -70,7 +70,7 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) {
if (null != calendarInstance) {
Calendar calendar = calendarInstance.getCalendar();
@@ -79,21 +79,21 @@ public final class LoanEventApiJsonValidator {
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+ "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, actualDisbursementDate.toString(),
calendar.getTitle());
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage,
actualDisbursementDate.toString(), calendar.getTitle());
}
}
}
}
public void validateTransaction(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
"note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
"bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
@@ -119,8 +119,9 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
"note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
"bankNumber"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
@@ -148,8 +149,7 @@ public final class LoanEventApiJsonValidator {
if (calendar != null && repaymentDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
final String errorMessage = "Transaction date '" + repaymentDate.toString()
+ "' does not fall on a meeting date.";
final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
calendar.getTitle());
}
@@ -157,7 +157,7 @@ public final class LoanEventApiJsonValidator {
}
}
}
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
// Validate all string payment detail fields for max length
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
@@ -260,20 +260,20 @@ public final class LoanEventApiJsonValidator {
final String assignmentDateStr = this.fromApiJsonHelper.extractStringNamed("assignmentDate", element);
baseDataValidator.reset().parameter("assignmentDate").value(assignmentDateStr).notBlank();
if(!StringUtils.isBlank(assignmentDateStr)){
if (!StringUtils.isBlank(assignmentDateStr)) {
final LocalDate assignmentDate = this.fromApiJsonHelper.extractLocalDateNamed("assignmentDate", element);
baseDataValidator.reset().parameter("assignmentDate").value(assignmentDate).notNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForBulkLoanReassignment(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("assignmentDate", "fromLoanOfficerId",
"toLoanOfficerId", "loans", "locale", "dateFormat"));
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("assignmentDate", "fromLoanOfficerId", "toLoanOfficerId",
"loans", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
@@ -294,4 +294,32 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateChargePaymentTransaction(final String json,boolean isChargeIdIncluded) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
Set<String> transactionParameters = null;
if(isChargeIdIncluded){
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat","chargeId"));
}else{
transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "locale", "dateFormat"));
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.charge.payment.transaction");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
if(isChargeIdIncluded){
final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", element);
baseDataValidator.reset().parameter("chargeId").value(chargeId).notNull().integerGreaterThanZero();
}
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
@@ -25,12 +25,15 @@ import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityExce
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.account.domain.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
import org.mifosplatform.portfolio.client.domain.Client;
@@ -63,10 +66,13 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -102,6 +108,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final CalendarRepository calendarRepository;
private final CalendarInstanceRepository calendarInstanceRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -114,7 +122,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanProductRepository loanProductRepository, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory,
final LoanSummaryWrapper loanSummaryWrapper, final GroupRepositoryWrapper groupRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository) {
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -135,6 +144,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.calendarRepository = calendarRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -147,73 +158,81 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
public CommandProcessingResult submitApplication(final JsonCommand command) {
try {
final AppUser currentUser = context.authenticatedUser();
final AppUser currentUser = context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json());
this.fromApiJsonDeserializer.validateForCreate(command.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final Long productId = fromJsonHelper.extractLongNamed("productId", command.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, loanProduct);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
final Long productId = fromJsonHelper.extractLongNamed("productId", command.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser);
validateSubmittedOnDate(newLoanApplication);
final LoanProductRelatedDetail productRelatedDetail = newLoanApplication.repaymentScheduleDetail();
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(), newLoanApplication.getTermPeriodFrequencyType(),
productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(),
productRelatedDetail.getRepaymentPeriodFrequencyType().getValue());
this.loanRepository.save(newLoanApplication);
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, loanProduct);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser);
validateSubmittedOnDate(newLoanApplication);
final LoanProductRelatedDetail productRelatedDetail = newLoanApplication.repaymentScheduleDetail();
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(),
newLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue());
if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) {
final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory
.determineLoanAccountNoGenerator(newLoanApplication.getId());
newLoanApplication.updateAccountNo(accountNoGenerator.generate());
this.loanRepository.save(newLoanApplication);
}
final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
if (StringUtils.isNotBlank(submittedOnNote)) {
Note note = Note.loanNote(newLoanApplication, submittedOnNote);
this.noteRepository.save(note);
}
if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) {
final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory
.determineLoanAccountNoGenerator(newLoanApplication.getId());
newLoanApplication.updateAccountNo(accountNoGenerator.generate());
this.loanRepository.save(newLoanApplication);
}
// Save calendar instance
final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null;
if (calendarId != null && calendarId != 0) {
calendar = this.calendarRepository.findOne(calendarId);
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(),
CalendarEntityType.LOANS.getValue());
this.calendarInstanceRepository.save(calendarInstance);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(newLoanApplication.getId()) //
.withOfficeId(newLoanApplication.getOfficeId()) //
.withClientId(newLoanApplication.getClientId()) //
.withGroupId(newLoanApplication.getGroupId()) //
.withLoanId(newLoanApplication.getId()) //
.build();
final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
if (StringUtils.isNotBlank(submittedOnNote)) {
Note note = Note.loanNote(newLoanApplication, submittedOnNote);
this.noteRepository.save(note);
}
// Save calendar instance
final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null;
if (calendarId != null && calendarId != 0) {
calendar = this.calendarRepository.findOne(calendarId);
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(),
CalendarEntityType.LOANS.getValue());
this.calendarInstanceRepository.save(calendarInstance);
}
// Save linked account information
final Long savingsAccountId = command.longValueOfParameterNamed("linkAccountId");
if (savingsAccountId != null) {
SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication, savingsAccount);
this.accountAssociationsRepository.save(accountAssociations);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(newLoanApplication.getId()) //
.withOfficeId(newLoanApplication.getOfficeId()) //
.withClientId(newLoanApplication.getClientId()) //
.withGroupId(newLoanApplication.getGroupId()) //
.withLoanId(newLoanApplication.getId()) //
.build();
} catch (DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) {
@@ -225,7 +244,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Loan existingLoanApplication = retrieveLoanBy(loanId);
checkClientOrGroupActive(existingLoanApplication);
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
loanId); }
@@ -269,12 +288,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.updateInterestRateFrequencyType();
}
}
validateSubmittedOnDate(existingLoanApplication);
final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail();
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(), existingLoanApplication.getTermPeriodFrequencyType(),
productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(),
productRelatedDetail.getRepaymentPeriodFrequencyType().getValue());
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(),
existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue());
final String fundIdParamName = "fundId";
if (changes.containsKey(fundIdParamName)) {
@@ -342,7 +361,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
calendar = this.calendarRepository.findOne(calendarId);
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
}
List<CalendarInstance> ciList = (List<CalendarInstance>) this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loanId,
CalendarEntityType.LOANS.getValue());
if (calendar != null) {
@@ -367,6 +386,54 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.calendarInstanceRepository.delete(calendarInstance);
}
// Save linked account information
final String linkAccountIdParamName = "linkAccountId";
final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName);
AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanId(loanId);
boolean isLinkedAccPresent = false;
if (savingsAccountId == null) {
if (accountAssociations != null) {
if (this.fromJsonHelper.parameterExists(linkAccountIdParamName, command.parsedJson())) {
this.accountAssociationsRepository.delete(accountAssociations);
changes.put(linkAccountIdParamName, null);
} else {
isLinkedAccPresent = true;
}
}
} else {
isLinkedAccPresent = true;
boolean isModified = false;
if (accountAssociations == null) {
isModified = true;
} else {
SavingsAccount savingsAccount = accountAssociations.linkedSavingsAccount();
if (savingsAccount == null || savingsAccount.getId() != savingsAccountId) {
isModified = true;
}
}
if (isModified) {
SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
if (accountAssociations == null) {
accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount);
} else {
accountAssociations.updateLinkedSavingsAccount(savingsAccount);
}
changes.put(linkAccountIdParamName, savingsAccountId);
this.accountAssociationsRepository.save(accountAssociations);
}
}
if (!isLinkedAccPresent) {
final Set<LoanCharge> charges = existingLoanApplication.charges();
for (LoanCharge loanCharge : charges) {
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
return new CommandProcessingResultBuilder() //
.withEntityId(loanId) //
.withOfficeId(existingLoanApplication.getOfficeId()) //
@@ -401,8 +468,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
logAsErrorUnexpectedDataIntegrityException(dve);
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
}
}
private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
}
@@ -572,7 +639,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
return loan;
}
private void validateSubmittedOnDate(final Loan loan) {
final LocalDate startDate = loan.loanProduct().getStartDate();
final LocalDate closeDate = loan.loanProduct().getCloseDate();
@@ -584,7 +651,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
throw new LoanApplicationDateException("submitted.on.date.cannot.be.before.the.loan.product.start.date", defaultUserMessage,
submittedOnDate.toString(), startDate.toString());
}
if (closeDate != null && submittedOnDate.isAfter(closeDate)) {
defaultUserMessage = "submittedOnDate cannot be after the loan product closeDate.";
throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.product.close.date", defaultUserMessage,
@@ -25,6 +25,7 @@ import org.mifosplatform.organisation.staff.exception.StaffRoleException;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
@@ -55,6 +56,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.useradministration.domain.AppUser;
@@ -158,6 +160,13 @@ public class LoanAssembler {
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if(loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()){
final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
if(savingsAccountId == null){
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
Loan loanApplication = null;
@@ -14,6 +14,7 @@ import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
@@ -65,19 +66,23 @@ public class LoanChargeAssembler {
locale);
final LocalDate dueDate = fromApiJsonHelper.extractLocalDateNamed("dueDate", loanChargeElement,
dateFormat, locale);
final Integer chargePaymentMode = fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
ChargeTimeType.fromInt(chargeTimeType);
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
}
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType);
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
}
ChargePaymentMode chargePaymentModeEnum = null;
if(chargePaymentMode != null){
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
}
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate);
chargeCalculation, dueDate, chargePaymentModeEnum);
loanCharges.add(loanCharge);
} else {
final Long loanChargeId = id;
@@ -17,4 +17,6 @@ public interface LoanChargeReadPlatformService {
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
}
@@ -55,6 +55,9 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
+ "lc.is_penalty as penalty, "
+ "lc.due_for_collection_as_of_date as dueAsOfDate, "
+ "lc.charge_calculation_enum as chargeCalculation, "
+ "lc.charge_payment_mode_enum as chargePaymentMode, "
+ "lc.is_paid_derived as paid, "
+ "lc.waived as waied, "
+ "c.currency_code as currencyCode, oc.name as currencyName, "
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c "
@@ -95,8 +98,14 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final boolean penalty = rs.getBoolean("penalty");
final int chargePaymentMode = rs.getInt("chargePaymentMode");
final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode);
final boolean paid = rs.getBoolean("paid");
final boolean waived = rs.getBoolean("waied");
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty);
chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode,
paid, waived, null);
}
}
@@ -107,7 +116,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions);
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null);
}
@Override
@@ -132,4 +141,32 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
@Override
public Collection<LoanChargeData> retrieveLoanChargesForFeePayment(final Integer paymentMode, final Integer loanStatus) {
final LoanChargeMapperWithLoanId rm = new LoanChargeMapperWithLoanId();
final String sql = "select " + rm.schema()
+ "where loan.loan_status_id= ? and lc.charge_payment_mode_enum=? and lc.waived =0 and lc.is_paid_derived=0";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanStatus, paymentMode });
}
private static final class LoanChargeMapperWithLoanId implements RowMapper<LoanChargeData> {
public String schema() {
return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, "
+ "lc.amount_outstanding_derived as amountOutstanding, "
+ "loan.id as loanId " + "from m_loan_charge lc "
+ "join m_loan loan on loan.id = lc.loan_id ";
}
@Override
public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final Long loanId = rs.getLong("loanId");
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, loanId);
}
}
}
@@ -11,6 +11,7 @@ import java.util.Map;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
@@ -67,4 +68,9 @@ public interface LoanWritePlatformService {
LoanTransaction acceptLoanTransfer(Long accountId, LocalDate transferDate, Office acceptedInOffice, Staff loanOfficer);
CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
void transferFeeCharges() throws JobExecutionException;
}
@@ -11,18 +11,23 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.holiday.domain.Holiday;
@@ -36,6 +41,9 @@ import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
@@ -45,9 +53,12 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBePayedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBePayedException.LOAN_CHARGE_CANNOT_BE_PAYED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
@@ -62,6 +73,7 @@ import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentComman
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
@@ -73,6 +85,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
@@ -88,6 +101,7 @@ import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -118,6 +132,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final LoanChargeReadPlatformService LoanChargeReadPlatformService;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -133,7 +149,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanProductReadPlatformService loanProductReadPlatformService,
final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final LoanChargeReadPlatformService loanChargeReadPlatformService) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -155,6 +173,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
this.LoanChargeReadPlatformService = loanChargeReadPlatformService;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -212,7 +232,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
if (!changes.isEmpty()) {
this.loanRepository.save(loan);
this.loanRepository.saveAndFlush(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -223,6 +243,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
Set<LoanCharge> loanCharges = loan.charges();
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, loanCharge.amount(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loanId,
"Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(),
loanCharge.getId());
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loan.getId()) //
@@ -332,8 +366,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.undoDisbursal(existingTransactionIds, existingReversedTransactionIds);
if (!changes.isEmpty()) {
this.loanRepository.save(loan);
this.loanRepository.saveAndFlush(loan);
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
@@ -381,11 +415,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
}
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
this.loanAccountDomainService.makeRepayment(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount,
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount,
paymentDetail, noteText, txnExternalId);
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
@@ -409,11 +443,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* TODO Vishwas, have a re-look at this implementation, defaulting
* it to null for now
***/
final Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId());
final PaymentDetail paymentDetail = null;
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
this.loanAccountDomainService.makeRepayment(singleLoanRepaymentCommand.getLoanId(), commandProcessingResultBuilder,
bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail,
bulkRepaymentCommand.getNote(), null);
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(),
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null);
changes.put("bulkTransations", singleLoanRepaymentCommand);
}
@@ -433,10 +467,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
if(accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)){
throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed","Loan transaction:"+transactionId+" update not allowed as it involves in account transfer",transactionId);
}
if (accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { throw new PlatformServiceUnavailableException(
"error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId
+ " update not allowed as it involves in account transfer", transactionId); }
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
@@ -505,7 +539,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
this.noteRepository.save(note);
}
this.accountTransfersWritePlatformService.reverseTransfers(loanId, PortfolioAccountType.LOAN);
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountType(loanId, PortfolioAccountType.LOAN);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -898,6 +932,85 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
@Override
@Transactional
public CommandProcessingResult payLoanCharge(final Long loanId, Long loanChargeId, final JsonCommand command,
boolean isChargeIdIncludedInJson) {
this.loanEventApiJsonValidator.validateChargePaymentTransaction(command.json(), isChargeIdIncludedInJson);
if (isChargeIdIncludedInJson) {
loanChargeId = command.longValueOfParameterNamed("chargeId");
}
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// Charges may be waived only when the loan associated with them are
// active
if (!loan.status().isActive()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.LOAN_INACTIVE,
loanCharge.getId()); }
// validate loan charge is not already paid or waived
if (loanCharge.isWaived()) {
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
loanCharge.getId()); }
if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); }
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, loanCharge.amountOutstanding(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId);
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanChargeId) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.withSavingsId(portfolioAccountData.accountId()).build();
}
@Override
@CronTarget(jobName = JobName.TRANSFER_FEE_CHARGE_FOR_LOANS)
public void transferFeeCharges() throws JobExecutionException {
Collection<LoanChargeData> chargeDatas = this.LoanChargeReadPlatformService.retrieveLoanChargesForFeePayment(
ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
StringBuilder sb = new StringBuilder();
if (chargeDatas != null) {
for (LoanChargeData chargeData : chargeDatas) {
if (chargeData.getDueDate()!=null && !chargeData.getDueDate().isAfter(new LocalDate())) {
PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId());
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());
try {
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} catch (PlatformApiDataValidationException e) {
sb.append("Validation exception while paying charge ").append(chargeData.getId()).append(" for loan id:")
.append(chargeData.getLoanId()).append("--------");
} catch (InsufficientAccountBalanceException e) {
sb.append("InsufficientAccountBalance Exception while paying charge ").append(chargeData.getId())
.append("for loan id:").append(chargeData.getLoanId()).append("--------");
}
}
}
}
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
}
private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }
@@ -0,0 +1,12 @@
package org.mifosplatform.portfolio.loanproduct.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LinkedAccountRequiredException extends AbstractPlatformDomainRuleException {
public LinkedAccountRequiredException(final String entity, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".requires.linked.account", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -302,6 +302,10 @@ public class LoanEnumerations {
optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND.getValue().longValue(),
LoanTransactionType.REFUND.getCode(), "Transfer Refund");
break;
case CHARGE_PAYMENT:
optionData = new LoanTransactionEnumData(LoanTransactionType.CHARGE_PAYMENT.getValue().longValue(),
LoanTransactionType.CHARGE_PAYMENT.getCode(), "Charge Payment");
break;
default:
break;
}
@@ -0,0 +1,12 @@
package org.mifosplatform.portfolio.savings.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class SavingsAccountClosingNotAllowedException extends AbstractPlatformDomainRuleException {
public SavingsAccountClosingNotAllowedException(final String entity, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".saving.account.close.notallowed", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -27,6 +27,6 @@ public class UndoTransactionSavingsAccountCommandHandler implements NewCommandSo
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
final Long transactionId = Long.valueOf(command.getTransactionId());
return this.writePlatformService.undoTransaction(command.getSavingsId(), transactionId);
return this.writePlatformService.undoTransaction(command.getSavingsId(), transactionId, false);
}
}
@@ -26,7 +26,7 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult postInterest(Long savingsId);
CommandProcessingResult undoTransaction(Long savingsId, Long transactionId);
CommandProcessingResult undoTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification);
void postInterestForAccounts();
@@ -38,6 +38,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
@@ -57,6 +58,7 @@ import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.mifosplatform.portfolio.savings.exception.SavingsAccountClosingNotAllowedException;
import org.mifosplatform.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
import org.mifosplatform.portfolio.savings.exception.TransactionUpdateNotAllowedException;
import org.mifosplatform.useradministration.domain.AppUser;
@@ -78,6 +80,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final SavingsAccountDomainService savingsAccountDomainService;
private final NoteRepository noteRepository;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -89,7 +92,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
@@ -101,6 +105,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountDomainService = savingsAccountDomainService;
this.noteRepository = noteRepository;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
}
@Transactional
@@ -299,7 +304,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
@Override
public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId) {
public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId, boolean allowAccountTransferModification) {
final List<Long> newTransactionIds = new ArrayList<Long>();
final List<Long> reversedTransactionIds = new ArrayList<Long>();
@@ -308,7 +313,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
transactionId, savingsId);
if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); }
if(accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)){
if(!allowAccountTransferModification && accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)){
throw new PlatformServiceUnavailableException("error.msg.saving.account.transfer.transaction.update.not.allowed","Savings account transaction:"+transactionId+" update not allowed as it involves in account transfer",transactionId);
}
@@ -451,6 +456,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validateClosing(command);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final boolean isLinkedWithAnyActiveLoan = accountAssociationsReadPlatformService.isLinkedWithAnyActiveLoan(savingsId);
if(isLinkedWithAnyActiveLoan){
String defaultUserMessage = "Closing savings account with id:"+savingsId+" is not allowed, since it is linked with one of the active loans";
throw new SavingsAccountClosingNotAllowedException("linked", defaultUserMessage, savingsId);
}
final Map<String, Object> changes = account.close(user, command, DateUtils.getLocalDateOfTenant());
if (!changes.isEmpty()) {
this.savingAccountRepository.save(account);