From d57a8faf30c436378a840ff544062a84a65b4e6a Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Wed, 18 Sep 2013 15:49:05 +0530 Subject: [PATCH] payment of loan charges from savings --- .../V117__loan_charge_from_savings.sql | 27 +++ ...ccrualBasedAccountingProcessorForLoan.java | 8 +- .../CashBasedAccountingProcessorForLoan.java | 3 +- .../commands/domain/CommandWrapper.java | 4 + .../service/CommandWrapperBuilder.java | 16 +- .../SynchronousCommandProcessingService.java | 2 + .../jobs/exception/JobExecutionException.java | 9 + .../infrastructure/jobs/service/JobName.java | 3 +- .../account/data/AccountAssociationsData.java | 18 ++ .../account/data/AccountTransferDTO.java | 99 +++++++++ .../account/data/PortfolioAccountData.java | 4 + .../account/domain/AccountAssociations.java | 53 +++++ .../domain/AccountAssociationsRepository.java | 14 ++ .../domain/AccountTransferAssembler.java | 15 ++ .../domain/AccountTransferRepository.java | 5 +- ...ccountAssociationsReadPlatformService.java | 11 + ...ntAssociationsReadPlatformServiceImpl.java | 124 +++++++++++ ...countTransfersReadPlatformServiceImpl.java | 2 +- .../AccountTransfersWritePlatformService.java | 7 +- ...ountTransfersWritePlatformServiceImpl.java | 77 ++++++- .../portfolio/charge/data/ChargeData.java | 35 ++-- .../portfolio/charge/domain/Charge.java | 28 ++- .../charge/domain/ChargePaymentMode.java | 40 ++++ .../LoanChargeCannotBePayedException.java | 47 +++++ ...initionCommandFromApiJsonDeserializer.java | 10 +- .../ChargeDropdownReadPlatformService.java | 2 + ...ChargeDropdownReadPlatformServiceImpl.java | 7 + .../charge/service/ChargeEnumerations.java | 22 ++ .../ChargeReadPlatformServiceImpl.java | 13 +- .../api/LoanChargesApiResource.java | 37 ++-- .../loanaccount/api/LoansApiResource.java | 77 +++++-- .../loanaccount/data/LoanAccountData.java | 61 ++++-- .../loanaccount/data/LoanChargeData.java | 83 +++++++- .../data/LoanTransactionEnumData.java | 14 ++ .../DefaultLoanLifecycleStateMachine.java | 1 + .../portfolio/loanaccount/domain/Loan.java | 133 ++++++++++-- .../domain/LoanAccountDomainService.java | 5 +- .../domain/LoanAccountDomainServiceJpa.java | 42 +++- .../loanaccount/domain/LoanCharge.java | 27 ++- .../loanaccount/domain/LoanChargePaidBy.java | 12 ++ .../loanaccount/domain/LoanEvent.java | 3 +- .../loanaccount/domain/LoanTransaction.java | 21 ++ .../domain/LoanTransactionType.java | 10 +- ...RepaymentScheduleTransactionProcessor.java | 27 +++ ...RepaymentScheduleTransactionProcessor.java | 10 +- ...RepaymentScheduleTransactionProcessor.java | 20 +- ...RepaymentScheduleTransactionProcessor.java | 11 +- ...RepaymentScheduleTransactionProcessor.java | 9 + ...RepaymentScheduleTransactionProcessor.java | 11 +- ...RepaymentScheduleTransactionProcessor.java | 23 +- .../handler/PayLoanChargeCommandHandler.java | 26 +++ ...ateLoanScheduleQueryFromApiJsonHelper.java | 2 +- ...anApplicationCommandFromApiJsonHelper.java | 41 +++- .../LoanEventApiJsonValidator.java | 66 ++++-- ...WritePlatformServiceJpaRepositoryImpl.java | 197 ++++++++++++------ .../loanaccount/service/LoanAssembler.java | 9 + .../service/LoanChargeAssembler.java | 13 +- .../LoanChargeReadPlatformService.java | 2 + .../LoanChargeReadPlatformServiceImpl.java | 41 +++- .../service/LoanWritePlatformService.java | 6 + ...WritePlatformServiceJpaRepositoryImpl.java | 141 +++++++++++-- .../LinkedAccountRequiredException.java | 12 ++ .../loanproduct/service/LoanEnumerations.java | 4 + ...ingsAccountClosingNotAllowedException.java | 12 ++ ...ansactionSavingsAccountCommandHandler.java | 2 +- .../SavingsAccountWritePlatformService.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 17 +- 67 files changed, 1695 insertions(+), 240 deletions(-) create mode 100755 mifosng-db/migrations/core_db/V117__loan_charge_from_savings.sql create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/exception/JobExecutionException.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountAssociationsData.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferDTO.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociations.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociationsRepository.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformService.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargePaymentMode.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBePayedException.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LinkedAccountRequiredException.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java diff --git a/mifosng-db/migrations/core_db/V117__loan_charge_from_savings.sql b/mifosng-db/migrations/core_db/V117__loan_charge_from_savings.sql new file mode 100755 index 000000000..f635fb942 --- /dev/null +++ b/mifosng-db/migrations/core_db/V117__loan_charge_from_savings.sql @@ -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); + + + \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 2e080c49b..adefe9ed6 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index f3a95d236..c1f0dc15f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -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); } /*** diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 8b3ede941..7d6ab3c03 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -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"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 32ca30133..119540c25 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -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"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index daf44520f..6fe400800 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -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()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/exception/JobExecutionException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/exception/JobExecutionException.java new file mode 100755 index 000000000..32e105907 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/exception/JobExecutionException.java @@ -0,0 +1,9 @@ +package org.mifosplatform.infrastructure.jobs.exception; + + +public class JobExecutionException extends Exception{ + + public JobExecutionException(String msg) { + super(msg); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java index 976003921..1bf267f4c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountAssociationsData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountAssociationsData.java new file mode 100755 index 000000000..0ea13a06e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountAssociationsData.java @@ -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; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferDTO.java new file mode 100755 index 000000000..f9fb6b889 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/AccountTransferDTO.java @@ -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; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java index 717a3efb5..1f9c45c46 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/data/PortfolioAccountData.java @@ -103,4 +103,8 @@ public class PortfolioAccountData { public BigDecimal getAmtForTransfer() { return this.amtForTransfer; } + + public Long accountId(){ + return this.id; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociations.java new file mode 100755 index 000000000..8adbaa7bd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociations.java @@ -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 { + + @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; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociationsRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociationsRepository.java new file mode 100755 index 000000000..364c7f5dd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountAssociationsRepository.java @@ -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, + JpaSpecificationExecutor { + + @Query("from AccountAssociations aa where aa.loanAccount.id= :loanId") + AccountAssociations findByLoanId(@Param("loanId") Long loanId); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java index 54625a820..3739d551d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferAssembler.java @@ -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()); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java index d16d86b29..34e866ca4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransferRepository.java @@ -15,5 +15,8 @@ import org.springframework.data.repository.query.Param; public interface AccountTransferRepository extends JpaRepository, JpaSpecificationExecutor { @Query("from AccountTransfer at where at.fromLoanAccount.id= :accountNumber and at.reversed=false") - List findByLoanId(@Param("accountNumber") Long accountNumber); + List findByFromLoanId(@Param("accountNumber") Long accountNumber); + + @Query("from AccountTransfer at where (at.fromLoanAccount.id= :accountNumber or at.toLoanAccount.id=:accountNumber) and at.reversed=false") + List findAllByLoanId(@Param("accountNumber") Long accountNumber); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformService.java new file mode 100755 index 000000000..e2f361331 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformService.java @@ -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); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java new file mode 100755 index 000000000..b0b228cb4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java @@ -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 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 { + + 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); + } + + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java index bc00341fd..8146af0ba 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -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 "); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java index ffd48dfb4..2c484b47e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformService.java @@ -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); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index b23e20adc..d1c5e7f93 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -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 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 acccountTransfers = null; + if (accountTypeId.isLoanAccount()) { + acccountTransfers = this.accountTransferRepository.findAllByLoanId(accountId); + } + if (acccountTransfers != null && acccountTransfers.size() > 0) { + undoTransactions(acccountTransfers); + } + } + + /** + * @param acccountTransfers + */ + private void undoTransactions(List 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(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java index fce174d18..b12ab1d0d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java @@ -28,30 +28,33 @@ public class ChargeData implements Comparable, Serializable { private final EnumOptionData chargeTimeType; private final EnumOptionData chargeAppliesTo; private final EnumOptionData chargeCalculationType; + private final EnumOptionData chargePaymentMode; private final Collection currencyOptions; private final List chargeCalculationTypeOptions; private final List chargeAppliesToOptions; private final List chargeTimeTypeOptions; + private final List chargePaymetModeOptions; public static ChargeData template(final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, - final List chargeTimeTypeOptions) { - return new ChargeData(null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, - chargeAppliesToOptions, chargeTimeTypeOptions); + final List chargeTimeTypeOptions, List 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, Serializable { EnumOptionData chargeTimeType = null; EnumOptionData chargeAppliesTo = null; EnumOptionData chargeCalculationType = null; + EnumOptionData chargePaymentMode = null; Boolean penalty = isPenalty; Boolean active = false; Collection currencyOptions = null; List chargeCalculationTypeOptions = null; List chargeAppliesToOptions = null; List chargeTimeTypeOptions = null; - return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, penalty, active, - currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions); + List 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 currencyOptions, - final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, - final List chargeTimeTypeOptions) { + EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, + final Collection currencyOptions, final List chargeCalculationTypeOptions, + final List chargeAppliesToOptions, final List chargeTimeTypeOptions, List chargePaymentModeOptions) { this.id = id; this.name = name; this.amount = amount; @@ -82,12 +87,14 @@ public class ChargeData implements Comparable, 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, 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); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java index 12ba2a31c..85c64b5bc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java @@ -46,6 +46,9 @@ public class Charge extends AbstractPersistable { @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 { 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 { } 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 { 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 { 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 { 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; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargePaymentMode.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargePaymentMode.java new file mode 100755 index 000000000..1bca59ebc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargePaymentMode.java @@ -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()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBePayedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBePayedException.java new file mode 100755 index 000000000..2f4406979 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBePayedException.java @@ -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); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java index 762e8d0bf..800b5426d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java @@ -35,7 +35,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { */ private final Set supportedParameters = new HashSet(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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java index 5bd2cb65a..706fac9da 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java @@ -16,4 +16,6 @@ public interface ChargeDropdownReadPlatformService { List retrieveApplicableToTypes(); List retrieveCollectionTimeTypes(); + + List retrivePaymentModes(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java index 614317f7f..27368266b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java @@ -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 retrieveCollectionTimeTypes() { return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE)); } + + @Override + public List retrivePaymentModes() { + return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER)); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java index c4d47c7a6..2d360bda6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java @@ -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; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index 5983878c3..566b5cc5c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -81,9 +81,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); + final List 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); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanChargesApiResource.java index a0734d319..4bbcb281a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanChargesApiResource.java @@ -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 diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 9e60bca46..d9d099a6a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -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 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 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 collateral = null; CalendarData meeting = null; Collection notes = null; + PortfolioAccountData linkedAccount = null; final Set mandatoryResponseParameters = new HashSet(); final Set 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 productOptions = null; @@ -330,6 +363,9 @@ public class LoansApiResource { Collection loanPurposeOptions = null; Collection loanCollateralOptions = null; Collection calendarOptions = null; + Collection 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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index 6227e5999..b900b006d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -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 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 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 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 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 termFrequencyTypeOptions, final Collection repaymentFrequencyTypeOptions, final Collection repaymentStrategyOptions, @@ -495,6 +505,8 @@ public class LoanAccountData { final Integer loancounter = null; final Integer loanProductCounter = null; + final PortfolioAccountData linkedAccount = null; + final Collection 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 loanPurposeOptions = null; final Collection loanCollateralOptions = null; final Collection calendarOptions = null; + final Collection 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 interestTypeOptions, final Collection interestCalculationPeriodTypeOptions, final Collection fundOptions, final Collection chargeOptions, final ChargeData chargeTemplate, final Collection loanOfficerOptions, final Collection loanPurposeOptions, - final Collection loanCollateralOptions, final Collection calendarOptions, final Collection notes) { + final Collection loanCollateralOptions, final Collection calendarOptions, final Collection notes, + final Collection 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 productOptions, - final Collection allowedLoanOfficers, final Collection calendarOptions) { + final Collection allowedLoanOfficers, final Collection calendarOptions,final Collection 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 fundOptions, final Collection chargeOptions, final ChargeData chargeTemplate, final Collection loanOfficerOptions, final Collection loanPurposeOptions, final Collection loanCollateralOptions, final Collection calendarOptions, - final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter, final Collection notes) { + final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter, final Collection notes, + final Collection 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; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java index aef04683c..e42f7d775 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java @@ -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 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 chargeOptions, final boolean penalty) { + final Collection 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; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java index dfec6baa5..404c2ecb8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java @@ -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; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java index 5a0e96c02..52d096ab4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 0069dd148..4acc2c466 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -501,6 +501,32 @@ public class Loan extends AbstractPersistable { 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 chargePaymentInstallments = new ArrayList(); + LocalDate startDate = getDisbursementDate(); + for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + if (charge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) { + chargePaymentInstallments.add(installment); + break; + } + startDate = installment.getDueDate(); + } + Set loanCharges = new HashSet(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 { 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 { **/ 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 { 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 undoDisbursal(final List existingTransactionIds, final List existingReversedTransactionIds) { final Map actualChanges = new LinkedHashMap(); @@ -1547,6 +1593,31 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } + public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine, + final List existingTransactionIds, final List existingReversedTransactionIds, + final boolean allowTransactionsOnHoliday, final List 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 existingTransactionIds, final List existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, @@ -1571,16 +1642,13 @@ public class Loan extends AbstractPersistable { 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 { 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 { 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 { 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 { } } } + + public Set charges() { + return this.charges; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java index 55b9e2fe3..81346f8d0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 0e43bf709..1fd68c6b5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -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 existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + 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 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 existingTransactionIds, final List existingReversedTransactionIds) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index c6000a1e0..d147f8abb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -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 { @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 { 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 { } 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 { 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 { 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 { .append(this.amount).append(this.getDueLocalDate()) // .toHashCode(); } + + + public ChargePaymentMode getChargePaymentMode() { + return ChargePaymentMode.fromInt(this.chargePaymentMode); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java index c2bd409b8..375eaeecd 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java @@ -58,5 +58,17 @@ public class LoanChargePaidBy extends AbstractPersistable { 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; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java index 025606d48..7f06285e3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java @@ -22,5 +22,6 @@ public enum LoanEvent { WRITE_OFF_OUTSTANDING, // LOAN_RESCHEDULE, // INTERST_REBATE_OWED, // - LOAN_OVERPAYMENT; + LOAN_OVERPAYMENT, + LOAN_CHARGE_PAYMENT; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 64e4e311e..61109ad42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -108,6 +108,12 @@ public final class LoanTransaction extends AbstractPersistable { 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 { 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(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java index b8ee6fc53..9ef8421d3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java @@ -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()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 3474c6a3c..4ffcd5a53 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -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 transactionstoBeProcessed = new ArrayList(); for (LoanTransaction loanTransaction : transactionsPostDisbursement) { + if (loanTransaction.isChargePayment()){ + Set chargePaidBies =loanTransaction.getLoanChargesPaid(); + Set transferCharges = new HashSet(); + for(LoanChargePaidBy chargePaidBy: chargePaidBies){ + transferCharges.add(chargePaidBy.getLoanCharge()); + } + List chargePaymentInstallments = new ArrayList(); + 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 diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java index 3b12ab0b8..d94d91f29 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java index dec0efdbb..b4fec4ea7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index c969f7ea1..e64f7cbcf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java index 5e21a8f17..81eb39446 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index f49774501..ce492facf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java index 019624122..e670743aa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java new file mode 100755 index 000000000..183998b37 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/PayLoanChargeCommandHandler.java @@ -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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index 687207ba3..6f528cca9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 48f44a7cc..a0a28bb3a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -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 dataValidationErrors = new ArrayList(); + 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); } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java index 478e1430b..3b48a5046 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java @@ -50,8 +50,8 @@ public final class LoanEventApiJsonValidator { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - final Set disbursementParameters = new HashSet(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", - "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); + final Set disbursementParameters = new HashSet(Arrays.asList("actualDisbursementDate", "externalId", "note", + "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); final Type typeOfMap = new TypeToken>() {}.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 transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note", - "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); + final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", + "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", + "bankNumber")); final Type typeOfMap = new TypeToken>() {}.getType(); fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters); @@ -119,8 +119,9 @@ public final class LoanEventApiJsonValidator { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note", - "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); + final Set transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "externalId", + "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", + "bankNumber")); final Type typeOfMap = new TypeToken>() {}.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 supportedParameters = new HashSet(Arrays.asList("assignmentDate", "fromLoanOfficerId", - "toLoanOfficerId", "loans", "locale", "dateFormat")); + final Set supportedParameters = new HashSet(Arrays.asList("assignmentDate", "fromLoanOfficerId", "toLoanOfficerId", + "loans", "locale", "dateFormat")); final Type typeOfMap = new TypeToken>() {}.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 transactionParameters = null; + if(isChargeIdIncluded){ + transactionParameters = new HashSet(Arrays.asList("transactionDate", "locale", "dateFormat","chargeId")); + }else{ + transactionParameters = new HashSet(Arrays.asList("transactionDate", "locale", "dateFormat")); + } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters); + + final List dataValidationErrors = new ArrayList(); + 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); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index c34b95a7b..571ae4ded 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -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 dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + final List dataValidationErrors = new ArrayList(); + 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 ciList = (List) 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 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, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index 07cd579d8..232d1b8d6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java index 10d42beac..72b52dd56 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformService.java index f7cbf136a..71fd6cb57 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformService.java @@ -17,4 +17,6 @@ public interface LoanChargeReadPlatformService { Collection retrieveLoanCharges(Long loanId); LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId); + + Collection retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index 9307315be..465396218 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -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 allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List 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 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 { + + 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); + } + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index c4c7050c3..7bc82239c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -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; + + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index abe1ae2a2..e796091dc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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 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 existingReversedTransactionIds = new ArrayList(); final Map 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 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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LinkedAccountRequiredException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LinkedAccountRequiredException.java new file mode 100755 index 000000000..932d1d8da --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LinkedAccountRequiredException.java @@ -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); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index c426545ca..904834899 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java new file mode 100755 index 000000000..5f7db3bbf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/SavingsAccountClosingNotAllowedException.java @@ -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); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java index a942e2be5..b025bdd14 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UndoTransactionSavingsAccountCommandHandler.java @@ -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); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 806713b36..f6860d3c9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -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(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index d65852b78..92314708e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -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 newTransactionIds = new ArrayList(); final List reversedTransactionIds = new ArrayList(); @@ -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 changes = account.close(user, command, DateUtils.getLocalDateOfTenant()); if (!changes.isEmpty()) { this.savingAccountRepository.save(account);