From e8c9381818cc9faa3b5e1a01276ace34ae3d0916 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Mon, 23 Sep 2013 12:25:51 +0530 Subject: [PATCH 01/15] update account transfers with new loan transaction id --- .../account/domain/AccountTransfer.java | 5 ++++ .../domain/AccountTransferRepository.java | 3 +++ .../AccountTransfersWritePlatformService.java | 3 +++ ...ountTransfersWritePlatformServiceImpl.java | 10 ++++++++ .../domain/ChangedTransactionDetail.java | 16 ++++--------- .../portfolio/loanaccount/domain/Loan.java | 12 +++++----- .../domain/LoanAccountDomainServiceJpa.java | 23 ++++++++++++++++--- ...RepaymentScheduleTransactionProcessor.java | 3 +-- ...WritePlatformServiceJpaRepositoryImpl.java | 15 +++++++----- 9 files changed, 62 insertions(+), 28 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java index 4fc639270..212e991a5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/domain/AccountTransfer.java @@ -175,4 +175,9 @@ public class AccountTransfer extends AbstractPersistable { public SavingsAccount getFromAccount() { return this.fromAccount; } + + + public void updateToLoanTransaction(LoanTransaction toLoanTransaction) { + this.toLoanTransaction = toLoanTransaction; + } } \ 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 fa04fc9cc..8fa7983af 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 @@ -19,4 +19,7 @@ public interface AccountTransferRepository extends JpaRepository findAllByLoanId(@Param("accountNumber") Long accountNumber); + + @Query("from AccountTransfer at where at.toLoanTransaction.id= :loanTransactionId and at.reversed=false") + AccountTransfer findByToLoanTransactionId(@Param("loanTransactionId") Long loanTransactionId); } \ No newline at end of file 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 2c484b47e..68d043e3d 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 @@ -9,6 +9,7 @@ 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; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; public interface AccountTransfersWritePlatformService { @@ -19,4 +20,6 @@ public interface AccountTransfersWritePlatformService { Long transferFunds(AccountTransferDTO accountTransferDTO); void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId); + + void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction); } \ 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 3c23a7fc5..029fd2e74 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 @@ -239,6 +239,16 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer return transferTransactionId; } + + @Override + @Transactional + public void updateLoanTransaction(final Long loanTransactionId,final LoanTransaction newLoanTransaction){ + final AccountTransfer transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); + if(transferTransaction!=null){ + transferTransaction.updateToLoanTransaction(newLoanTransaction); + this.accountTransferRepository.save(transferTransaction); + } + } 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/loanaccount/domain/ChangedTransactionDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java index 2cc96cba1..69d0889e8 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java @@ -5,8 +5,8 @@ */ package org.mifosplatform.portfolio.loanaccount.domain; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; /** * Stores details of {@link LoanTransaction}'s that were reversed or newly @@ -16,16 +16,10 @@ import java.util.List; */ public class ChangedTransactionDetail { - private final List newTransactions = new ArrayList(); + private final Map newTransactionMappings = new HashMap(); - private final List reversedTransactions = new ArrayList(); - - public List getNewTransactions() { - return this.newTransactions; - } - - public List getReversedTransactions() { - return this.reversedTransactions; + public Map getNewTransactionMappings() { + return this.newTransactionMappings; } } 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 fd788bc21..a64e72a30 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 @@ -444,10 +444,10 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); - for (final LoanTransaction transaction : changedTransactionDetail.getNewTransactions()) { - transaction.updateLoan(this); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + mapEntry.getValue().updateLoan(this); } - this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions()); + this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); } else { // just reprocess the loan schedule only for now. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); @@ -1710,10 +1710,10 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); - for (final LoanTransaction newLoanTransaction : changedTransactionDetail.getNewTransactions()) { - newLoanTransaction.updateLoan(this); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + mapEntry.getValue().updateLoan(this); } - this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions()); + this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); } updateLoanSummaryDerivedFields(); 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 32d88f2b7..57d3c4bfe 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 @@ -24,6 +24,8 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.account.domain.AccountTransfer; +import org.mifosplatform.portfolio.account.domain.AccountTransferRepository; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.group.domain.Group; @@ -49,6 +51,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final NoteRepository noteRepository; + private final AccountTransferRepository accountTransferRepository; @Autowired public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, @@ -56,7 +59,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, - final JournalEntryWritePlatformService journalEntryWritePlatformService) { + final JournalEntryWritePlatformService journalEntryWritePlatformService, + final AccountTransferRepository accountTransferRepository) { this.loanAccountAssembler = loanAccountAssembler; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -66,6 +70,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.workingDaysRepository = workingDaysRepository; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; + this.accountTransferRepository = accountTransferRepository; } @Transactional @@ -115,8 +120,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { * recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { - this.loanTransactionRepository.save(loanTransaction); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + this.loanTransactionRepository.save(mapEntry.getValue()); + updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } this.loanRepository.save(loan); @@ -125,6 +131,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); this.noteRepository.save(note); } + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); @@ -247,4 +255,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanTransaction.reverse(); this.loanTransactionRepository.save(loanTransaction); } + + private void updateLoanTransaction(final Long loanTransactionId,final LoanTransaction newLoanTransaction){ + final AccountTransfer transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); + if(transferTransaction!=null){ + transferTransaction.updateToLoanTransaction(newLoanTransaction); + this.accountTransferRepository.save(transferTransaction); + } + } + } \ 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 404e3d290..4fca20850 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 @@ -118,8 +118,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen **/ if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) { loanTransaction.reverse(); - changedTransactionDetail.getReversedTransactions().add(loanTransaction); - changedTransactionDetail.getNewTransactions().add(newLoanTransaction); + changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction); } } 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 5021d484a..57eb08c83 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 @@ -523,8 +523,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * payment recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { - this.loanTransactionRepository.save(loanTransaction); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + this.loanTransactionRepository.save(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } @@ -598,8 +599,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { - this.loanTransactionRepository.save(loanTransaction); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + this.loanTransactionRepository.save(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } @@ -819,8 +821,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * latest payment recorded against the loan) ***/ if (changedTransactionDetail != null) { - for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { - this.loanTransactionRepository.save(loanTransaction); + for(Map.Entry mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){ + this.loanTransactionRepository.save(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } From ac31249a71fc4d94acde246e84268f3943e0ce50 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Mon, 23 Sep 2013 13:09:43 +0530 Subject: [PATCH 02/15] Added missing Braces for create holidays in API doc --- api-docs/apiLive.htm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 80ebad7ea..d84d39975 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -9158,8 +9158,8 @@ Request Body: "toDate": "25 October 2013", "repaymentsRescheduledTo": "26 October 2013", "offices": [ - "officeId":"1", - "officeId":"2" + {"officeId":"1"}, + {"officeId":"2"} ] } From b0500f6ee9ecc74fc343ef00cbd87e23f0a396c1 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Mon, 23 Sep 2013 20:57:18 +0530 Subject: [PATCH 03/15] added unique number generation for account transaction id --- ...WritePlatformServiceJpaRepositoryImpl.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 506f42a08..5056f4c5d 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -40,10 +40,12 @@ 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.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.organisation.office.domain.OrganisationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; +import org.mifosplatform.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -67,6 +69,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private final AccountingRuleRepository accountingRuleRepository; private final GLAccountReadPlatformService glAccountReadPlatformService; private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository; + private final PlatformSecurityContext context; @Autowired public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, @@ -76,7 +79,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory, final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory, final GLAccountReadPlatformService glAccountReadPlatformService, - final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository) { + final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, + final PlatformSecurityContext context) { this.glClosureRepository = glClosureRepository; this.officeRepository = officeRepository; this.glJournalEntryRepository = glJournalEntryRepository; @@ -88,6 +92,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa this.accountingRuleRepository = accountingRuleRepository; this.glAccountReadPlatformService = glAccountReadPlatformService; this.organisationCurrencyRepository = organisationCurrencyRepository; + this.context = context; } @Transactional @@ -109,7 +114,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa /** Set a transaction Id and save these Journal entries **/ final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue()); - final String transactionId = generateTransactionId(); + final String transactionId = generateTransactionId(officeId); final String referenceNumber = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue()); if (accountRuleId != null) { @@ -263,10 +268,10 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa .getTransactionId()); if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); } - - final String reversalTransactionId = generateTransactionId(); + Long officeId = journalEntries.get(0).getOffice().getId(); + final String reversalTransactionId = generateTransactionId(officeId); final boolean manualEntry = true; - + for (final JournalEntry journalEntry : journalEntries) { JournalEntry reversalJournalEntry; final String reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId() @@ -377,8 +382,12 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa * TODO: Need a better implementation with guaranteed uniqueness (but not a * long UUID)...maybe something tied to system clock.. */ - private String generateTransactionId() { - return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + private String generateTransactionId(Long officeId) { + AppUser user = this.context.authenticatedUser(); + Long time = System.currentTimeMillis(); + String uniqueVal = String.valueOf(time) + user.getId() + officeId; + String transactionId = Long.toHexString(Long.parseLong(uniqueVal)); + return transactionId; } private void handleJournalEntryDataIntegrityIssues(final DataIntegrityViolationException dve) { From d95172ae814dbc552a7453886c1da215d6a54b9c Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Mon, 23 Sep 2013 20:59:36 +0530 Subject: [PATCH 04/15] removed unused imports --- .../JournalEntryWritePlatformServiceJpaRepositoryImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 5056f4c5d..6e5532e09 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -11,7 +11,6 @@ import java.util.Date; import java.util.List; import java.util.Map; -import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; import org.mifosplatform.accounting.closure.domain.GLClosure; From 7330ed08ea8c1905c4532996cd6d1f88d7cffabc Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Tue, 24 Sep 2013 18:15:12 +0530 Subject: [PATCH 05/15] Savings fee code cleanup and added shcedule for paying charges --- api-docs/apiLive.htm | 2 + ...batch_job_entry_for_pay_savings_charge.sql | 1 + .../infrastructure/jobs/service/JobName.java | 2 +- .../service/HolidayWritePlatformService.java | 5 + ...WritePlatformServiceJpaRepositoryImpl.java | 19 ++- .../WorkingDaysWritePlatformService.java | 10 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 34 +++++ .../charge/api/ChargesApiResource.java | 3 +- .../portfolio/charge/data/ChargeData.java | 55 ++++++-- .../ChargeDropdownReadPlatformService.java | 9 ++ ...ChargeDropdownReadPlatformServiceImpl.java | 27 ++++ .../ChargeReadPlatformServiceImpl.java | 10 +- .../LoanChargeReadPlatformServiceImpl.java | 13 +- .../savings/SavingsApiConstants.java | 3 + .../api/SavingsAccountChargesApiResource.java | 2 +- .../SavingsAccountChargeDataValidator.java | 29 +++- .../savings/domain/SavingsAccount.java | 80 +++++++---- .../savings/domain/SavingsAccountCharge.java | 26 +++- .../SavingsAccountChargeRepository.java | 8 ++ ...SavingsAccountChargeRepositoryWrapper.java | 12 ++ .../savings/domain/SavingsAccountSummary.java | 10 ++ .../domain/SavingsAccountTransaction.java | 22 +++ ...vingsAccountTransactionSummaryWrapper.java | 24 +++- ...iveSavingsAccountChargeCommandHandler.java | 2 +- ...sAccountChargeReadPlatformServiceImpl.java | 11 +- .../SavingsAccountWritePlatformService.java | 4 +- ...WritePlatformServiceJpaRepositoryImpl.java | 132 +++++++++++++++--- 27 files changed, 482 insertions(+), 73 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index d84d39975..40b998a06 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -14344,8 +14344,10 @@ POST savingsaccounts/1/charges/2?command=paycharge Content-Type: application/json Request Body: { + "dateFormat": "dd MMMM yyyy", "locale": "en", "amount": "60", + "dueDate": "12 September 2013" } diff --git a/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql b/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql new file mode 100644 index 000000000..fd5fb5892 --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_5__batch_job_entry_for_pay_savings_charge.sql @@ -0,0 +1 @@ +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 ('Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, NULL, NULL, 'Pay Due Savings ChargesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0); 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 57fc53e28..022458f69 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 @@ -5,7 +5,7 @@ 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"), TRANSFER_FEE_CHARGE_FOR_LOANS( - "Transfer Fee For Loans From Savings"); + "Transfer Fee For Loans From Savings"), PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"); private final String name; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java index 593cf6523..6f702fa43 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java @@ -1,9 +1,14 @@ package org.mifosplatform.organisation.holiday.service; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; public interface HolidayWritePlatformService { CommandProcessingResult createHoliday(JsonCommand command); + + boolean isHoliday(Long officeId, LocalDate transactionDate); + + boolean isTransactionAllowedOnHoliday(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java index 68c8a8b05..db8578c2d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java @@ -1,9 +1,11 @@ package org.mifosplatform.organisation.holiday.service; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.joda.time.LocalDate; +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; @@ -39,7 +41,7 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit private final HolidayDataValidator fromApiJsonDeserializer; private final HolidayRepository holidayRepository; private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; - + private final ConfigurationDomainService configurationDomainService; private final PlatformSecurityContext context; private final OfficeRepository officeRepository; private final FromJsonHelper fromApiJsonHelper; @@ -47,13 +49,14 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit @Autowired public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer, final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository, - final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper) { + final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper, final ConfigurationDomainService configurationDomainService) { this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.holidayRepository = holidayRepository; this.context = context; this.officeRepository = officeRepository; this.fromApiJsonHelper = fromApiJsonHelper; this.daysRepositoryWrapper = daysRepositoryWrapper; + this.configurationDomainService = configurationDomainService; } @Transactional @@ -152,4 +155,16 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit toDate.toString(), repaymentsRescheduledTo.toString()); } } + + @Override + public boolean isHoliday(Long officeId, LocalDate transactionDate) { + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, transactionDate.toDate()); + return HolidayUtil.isHoliday(transactionDate, holidays); + } + + @Override + public boolean isTransactionAllowedOnHoliday() { + return this.configurationDomainService.allowTransactionsOnHolidayEnabled(); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java new file mode 100644 index 000000000..e71ae2ffd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformService.java @@ -0,0 +1,10 @@ +package org.mifosplatform.organisation.workingdays.service; + +import org.joda.time.LocalDate; + +public interface WorkingDaysWritePlatformService { + + boolean isWorkingDay(LocalDate transactionDate); + + boolean isTransactionAllowedOnNonWorkingDay(); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..4e2288715 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,34 @@ +package org.mifosplatform.organisation.workingdays.service; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; +import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService { + + private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; + private final ConfigurationDomainService configurationDomainService; + + @Autowired + public WorkingDaysWritePlatformServiceJpaRepositoryImpl(final WorkingDaysRepositoryWrapper daysRepositoryWrapper, + final ConfigurationDomainService configurationDomainService) { + this.daysRepositoryWrapper = daysRepositoryWrapper; + this.configurationDomainService = configurationDomainService; + } + + @Override + public boolean isWorkingDay(LocalDate transactionDate) { + final WorkingDays workingDays = this.daysRepositoryWrapper.findOne(); + return WorkingDaysUtil.isWorkingDay(workingDays, transactionDate); + } + + @Override + public boolean isTransactionAllowedOnNonWorkingDay() { + return this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java index b5d8e2261..154c7f108 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiResource.java @@ -43,7 +43,8 @@ public class ChargesApiResource { private final Set CHARGES_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "name", "amount", "currency", "penalty", "active", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", - "chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions")); + "chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions", "loanChargeCalculationTypeOptions", + "loanChargeTimeTypeOptions", "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions")); private final String resourceNameForPermissions = "CHARGE"; 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 7ad450aa7..64252a5a3 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 @@ -33,30 +33,53 @@ public class ChargeData implements Comparable, Serializable { private final EnumOptionData chargePaymentMode; private final Collection currencyOptions; - private final List chargeCalculationTypeOptions; - private final List chargeAppliesToOptions; - private final List chargeTimeTypeOptions; - private final List chargePaymetModeOptions; + private final List chargeCalculationTypeOptions;// + private final List chargeAppliesToOptions;// + private final List chargeTimeTypeOptions;// + private final List chargePaymetModeOptions;// + + private final List loanChargeCalculationTypeOptions; + private final List loanChargeTimeTypeOptions; + private final List savingsChargeCalculationTypeOptions; + private final List savingsChargeTimeTypeOptions; public static ChargeData template(final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, - final List chargeTimeTypeOptions, final List chargePaymentModeOptions) { + final List chargeTimeTypeOptions, final List chargePaymentModeOptions, + final List loansChargeCalculationTypeOptions, final List loansChargeTimeTypeOptions, + final List savingsChargeCalculationTypeOptions, final List savingsChargeTimeTypeOptions) { + return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, - chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions); + chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, + loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } 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.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions, - template.chargePaymetModeOptions); + template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, + template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions); } 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 EnumOptionData chargePaymentMode, final boolean penalty, final boolean active) { + + final Collection currencyOptions = null; + final List chargeCalculationTypeOptions = null; + final List chargeAppliesToOptions = null; + final List chargeTimeTypeOptions = null; + final List chargePaymentModeOptions = null; + final List loansChargeCalculationTypeOptions = null; + final List loansChargeTimeTypeOptions = null; + final List savingsChargeCalculationTypeOptions = null; + final List savingsChargeTimeTypeOptions = null; + return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, - penalty, active, null, null, null, null, null); + penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, + chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) { @@ -73,9 +96,15 @@ public class ChargeData implements Comparable, Serializable { final List chargeAppliesToOptions = null; final List chargeTimeTypeOptions = null; final List chargePaymentModeOptions = null; + final List loansChargeCalculationTypeOptions = null; + final List loansChargeTimeTypeOptions = null; + final List savingsChargeCalculationTypeOptions = null; + final List savingsChargeTimeTypeOptions = null; + return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, - chargePaymentModeOptions); + chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, @@ -83,7 +112,9 @@ public class ChargeData implements Comparable, Serializable { final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, final List chargeTimeTypeOptions, - final List chargePaymentModeOptions) { + final List chargePaymentModeOptions, final List loansChargeCalculationTypeOptions, + final List loansChargeTimeTypeOptions, final List savingsChargeCalculationTypeOptions, + final List savingsChargeTimeTypeOptions) { this.id = id; this.name = name; this.amount = amount; @@ -99,6 +130,10 @@ public class ChargeData implements Comparable, Serializable { this.chargeAppliesToOptions = chargeAppliesToOptions; this.chargeTimeTypeOptions = chargeTimeTypeOptions; this.chargePaymetModeOptions = chargePaymentModeOptions; + this.savingsChargeCalculationTypeOptions = savingsChargeCalculationTypeOptions; + this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions; + this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions; + this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions; } @Override 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 f5e445e47..60f6c9e17 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 @@ -18,4 +18,13 @@ public interface ChargeDropdownReadPlatformService { List retrieveCollectionTimeTypes(); List retrivePaymentModes(); + + List retrieveLoanCalculationTypes(); + + List retrieveLoanCollectionTimeTypes(); + + List retrieveSavingsCalculationTypes(); + + List retrieveSavingsCollectionTimeTypes(); + } 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 fcf2d9998..0114eb0eb 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 @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.charge.service; import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType; import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode; +import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType; import java.util.ArrayList; import java.util.Arrays; @@ -68,4 +69,30 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead public List retrivePaymentModes() { return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER)); } + + @Override + public List retrieveLoanCalculationTypes() { + return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT)); + } + + @Override + public List retrieveLoanCollectionTimeTypes() { + return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE)); + } + + @Override + public List retrieveSavingsCalculationTypes() { + return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT) + //Enable when percentage of amount is supported for savings charges + //, chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT) + ); + } + + @Override + public List retrieveSavingsCollectionTimeTypes() { + return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE) + //enable when support is added for recurring Monthly and yearly charges + //, chargeTimeType(ChargeTimeType.MONTHLY), chargeTimeType(ChargeTimeType.YEARLY) + ); + } } \ No newline at end of file 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 424852e07..15926d63d 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 @@ -82,9 +82,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final List allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); final List chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes(); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions, - allowedChargeTimeOptions, chargePaymentOptions); + allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); } @Override 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 939b1b787..d4bcac095 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 @@ -115,8 +115,17 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); - - return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); + + return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, + loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, + savingsChargeTimeTypeOptions); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index fd9cab5d9..5a77b82ea 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -192,4 +192,7 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); + + public static final Set SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( + amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java index 97c3f343c..458cb4ed1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountChargesApiResource.java @@ -155,7 +155,7 @@ public class SavingsAccountChargesApiResource { @Path("{savingsAccountChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String waiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, + public String payOrWaiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId, @PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java index 7949ad3a2..50161bfed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; @@ -18,6 +19,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -57,7 +59,8 @@ public class SavingsAccountChargeDataValidator { baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { - this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -79,12 +82,34 @@ public class SavingsAccountChargeDataValidator { baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) { - this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validatePayCharge(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element); + baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); + + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 6cd2e34db..41987277b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -14,6 +14,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -735,7 +736,7 @@ public class SavingsAccount extends AbstractPersistable { return transactionBeforeLastInterestPosting; } - + public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount) { final List transactionsSortedByDate = retreiveListOfTransactions(); Money runningBalance = Money.zero(this.currency); @@ -1893,7 +1894,6 @@ public class SavingsAccount extends AbstractPersistable { } public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { - // TODO AA: revisit if (this.currency == null) { return false; } return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode); } @@ -1907,7 +1907,7 @@ public class SavingsAccount extends AbstractPersistable { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); @@ -1935,13 +1935,6 @@ public class SavingsAccount extends AbstractPersistable { // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); - // TODO : AA revisit the code - /* - * Money feeChargesWaived = amountWaived; Money penaltyChargesWaived = - * Money.zero(this.getCurrency()); if - * (savingsAccountCharge.isPenaltyCharge()) { penaltyChargesWaived = - * amountWaived; feeChargesWaived = Money.zero(this.getCurrency()); } - */ // persist an entry in transaction details final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), @@ -1958,18 +1951,13 @@ public class SavingsAccount extends AbstractPersistable { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (isNotActive()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active"); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - if (!hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "transaction.invalid.account.currency.and.charge.currency.not.same"); @@ -1979,7 +1967,7 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter("dueDate").value(getActivationLocalDate().toString(formatter)) + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -1991,12 +1979,12 @@ public class SavingsAccount extends AbstractPersistable { } - public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final LocalDate transactionDate, + final DateTimeFormatter formatter, final List existingTransactionIds, final List existingReversedTransactionIds) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate"); + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); if (isClosed()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed"); @@ -2008,6 +1996,12 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + if (transactionDate != null && transactionDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); // validate charge is not already paid or waived @@ -2018,17 +2012,19 @@ public class SavingsAccount extends AbstractPersistable { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + + final Money amountPaidMoney = Money.of(currency, amountPaid); + if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaid); - - // If null reset to current date - final LocalDate transactionDate = (savingsAccountCharge.getDueLocalDate() == null) ? DateUtils.getLocalDateOfTenant() - : savingsAccountCharge.getDueLocalDate(); - - // persist an entry in transaction details + final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaidMoney); + + // persist transaction details final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, chargePaid); final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, @@ -2051,6 +2047,36 @@ public class SavingsAccount extends AbstractPersistable { } + public void payDueCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + final Money amountPaid = savingsAccountCharge.payDueCharge(getCurrency()); + + // persist transaction details + final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, amountPaid); + + final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, + amountPaid.getAmount()); + + chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); + + this.transactions.add(chargeTransaction); + + final MathContext mc = MathContext.DECIMAL64; + if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + calculateInterestUsing(mc, today); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + validateAccountBalanceDoesNotBecomeNegative(amountPaid.getAmount()); + } + private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) { SavingsAccountCharge charge = null; for (final SavingsAccountCharge existingCharge : this.charges) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index 33c5efa66..4b296b9f6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -228,13 +228,25 @@ public class SavingsAccountCharge extends AbstractPersistable { return getAmountWaived(currency); } - public Money pay(final MonetaryCurrency currency, final BigDecimal chargePaid) { + public Money pay(final MonetaryCurrency currency, final Money amountPaid) { Money amountPaidToDate = Money.of(currency, this.amountPaid); - final Money amountPaid = Money.of(currency, chargePaid); Money amountOutstanding = Money.of(currency, this.amountOutstanding); amountPaidToDate = amountPaidToDate.plus(amountPaid); - amountOutstanding = amountOutstanding.minus(chargePaid); + amountOutstanding = amountOutstanding.minus(amountPaid); + this.amountPaid = amountPaidToDate.getAmount(); + this.amountOutstanding = amountOutstanding.getAmount(); + this.paid = determineIfFullyPaid(); + return amountPaid; + } + + public Money payDueCharge(final MonetaryCurrency currency) { + + Money amountPaidToDate = Money.of(currency, this.amountPaid); + Money amountOutstanding = Money.of(currency, this.amountOutstanding); + final Money amountPaid = Money.of(currency, this.amountOutstanding);; + amountPaidToDate = amountPaidToDate.plus(amountPaid); + amountOutstanding = amountOutstanding.minus(amountPaid); this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = amountOutstanding.getAmount(); this.paid = determineIfFullyPaid(); @@ -440,6 +452,10 @@ public class SavingsAccountCharge extends AbstractPersistable { return Money.of(currency, this.amountWrittenOff); } + public Money getAmountOutstanding(final MonetaryCurrency currency){ + return Money.of(currency, this.amountOutstanding); + } + /** * @param incrementBy * Amount used to pay off this charge @@ -486,6 +502,10 @@ public class SavingsAccountCharge extends AbstractPersistable { return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate(); } + public SavingsAccount savingsAccount(){ + return this.savingsAccount; + } + @Override public boolean equals(final Object obj) { if (obj == null) { return false; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java index 38e007ffa..959e22f8f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java @@ -5,11 +5,19 @@ */ package org.mifosplatform.portfolio.savings.domain; +import java.util.Date; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface SavingsAccountChargeRepository extends JpaRepository, JpaSpecificationExecutor { SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId); + + @Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0") + List findPendingCharges(@Param("transactionDate") Date transactionDate); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java index 87bebc69e..68187bbd6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java @@ -5,6 +5,10 @@ */ package org.mifosplatform.portfolio.savings.domain; +import java.util.Collection; +import java.util.Date; +import java.util.List; + import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,11 +40,19 @@ public class SavingsAccountChargeRepositoryWrapper { if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); } return savingsAccountCharge; } + + public List findPendingCharges(final Date transactionDate){ + return this.repository.findPendingCharges(transactionDate); + } public void save(final SavingsAccountCharge savingsAccountCharge) { this.repository.save(savingsAccountCharge); } + public void save(final Iterable savingsAccountCharges){ + this.repository.save(savingsAccountCharges); + } + public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) { this.repository.saveAndFlush(savingsAccountCharge); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index 91e26bcdd..3a79cef14 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -10,6 +10,7 @@ import java.util.List; import javax.persistence.Column; import javax.persistence.Embeddable; +import javax.persistence.Transient; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -49,6 +50,13 @@ public final class SavingsAccountSummary { @Column(name = "account_balance_derived", scale = 6, precision = 19) private BigDecimal accountBalance = BigDecimal.ZERO; + //TODO: AA do we need this data to be persisted. + @Transient + private BigDecimal totalFeeChargesWaived = BigDecimal.ZERO; + + @Transient + private BigDecimal totalPenaltyChargesWaived = BigDecimal.ZERO; + protected SavingsAccountSummary() { // } @@ -63,6 +71,8 @@ public final class SavingsAccountSummary { this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions); this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions); this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions); + this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, transactions); + this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 7475e64aa..29005cbb5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -437,6 +437,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public boolean isCharge() { return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge(); } + + public boolean isWaiveCharge(){ + return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge(); + } public boolean isFeeCharge() { final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); @@ -456,6 +460,24 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return isPenaltyCharge() && isNotReversed(); } + public boolean isWaiveFeeCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + } + + public boolean isWaivePenaltyCharge() { + final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); + return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + } + + public boolean isWaiveFeeChargeAndNotReversed() { + return isWaiveFeeCharge() && isNotReversed(); + } + + public boolean isWaivePenaltyChargeAndNotReversed() { + return isWaivePenaltyCharge() && isNotReversed(); + } + private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy() { if (!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)) { return this.savingsAccountChargesPaid.iterator().next(); } return null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index b75caf58a..c61ed8fc1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -72,17 +72,37 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isFeeChargeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isFeeChargeAndNotReversed()) { total = total.plus(transaction.getAmount(currency)); } } return total.getAmountDefaultedToNullIfZero(); } + public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (final SavingsAccountTransaction transaction : transactions) { + if (transaction.isWaiveFeeChargeAndNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isPenaltyChargeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isPenaltyChargeAndNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } + + public BigDecimal calculateTotalPenaltyChargeWaived(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (final SavingsAccountTransaction transaction : transactions) { + if (transaction.isWaivePenaltyChargeAndNotReversed()) { total = total.plus(transaction.getAmount(currency)); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java index e9678a379..8630ea097 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WaiveSavingsAccountChargeCommandHandler.java @@ -27,6 +27,6 @@ public class WaiveSavingsAccountChargeCommandHandler implements NewCommandSource @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId(), command); + return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index 656ad16b1..adf756355 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -112,9 +112,18 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); + final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveLoanCalculationTypes(); + final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); + final List savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCalculationTypes(); + final List savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService + .retrieveSavingsCollectionTimeTypes(); // TODO AA : revisit for merge conflict - Not sure method signature - return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null); + return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, + loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, + savingsChargeTimeTypeOptions); } @Override 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 d9f75c0ee..77130b111 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 @@ -48,7 +48,9 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); - CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId); CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); + + void payDueCharges(); } \ No newline at end of file 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 56eeefc9b..63fb352f3 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 @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.service; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -34,11 +35,13 @@ import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.service.JobName; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.organisation.workingdays.service.WorkingDaysWritePlatformService; import org.mifosplatform.portfolio.account.PortfolioAccountType; import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService; import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService; @@ -65,6 +68,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.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountClosingNotAllowedException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; import org.mifosplatform.portfolio.savings.exception.TransactionUpdateNotAllowedException; @@ -91,6 +95,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; private final ChargeRepositoryWrapper chargeRepository; private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository; + private final HolidayWritePlatformService holidayWritePlatformService; + private final WorkingDaysWritePlatformService workingDaysWritePlatformService; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -105,7 +111,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, - final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository) { + final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository, + final HolidayWritePlatformService holidayWritePlatformService, + final WorkingDaysWritePlatformService workingDaysWritePlatformService) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; @@ -121,6 +129,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService; this.chargeRepository = chargeRepository; this.savingsAccountChargeRepository = savingsAccountChargeRepository; + this.holidayWritePlatformService = holidayWritePlatformService; + this.workingDaysWritePlatformService = workingDaysWritePlatformService; } @Transactional @@ -586,6 +596,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) { this.context.authenticatedUser(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final Long savingsAccountId = command.getSavingsId(); this.savingsAccountChargeDataValidator.validateAdd(command.json()); @@ -599,6 +613,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command); + if (savingsAccountCharge.getDueLocalDate() != null) { + // transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition); this.savingAccountRepository.saveAndFlush(savingsAccount); @@ -616,9 +647,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command) { + this.context.authenticatedUser(); - this.savingsAccountChargeDataValidator.validateUpdate(command.json()); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final Long savingsAccountId = command.getSavingsId(); // SavingsAccount Charge entity final Long savingsChargeId = command.entityId(); @@ -630,20 +664,30 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccountId); final Map changes = savingsAccountCharge.update(command); + + if (savingsAccountCharge.getDueLocalDate() != null) { + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + // transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); - // TODO AA: revisit the code - /* - * // Charges may be edited only when the loan associated with them are - * // yet to be approved (are in submitted and pending status) if - * (!loan.status().isSubmittedAndPendingApproval()) { throw new - * LoanChargeCannotBeUpdatedException( - * LOAN_CHARGE_CANNOT_BE_UPDATED_REASON - * .LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, - * loanCharge.getId()); } - */ return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsChargeId) // // .withOfficeId(savings) // // .withClientId(loan.getClientId()) // @@ -655,7 +699,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { + public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { this.context.authenticatedUser(); @@ -670,7 +714,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.saveAndFlush(savingsAccount); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsAccountChargeId) // .withOfficeId(savingsAccount.officeId()) // .withClientId(savingsAccount.clientId()) // @@ -707,21 +750,45 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { - this.context.authenticatedUser(); + this.context.authenticatedUser(); + this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); + final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); checkClientOrGroupActive(savingsAccount); final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName); - savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, existingTransactionIds, existingReversedTransactionIds); + final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + //transaction date should not be on a holiday or non working day + if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() + && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), transactionDate)) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() + && !this.workingDaysWritePlatformService.isWorkingDay(transactionDate)) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.saveAndFlush(savingsAccount); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsAccountChargeId) // .withOfficeId(savingsAccount.officeId()) // .withClientId(savingsAccount.clientId()) // @@ -731,4 +798,33 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } + @Transactional + @Override + @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) + public void payDueCharges() { + //always use current date as transaction date for batch job + final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); + final List pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); + final List paidCharges = new ArrayList(pendingCharges.size()); + for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { + // Get Savings account from savings charge + final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.savingAccountAssembler.assignSavingAccountHelpers(account); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + try { + account.payDueCharge(savingsAccountCharge, transactionDate, existingTransactionIds, existingReversedTransactionIds); + paidCharges.add(savingsAccountCharge); + } catch (InsufficientAccountBalanceException isab) { + // TODO: AA - provide a meaningful error with failed + // transactions list + } + } + + this.savingsAccountChargeRepository.save(paidCharges); + } + + + } \ No newline at end of file From 5776ee3b534d028ba104025cbf769ca6f21b7a90 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Tue, 24 Sep 2013 23:49:10 +0530 Subject: [PATCH 06/15] handle penalties/fees separately for savings and add ability to map penalties/fees to separate income accounts --- ...ome_from_penalties_for savings_product.sql | 5 + .../common/AccountingConstants.java | 11 +- .../data/SavingsTransactionDTO.java | 18 ++- .../service/AccountingProcessorHelper.java | 104 +++++++++++++++++- ...ashBasedAccountingProcessorForSavings.java | 17 ++- ...AccountMappingFromApiJsonDeserializer.java | 5 + ...ToGLAccountMappingReadPlatformService.java | 4 + ...AccountMappingReadPlatformServiceImpl.java | 13 +++ ...ccountMappingWritePlatformServiceImpl.java | 6 + ...avingsProductToGLAccountMappingHelper.java | 21 ++++ .../savings/SavingsApiConstants.java | 5 +- .../api/SavingsProductsApiResource.java | 7 +- .../savings/data/SavingsProductData.java | 39 +++++-- .../data/SavingsProductDataValidator.java | 49 +++++++++ .../domain/SavingsAccountTransaction.java | 21 ++++ ...WritePlatformServiceJpaRepositoryImpl.java | 2 + 16 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql diff --git a/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql b/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql new file mode 100644 index 000000000..54d237239 --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql @@ -0,0 +1,5 @@ +/***Set defaults for income from Penalties for existing Saving Products**/ +INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`) +select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 5 +from acc_product_mapping mapping +where mapping.financial_account_type = 4 and mapping.product_type=2; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java index 1ccaabab3..92604063b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java @@ -108,7 +108,8 @@ public class AccountingConstants { /*** Accounting placeholders for cash based accounting for savings products ***/ public static enum CASH_ACCOUNTS_FOR_SAVINGS { - SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), TRANSFERS_SUSPENSE(10); + SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), TRANSFERS_SUSPENSE( + 10); private final Integer value; @@ -144,9 +145,11 @@ public class AccountingConstants { ***/ public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS { SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES( - "incomeFromFeeAccountId"), INTEREST_ON_SAVINGS("interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( - "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE( - "transfersInSuspenseAccountId"); + "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), INTEREST_ON_SAVINGS( + "interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( + "paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java index 00a4de949..dc0f50683 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java @@ -7,6 +7,7 @@ package org.mifosplatform.accounting.journalentry.data; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; @@ -23,14 +24,21 @@ public class SavingsTransactionDTO { /*** Boolean values determines if the transaction is reversed ***/ private final boolean reversed; + /** Breakdowns of fees and penalties this Transaction pays **/ + private final List penaltyPayments; + private final List feePayments; + public SavingsTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate, - final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed) { + final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed, + final List feePayments, final List penaltyPayments) { this.paymentTypeId = paymentTypeId; this.transactionId = transactionId; this.transactionDate = transactionDate; this.amount = amount; this.reversed = reversed; this.transactionType = transactionType; + this.feePayments = feePayments; + this.penaltyPayments = penaltyPayments; this.officeId = officeId; } @@ -62,4 +70,12 @@ public class SavingsTransactionDTO { return this.reversed; } + public List getPenaltyPayments() { + return this.penaltyPayments; + } + + public List getFeePayments() { + return this.feePayments; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java index 6dc54bf5e..07f62a68c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java @@ -92,7 +92,7 @@ public class AccountingProcessorHelper { final List> loanChargesPaidData = (List>) map.get("loanChargesPaid"); for (final Map loanChargePaid : loanChargesPaidData) { final Long chargeId = (Long) loanChargePaid.get("chargeId"); - final Long loanChargeId = (Long) loanChargePaid.get("chargeId"); + final Long loanChargeId = (Long) loanChargePaid.get("loanChargeId"); final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); @@ -135,8 +135,28 @@ public class AccountingProcessorHelper { final boolean reversed = (Boolean) map.get("reversed"); final Long paymentTypeId = (Long) map.get("paymentTypeId"); + final List feePayments = new ArrayList(); + final List penaltyPayments = new ArrayList(); + // extract charge payment details (if exists) + if (map.containsKey("savingsChargesPaid")) { + @SuppressWarnings("unchecked") + final List> savingsChargesPaidData = (List>) map.get("savingsChargesPaid"); + for (final Map loanChargePaid : savingsChargesPaidData) { + final Long chargeId = (Long) loanChargePaid.get("chargeId"); + final Long loanChargeId = (Long) loanChargePaid.get("savingsChargeId"); + final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); + final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); + final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); + if (isPenalty) { + penaltyPayments.add(chargePaymentDTO); + } else { + feePayments.add(chargePaymentDTO); + } + } + } + final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, - transactionDate, transactionType, amount, reversed); + transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments); newSavingsTransactions.add(transaction); @@ -378,6 +398,61 @@ public class AccountingProcessorHelper { totalCreditedAmount, totalAmount); } } + /** + * Convenience method that creates a pair of related Debits and Credits for + * Cash Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * @param paymentTypeId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode, + final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeDebited, final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeCredited, + final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, + final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal, + final List chargePaymentDTOs) { + // TODO Vishwas: Remove this validation, as and when appropriate Junit + // tests are written for accounting + /** + * Accounting module currently supports a single charge per transaction, + * throw an error if this is not the case here so any developers + * changing the expected portfolio behavior would also take care of + * modifying the accounting code appropriately + **/ + if (chargePaymentDTOs.size() != 1) { throw new PlatformDataIntegrityException( + "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code", + "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); } + ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); + + final GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), + chargePaymentDTO.getChargeId()); + final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), + paymentTypeId); + if (isReversal) { + createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + } else { + createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + } + } + private void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId, final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { @@ -464,6 +539,29 @@ public class AccountingProcessorHelper { return accountMapping.getGlAccount(); } + private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, final Long chargeId) { + ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId); + /***** + * Get more specific mappings for Charges and penalties (based on the + * actual charge /penalty coupled with the loan product). Note the + * income from fees and income from penalties placeholder ID would be + * the same for both cash and accrual based accounts + *****/ + + // Vishwas TODO: remove this condition as it should always be true + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue() + || accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) { + final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId, chargeId); + if (chargeSpecificIncomeAccountMapping != null) { + accountMapping = chargeSpecificIncomeAccountMapping; + } + } + return accountMapping.getGlAccount(); + } + private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId, final Long paymentTypeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, @@ -473,7 +571,7 @@ public class AccountingProcessorHelper { * channels). Note that fund source placeholder ID would be same for * both cash and accrual accounts ***/ - if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()) { + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue()) { final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId, PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java index 718e85ab3..07991cbd4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java @@ -7,9 +7,11 @@ package org.mifosplatform.accounting.journalentry.service; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; +import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO; import org.mifosplatform.accounting.journalentry.data.SavingsDTO; import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO; import org.mifosplatform.organisation.office.domain.Office; @@ -39,6 +41,8 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId(); final boolean isReversal = savingsTransactionDTO.isReversed(); final BigDecimal amount = savingsTransactionDTO.getAmount(); + final List feePayments = savingsTransactionDTO.getFeePayments(); + final List penaltyPayments = savingsTransactionDTO.getPenaltyPayments(); this.helper.checkForBranchClosures(latestGLClosure, transactionDate); @@ -68,9 +72,16 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess /** Handle Fees Deductions and reversals of Fees Deductions **/ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { - this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, - CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId, - paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + // Is the Charge a penalty? + if (penaltyPayments.size() > 0) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments); + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); + } } /** Handle Transfers proposal **/ diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 5f08d7c58..978597db2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -176,6 +176,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .notNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()) + .value(incomeFromPenaltyId).notNull().integerGreaterThanZero(); + } throwExceptionIfValidationWarningsExist(dataValidationErrors); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java index 0cfa464b1..8c56d01ff 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java @@ -24,5 +24,9 @@ public interface ProductToGLAccountMappingReadPlatformService { public Map fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType); public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId); + + public List fetchFeeToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId); + + public List fetchPenaltyToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index c4e494969..cc59f506b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -169,6 +169,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) { accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), glAccountId); + } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE)) { accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS)) { @@ -225,6 +227,16 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true); } + @Override + public List fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, false); + } + + @Override + public List fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, true); + } + private List fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType, final Long loanProductId, final boolean penalty) { final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); @@ -249,4 +261,5 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product } return chargeToGLAccountMappers; } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 5ac062f57..88e62779f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -154,6 +154,10 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue()); + this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue()); + // expenses this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId, @@ -169,6 +173,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc // advanced accounting mappings this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); + this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null); break; /*** Do not have accrual based accounting for now ***/ case ACCRUAL_BASED: @@ -236,6 +241,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc element, accountingRuleType); this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId, changes); + this.savingsProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, savingsProductId, changes); } return changes; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java index 30825cc97..02b426f91 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -94,6 +94,20 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING); } + public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + final Map changes) { + // save both fee and penalty charges + saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); + saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); + } + + public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + final Map changes) { + // update both fee and penalty charges + updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); + updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); + } + public Map populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element, final AccountingRuleType accountingRuleType) { final Map changes = new HashMap(); @@ -102,6 +116,8 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed( SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); + final Long incomeFromPenaltiesId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); final Long interestOnSavingsId = this.fromApiJsonHelper.extractLongNamed( SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), @@ -117,6 +133,7 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingsReferenceId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeesId); + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId); break; case ACCRUAL_BASED: @@ -150,6 +167,10 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.toString(), changes); + mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.toString(), changes); + // expenses mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(), diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index 5a77b82ea..b669f9672 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -128,10 +128,13 @@ public class SavingsApiConstants { withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), - SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())); + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue())); /** * These parameters will match the class level parameters of diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java index d9cebb180..3d416d574 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java @@ -26,6 +26,7 @@ import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService; import org.mifosplatform.commands.domain.CommandWrapper; @@ -159,8 +160,12 @@ public class SavingsProductsApiResource { .fetchAccountMappingDetailsForSavingsProduct(productId, savingProductData.accountingRuleTypeId()); final Collection paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId); + Collection feeToGLAccountMappings = this.accountMappingReadPlatformService + .fetchFeeToIncomeAccountMappingsForSavingsProduct(productId); + Collection penaltyToGLAccountMappings = this.accountMappingReadPlatformService + .fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId); savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings, - paymentChannelToFundSourceMappings); + paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings); } if (settings.isTemplate()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index 73221a561..a00e21f2e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -13,6 +13,7 @@ import java.util.Map; import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -46,6 +47,8 @@ public class SavingsProductData { private final EnumOptionData accountingRule; private final Map accountingMappings; private final Collection paymentChannelToFundSourceMappings; + private final Collection feeToIncomeAccountMappings; + private final Collection penaltyToIncomeAccountMappings; // charges private final Collection charges; @@ -91,6 +94,8 @@ public class SavingsProductData { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; final Collection charges = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -98,7 +103,8 @@ public class SavingsProductData { accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData withCharges(final SavingsProductData product, final Collection charges) { @@ -111,7 +117,8 @@ public class SavingsProductData { product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions, product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions, product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, - product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions); + product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, + product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings); } /** @@ -138,11 +145,15 @@ public class SavingsProductData { existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); + accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, + existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings); } public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, - final Map accountingMappings, final Collection paymentChannelToFundSourceMappings) { + final Map accountingMappings, + final Collection paymentChannelToFundSourceMappings, + final Collection feeToIncomeAccountMappings, + final Collection penaltyToIncomeAccountMappings) { final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -167,7 +178,8 @@ public class SavingsProductData { paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); + accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, @@ -194,6 +206,8 @@ public class SavingsProductData { final Collection chargeOptions = null; final Collection penaltyOptions = null; final Collection charges = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -201,7 +215,8 @@ public class SavingsProductData { accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData lookup(final Long id, final String name) { @@ -238,6 +253,8 @@ public class SavingsProductData { final Collection charges = null; final Collection chargeOptions = null; final Collection penaltyOptions = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -245,7 +262,8 @@ public class SavingsProductData { accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, @@ -263,7 +281,9 @@ public class SavingsProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection charges, - final Collection chargeOptions, final Collection penaltyOptions) { + final Collection chargeOptions, final Collection penaltyOptions, + final Collection feeToIncomeAccountMappings, + final Collection penaltyToIncomeAccountMappings) { this.id = id; this.name = name; this.description = description; @@ -306,6 +326,9 @@ public class SavingsProductData { // Savings product this.penaltyOptions = penaltyOptions;// penalties available for adding // to Savings product + + this.feeToIncomeAccountMappings = feeToIncomeAccountMappings; + this.penaltyToIncomeAccountMappings = penaltyToIncomeAccountMappings; } public boolean hasAccountingEnabled() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index 0cadc5f04..72a5077fe 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -235,7 +235,13 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .notNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()) + .value(incomeFromPenaltyId).notNull().integerGreaterThanZero(); + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + validateChargeToIncomeAccountMappings(baseDataValidator, element); } throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -380,7 +386,13 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .ignoreIfNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) + .ignoreIfNull().integerGreaterThanZero(); + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + validateChargeToIncomeAccountMappings(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -425,4 +437,41 @@ public class SavingsProductDataValidator { } } } + + private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + // validate for both fee and penalty charges + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + } + + private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element, + final boolean isPenalty) { + String parameterName; + if (isPenalty) { + parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); + } else { + parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(); + } + + if (this.fromApiJsonHelper.parameterExists(parameterName, element)) { + final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element); + if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) { + int i = 0; + do { + final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); + final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue(), + jsonObject); + final Long incomeAccountId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), jsonObject); + baseDataValidator.reset() + .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()) + .value(chargeId).notNull().integerGreaterThanZero(); + baseDataValidator.reset() + .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()) + .value(incomeAccountId).notNull().integerGreaterThanZero(); + i++; + } while (i < chargeToIncomeAccountMappingArray.size()); + } + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 29005cbb5..a9df3aa95 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -6,9 +6,11 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -315,6 +317,25 @@ public final class SavingsAccountTransaction extends AbstractPersistable { thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); } + /*** + * Sending data in a map, though in savings we currently expect a transaction to + * always repay a single charge (or may repay a part of a single charge + * too) + ***/ + if (!this.savingsAccountChargesPaid.isEmpty()) { + final List> savingsChargesPaidData = new ArrayList>(); + for (final SavingsAccountChargePaidBy chargePaidBy : this.savingsAccountChargesPaid) { + final Map savingChargePaidData = new LinkedHashMap(); + savingChargePaidData.put("chargeId", chargePaidBy.getSavingsAccountCharge().getCharge().getId()); + savingChargePaidData.put("isPenalty", chargePaidBy.getSavingsAccountCharge().getCharge().isPenalty()); + savingChargePaidData.put("savingsChargeId", chargePaidBy.getSavingsAccountCharge().getId()); + savingChargePaidData.put("amount", chargePaidBy.getAmount()); + + savingsChargesPaidData.add(savingChargePaidData); + } + thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData); + } + return thisTransactionData; } 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 63fb352f3..ff5489ac0 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 @@ -787,6 +787,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.saveAndFlush(savingsAccount); + + postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountChargeId) // From aabe9549e0e76c76cbe7dd8bfd8780cce56c4dca Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Wed, 25 Sep 2013 16:34:22 +0530 Subject: [PATCH 07/15] added support for undo savings fees --- .../core/service/DateUtils.java | 4 + .../SavingsAccountTransactionType.java | 2 +- .../savings/SavingsApiConstants.java | 1 + .../savings/domain/SavingsAccount.java | 149 +++++++++--------- .../savings/domain/SavingsAccountCharge.java | 34 ++-- .../SavingsAccountChargeRepository.java | 2 +- ...SavingsAccountChargeRepositoryWrapper.java | 1 - .../domain/SavingsAccountTransaction.java | 4 + .../SavingsAccountWritePlatformService.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 128 ++++++++------- 10 files changed, 176 insertions(+), 151 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java index 9a56e0b41..6643de289 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java @@ -63,4 +63,8 @@ public class DateUtils { final String formattedSqlDate = df.format(date); return formattedSqlDate; } + + public static boolean isInFuture(final LocalDate date){ + return date.isAfter(getLocalDateOfTenant()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java index 2b57156ef..74dfd4acb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java @@ -19,7 +19,7 @@ public enum SavingsAccountTransactionType { INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), // WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), // ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), // - WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharges"), // + WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), // PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), // INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), // APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index b669f9672..fb1fd6d77 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -34,6 +34,7 @@ public class SavingsApiConstants { public static String adjustTransactionAction = ".adjusttransaction"; public static String closeAction = ".close"; public static String payChargeTransactionAction = ".paycharge"; + public static String waiveChargeTransactionAction = ".waivecharge"; // command public static String COMMAND_UNDO_TRANSACTION = "undo"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 41987277b..347068b9d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -8,13 +8,13 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -1497,15 +1497,18 @@ public class SavingsAccount extends AbstractPersistable { .withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1); this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); } - } else if (transactionToUndo.isCharge()) { + } else if (transactionToUndo.isCharge() || transactionToUndo.isWaiveCharge()) { // undo charge final Set chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid(); for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); - chargeToUndo.resetPaidAmount(this.currency); + if(transactionToUndo.isCharge()){ + chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + }else if (transactionToUndo.isWaiveCharge()){ + chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + } } } - } } @@ -1899,6 +1902,20 @@ public class SavingsAccount extends AbstractPersistable { } public void removeCharge(final SavingsAccountCharge charge) { + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (isClosed()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.closed"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (isActive() || isApproved()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.active"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + this.charges.remove(charge); } @@ -1936,15 +1953,7 @@ public class SavingsAccount extends AbstractPersistable { // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); - // persist an entry in transaction details - final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), - savingsAccountCharge.getDueLocalDate(), amountWaived); - - this.transactions.add(chargeTransaction); - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - final MathContext mc = MathContext.DECIMAL64; - calculateInterestUsing(mc, savingsAccountCharge.getDueLocalDate()); + handleWaiverChargeTransactions(savingsAccountCharge, amountWaived); } public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) { @@ -1966,10 +1975,16 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); - if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); + if (savingsAccountCharge.isOnSpecifiedDueDate()){ + if(getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())){ + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + }else if(getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())){ + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } } validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate); @@ -1979,7 +1994,7 @@ public class SavingsAccount extends AbstractPersistable { } - public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final LocalDate transactionDate, + public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate, final DateTimeFormatter formatter, final List existingTransactionIds, final List existingReversedTransactionIds) { final List dataValidationErrors = new ArrayList(); @@ -1996,13 +2011,23 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (transactionDate != null && transactionDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); + if (savingsAccountCharge.isOnSpecifiedDueDate()){ + if(getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())){ + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + }else if(getSubmittedOnLocalDate() != null && transactionDate.isBefore(getSubmittedOnLocalDate())){ + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("transaction.before.submittedOnDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } } - final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); + if (DateUtils.isInFuture(transactionDate)){ + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } // validate charge is not already paid or waived if (savingsAccountCharge.isWaived()) { @@ -2012,9 +2037,9 @@ public class SavingsAccount extends AbstractPersistable { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + final Money amountPaidMoney = Money.of(currency, amountPaid); - if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)){ + if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } @@ -2022,61 +2047,33 @@ public class SavingsAccount extends AbstractPersistable { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaidMoney); - - // persist transaction details - final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, chargePaid); - - final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, - amountPaid); - chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); - this.transactions.add(chargeTransaction); - - final MathContext mc = MathContext.DECIMAL64; - if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); - } else { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - calculateInterestUsing(mc, today); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - - validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.payChargeTransactionAction); + savingsAccountCharge.pay(getCurrency(), amountPaidMoney); + handlePayChargeTransactions(savingsAccountCharge, amountPaidMoney, transactionDate); } - public void payDueCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final List existingTransactionIds, - final List existingReversedTransactionIds) { - - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money amountPaid = savingsAccountCharge.payDueCharge(getCurrency()); - - // persist transaction details - final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, amountPaid); - - final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, - amountPaid.getAmount()); - - chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy); - - this.transactions.add(chargeTransaction); - - final MathContext mc = MathContext.DECIMAL64; - if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); - } else { - final LocalDate today = DateUtils.getLocalDateOfTenant(); - calculateInterestUsing(mc, today); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - validateAccountBalanceDoesNotBecomeNegative(amountPaid.getAmount()); + private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, + final LocalDate transactionDate) { + final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, + transactionAmount); + handleChargeTransactions(savingsAccountCharge, chargeTransaction); } - + + private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount) { + final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(), + DateUtils.getLocalDateOfTenant(), transactionAmount); + handleChargeTransactions(savingsAccountCharge, chargeTransaction); + } + + private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction) { + // Provide a link between transaction and savings charge for which + // amount is waived. + final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, transaction + .getAmount(this.getCurrency()).getAmount()); + transaction.getSavingsAccountChargesPaid().add(chargePaidBy); + this.getTransactions().add(transaction); + } + private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) { SavingsAccountCharge charge = null; for (final SavingsAccountCharge existingCharge : this.charges) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index 4b296b9f6..8a45e448b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -214,8 +214,10 @@ public class SavingsAccountCharge extends AbstractPersistable { this.waived = false; } - public void resetPaidAmount(final MonetaryCurrency currency) { - this.amountPaid = BigDecimal.ZERO; + public void undoPayment(final MonetaryCurrency currency, final Money transactionAmount) { + Money amountPaid = getAmountPaid(currency); + amountPaid = amountPaid.minus(transactionAmount); + this.amountPaid = amountPaid.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); this.paid = false; } @@ -223,10 +225,17 @@ public class SavingsAccountCharge extends AbstractPersistable { public Money waive(final MonetaryCurrency currency) { this.amountWaived = this.amountOutstanding; this.amountOutstanding = BigDecimal.ZERO; - this.paid = false; this.waived = true; return getAmountWaived(currency); } + + public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) { + Money amountWaived = getAmountWaived(currency); + amountWaived = amountWaived.minus(transactionAmount); + this.amountWaived = amountWaived.getAmount(); + this.amountOutstanding = calculateAmountOutstanding(currency); + this.waived = false; + } public Money pay(final MonetaryCurrency currency, final Money amountPaid) { @@ -237,20 +246,7 @@ public class SavingsAccountCharge extends AbstractPersistable { this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = amountOutstanding.getAmount(); this.paid = determineIfFullyPaid(); - return amountPaid; - } - - public Money payDueCharge(final MonetaryCurrency currency) { - - Money amountPaidToDate = Money.of(currency, this.amountPaid); - Money amountOutstanding = Money.of(currency, this.amountOutstanding); - final Money amountPaid = Money.of(currency, this.amountOutstanding);; - amountPaidToDate = amountPaidToDate.plus(amountPaid); - amountOutstanding = amountOutstanding.minus(amountPaid); - this.amountPaid = amountPaidToDate.getAmount(); - this.amountOutstanding = amountOutstanding.getAmount(); - this.paid = determineIfFullyPaid(); - return amountPaid; + return amountOutstanding; } private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { @@ -409,6 +405,10 @@ public class SavingsAccountCharge extends AbstractPersistable { public BigDecimal amount() { return this.amount; } + + public BigDecimal amoutOutstanding(){ + return this.amountOutstanding; + } public boolean isFeeCharge() { return !this.penaltyCharge; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java index 959e22f8f..714988d9d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepository.java @@ -18,6 +18,6 @@ public interface SavingsAccountChargeRepository extends JpaRepository findPendingCharges(@Param("transactionDate") Date transactionDate); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java index 68187bbd6..8aba4acb6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeRepositoryWrapper.java @@ -5,7 +5,6 @@ */ package org.mifosplatform.portfolio.savings.domain; -import java.util.Collection; import java.util.Date; import java.util.List; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index a9df3aa95..a4865e2b2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -210,6 +210,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal(); } + public boolean isPostInterestCalculationRequired(){ + return this.isDeposit() || this.isWithdrawal() || this.isCharge(); + } + public boolean isInterestPostingAndNotReversed() { return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed(); } 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 77130b111..7d02115aa 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 @@ -52,5 +52,5 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command); - void payDueCharges(); + void processDueCharges(); } \ No newline at end of file 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 ff5489ac0..8915e1c51 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 @@ -353,7 +353,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } account.undoTransaction(transactionId, reversedTransactionIds); checkClientOrGroupActive(account); - if ((savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) + if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { account.postInterest(mc, today, newTransactionIds, reversedTransactionIds); } else { @@ -606,7 +606,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); checkClientOrGroupActive(savingsAccount); - final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); @@ -635,12 +636,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.saveAndFlush(savingsAccount); return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccount.officeId()) // .withClientId(savingsAccount.clientId()) // .withGroupId(savingsAccount.groupId()) // - .withLoanId(savingsAccountId) // + .withSavingsId(savingsAccountId) // .build(); } @@ -688,11 +688,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); return new CommandProcessingResultBuilder() // - .withEntityId(savingsChargeId) // - // .withOfficeId(savings) // - // .withClientId(loan.getClientId()) // - // .withGroupId(loan.getGroupId()) // - // .withLoanId(savingsAccountId) // + .withEntityId(savingsAccountCharge.getId()) // + .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // + .withClientId(savingsAccountCharge.savingsAccount().clientId()) // + .withGroupId(savingsAccountCharge.savingsAccount().groupId()) // + .withSavingsId(savingsAccountCharge.savingsAccount().getId()) // .with(changes) // .build(); } @@ -702,22 +702,37 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { this.context.authenticatedUser(); - + + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); + + // Get Savings account from savings charge + final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.savingAccountAssembler.assignSavingAccountHelpers(account); + final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); - checkClientOrGroupActive(savingsAccount); + account.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds); - savingsAccount.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds); + final MathContext mc = MathContext.DECIMAL64; + if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(mc, today); + } - this.savingAccountRepository.saveAndFlush(savingsAccount); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction); + + this.savingAccountRepository.saveAndFlush(account); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountChargeId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // .withSavingsId(savingsAccountId) // .build(); } @@ -733,8 +748,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, savingsAccountId); - // TODO AA: validate before deleting a charge - savingsAccount.removeCharge(savingsAccountCharge); this.savingAccountRepository.saveAndFlush(savingsAccount); @@ -747,31 +760,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .build(); } - @Transactional @Override public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) { this.context.authenticatedUser(); this.savingsAccountChargeDataValidator.validatePayCharge(command.json()); - - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - - final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); - checkClientOrGroupActive(savingsAccount); - - final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName); + final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); //transaction date should not be on a holiday or non working day if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() - && this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), transactionDate)) { + && this.holidayWritePlatformService.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -783,50 +790,63 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - - savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); - - this.savingAccountRepository.saveAndFlush(savingsAccount); - postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); - + this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt); return new CommandProcessingResultBuilder() // - .withEntityId(savingsAccountChargeId) // - .withOfficeId(savingsAccount.officeId()) // - .withClientId(savingsAccount.clientId()) // - .withGroupId(savingsAccount.groupId()) // - .withSavingsId(savingsAccountId) // + .withEntityId(savingsAccountCharge.getId()) // + .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // + .withClientId(savingsAccountCharge.savingsAccount().clientId()) // + .withGroupId(savingsAccountCharge.savingsAccount().groupId()) // + .withSavingsId(savingsAccountCharge.savingsAccount().getId()) // .build(); } - @Transactional @Override @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) - public void payDueCharges() { + public void processDueCharges() { //always use current date as transaction date for batch job final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); final List pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); - final List paidCharges = new ArrayList(pendingCharges.size()); + //final List paidCharges = new ArrayList(pendingCharges.size()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { - // Get Savings account from savings charge - final SavingsAccount account = savingsAccountCharge.savingsAccount(); - this.savingAccountAssembler.assignSavingAccountHelpers(account); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - try { - account.payDueCharge(savingsAccountCharge, transactionDate, existingTransactionIds, existingReversedTransactionIds); - paidCharges.add(savingsAccountCharge); + payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); } catch (InsufficientAccountBalanceException isab) { // TODO: AA - provide a meaningful error with failed // transactions list } } - this.savingsAccountChargeRepository.save(paidCharges); + // this.savingsAccountChargeRepository.save(paidCharges); } - + @Transactional + private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid, + final DateTimeFormatter formatter) { + // Get Savings account from savings charge + final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.savingAccountAssembler.assignSavingAccountHelpers(account); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, existingTransactionIds, + existingReversedTransactionIds); + + final MathContext mc = MathContext.DECIMAL64; + if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(mc, today); + } + + account.validateAccountBalanceDoesNotBecomeNegative(amountPaid); + + this.savingAccountRepository.save(account); + + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + } } \ No newline at end of file From 5d2c331fe154afdf84053b034b5bd9d708377ac1 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Thu, 26 Sep 2013 12:27:44 +0530 Subject: [PATCH 08/15] savings charge currency must match savings account currency --- .../savings/domain/SavingsAccount.java | 3 --- .../domain/SavingsAccountAssembler.java | 2 +- .../savings/domain/SavingsAccountCharge.java | 5 +++++ .../domain/SavingsAccountChargeAssembler.java | 22 ++++++++++++++++++- ...WritePlatformServiceJpaRepositoryImpl.java | 5 +---- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 347068b9d..b8a45edf0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -336,9 +336,6 @@ public class SavingsAccount extends AbstractPersistable { if (!CollectionUtils.isEmpty(savingsAccountCharges)) { this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges); - // TODO:Add charges details to savings summary - // this.summary = - // updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } this.summary = new SavingsAccountSummary(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index a5e25328e..bc480c9bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -236,7 +236,7 @@ public class SavingsAccountAssembler { monthDayOfAnnualFee = product.monthDayOfAnnualFee(); } - final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element); + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode()); final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index 8a45e448b..fbdb113fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -505,6 +505,11 @@ public class SavingsAccountCharge extends AbstractPersistable { public SavingsAccount savingsAccount(){ return this.savingsAccount; } + + public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { + if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; } + return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode); + } @Override public boolean equals(final Object obj) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java index 4da5cc734..2fa0b7b85 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.savings.domain; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; @@ -14,11 +15,16 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDat import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; @@ -48,7 +54,7 @@ public class SavingsAccountChargeAssembler { this.savingsAccountChargeRepository = savingsAccountChargeRepository; } - public Set fromParsedJson(final JsonElement element) { + public Set fromParsedJson(final JsonElement element, final String productCurrencyCode) { final Set savingsAccountCharges = new HashSet(); @@ -106,6 +112,20 @@ public class SavingsAccountChargeAssembler { } } + this.validateSavingsCharges(savingsAccountCharges, productCurrencyCode); return savingsAccountCharges; } + + private void validateSavingsCharges(final Set charges, final String productCurrencyCode) { + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + for (SavingsAccountCharge savingsAccountCharge : charges) { + if(!savingsAccountCharge.hasCurrencyCodeOf(productCurrencyCode)){ + baseDataValidator.reset().parameter("currency").value(savingsAccountCharge.getCharge().getId()) + .failWithCodeNoParameterAddedToErrorCode("currency.and.charge.currency.not.same"); + } + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 8454a6ad4..9d65f9515 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -233,10 +233,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl } if (changes.containsKey("charges")) { - // final Set savingsProductCharges = - // this.savi.assembleListOfSavingsProductCharges(command, - // product.currency().getCode()); - final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson()); + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(), account.getCurrency().getCode()); final boolean updated = account.update(charges); if (!updated) { changes.remove("charges"); From 4ee40b7f5794a312254957700cba7f7741deaf18 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Thu, 26 Sep 2013 19:23:02 +0530 Subject: [PATCH 09/15] cleaning up accounting integration for savings --- .../data/SavingsAccountTransactionDTO.java | 16 +- .../savings/domain/SavingsAccount.java | 77 +++----- .../SavingsAccountDomainServiceJpa.java | 32 ++-- ...WritePlatformServiceJpaRepositoryImpl.java | 173 ++++++++++-------- ...WritePlatformServiceJpaRepositoryImpl.java | 28 +-- 5 files changed, 146 insertions(+), 180 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java index e6d8b49de..5fdeeb71a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDTO.java @@ -1,7 +1,6 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; -import java.util.List; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormatter; @@ -12,18 +11,13 @@ public class SavingsAccountTransactionDTO { private final DateTimeFormatter formatter; private final LocalDate transactionDate; private final BigDecimal transactionAmount; - private final List existingTransactionIds; - private final List existingReversedTransactionIds; private final PaymentDetail paymentDetail; public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final LocalDate transactionDate, - final BigDecimal transactionAmount, final List existingTransactionIds, final List existingReversedTransactionIds, - final PaymentDetail paymentDetail) { + final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { this.formatter = formatter; this.transactionDate = transactionDate; this.transactionAmount = transactionAmount; - this.existingTransactionIds = existingTransactionIds; - this.existingReversedTransactionIds = existingReversedTransactionIds; this.paymentDetail = paymentDetail; } @@ -39,14 +33,6 @@ public class SavingsAccountTransactionDTO { return this.transactionAmount; } - public List getExistingTransactionIds() { - return this.existingTransactionIds; - } - - public List getExistingReversedTransactionIds() { - return this.existingReversedTransactionIds; - } - public PaymentDetail getPaymentDetail() { return this.paymentDetail; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index b8a45edf0..7f9c390a2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -376,16 +376,13 @@ public class SavingsAccount extends AbstractPersistable { return SavingsAccountStatusType.fromInt(this.status).isClosed(); } - public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate) { final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate); Money interestPostedToDate = Money.zero(this.currency); boolean recalucateDailyBalanceDetails = false; - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); for (final PostingPeriod interestPostingPeriod : postingPeriods) { @@ -608,9 +605,6 @@ public class SavingsAccount extends AbstractPersistable { } validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_DEPOSIT, transactionDTO.getTransactionDate()); - transactionDTO.getExistingTransactionIds().addAll(findExistingTransactionIds()); - transactionDTO.getExistingReversedTransactionIds().addAll(findExistingReversedTransactionIds()); - final Money amount = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(), @@ -683,9 +677,6 @@ public class SavingsAccount extends AbstractPersistable { } validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_WITHDRAWAL, transactionDTO.getTransactionDate()); - transactionDTO.getExistingTransactionIds().addAll(findExistingTransactionIds()); - transactionDTO.getExistingReversedTransactionIds().addAll(findExistingReversedTransactionIds()); - final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(), transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney); @@ -733,7 +724,7 @@ public class SavingsAccount extends AbstractPersistable { return transactionBeforeLastInterestPosting; } - + public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount) { final List transactionsSortedByDate = retreiveListOfTransactions(); Money runningBalance = Money.zero(this.currency); @@ -779,8 +770,7 @@ public class SavingsAccount extends AbstractPersistable { } public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter, - final LocalDate annualFeeTransactionDate, final LocalDate today, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final LocalDate annualFeeTransactionDate, final LocalDate today) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -828,9 +818,6 @@ public class SavingsAccount extends AbstractPersistable { } validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_PAY_CHARGE, annualFeeTransactionDate); - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money annualFee = Money.of(this.currency, this.annualFeeAmount); final SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, office(), annualFeeTransactionDate, annualFee); @@ -1188,8 +1175,8 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - public Map deriveAccountingBridgeData(final CurrencyData currencyData, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public Map deriveAccountingBridgeData(final CurrencyData currencyData, final Set existingTransactionIds, + final Set existingReversedTransactionIds) { final Map accountingBridgeData = new LinkedHashMap(); accountingBridgeData.put("savingsId", getId()); @@ -1466,7 +1453,7 @@ public class SavingsAccount extends AbstractPersistable { return actualChanges; } - public void undoTransaction(final Long transactionId, final List reversedTransactionIds) { + public void undoTransaction(final Long transactionId) { SavingsAccountTransaction transactionToUndo = null; for (final SavingsAccountTransaction transaction : this.transactions) { @@ -1483,8 +1470,6 @@ public class SavingsAccount extends AbstractPersistable { validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate()); transactionToUndo.reverse(); - reversedTransactionIds.add(transactionId); - if (transactionToUndo.isAnnualFee()) { this.annualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); if (this.annualFeeNextDueDate == null) { @@ -1499,9 +1484,9 @@ public class SavingsAccount extends AbstractPersistable { final Set chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid(); for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); - if(transactionToUndo.isCharge()){ + if (transactionToUndo.isCharge()) { chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); - }else if (transactionToUndo.isWaiveCharge()){ + } else if (transactionToUndo.isWaiveCharge()) { chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); } } @@ -1650,8 +1635,7 @@ public class SavingsAccount extends AbstractPersistable { return actualChanges; } - public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, - final List existingTransactionIds, final List existingReversedTransactionIds) { + public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { final Map actualChanges = new LinkedHashMap(); @@ -1734,7 +1718,7 @@ public class SavingsAccount extends AbstractPersistable { final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); if (minRequiredOpeningBalance.isGreaterThanZero()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, activationDate, - minRequiredOpeningBalance.getAmount(), existingTransactionIds, existingReversedTransactionIds, null); + minRequiredOpeningBalance.getAmount(), null); deposit(transactionDTO); // no openingBalance concept supported yet but probably will to @@ -1912,12 +1896,11 @@ public class SavingsAccount extends AbstractPersistable { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.active"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + this.charges.remove(charge); } - public void waiveCharge(final Long savingsAccountChargeId, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public void waiveCharge(final Long savingsAccountChargeId) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -1944,9 +1927,6 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - // waive charge final Money amountWaived = savingsAccountCharge.waive(getCurrency()); @@ -1972,16 +1952,16 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate(); - if (savingsAccountCharge.isOnSpecifiedDueDate()){ - if(getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())){ + if (savingsAccountCharge.isOnSpecifiedDueDate()) { + if (getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); + .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); - }else if(getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())){ + } else if (getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate"); + .failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate"); throw new PlatformApiDataValidationException(dataValidationErrors); - } + } } validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate); @@ -1992,7 +1972,7 @@ public class SavingsAccount extends AbstractPersistable { } public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate, - final DateTimeFormatter formatter, final List existingTransactionIds, final List existingReversedTransactionIds) { + final DateTimeFormatter formatter) { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -2008,21 +1988,21 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (savingsAccountCharge.isOnSpecifiedDueDate()){ - if(getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())){ + if (savingsAccountCharge.isOnSpecifiedDueDate()) { + if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); + .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); throw new PlatformApiDataValidationException(dataValidationErrors); - }else if(getSubmittedOnLocalDate() != null && transactionDate.isBefore(getSubmittedOnLocalDate())){ + } else if (getSubmittedOnLocalDate() != null && transactionDate.isBefore(getSubmittedOnLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.before.submittedOnDate"); + .failWithCodeNoParameterAddedToErrorCode("transaction.before.submittedOnDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } } - - if (DateUtils.isInFuture(transactionDate)){ + + if (DateUtils.isInFuture(transactionDate)) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); + .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -2041,9 +2021,6 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - savingsAccountCharge.pay(getCurrency(), amountPaidMoney); handlePayChargeTransactions(savingsAccountCharge, amountPaidMoney, transactionDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index ac76d853c..b7c474c6a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -7,9 +7,9 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; import java.math.MathContext; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormatter; @@ -49,16 +49,17 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final boolean applyWithdrawFee) { - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - existingTransactionIds, existingReversedTransactionIds, paymentDetail); + paymentDetail); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, applyWithdrawFee); final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today); @@ -77,16 +78,17 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - existingTransactionIds, existingReversedTransactionIds, paymentDetail); + paymentDetail); final SavingsAccountTransaction deposit = account.deposit(transactionDTO); final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today); @@ -106,8 +108,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi return transaction.getId(); } - private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, - final List existingReversedTransactionIds) { + private void updateExistingTransactionsDetails(SavingsAccount account, Set existingTransactionIds, + Set existingReversedTransactionIds) { + existingTransactionIds.addAll(account.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); + } + + private void postJournalEntries(final SavingsAccount savingsAccount, final Set existingTransactionIds, + final Set existingReversedTransactionIds) { final MonetaryCurrency currency = savingsAccount.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); 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 8915e1c51..f01ef4950 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 @@ -14,10 +14,12 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.HashSet; 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; @@ -144,11 +146,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); checkClientOrGroupActive(account); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - final Map changes = account.activate(user, command, DateUtils.getLocalDateOfTenant(), - existingReversedTransactionIds, existingReversedTransactionIds); + final Map changes = account.activate(user, command, DateUtils.getLocalDateOfTenant()); if (!changes.isEmpty()) { this.savingAccountRepository.save(account); } @@ -243,14 +245,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final MathContext mc = MathContext.DECIMAL64; final LocalDate today = DateUtils.getLocalDateOfTenant(); - final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today, existingTransactionIds, - existingReversedTransactionIds); + final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today); final Long transactionId = saveTransactionToGenerateTransactionId(annualFee); this.savingAccountRepository.save(account); @@ -306,12 +308,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional private void postInterest(final SavingsAccount account) { - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); this.savingAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -332,8 +335,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId, final boolean allowAccountTransferModification) { - final List newTransactionIds = new ArrayList(); - final List reversedTransactionIds = new ArrayList(); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository .findOneByIdAndSavingsAccountId(transactionId, savingsId); @@ -347,21 +352,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN); - final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction); } - account.undoTransaction(transactionId, reversedTransactionIds); + account.undoTransaction(transactionId); checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, newTransactionIds, reversedTransactionIds); + account.postInterest(mc, today); } else { account.calculateInterestUsing(mc, today); } account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction); account.activateAccountBasedOnBalance(); - postJournalEntries(account, newTransactionIds, reversedTransactionIds); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // @@ -394,41 +398,42 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction); } + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); final Map changes = new LinkedHashMap(); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - account.undoTransaction(transactionId, existingReversedTransactionIds); + account.undoTransaction(transactionId); // for undo withdrawal fee final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository .findOneByIdAndSavingsAccountId(transactionId + 1, savingsId); if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) { - account.undoTransaction(transactionId + 1, existingReversedTransactionIds); + account.undoTransaction(transactionId + 1); } SavingsAccountTransaction transaction = null; if (savingsAccountTransaction.isDeposit()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - existingTransactionIds, existingReversedTransactionIds, paymentDetail); + paymentDetail); transaction = account.deposit(transactionDTO); } else { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - existingTransactionIds, existingReversedTransactionIds, paymentDetail); + paymentDetail); transaction = account.withdraw(transactionDTO, true); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); if (account.isBeforeLastPostingPeriod(transactionDate) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); } else { account.calculateInterestUsing(mc, today); } @@ -456,17 +461,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi throw new PlatformApiDataValidationException(dataValidationErrors); } - private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, - final List existingReversedTransactionIds) { - - final MonetaryCurrency currency = savingsAccount.getCurrency(); - final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); - - final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), - existingTransactionIds, existingReversedTransactionIds); - this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); - } - private void checkClientOrGroupActive(final SavingsAccount account) { final Client client = account.getClient(); if (client != null) { @@ -517,8 +511,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate) { final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId); - final List existingTransactionIds = new ArrayList(savingsAccount.findExistingTransactionIds()); - final List existingReversedTransactionIds = new ArrayList(savingsAccount.findExistingReversedTransactionIds()); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount, savingsAccount.office(), transferDate); @@ -539,8 +534,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate) { final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId); - final List existingTransactionIds = new ArrayList(savingsAccount.findExistingTransactionIds()); - final List existingReversedTransactionIds = new ArrayList(savingsAccount.findExistingReversedTransactionIds()); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount, savingsAccount.office(), transferDate); @@ -569,8 +565,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Office acceptedInOffice, final Staff fieldOfficer) { final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId); - final List existingTransactionIds = new ArrayList(savingsAccount.findExistingTransactionIds()); - final List existingReversedTransactionIds = new ArrayList(savingsAccount.findExistingReversedTransactionIds()); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount, acceptedInOffice, transferDate); @@ -579,7 +576,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (fieldOfficer != null) { savingsAccount.update(fieldOfficer); } - + final MathContext mc = MathContext.DECIMAL64; savingsAccount.calculateInterestUsing(mc, transferDate); @@ -622,15 +619,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } } - + savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition); this.savingAccountRepository.saveAndFlush(savingsAccount); @@ -647,11 +644,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command) { - + this.context.authenticatedUser(); this.savingsAccountChargeDataValidator.validateUpdate(command.json()); final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); final Long savingsAccountId = command.getSavingsId(); // SavingsAccount Charge entity @@ -676,15 +674,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() && !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); + .failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } } - + this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge); return new CommandProcessingResultBuilder() // @@ -702,23 +700,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) { this.context.authenticatedUser(); - + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, savingsAccountId); - + // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); this.savingAccountAssembler.assignSavingAccountHelpers(account); - - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - account.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds); + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + + account.waiveCharge(savingsAccountChargeId); final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today); @@ -728,6 +727,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.saveAndFlush(account); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountChargeId) // .withOfficeId(account.officeId()) // @@ -769,28 +770,29 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); - + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, savingsAccountId); final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); - //transaction date should not be on a holiday or non working day + // transaction date should not be on a holiday or non working day if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday() && this.holidayWritePlatformService.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay() && !this.workingDaysWritePlatformService.isWorkingDay(transactionDate)) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt)) - .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day"); + .failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - + this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // @@ -805,38 +807,39 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override @CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES) public void processDueCharges() { - //always use current date as transaction date for batch job + // always use current date as transaction date for batch job final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); final List pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); - //final List paidCharges = new ArrayList(pendingCharges.size()); + // final List paidCharges = new + // ArrayList(pendingCharges.size()); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { try { - payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); + payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); } catch (InsufficientAccountBalanceException isab) { // TODO: AA - provide a meaningful error with failed // transactions list } } - - // this.savingsAccountChargeRepository.save(paidCharges); + + // this.savingsAccountChargeRepository.save(paidCharges); } - + @Transactional private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid, final DateTimeFormatter formatter) { // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); this.savingAccountAssembler.assignSavingAccountHelpers(account); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, existingTransactionIds, - existingReversedTransactionIds); - + final Set existingTransactionIds = new HashSet(); + final Set existingReversedTransactionIds = new HashSet(); + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter); + final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds); + account.postInterest(mc, today); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today); @@ -845,8 +848,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi account.validateAccountBalanceDoesNotBecomeNegative(amountPaid); this.savingAccountRepository.save(account); - + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); } + private void updateExistingTransactionsDetails(SavingsAccount account, Set existingTransactionIds, + Set existingReversedTransactionIds) { + existingTransactionIds.addAll(account.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); + } + + private void postJournalEntries(final SavingsAccount savingsAccount, final Set existingTransactionIds, + final Set existingReversedTransactionIds) { + + final MonetaryCurrency currency = savingsAccount.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); + + final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds); + this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 9d65f9515..d5e4755a6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; -import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; @@ -24,9 +23,6 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; -import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; -import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; -import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; @@ -75,8 +71,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl private final NoteRepository noteRepository; private final StaffRepositoryWrapper staffRepository; private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator; - private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; - private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; @Autowired @@ -87,8 +81,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository, final NoteRepository noteRepository, final StaffRepositoryWrapper staffRepository, final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator, - final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, - final JournalEntryWritePlatformService journalEntryWritePlatformService, final SavingsAccountChargeAssembler savingsAccountChargeAssembler) { this.context = context; this.savingAccountRepository = savingAccountRepository; @@ -101,8 +93,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.noteRepository = noteRepository; this.staffRepository = staffRepository; this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator; - this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; - this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; } @@ -140,8 +130,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl try { this.savingsAccountDataValidator.validateForSubmit(command.json()); - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command); this.savingAccountRepository.save(account); @@ -153,8 +141,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.savingAccountRepository.save(account); } - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); - final Long savingsId = account.getId(); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // @@ -233,7 +219,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl } if (changes.containsKey("charges")) { - final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(), account.getCurrency().getCode()); + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(), + account.getCurrency().getCode()); final boolean updated = account.update(charges); if (!updated) { changes.remove("charges"); @@ -426,17 +413,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl .build(); } - private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, - final List existingReversedTransactionIds) { - - final MonetaryCurrency currency = savingsAccount.getCurrency(); - final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); - - final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), - existingTransactionIds, existingReversedTransactionIds); - this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); - } - private void checkClientOrGroupActive(final SavingsAccount account) { final Client client = account.getClient(); if (client != null) { From 9470e3a858b898d6aa6fd86a69b3a59dc3d42966 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Thu, 26 Sep 2013 20:47:39 +0530 Subject: [PATCH 10/15] fix for daily interest calculation --- .../loanaccount/loanschedule/domain/LoanApplicationTerms.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 008e70ddc..4dd8cca6c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -542,7 +542,7 @@ public final class LoanApplicationTerms { switch (this.interestCalculationPeriodMethod) { case DAILY: final BigDecimal dailyInterestRate = this.annualNominalInterestRate.divide(BigDecimal.valueOf(Long.valueOf(365)), mc) - .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(this.repaymentEvery)); + .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc); final BigDecimal equivalentInterestRateForPeriod = dailyInterestRate .multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod))); From c3f127e1e9e841caf3ca6e8553e14c4911e7f9bc Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 26 Sep 2013 23:52:46 +0100 Subject: [PATCH 11/15] MIFOSX-694: add missing users cache to ehcache xml file. --- .../service/OfficeReadPlatformService.java | 3 - .../OfficeReadPlatformServiceImpl.java | 55 +------------------ ...WritePlatformServiceJpaRepositoryImpl.java | 13 ++++- .../resources/META-INF/spring/ehcache.xml | 3 + 4 files changed, 14 insertions(+), 60 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformService.java index 51144ddc0..c59b34f95 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformService.java @@ -7,14 +7,11 @@ package org.mifosplatform.organisation.office.service; import java.util.Collection; -import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.organisation.office.data.OfficeData; import org.mifosplatform.organisation.office.data.OfficeTransactionData; public interface OfficeReadPlatformService { - MifosPlatformTenant loadTenantById(String tenantIdentifier); - Collection retrieveAllOffices(boolean includeAllOffices); Collection retrieveAllOfficesForDropdown(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java index ef6864819..29c5c67af 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/service/OfficeReadPlatformServiceImpl.java @@ -11,13 +11,9 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import javax.sql.DataSource; - import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; -import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; -import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService; @@ -26,7 +22,6 @@ import org.mifosplatform.organisation.office.data.OfficeTransactionData; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -37,19 +32,16 @@ import org.springframework.stereotype.Service; public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService { private final JdbcTemplate jdbcTemplate; - private final JdbcTemplate tenantsJdbcTemplate; private final PlatformSecurityContext context; private final CurrencyReadPlatformService currencyReadPlatformService; private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)"; @Autowired public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context, - final CurrencyReadPlatformService currencyReadPlatformService, final RoutingDataSource dataSource, - @Qualifier("tenantDataSourceJndi") final DataSource tenantsJndiDataSource) { + final CurrencyReadPlatformService currencyReadPlatformService, final RoutingDataSource dataSource) { this.context = context; this.currencyReadPlatformService = currencyReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); - this.tenantsJdbcTemplate = new JdbcTemplate(tenantsJndiDataSource); } private static final class OfficeMapper implements RowMapper { @@ -134,7 +126,6 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService } } - /** TODO: Vishwas This change breaks caching, need to have a relook **/ @Override @Cacheable(value = "offices", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')") public Collection retrieveAllOffices(final boolean includeAllOffices) { @@ -243,48 +234,4 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService public PlatformSecurityContext getContext() { return this.context; } - - @Override - @Cacheable(value = "tenantsById", key = "#tenantIdentifier") - public MifosPlatformTenant loadTenantById(final String tenantIdentifier) { - - try { - final TenantMapper rm = new TenantMapper(); - final String sql = "select " + rm.schema() + " where t.identifier like ?"; - - return this.tenantsJdbcTemplate.queryForObject(sql, rm, new Object[] { tenantIdentifier }); - } catch (final EmptyResultDataAccessException e) { - throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid."); - } - } - - private static final class TenantMapper implements RowMapper { - - private final StringBuilder sqlBuilder = new StringBuilder( - "id, name,identifier, schema_name as schemaName, schema_server as schemaServer, schema_server_port as schemaServerPort, auto_update as autoUpdate, ")// - .append(" schema_username as schemaUsername, schema_password as schemaPassword , timezone_id as timezoneId ")// - .append(" from tenants t");// - - public String schema() { - return this.sqlBuilder.toString(); - } - - @Override - public MifosPlatformTenant mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String tenantIdentifier = rs.getString("identifier"); - final String name = rs.getString("name"); - final String schemaName = rs.getString("schemaName"); - final String schemaServer = rs.getString("schemaServer"); - final String schemaServerPort = rs.getString("schemaServerPort"); - final String schemaUsername = rs.getString("schemaUsername"); - final String schemaPassword = rs.getString("schemaPassword"); - final String timezoneId = rs.getString("timezoneId"); - final boolean autoUpdateEnabled = rs.getBoolean("autoUpdate"); - - return new MifosPlatformTenant(id, tenantIdentifier, name, schemaName, schemaServer, schemaServerPort, schemaUsername, - schemaPassword, timezoneId, autoUpdateEnabled); - } - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java index b1802c506..23ef92d42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java @@ -34,6 +34,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Caching; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,7 +68,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit @Transactional @Override - @CacheEvict(value = "usersByUsername", allEntries = true) + @Caching(evict = { + @CacheEvict(value = "users", allEntries = true), + @CacheEvict(value = "usersByUsername", allEntries = true)}) public CommandProcessingResult createUser(final JsonCommand command) { try { @@ -111,7 +114,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit @Transactional @Override - @CacheEvict(value = "usersByUsername", allEntries = true) + @Caching(evict = { + @CacheEvict(value = "users", allEntries = true), + @CacheEvict(value = "usersByUsername", allEntries = true)}) public CommandProcessingResult updateUser(final Long userId, final JsonCommand command) { try { @@ -172,7 +177,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit @Transactional @Override - @CacheEvict(value = "usersByUsername", allEntries = true) + @Caching(evict = { + @CacheEvict(value = "users", allEntries = true), + @CacheEvict(value = "usersByUsername", allEntries = true)}) public CommandProcessingResult deleteUser(final Long userId) { final AppUser user = this.appUserRepository.findOne(userId); diff --git a/mifosng-provider/src/main/resources/META-INF/spring/ehcache.xml b/mifosng-provider/src/main/resources/META-INF/spring/ehcache.xml index 5b02dcb18..7a63ad5c8 100644 --- a/mifosng-provider/src/main/resources/META-INF/spring/ehcache.xml +++ b/mifosng-provider/src/main/resources/META-INF/spring/ehcache.xml @@ -6,6 +6,9 @@ xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> + + Date: Fri, 27 Sep 2013 13:13:14 +0530 Subject: [PATCH 12/15] =?UTF-8?q?Added=20existing=20transaction=20id?= =?UTF-8?q?=E2=80=99s=20for=20accounting=20purpose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/mifosplatform/portfolio/loanaccount/domain/Loan.java | 5 ++++- .../loanaccount/domain/LoanAccountDomainServiceJpa.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) 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 a64e72a30..f726df573 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 @@ -1493,7 +1493,10 @@ public class Loan extends AbstractPersistable { return disbursementTransaction; } - public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment) { + public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment, + final List existingTransactionIds,final List existingReversedTransactionIds) { + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); LoanCharge charge = null; for (final LoanCharge loanCharge : this.charges) { if (chargeId.equals(loanCharge.getId())) { 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 57d3c4bfe..93b146826 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 @@ -166,7 +166,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); if (loanTransactionType.isRepaymentAtDisbursement()) { - loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction); + loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); } else { loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction); From 0694dde72fd926c365bf643fed729b0045436861 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Fri, 27 Sep 2013 17:19:46 +0530 Subject: [PATCH 13/15] Move withdrawal and annual fee charges to charges workflow --- ..._move_withdrawal_annual_fee_to_charges.sql | 25 + .../SynchronousCommandProcessingService.java | 4 +- .../core/serialization/FromJsonHelper.java | 9 + .../core/service/DateUtils.java | 4 +- .../portfolio/charge/data/ChargeData.java | 4 +- .../portfolio/charge/domain/Charge.java | 10 + .../charge/domain/ChargeCalculationType.java | 2 +- .../charge/domain/ChargeTimeType.java | 42 +- ...ChargeDropdownReadPlatformServiceImpl.java | 23 +- .../charge/service/ChargeEnumerations.java | 14 +- .../SavingsAccountTransactionType.java | 8 +- .../savings/SavingsApiConstants.java | 30 +- .../api/SavingsAccountsApiResource.java | 10 +- .../data/SavingsAccountChargeData.java | 24 +- .../SavingsAccountChargeDataValidator.java | 9 +- .../savings/data/SavingsAccountData.java | 145 ++++-- .../data/SavingsAccountDataValidator.java | 109 ++-- .../savings/data/SavingsProductData.java | 74 +-- .../data/SavingsProductDataValidator.java | 54 +- .../savings/domain/SavingsAccount.java | 475 ++++++------------ .../domain/SavingsAccountAssembler.java | 40 +- .../savings/domain/SavingsAccountCharge.java | 173 +++++-- .../domain/SavingsAccountChargeAssembler.java | 33 +- .../domain/SavingsAccountTransaction.java | 22 +- .../savings/domain/SavingsProduct.java | 132 ++--- .../domain/SavingsProductAssembler.java | 18 +- .../domain/SavingsProductChargeAssembler.java | 9 +- ...AnnualFeeSavingsAccountCommandHandler.java | 5 +- ...sAccountChargeReadPlatformServiceImpl.java | 17 +- .../SavingsAccountReadPlatformService.java | 3 +- ...SavingsAccountReadPlatformServiceImpl.java | 66 ++- .../SavingsAccountWritePlatformService.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 42 +- ...SavingsProductReadPlatformServiceImpl.java | 22 +- .../ScheduledJobRunnerServiceImpl.java | 12 +- 35 files changed, 812 insertions(+), 859 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql diff --git a/mifosng-db/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql b/mifosng-db/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql new file mode 100644 index 000000000..791f391ef --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_7__move_withdrawal_annual_fee_to_charges.sql @@ -0,0 +1,25 @@ +ALTER TABLE `m_savings_account_charge` + ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `waived`; + +ALTER TABLE `m_savings_account` + DROP COLUMN `withdrawal_fee_amount`, + DROP COLUMN `withdrawal_fee_type_enum`; + +ALTER TABLE `m_savings_account_charge` + ADD COLUMN `fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `due_for_collection_as_of_date`, + ADD COLUMN `fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `fee_on_month`; + +ALTER TABLE `m_savings_account_charge` + CHANGE COLUMN `due_for_collection_as_of_date` `charge_due_date` DATE NULL DEFAULT NULL AFTER `charge_time_enum`; + + +ALTER TABLE `m_savings_account` + DROP COLUMN `annual_fee_amount`, + DROP COLUMN `annual_fee_on_month`, + DROP COLUMN `annual_fee_on_day`, + DROP COLUMN `annual_fee_next_due_date`; + +ALTER TABLE `m_savings_product` + DROP COLUMN `annual_fee_amount`, + DROP COLUMN `annual_fee_on_month`, + DROP COLUMN `annual_fee_on_day`; \ No newline at end of file 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 1940a4322..9cd185eac 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 @@ -419,9 +419,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isSavingsAccountInterestPosting()) { handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class); - } else if (wrapper.isSavingsAccountApplyAnnualFee()) { + } /*else if (wrapper.isSavingsAccountApplyAnnualFee()) { handler = this.applicationContext.getBean("applyAnnualFeeSavingsAccountCommandHandler", NewCommandSourceHandler.class); - } else if (wrapper.isSavingsAccountUndoTransaction()) { + } */else if (wrapper.isSavingsAccountUndoTransaction()) { handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isAdjustSavingsAccountTransaction()) { handler = this.applicationContext.getBean("savingsTransactionAdjustmentCommandHandler", NewCommandSourceHandler.class); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java index 19c8d8766..4d0cc7c58 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java @@ -149,6 +149,11 @@ public class FromJsonHelper { public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) { return this.helperDelegator.extractMonthDayNamed(parameterName, element); } + + public MonthDay extractMonthDayNamed(final String parameterName, final JsonObject object, final String dateFormat, + final Locale clientApplicationLocale) { + return this.helperDelegator.extractMonthDayNamed(parameterName, object, dateFormat, clientApplicationLocale); + } public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element) { return this.helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet()); @@ -216,4 +221,8 @@ public class FromJsonHelper { public String extractDateFormatParameter(final JsonObject element) { return this.helperDelegator.extractDateFormatParameter(element); } + + public String extractMonthDayFormatParameter(final JsonObject element) { + return this.helperDelegator.extractMonthDayFormatParameter(element); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java index 6643de289..fefaaa2ec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java @@ -64,7 +64,7 @@ public class DateUtils { return formattedSqlDate; } - public static boolean isInFuture(final LocalDate date){ - return date.isAfter(getLocalDateOfTenant()); + public static boolean isDateInTheFuture(final LocalDate localDate){ + return localDate.isAfter(getLocalDateOfTenant()); } } \ No newline at end of file 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 64252a5a3..77de17e18 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 @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.List; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; @@ -176,9 +177,10 @@ public class ChargeData implements Comparable, Serializable { final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO; final Collection chargeOptions = null; final LocalDate dueAsOfDate = null; + final MonthDay feeOnMonthDay = null; return SavingsAccountChargeData.instance(savingsChargeId, this.id, this.name, this.currency, this.amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage, - amountPercentageAppliedTo, chargeOptions, this.penalty); + amountPercentageAppliedTo, chargeOptions, this.penalty, feeOnMonthDay); } } \ 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 0562aacb0..f9859a3be 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 @@ -114,6 +114,11 @@ public class Charge extends AbstractPersistable { baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); } + + if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { + baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) + .failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal"); + } } else if (isLoanCharge()) { @@ -277,6 +282,11 @@ public class Charge extends AbstractPersistable { baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); } + + if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { + baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) + .failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal"); + } } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java index 22a107ff9..7d2689f73 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java @@ -57,6 +57,6 @@ public enum ChargeCalculationType { } public boolean isAllowedSavingsChargeCalculationType() { - return isFlat(); + return isFlat() || isPercentageOfAmount(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java index e79085c51..15f64fbd0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java @@ -8,8 +8,12 @@ package org.mifosplatform.portfolio.charge.domain; public enum ChargeTimeType { INVALID(0, "chargeTimeType.invalid"), // - DISBURSEMENT(1, "chargeTimeType.disbursement"), // - SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), MONTHLY(3, "chargeTimeType.monthly"), YEARLY(4, "chargeTimeType.yearly"); + DISBURSEMENT(1, "chargeTimeType.disbursement"), //only for loan charges + SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and savings charges + SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for savings + SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings + WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings + ANNUAL_FEE(6, "chargeTimeType.annualFee"); // only for savings private final Integer value; private final String code; @@ -32,7 +36,11 @@ public enum ChargeTimeType { } public static int maxValue() { - return 4; + int max = 1;// Default it to one (1) + for (ChargeTimeType chargeTimeType : ChargeTimeType.values()) { + max = (chargeTimeType.getValue() > max) ? chargeTimeType.getValue() : max; + } + return max; } public static ChargeTimeType fromInt(final Integer chargeTime) { @@ -45,10 +53,16 @@ public enum ChargeTimeType { chargeTimeType = SPECIFIED_DUE_DATE; break; case 3: - chargeTimeType = MONTHLY; + chargeTimeType = SAVINGS_ACTIVATION; break; case 4: - chargeTimeType = YEARLY; + chargeTimeType = SAVINGS_CLOSURE; + break; + case 5: + chargeTimeType = WITHDRAWAL_FEE; + break; + case 6: + chargeTimeType = ANNUAL_FEE; break; default: chargeTimeType = INVALID; @@ -65,12 +79,20 @@ public enum ChargeTimeType { return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); } - public boolean isMonthly() { - return this.value.equals(ChargeTimeType.MONTHLY.getValue()); + public boolean isSavingsActivation() { + return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue()); } - public boolean isYearly() { - return this.value.equals(ChargeTimeType.YEARLY.getValue()); + public boolean isSavingsClosure() { + return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue()); + } + + public boolean isWithdrawalFee() { + return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue()); + } + + public boolean isAnnualFee() { + return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue()); } public boolean isAllowedLoanChargeTime() { @@ -78,6 +100,6 @@ public enum ChargeTimeType { } public boolean isAllowedSavingsChargeTime() { - return isOnSpecifiedDueDate() || isMonthly() || isYearly(); + return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee(); } } \ No newline at end of file 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 0114eb0eb..41d761495 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 @@ -49,15 +49,7 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead public List retrieveCollectionTimeTypes() { final List chargeTimeTypes = new ArrayList(); for (final ChargeTimeType chargeTimeType : ChargeTimeType.values()) { - if (ChargeTimeType.INVALID.equals(chargeTimeType) || ChargeTimeType.MONTHLY.equals(chargeTimeType) // To - // be - // implemented - // for - // Savings - || ChargeTimeType.YEARLY.equals(chargeTimeType)) { - // implemented - // for - // Savings + if (ChargeTimeType.INVALID.equals(chargeTimeType)) { continue; } chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType)); @@ -82,17 +74,14 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead @Override public List retrieveSavingsCalculationTypes() { - return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT) - //Enable when percentage of amount is supported for savings charges - //, chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT) - ); + return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), + chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT)); } @Override public List retrieveSavingsCollectionTimeTypes() { - return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE) - //enable when support is added for recurring Monthly and yearly charges - //, chargeTimeType(ChargeTimeType.MONTHLY), chargeTimeType(ChargeTimeType.YEARLY) - ); + return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION), + chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE), chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE), + chargeTimeType(ChargeTimeType.ANNUAL_FEE)); } } \ 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 b5da02db2..61f71acd4 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 @@ -28,11 +28,17 @@ public class ChargeEnumerations { optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date"); break; - case MONTHLY: - optionData = new EnumOptionData(ChargeTimeType.MONTHLY.getValue().longValue(), ChargeTimeType.MONTHLY.getCode(), "Monthly"); + case SAVINGS_ACTIVATION: + optionData = new EnumOptionData(ChargeTimeType.SAVINGS_ACTIVATION.getValue().longValue(), ChargeTimeType.SAVINGS_ACTIVATION.getCode(), "Savings Activation"); break; - case YEARLY: - optionData = new EnumOptionData(ChargeTimeType.YEARLY.getValue().longValue(), ChargeTimeType.YEARLY.getCode(), "Yearly"); + case SAVINGS_CLOSURE: + optionData = new EnumOptionData(ChargeTimeType.SAVINGS_CLOSURE.getValue().longValue(), ChargeTimeType.SAVINGS_CLOSURE.getCode(), "Savings Closure"); + break; + case WITHDRAWAL_FEE: + optionData = new EnumOptionData(ChargeTimeType.WITHDRAWAL_FEE.getValue().longValue(), ChargeTimeType.WITHDRAWAL_FEE.getCode(), "Withdrawal Fee"); + break; + case ANNUAL_FEE: + optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(), "Annual Fee"); break; default: optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java index 74dfd4acb..4cb3b6105 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java @@ -105,9 +105,13 @@ public enum SavingsAccountTransactionType { return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue()); } - public boolean isCharge() { + public boolean isPayCharge() { return this.value.equals(SavingsAccountTransactionType.PAY_CHARGE.getValue()); } + + public boolean isChargeTransaction(){ + return isPayCharge() || isWithdrawalFee() || isAnnualFee(); + } public boolean isWaiveCharge() { return this.value.equals(SavingsAccountTransactionType.WAIVE_CHARGES.getValue()); @@ -130,7 +134,7 @@ public enum SavingsAccountTransactionType { } public boolean isDebit() { - return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isCharge(); + return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge(); } public boolean isCredit() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index fb1fd6d77..bdab0a9b9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -81,8 +81,8 @@ public class SavingsApiConstants { public static final String withdrawalFeeAmountParamName = "withdrawalFeeAmount"; public static final String withdrawalFeeTypeParamName = "withdrawalFeeType"; public static final String withdrawalFeeForTransfersParamName = "withdrawalFeeForTransfers"; - public static final String annualFeeAmountParamName = "annualFeeAmount"; - public static final String annualFeeOnMonthDayParamName = "annualFeeOnMonthDay"; + public static final String feeAmountParamName = "feeAmount";//to be deleted + public static final String feeOnMonthDayParamName = "feeOnMonthDay"; public static final String accountingRuleParamName = "accountingRule"; public static final String paymentTypeIdParamName = "paymentTypeId"; public static final String transactionAccountNumberParamName = "accountNumber"; @@ -118,16 +118,22 @@ public class SavingsApiConstants { public static final String amountParamName = "amount"; public static final String amountPaidParamName = "amountPaid"; public static final String chargeOptionsParamName = "chargeOptions"; + public static final String chargePaymentModeParamName = "chargePaymentMode"; public static final String noteParamName = "note"; + + //Savings account associations + public static final String transactions = "transactions"; + public static final String charges = "charges"; + public static final Set SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, - withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName, - annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName, + withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, + feeOnMonthDayParamName, accountingRuleParamName, chargesParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), @@ -147,7 +153,7 @@ public class SavingsApiConstants { nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, - withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, "currencyOptions", + withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, "currencyOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions")); @@ -156,8 +162,10 @@ public class SavingsApiConstants { productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, - withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, chargesParamName)); + lockinPeriodFrequencyTypeParamName, + //withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, + //withdrawalFeeForTransfersParamName, + feeAmountParamName, feeOnMonthDayParamName, chargesParamName)); /** * These parameters will match the class level parameters of @@ -169,10 +177,10 @@ public class SavingsApiConstants { groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, - withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName, - annualFeeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions", + withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, + feeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", - "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions")); + "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee")); public static final Set SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName, @@ -195,7 +203,7 @@ public class SavingsApiConstants { amountPaidParamName, chargeOptionsParamName)); public static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( - chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); + chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName)); public static final Set SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java index 2834c5946..a98d147b6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java @@ -154,11 +154,11 @@ public class SavingsAccountsApiResource { if (!associationParameters.isEmpty()) { if (associationParameters.contains("all")) { - associationParameters.addAll(Arrays.asList("transactions", "charges")); + associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); } - if (associationParameters.contains("transactions")) { - mandatoryResponseParameters.add("transactions"); + if (associationParameters.contains(SavingsApiConstants.transactions)) { + mandatoryResponseParameters.add(SavingsApiConstants.transactions); final Collection currentTransactions = this.savingsAccountReadPlatformService .retrieveAllTransactions(accountId); if (!CollectionUtils.isEmpty(currentTransactions)) { @@ -166,8 +166,8 @@ public class SavingsAccountsApiResource { } } - if (associationParameters.contains("charges")) { - mandatoryResponseParameters.add("charges"); + if (associationParameters.contains(SavingsApiConstants.charges)) { + mandatoryResponseParameters.add(SavingsApiConstants.charges); final Collection currentCharges = this.savingsAccountChargeReadPlatformService .retrieveSavingsAccountCharges(accountId); if (!CollectionUtils.isEmpty(currentCharges)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java index d7db3d3f9..351e37c1f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeData.java @@ -9,9 +9,11 @@ import java.math.BigDecimal; import java.util.Collection; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; 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.ChargeTimeType; /** * Immutable data object for Savings Account charge data. @@ -27,12 +29,14 @@ public class SavingsAccountChargeData { @SuppressWarnings("unused") private final String name; - @SuppressWarnings("unused") private final EnumOptionData chargeTimeType; @SuppressWarnings("unused") private final LocalDate dueDate; + @SuppressWarnings("unused") + private final MonthDay feeOnMonthDay; + private final EnumOptionData chargeCalculationType; private final BigDecimal percentage; @@ -82,28 +86,30 @@ public class SavingsAccountChargeData { final BigDecimal percentage = BigDecimal.ZERO; final boolean penalty = false; final LocalDate dueAsOfDate = null; + final MonthDay feeOnMonthDay = null; + return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, - penalty); + penalty, feeOnMonthDay); } public static SavingsAccountChargeData instance(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 dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, - final Collection chargeOptions, final boolean penalty) { + final Collection chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) { return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, - penalty); + penalty, feeOnMonthDay); } private SavingsAccountChargeData(final Long id, final Long chargeId, final String name, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, - final Collection chargeOptions, final boolean penalty) { + final Collection chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) { this.id = id; this.chargeId = chargeId; this.name = name; @@ -121,10 +127,18 @@ public class SavingsAccountChargeData { this.amountOrPercentage = getAmountOrPercentage(); this.chargeOptions = chargeOptions; this.penalty = penalty; + this.feeOnMonthDay = feeOnMonthDay; } private BigDecimal getAmountOrPercentage() { return (this.chargeCalculationType != null) && (this.chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount; } + public boolean isWithdrawalFee(){ + return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isWithdrawalFee(); + } + + public boolean isAnnualFee(){ + return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isAnnualFee(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java index 50161bfed..9cfc4da44 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountChargeDataValidator.java @@ -11,6 +11,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_AC import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -20,6 +21,7 @@ import java.util.Map; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -62,7 +64,12 @@ public class SavingsAccountChargeDataValidator { final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); } - + + if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) { + final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java index 3e9b2d7de..39d0e179a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java @@ -7,11 +7,10 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; import java.util.Collection; +import java.util.HashSet; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.joda.time.LocalDate; -import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.staff.data.StaffData; @@ -44,18 +43,13 @@ public class SavingsAccountData { private final BigDecimal minRequiredOpeningBalance; private final Integer lockinPeriodFrequency; private final EnumOptionData lockinPeriodFrequencyType; - private final BigDecimal withdrawalFeeAmount; - private final EnumOptionData withdrawalFeeType; private final boolean withdrawalFeeForTransfers; - private final BigDecimal annualFeeAmount; - private final MonthDay annualFeeOnMonthDay; - private final LocalDate annualFeeNextDueDate; // associations private final SavingsAccountSummaryData summary; @SuppressWarnings("unused") private final Collection transactions; - @SuppressWarnings("unused") + private final Collection charges; // template @@ -69,6 +63,11 @@ public class SavingsAccountData { private final Collection withdrawalFeeTypeOptions; private final Collection chargeOptions; + @SuppressWarnings("unused") + private final SavingsAccountChargeData withdrawalFee; + @SuppressWarnings("unused") + private final SavingsAccountChargeData annualFee; + public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName, final SavingsAccountStatusEnumData status, @@ -76,9 +75,7 @@ public class SavingsAccountData { final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType, - final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary) { + final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary) { final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -96,15 +93,44 @@ public class SavingsAccountData { return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, - chargeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, charges, chargeOptions); } public static SavingsAccountData lookup(final Long accountId, final String accountNo) { + final String externalId = null; + final Long productId = null; + final Long groupId = null; + final Long clientId = null; + final String clientName = null; + final String groupName = null; + final String productName = null; + final Long fieldOfficerId = null; + final String fieldOfficerName = null; + final SavingsAccountStatusEnumData status = null; + final SavingsAccountApplicationTimelineData timeline = null; + final CurrencyData currency = null; + final BigDecimal nominalAnnualInterestRate = null; + final EnumOptionData interestPeriodType = null; + final EnumOptionData interestPostingPeriodType = null; + final EnumOptionData interestCalculationType = null; + final EnumOptionData interestCalculationDaysInYearType = null; + final BigDecimal minRequiredOpeningBalance = null; + final Integer lockinPeriodFrequency = null; + final EnumOptionData lockinPeriodFrequencyType = null; +// final BigDecimal withdrawalFeeAmount = null; +// final EnumOptionData withdrawalFeeType = null; + final boolean withdrawalFeeForTransfers = false; +// final BigDecimal annualFeeAmount = null; +// final MonthDay annualFeeOnMonthDay = null; +// final LocalDate annualFeeNextDueDate = null; + + final SavingsAccountSummaryData summary = null; + final Collection transactions = null; + final Collection productOptions = null; final Collection fieldOfficerOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -114,12 +140,13 @@ public class SavingsAccountData { final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; - final Collection transactions = null; final Collection charges = null; final Collection chargeOptions = null; - return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, false, null, null, null, null, transactions, productOptions, + return new SavingsAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId , fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions); @@ -149,12 +176,11 @@ public class SavingsAccountData { account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, - account.lockinPeriodFrequencyType, account.withdrawalFeeAmount, account.withdrawalFeeType, - account.withdrawalFeeForTransfers, account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, - account.summary, transactions, template.productOptions, template.fieldOfficerOptions, - template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions, - template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions, - template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions); + account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, transactions, + template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions, + template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions, + template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions, + template.withdrawalFeeTypeOptions, charges, template.chargeOptions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -172,11 +198,10 @@ public class SavingsAccountData { account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, - account.lockinPeriodFrequencyType, account.withdrawalFeeAmount, account.withdrawalFeeType, - account.withdrawalFeeForTransfers, account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, - account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions); + account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, transactions, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, charges, chargeOptions); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -200,12 +225,12 @@ public class SavingsAccountData { final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; - final BigDecimal withdrawalFeeAmount = null; - final EnumOptionData withdrawalFeeType = null; +// final BigDecimal withdrawalFeeAmount = null; +// final EnumOptionData withdrawalFeeType = null; final boolean withdrawalFeeForTransfers = false; - final BigDecimal annualFeeAmount = null; - final MonthDay annualFeeOnMonthDay = null; - final LocalDate annualFeeNextDueDate = null; +// final BigDecimal annualFeeAmount = null; +// final MonthDay annualFeeOnMonthDay = null; +// final LocalDate annualFeeNextDueDate = null; final SavingsAccountSummaryData summary = null; final Collection transactions = null; @@ -225,11 +250,10 @@ public class SavingsAccountData { return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, - chargeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, charges, chargeOptions); } private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, @@ -238,11 +262,10 @@ public class SavingsAccountData { final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount, - final MonthDay annualFeeOnMonthDay, final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary, - final Collection transactions, final Collection productOptions, - final Collection fieldOfficerOptions, final Collection interestCompoundingPeriodTypeOptions, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, + final SavingsAccountSummaryData summary, final Collection transactions, + final Collection productOptions, final Collection fieldOfficerOptions, + final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, @@ -270,12 +293,12 @@ public class SavingsAccountData { this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; - this.withdrawalFeeAmount = withdrawalFeeAmount; - this.withdrawalFeeType = withdrawalFeeType; +// this.withdrawalFeeAmount = withdrawalFeeAmount; +// this.withdrawalFeeType = withdrawalFeeType; this.withdrawalFeeForTransfers = withdrawalFeeForTransfers; - this.annualFeeAmount = annualFeeAmount; - this.annualFeeOnMonthDay = annualFeeOnMonthDay; - this.annualFeeNextDueDate = annualFeeNextDueDate; +// this.annualFeeAmount = annualFeeAmount; +// this.annualFeeOnMonthDay = annualFeeOnMonthDay; +// this.annualFeeNextDueDate = annualFeeNextDueDate; this.summary = summary; this.transactions = transactions; @@ -292,8 +315,26 @@ public class SavingsAccountData { this.charges = charges;// charges associated with Savings account // charges available for adding to Savings account this.chargeOptions = chargeOptions; + + this.withdrawalFee = getWithdrawalFee(); + + this.annualFee = getAnnualFee(); } + private SavingsAccountChargeData getWithdrawalFee() { + for (SavingsAccountChargeData charge : this.charges()) { + if (charge.isWithdrawalFee()) return charge; + } + return null; + } + + private SavingsAccountChargeData getAnnualFee() { + for (SavingsAccountChargeData charge : this.charges()) { + if (charge.isAnnualFee()) return charge; + } + return null; + } + public Long id() { return this.id; } @@ -328,4 +369,8 @@ public class SavingsAccountData { public int hashCode() { return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode(); } + + public Collection charges() { + return (this.charges == null) ? new HashSet() : this.charges; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java index 2cd1510bc..39bb81e7c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java @@ -6,10 +6,13 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; @@ -22,14 +25,13 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequire import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.lang.StringUtils; @@ -45,11 +47,12 @@ import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; @Component @@ -176,32 +179,6 @@ public class SavingsAccountDataValidator { } } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { - - final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, - element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).zeroOrPositiveAmount(); - - if (withdrawalFeeAmount != null) { - final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType) - .isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues()); - } - } - - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { - final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() - .isOneOfTheseValues(1, 2); - - if (withdrawalFeeType != null) { - final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( - withdrawalFeeAmountParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull() - .zeroOrPositiveAmount(); - } - } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) { final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed( withdrawalFeeForTransfersParamName, element); @@ -209,29 +186,34 @@ public class SavingsAccountDataValidator { .ignoreIfNull().validateForBooleanValue(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount(); - - if (annualFeeAmount != null) { - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull(); - } - } - - if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { - - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); - if (monthDayOfAnnualFee != null) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, - element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount(); - } - } - + validateSavingsCharges(element, baseDataValidator); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } + private void validateSavingsCharges(final JsonElement element, final DataValidatorBuilder baseDataValidator) { + + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + + final JsonObject savingsChargeElement = array.get(i).getAsJsonObject(); + + //final Long id = this.fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement); + + final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement); + baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).longGreaterThanZero(); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale); + baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); + } + } + } + } + public void validateForUpdate(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -359,19 +341,6 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3); } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { - final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, - element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { - final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() - .isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues()); - } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) { final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed( withdrawalFeeForTransfersParamName, element); @@ -379,14 +348,14 @@ public class SavingsAccountDataValidator { .ignoreIfNull().validateForBooleanValue(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); + if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) { + final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element); + baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); + if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) { + final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); } throwExceptionIfValidationWarningsExist(dataValidationErrors); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index a00e21f2e..c5bbf8e56 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -10,7 +10,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; @@ -37,11 +36,7 @@ public class SavingsProductData { private final BigDecimal minRequiredOpeningBalance; private final Integer lockinPeriodFrequency; private final EnumOptionData lockinPeriodFrequencyType; - private final BigDecimal withdrawalFeeAmount; - private final EnumOptionData withdrawalFeeType; private final boolean withdrawalFeeForTransfers; - private final BigDecimal annualFeeAmount; - private final MonthDay annualFeeOnMonthDay; // accounting private final EnumOptionData accountingRule; @@ -86,11 +81,7 @@ public class SavingsProductData { final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; - final BigDecimal withdrawalFeeAmount = null; - final EnumOptionData withdrawalFeeType = null; final boolean withdrawalFeeForTransfers = false; - final BigDecimal annualFeeAmount = null; - final MonthDay annualFeeMonthDay = null; final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; final Collection charges = null; @@ -99,11 +90,11 @@ public class SavingsProductData { return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, - currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRule, accountingMappings, + paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, + interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, + accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings); } @@ -111,13 +102,12 @@ public class SavingsProductData { return new SavingsProductData(product.id, product.name, product.description, product.currency, product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType, product.interestCalculationType, product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance, product.lockinPeriodFrequency, - product.lockinPeriodFrequencyType, product.withdrawalFeeAmount, product.withdrawalFeeType, - product.withdrawalFeeForTransfers, product.accountingRule, product.annualFeeAmount, product.annualFeeOnMonthDay, - product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions, - product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions, - product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions, - product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, - product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, + product.lockinPeriodFrequencyType, product.withdrawalFeeForTransfers, product.accountingRule, product.accountingMappings, + product.paymentChannelToFundSourceMappings, product.currencyOptions, product.interestCompoundingPeriodTypeOptions, + product.interestPostingPeriodTypeOptions, product.interestCalculationTypeOptions, + product.interestCalculationDaysInYearTypeOptions, product.lockinPeriodFrequencyTypeOptions, + product.withdrawalFeeTypeOptions, product.paymentTypeOptions, product.accountingRuleOptions, + product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings); } @@ -139,9 +129,8 @@ public class SavingsProductData { existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, - existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount, - existingProduct.withdrawalFeeType, existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, - existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, existingProduct.accountingMappings, + existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, + existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, @@ -172,9 +161,8 @@ public class SavingsProductData { existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, - existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount, - existingProduct.withdrawalFeeType, existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, - existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, accountingMappings, + existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, + existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, @@ -186,9 +174,8 @@ public class SavingsProductData { final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount, - final MonthDay annualFeeOnMonthDay, final EnumOptionData accountingType) { + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, + final EnumOptionData accountingType) { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; @@ -211,11 +198,11 @@ public class SavingsProductData { return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, - currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings, + paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, + interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, + accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings); } @@ -231,12 +218,8 @@ public class SavingsProductData { final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; - final BigDecimal withdrawalFeeAmount = null; - final EnumOptionData withdrawalFeeType = null; final boolean withdrawalFeeForTransfers = false; final EnumOptionData accountingType = null; - final BigDecimal annualFeeAmount = null; - final MonthDay annualFeeMonthDay = null; final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; @@ -258,8 +241,8 @@ public class SavingsProductData { return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, + accountingType, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, @@ -270,9 +253,8 @@ public class SavingsProductData { final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final EnumOptionData accountingType, - final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final Map accountingMappings, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, + final EnumOptionData accountingType, final Map accountingMappings, final Collection paymentChannelToFundSourceMappings, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, @@ -297,11 +279,7 @@ public class SavingsProductData { this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; - this.withdrawalFeeAmount = withdrawalFeeAmount; - this.withdrawalFeeType = withdrawalFeeType; this.withdrawalFeeForTransfers = withdrawalFeeForTransfers; - this.annualFeeAmount = annualFeeAmount; - this.annualFeeOnMonthDay = annualFeeOnMonthDay; this.currencyOptions = currencyOptions; this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index 72a5077fe..ce39ed6e2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -7,8 +7,8 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; @@ -22,9 +22,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -46,7 +44,6 @@ import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -151,7 +148,7 @@ public class SavingsProductDataValidator { } } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { + /*if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); @@ -176,7 +173,7 @@ public class SavingsProductDataValidator { .zeroOrPositiveAmount(); } } - +*/ if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) { final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed( withdrawalFeeForTransfersParamName, element); @@ -184,23 +181,23 @@ public class SavingsProductDataValidator { .ignoreIfNull().validateForBooleanValue(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount(); + if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) { + final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element); + baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount(); if (annualFeeAmount != null) { - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull(); + final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull(); } } - if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { + if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) { - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element); if (monthDayOfAnnualFee != null) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, + final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount(); + baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount(); } } @@ -331,19 +328,6 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3); } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { - final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, - element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { - final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() - .isOneOfTheseValues(1, 2); - } - if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) { final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed( withdrawalFeeForTransfersParamName, element); @@ -351,14 +335,14 @@ public class SavingsProductDataValidator { .ignoreIfNull().validateForBooleanValue(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { - final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); + if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) { + final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element); + baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); } - if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { - final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); + if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) { + final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); } final Long savingsControlAccountId = this.fromApiJsonHelper.extractLongNamed( diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 7f9c390a2..7e6e2d291 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -6,15 +6,10 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -49,7 +44,6 @@ import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; -import org.joda.time.MonthDay; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; @@ -76,7 +70,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO; import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; @@ -223,28 +216,9 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "lockedin_until_date_derived", nullable = true) private Date lockedInUntilDate; - @Column(name = "withdrawal_fee_amount", scale = 6, precision = 19, nullable = true) - private BigDecimal withdrawalFeeAmount; - - @Column(name = "withdrawal_fee_type_enum", nullable = true) - private Integer withdrawalFeeType; - @Column(name = "withdrawal_fee_for_transfer", nullable = true) private boolean withdrawalFeeApplicableForTransfer; - @Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true) - private BigDecimal annualFeeAmount; - - @Column(name = "annual_fee_on_month", nullable = true) - private Integer annualFeeOnMonth; - - @Column(name = "annual_fee_on_day", nullable = true) - private Integer annualFeeOnDay; - - @Temporal(TemporalType.DATE) - @Column(name = "annual_fee_next_due_date", nullable = true) - private Date annualFeeNextDueDate; - @Embedded private SavingsAccountSummary summary; @@ -275,16 +249,13 @@ public class SavingsAccount extends AbstractPersistable { final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final Set savingsAccountCharges) { + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay, - savingsAccountCharges); + withdrawalFeeApplicableForTransfer, savingsAccountCharges); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, @@ -294,9 +265,7 @@ public class SavingsAccount extends AbstractPersistable { final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final Set savingsAccountCharges) { + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges) { this.client = client; this.group = group; this.product = product; @@ -322,18 +291,8 @@ public class SavingsAccount extends AbstractPersistable { if (lockinPeriodFrequencyType != null) { this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue(); } - this.withdrawalFeeAmount = withdrawalFeeAmount; - if (withdrawalFeeType != null) { - this.withdrawalFeeType = withdrawalFeeType.getValue(); - } this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer; - this.annualFeeAmount = annualFeeAmount; - if (annualFeeOnMonthDay != null) { - this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear(); - this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); - } - if (!CollectionUtils.isEmpty(savingsAccountCharges)) { this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges); } @@ -616,7 +575,7 @@ public class SavingsAccount extends AbstractPersistable { return transaction; } - private LocalDate getActivationLocalDate() { + public LocalDate getActivationLocalDate() { LocalDate activationLocalDate = null; if (this.activatedOnDate != null) { activationLocalDate = new LocalDate(this.activatedOnDate); @@ -682,33 +641,20 @@ public class SavingsAccount extends AbstractPersistable { transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney); this.transactions.add(transaction); - if (applyWithdrawFee && isAutomaticWithdrawalFee()) { - - SavingsAccountTransaction withdrawalFeeTransaction = null; - Money feeAmount = Money.zero(this.currency); - switch (SavingsWithdrawalFeesType.fromInt(this.withdrawalFeeType)) { - case INVALID: - break; - case FLAT: - feeAmount = Money.of(this.currency, this.withdrawalFeeAmount); - withdrawalFeeTransaction = SavingsAccountTransaction - .fee(this, office(), transactionDTO.getTransactionDate(), feeAmount); - this.transactions.add(withdrawalFeeTransaction); - break; - case PERCENT_OF_AMOUNT: - final BigDecimal feeAmountDecimal = transactionDTO.getTransactionAmount().multiply(this.withdrawalFeeAmount) - .divide(BigDecimal.valueOf(100l)); - feeAmount = Money.of(this.currency, feeAmountDecimal); - withdrawalFeeTransaction = SavingsAccountTransaction - .fee(this, office(), transactionDTO.getTransactionDate(), feeAmount); - this.transactions.add(withdrawalFeeTransaction); - break; + if (applyWithdrawFee){ + //auto pay withdrawal fee + payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate()); + } + return transaction; + } + + private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate){ + for (SavingsAccountCharge charge : this.charges()) { + if (charge.isWithdrawalFee()){ + charge.updateWithdralFeeAmount(transactionAmoount); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate); } } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - - return transaction; } public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) { @@ -768,128 +714,7 @@ public class SavingsAccount extends AbstractPersistable { } } } - - public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter, - final LocalDate annualFeeTransactionDate, final LocalDate today) { - - final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.applyAnnualFeeTransactionAction); - - if (isNotActive()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active"); - - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - final LocalDate nextAnnualFeeDueDate = getNextAnnualFeeDueDate(); - if (nextAnnualFeeDueDate == null || annualFeeSettingsNotSet()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings"); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (nextAnnualFeeDueDate.isBefore(getActivationLocalDate())) { - - baseDataValidator.reset().parameter("annualFeeTransactionDate").value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("before.activationDate"); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isDateInTheFuture(annualFeeTransactionDate)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.in.the.future"); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isNotValidAnnualFeeTransactionDate(annualFeeTransactionDate, today)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date"); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); - if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(annualFeeTransactionDate)) { - baseDataValidator.reset().parameter("annualFeeTransactionDate").value(annualFeeTransactionDate.toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date"); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_PAY_CHARGE, annualFeeTransactionDate); - - final Money annualFee = Money.of(this.currency, this.annualFeeAmount); - final SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, office(), - annualFeeTransactionDate, annualFee); - this.transactions.add(annualFeeTransaction); - - validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.applyAnnualFeeTransactionAction); - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - - calculateInterestUsing(mc, today); - - currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); - if (currentAnnualFeeNextDueDate != null) { - final LocalDate newAnnualFeeNextDueDate = new LocalDate(currentAnnualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth) - .withDayOfMonth(this.annualFeeOnDay).plusYears(1); - this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); - } else { - updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); - } - - return annualFeeTransaction; - } - - private boolean isNotValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) { - return !isValidAnnualFeeTransactionDate(annualFeeTransactionDate, today); - } - - private boolean isValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) { - - LocalDate startingDate = getActivationLocalDate(); - boolean isValid = false; - while (!startingDate.isAfter(today) && !isValid) { - LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay); - if (startingDate.isAfter(nextDueLocalDate)) { - nextDueLocalDate = nextDueLocalDate.plusYears(1); - } - isValid = nextDueLocalDate.isEqual(annualFeeTransactionDate); - - startingDate = nextDueLocalDate.plusYears(1); - } - - return isValid; - } - - private void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) { - LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay); - if (startingDate.isAfter(nextDueLocalDate)) { - nextDueLocalDate = nextDueLocalDate.plusYears(1); - } - this.annualFeeNextDueDate = nextDueLocalDate.toDate(); - } - - private LocalDate getNextAnnualFeeDueDate() { - LocalDate nextAnnualFeeDueDate = null; - if (this.annualFeeNextDueDate != null) { - nextAnnualFeeDueDate = new LocalDate(this.annualFeeNextDueDate); - } - return nextAnnualFeeDueDate; - } - - private boolean annualFeeSettingsNotSet() { - return !annualFeeSettingsSet(); - } - - private boolean annualFeeSettingsSet() { - return this.annualFeeOnDay != null && this.annualFeeOnMonth != null; - } - - private boolean isAutomaticWithdrawalFee() { - return this.withdrawalFeeType != null; - } - + private boolean isAccountLocked(final LocalDate transactionDate) { boolean isLocked = false; final boolean accountHasLockedInSetting = this.lockedInUntilDate != null; @@ -1037,55 +862,12 @@ public class SavingsAccount extends AbstractPersistable { this.lockinPeriodFrequencyType = null; } - if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); - actualChanges.put(withdrawalFeeAmountParamName, newValue); - actualChanges.put(localeParamName, localeAsInput); - this.withdrawalFeeAmount = newValue; - } - - if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { - final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeTypeParamName); - actualChanges.put(withdrawalFeeTypeParamName, newValue); - this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue; - } - if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) { final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); actualChanges.put(withdrawalFeeForTransfersParamName, newValue); this.withdrawalFeeApplicableForTransfer = newValue; } - // set period type to null if frequency is null - if (this.withdrawalFeeAmount == null) { - this.withdrawalFeeType = null; - } - - if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); - actualChanges.put(annualFeeAmountParamName, newValue); - actualChanges.put(localeParamName, localeAsInput); - this.annualFeeAmount = newValue; - } - - if (command.hasParameter(annualFeeOnMonthDayParamName)) { - final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); - final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); - final Integer dayOfMonthValue = monthDay.getDayOfMonth(); - if (this.annualFeeOnDay != dayOfMonthValue) { - actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); - actualChanges.put(localeParamName, localeAsInput); - this.annualFeeOnDay = dayOfMonthValue; - } - - final Integer monthOfYear = monthDay.getMonthOfYear(); - if (this.annualFeeOnMonth != monthOfYear) { - actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); - actualChanges.put(localeParamName, localeAsInput); - this.annualFeeOnMonth = monthOfYear; - } - } - // charges final String chargesParamName = "charges"; if (command.hasParameter(chargesParamName)) { @@ -1095,60 +877,8 @@ public class SavingsAccount extends AbstractPersistable { } } - // set period type to null if frequency is null - if (this.annualFeeAmount == null) { - this.annualFeeOnDay = null; - this.annualFeeOnMonth = null; - } - validateLockinDetails(); - validateWithdrawalFeeDetails(); - validateAnnualFeeDetails(); - } - - private void validateAnnualFeeDetails() { - - final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); - - if (this.annualFeeAmount == null) { - - if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) { - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull(); - } - } else { - - if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) { - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull(); - } - - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount(); - } - - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - } - - private void validateWithdrawalFeeDetails() { - - final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); - - if (this.withdrawalFeeAmount == null) { - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).ignoreIfNull() - .isOneOfTheseValues(1, 2); - - if (this.withdrawalFeeType != null) { - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).notNull(); - } - } else { - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).zeroOrPositiveAmount(); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).notNull() - .isOneOfTheseValues(1, 2); - } - - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } private void validateLockinDetails() { @@ -1311,8 +1041,8 @@ public class SavingsAccount extends AbstractPersistable { public void validateNewApplicationState(final LocalDate todayDateOfTenant) { validateLockinDetails(); - validateWithdrawalFeeDetails(); - validateAnnualFeeDetails(); + //validateWithdrawalFeeDetails(); + //validateAnnualFeeDetails(); final LocalDate submittedOn = getSubmittedOnLocalDate(); @@ -1470,22 +1200,16 @@ public class SavingsAccount extends AbstractPersistable { validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate()); transactionToUndo.reverse(); - if (transactionToUndo.isAnnualFee()) { - this.annualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); - if (this.annualFeeNextDueDate == null) { - updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); - } else { - final LocalDate newAnnualFeeNextDueDate = new LocalDate(this.annualFeeNextDueDate) - .withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1); - this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate(); - } - } else if (transactionToUndo.isCharge() || transactionToUndo.isWaiveCharge()) { + if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) { // undo charge final Set chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid(); for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) { final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); - if (transactionToUndo.isCharge()) { + if (transactionToUndo.isChargeTransaction()) { chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); + if(chargeToUndo.isAnnualFee()){ + chargeToUndo.updateToPreviousAnnualFeeDueDate(); + } } else if (transactionToUndo.isWaiveCharge()) { chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); } @@ -1672,10 +1396,10 @@ public class SavingsAccount extends AbstractPersistable { this.activatedBy = currentUser; this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); - if (annualFeeSettingsSet()) { + /*if (annualFeeSettingsSet()) { updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); } - +*/ if (this.client != null && this.client.isActivatedAfter(activationDate)) { final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); final String dateAsString = formatter.print(this.client.getActivationLocalDate()); @@ -1730,9 +1454,27 @@ public class SavingsAccount extends AbstractPersistable { // correct. recalculateDailyBalances(openingAccountBalance); } - + + + //update annual fee due date + for (SavingsAccountCharge charge : this.charges()) { + charge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); + } + + //auto pay the activation time charges + this.payActivationCharges(); + //TODO : AA add activation charges to actual changes list + return actualChanges; } + + private void payActivationCharges() { + for (SavingsAccountCharge savingsAccountCharge : this.charges()) { + if (savingsAccountCharge.isSavingsActivation()) { + payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate()); + } + } + } public Map close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { @@ -1918,6 +1660,11 @@ public class SavingsAccount extends AbstractPersistable { final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId); + if (savingsAccountCharge.isWithdrawalFee()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + // validate charge is not already paid or waived if (savingsAccountCharge.isWaived()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived"); @@ -1963,14 +1710,55 @@ public class SavingsAccount extends AbstractPersistable { throw new PlatformApiDataValidationException(dataValidationErrors); } } + + if (savingsAccountCharge.isSavingsActivation() && !(isSubmittedAndPendingApproval() || (isApproved() && isNotActive()))) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "not.valid.account.status.cannot.add.activation.time.charge"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate); - + //Only one withdrawal fee is supported per account + if(savingsAccountCharge.isWithdrawalFee()){ + if(this.isWithDrawalFeeExists()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "multiple.withdrawal.fee.per.account.not.supported"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + //Only one annual fee is supported per account + if(savingsAccountCharge.isAnnualFee()){ + if(this.isAnnualFeeExists()){ + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "multiple.annual.fee.per.account.not.supported"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + //activation charge and withdrawal charges not required this validation + if(savingsAccountCharge.isOnSpecifiedDueDate()){ + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate); + } + // add new charge to savings account - getCharges().add(savingsAccountCharge); + charges().add(savingsAccountCharge); } + private boolean isWithDrawalFeeExists() { + for (SavingsAccountCharge charge : this.charges()) { + if(charge.isWithdrawalFee()) return true; + } + return false; + } + + private boolean isAnnualFeeExists() { + for (SavingsAccountCharge charge : this.charges()) { + if(charge.isAnnualFee()) return true; + } + return false; + } + public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate, final DateTimeFormatter formatter) { @@ -1988,24 +1776,45 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - if (savingsAccountCharge.isOnSpecifiedDueDate()) { - if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); - } else if (getSubmittedOnLocalDate() != null && transactionDate.isBefore(getSubmittedOnLocalDate())) { - baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) - .failWithCodeNoParameterAddedToErrorCode("transaction.before.submittedOnDate"); - throw new PlatformApiDataValidationException(dataValidationErrors); - } + if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate"); + throw new PlatformApiDataValidationException(dataValidationErrors); } - if (DateUtils.isInFuture(transactionDate)) { + if (DateUtils.isDateInTheFuture(transactionDate)) { baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter)) .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate"); throw new PlatformApiDataValidationException(dataValidationErrors); } + + if (savingsAccountCharge.isSavingsActivation()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "transaction.not.valid.cannot.pay.activation.time.charge.is.automated"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (savingsAccountCharge.isAnnualFee()){ + final LocalDate annualFeeDueDate = savingsAccountCharge.getDueLocalDate(); + if (annualFeeDueDate == null) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (!annualFeeDueDate.equals(transactionDate)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate(); + if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(transactionDate)) { + baseDataValidator.reset().parameter("dueDate").value(transactionDate.toString(formatter)) + .failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date"); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + // validate charge is not already paid or waived if (savingsAccountCharge.isWaived()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived"); @@ -2015,21 +1824,35 @@ public class SavingsAccount extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - final Money amountPaidMoney = Money.of(currency, amountPaid); - if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(amountPaidMoney)) { + final Money chargePaid = Money.of(currency, amountPaid); + if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(chargePaid)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access"); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - savingsAccountCharge.pay(getCurrency(), amountPaidMoney); - - handlePayChargeTransactions(savingsAccountCharge, amountPaidMoney, transactionDate); + this.payCharge(savingsAccountCharge, chargePaid, transactionDate); + + //update next annual fee due date + savingsAccountCharge.updateToNextAnnualFeeDueDate(); } + public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate) { + savingsAccountCharge.pay(getCurrency(), amountPaid); + handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate); + } + private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, final LocalDate transactionDate) { - final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, - transactionAmount); + SavingsAccountTransaction chargeTransaction = null; + + if (savingsAccountCharge.isWithdrawalFee()){ + chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount); + }else if (savingsAccountCharge.isAnnualFee()){ + chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount); + }else { + chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount); + } + handleChargeTransactions(savingsAccountCharge, chargeTransaction); } @@ -2062,7 +1885,7 @@ public class SavingsAccount extends AbstractPersistable { return charge; } - private Set getCharges() { + public Set charges() { return (this.charges == null) ? new HashSet() : this.charges; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index bc480c9bb..35e19bc3a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -6,8 +6,6 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; @@ -22,15 +20,12 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequire import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; import java.util.Set; import org.joda.time.LocalDate; -import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.service.DateUtils; @@ -50,7 +45,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -199,50 +193,18 @@ public class SavingsAccountAssembler { } else { lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); } - - BigDecimal withdrawalFeeAmount = null; - if (command.parameterExists(withdrawalFeeAmountParamName)) { - withdrawalFeeAmount = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName); - } else { - withdrawalFeeAmount = product.withdrawalFeeAmount(); - } - - SavingsWithdrawalFeesType withdrawalFeeType = null; - if (command.parameterExists(withdrawalFeeAmountParamName)) { - final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); - if (withdrawalFeeTypeValue != null) { - withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue); - } - } else { - withdrawalFeeType = product.withdrawalFeeType(); - } - boolean iswithdrawalFeeApplicableForTransfer = false; if (command.parameterExists(withdrawalFeeForTransfersParamName)) { iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); } - BigDecimal annualFeeAmount = null; - if (command.parameterExists(annualFeeAmountParamName)) { - annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); - } else { - annualFeeAmount = product.annualFeeAmount(); - } - - MonthDay monthDayOfAnnualFee = null; - if (command.parameterExists(annualFeeOnMonthDayParamName)) { - monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); - } else { - monthDayOfAnnualFee = product.monthDayOfAnnualFee(); - } final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode()); final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer, annualFeeAmount, - monthDayOfAnnualFee, charges); + lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index fbdb113fa..ca25f95d0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -9,6 +9,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountPara import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import java.math.BigDecimal; import java.math.MathContext; @@ -28,6 +29,7 @@ import javax.persistence.TemporalType; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -53,15 +55,22 @@ public class SavingsAccountCharge extends AbstractPersistable { private Integer chargeTime; @Temporal(TemporalType.DATE) - @Column(name = "due_for_collection_as_of_date") + @Column(name = "charge_due_date") private Date dueDate; + @Column(name = "fee_on_month", nullable = true) + private Integer feeOnMonth; + + @Column(name = "fee_on_day", nullable = true) + private Integer feeOnDay; + @Column(name = "charge_calculation_enum") private Integer chargeCalculation; @Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true) private BigDecimal percentage; + // TODO AA: This field may not require for savings charges @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true) private BigDecimal amountPercentageAppliedTo; @@ -89,29 +98,38 @@ public class SavingsAccountCharge extends AbstractPersistable { @Column(name = "waived", nullable = false) private boolean waived = false; + @Column(name = "is_active", nullable = false) + private boolean status = true; + public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition, final JsonCommand command) { final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); + final MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); final ChargeTimeType chargeTime = null; final ChargeCalculationType chargeCalculation = null; + final boolean status = true; - return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, + feeOnMonthDay); } // TODO AA: refactor method signature - public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amount, - final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) { - return new SavingsAccountCharge(null, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable, + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, + final MonthDay feeOnMonthDay) { + return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status, + feeOnMonthDay); } protected SavingsAccountCharge() { // } - public SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount, - final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) { + private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount, + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, + final MonthDay feeOnMonthDay) { this.savingsAccount = savingsAccount; this.charge = chargeDefinition; @@ -127,6 +145,16 @@ public class SavingsAccountCharge extends AbstractPersistable { } + if (isAnnualFee()) { + if (feeOnMonthDay == null) { + final String defaultUserMessage = "Savings Account charge is missing due date."; + throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, + defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName()); + } + this.feeOnMonth = feeOnMonthDay.getMonthOfYear(); + this.feeOnDay = feeOnMonthDay.getDayOfMonth(); + } + this.dueDate = (dueDate == null) ? null : dueDate.toDate(); this.chargeCalculation = chargeDefinition.getChargeCalculation(); @@ -139,13 +167,18 @@ public class SavingsAccountCharge extends AbstractPersistable { chargeAmount = amount; } - final BigDecimal transactionAmount = new BigDecimal(0);// percentage - // based fee is - // not yet - // supported for - // savings + final BigDecimal transactionAmount = new BigDecimal(0); + populateDerivedFields(transactionAmount, chargeAmount); + resetOutstandingAmount();// reset only if it's withdrawal or annual fee this.paid = determineIfFullyPaid(); + this.status = status; + } + + private void resetOutstandingAmount() { + if (this.isWithdrawalFee()) { + this.amountOutstanding = BigDecimal.ZERO; + } } private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) { @@ -219,6 +252,9 @@ public class SavingsAccountCharge extends AbstractPersistable { amountPaid = amountPaid.minus(transactionAmount); this.amountPaid = amountPaid.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); + if (this.isWithdrawalFee()) { + this.amountOutstanding = BigDecimal.ZERO; + } this.paid = false; } @@ -228,7 +264,7 @@ public class SavingsAccountCharge extends AbstractPersistable { this.waived = true; return getAmountWaived(currency); } - + public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) { Money amountWaived = getAmountWaived(currency); amountWaived = amountWaived.minus(transactionAmount); @@ -257,16 +293,17 @@ public class SavingsAccountCharge extends AbstractPersistable { this.savingsAccount = savingsAccount; } - public void update(final BigDecimal amount, final LocalDate dueDate) { - final BigDecimal transactionAmount = BigDecimal.ZERO;// percentage based - // fee calculation - // is not yet - // supported for - // Savings + public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay) { + final BigDecimal transactionAmount = BigDecimal.ZERO; if (dueDate != null) { this.dueDate = dueDate.toDate(); } + if (feeOnMonthDay != null) { + this.feeOnMonth = feeOnMonthDay.getMonthOfYear(); + this.feeOnDay = feeOnMonthDay.getDayOfMonth(); + } + if (amount != null) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case INVALID: @@ -313,6 +350,24 @@ public class SavingsAccountCharge extends AbstractPersistable { this.dueDate = newValue.toDate(); } + if (command.hasParameter(feeOnMonthDayParamName)) { + final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName); + final Integer dayOfMonthValue = monthDay.getDayOfMonth(); + if (this.feeOnDay != dayOfMonthValue) { + actualChanges.put(feeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.feeOnDay = dayOfMonthValue; + } + + final Integer monthOfYear = monthDay.getMonthOfYear(); + if (this.feeOnMonth != monthOfYear) { + actualChanges.put(feeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.feeOnMonth = monthOfYear; + } + } + if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName); actualChanges.put(amountParamName, newValue); @@ -322,6 +377,7 @@ public class SavingsAccountCharge extends AbstractPersistable { break; case FLAT: this.amount = newValue; + this.amountOutstanding = calculateOutstanding(); break; case PERCENT_OF_AMOUNT: this.percentage = newValue; @@ -347,10 +403,6 @@ public class SavingsAccountCharge extends AbstractPersistable { return actualChanges; } - public boolean isSpecifiedDueDate() { - return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE); - } - private boolean isGreaterThanZero(final BigDecimal value) { return value.compareTo(BigDecimal.ZERO) == 1; } @@ -405,8 +457,8 @@ public class SavingsAccountCharge extends AbstractPersistable { public BigDecimal amount() { return this.amount; } - - public BigDecimal amoutOutstanding(){ + + public BigDecimal amoutOutstanding() { return this.amountOutstanding; } @@ -452,10 +504,10 @@ public class SavingsAccountCharge extends AbstractPersistable { return Money.of(currency, this.amountWrittenOff); } - public Money getAmountOutstanding(final MonetaryCurrency currency){ + public Money getAmountOutstanding(final MonetaryCurrency currency) { return Money.of(currency, this.amountOutstanding); } - + /** * @param incrementBy * Amount used to pay off this charge @@ -498,19 +550,35 @@ public class SavingsAccountCharge extends AbstractPersistable { return this.charge; } + public SavingsAccount savingsAccount() { + return this.savingsAccount; + } + public boolean isOnSpecifiedDueDate() { return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate(); } - public SavingsAccount savingsAccount(){ - return this.savingsAccount; + public boolean isSavingsActivation() { + return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation(); } - + + public boolean isSavingsClosure() { + return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure(); + } + + public boolean isWithdrawalFee() { + return ChargeTimeType.fromInt(this.chargeTime).isWithdrawalFee(); + } + + public boolean isAnnualFee() { + return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee(); + } + public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; } return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode); } - + @Override public boolean equals(final Object obj) { if (obj == null) { return false; } @@ -533,4 +601,47 @@ public class SavingsAccountCharge extends AbstractPersistable { .append(this.amount).append(getDueLocalDate()) // .toHashCode(); } + + public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) { + BigDecimal amountPaybale = BigDecimal.ZERO; + if (ChargeCalculationType.fromInt(this.chargeCalculation).isFlat()) { + amountPaybale = this.amount; + } else if (ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount()) { + amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100l)); + } + this.amountOutstanding = amountPaybale; + return amountPaybale; + } + + public void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) { + if (isAnnualFee()) { + LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay); + if (startingDate.isAfter(nextDueLocalDate)) { + nextDueLocalDate = nextDueLocalDate.plusYears(1); + } + this.dueDate = nextDueLocalDate.toDate(); + } + } + + public void updateToNextAnnualFeeDueDate() { + if (isAnnualFee()) { + LocalDate nextDueLocalDate = new LocalDate(dueDate); + this.dueDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).plusYears(1).toDate(); + } + } + + public void updateToPreviousAnnualFeeDueDate() { + if (isAnnualFee()) { + LocalDate previousDueLocalDate = new LocalDate(dueDate); + this.dueDate = previousDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).minusYears(1).toDate(); + } + } + + public boolean feeSettingsNotSet() { + return !feeSettingsSet(); + } + + public boolean feeSettingsSet() { + return this.feeOnDay != null && this.feeOnMonth != null; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java index 2fa0b7b85..199520f89 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java @@ -12,6 +12,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdPa import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; import java.math.BigDecimal; @@ -22,6 +23,7 @@ import java.util.Locale; import java.util.Set; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; @@ -61,6 +63,7 @@ public class SavingsAccountChargeAssembler { if (element.isJsonObject()) { final JsonObject topLevelJsonElement = element.getAsJsonObject(); final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement); final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) { final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray(); @@ -78,6 +81,8 @@ public class SavingsAccountChargeAssembler { final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement, dateFormat, locale); + final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale); + if (id == null) { final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); @@ -95,8 +100,10 @@ public class SavingsAccountChargeAssembler { if (chargeCalculationType != null) { ChargeCalculationType.fromInt(chargeCalculationType); } + + final boolean status = true; final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount( - chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay); savingsAccountCharges.add(savingsAccountCharge); } else { final Long savingsAccountChargeId = id; @@ -104,7 +111,7 @@ public class SavingsAccountChargeAssembler { .findOne(savingsAccountChargeId); if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } - savingsAccountCharge.update(amount, dueDate); + savingsAccountCharge.update(amount, dueDate, feeOnMonthDay); savingsAccountCharges.add(savingsAccountCharge); } @@ -120,11 +127,27 @@ public class SavingsAccountChargeAssembler { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); - for (SavingsAccountCharge savingsAccountCharge : charges) { - if(!savingsAccountCharge.hasCurrencyCodeOf(productCurrencyCode)){ - baseDataValidator.reset().parameter("currency").value(savingsAccountCharge.getCharge().getId()) + boolean isOneWithdrawalPresent = false; + boolean isOneAnnualPresent = false; + for (SavingsAccountCharge charge : charges) { + if(!charge.hasCurrencyCodeOf(productCurrencyCode)){ + baseDataValidator.reset().parameter("currency").value(charge.getCharge().getId()) .failWithCodeNoParameterAddedToErrorCode("currency.and.charge.currency.not.same"); } + + if (charge.isWithdrawalFee()) { + if (isOneWithdrawalPresent) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.withdrawal.fee.per.account.not.supported"); + } + isOneWithdrawalPresent = true; + } + + if (charge.isAnnualFee()) { + if (isOneAnnualPresent) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.annual.fee.per.account.not.supported"); + } + isOneAnnualPresent = true; + } } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index a4865e2b2..77765cc0e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -116,7 +116,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { amount, isReversed); } - public static SavingsAccountTransaction fee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, @@ -211,7 +211,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean isPostInterestCalculationRequired(){ - return this.isDeposit() || this.isWithdrawal() || this.isCharge(); + return this.isDeposit() || this.isWithdrawal() || this.isPayCharge(); } public boolean isInterestPostingAndNotReversed() { @@ -221,6 +221,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public boolean isWithdrawalFeeAndNotReversed() { return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee() && isNotReversed(); } + + public boolean isWithdrawalFee() { + return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee(); + } public boolean isAnnualFeeAndNotReversed() { return isAnnualFee() && isNotReversed(); @@ -456,11 +460,15 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean isDebit() { - return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isCharge(); + return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isPayCharge(); } - public boolean isCharge() { - return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge(); + public boolean isPayCharge() { + return SavingsAccountTransactionType.fromInt(this.typeOf).isPayCharge(); + } + + public boolean isChargeTransaction(){ + return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction(); } public boolean isWaiveCharge(){ @@ -469,12 +477,12 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public boolean isFeeCharge() { final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); - return (isCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; + return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false; } public boolean isPenaltyCharge() { final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy(); - return (isCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; + return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false; } public boolean isFeeChargeAndNotReversed() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index 6aff92596..d45cde179 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -7,8 +7,6 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -24,9 +22,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; import java.util.ArrayList; @@ -44,7 +40,6 @@ import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; -import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -58,7 +53,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.data.jpa.domain.AbstractPersistable; import com.google.gson.JsonArray; @@ -123,24 +117,9 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "accounting_type", nullable = false) private Integer accountingRule; - @Column(name = "withdrawal_fee_amount", scale = 6, precision = 19, nullable = true) - private BigDecimal withdrawalFeeAmount; - - @Column(name = "withdrawal_fee_type_enum", nullable = true) - private Integer withdrawalFeeType; - @Column(name = "withdrawal_fee_for_transfer") private boolean withdrawalFeeApplicableForTransfer; - @Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true) - private BigDecimal annualFeeAmount; - - @Column(name = "annual_fee_on_month", nullable = true) - private Integer annualFeeOnMonth; - - @Column(name = "annual_fee_on_day", nullable = true) - private Integer annualFeeOnDay; - @ManyToMany @JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) private Set charges; @@ -150,14 +129,11 @@ public class SavingsProduct extends AbstractPersistable { final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final AccountingRuleType accountingRuleType, final Set charges) { + final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges) { return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, - annualFeeOnMonthDay, accountingRuleType, charges); + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges); } protected SavingsProduct() { @@ -170,9 +146,7 @@ public class SavingsProduct extends AbstractPersistable { final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, - final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final AccountingRuleType accountingRuleType, final Set charges) { + final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges) { this.name = name; this.description = description; @@ -192,17 +166,13 @@ public class SavingsProduct extends AbstractPersistable { if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) { this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue(); } - - this.withdrawalFeeAmount = withdrawalFeeAmount; - if (withdrawalFeeAmount != null && withdrawalFeeType != null) { - this.withdrawalFeeType = withdrawalFeeType.getValue(); - } + this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer; - this.annualFeeAmount = annualFeeAmount; + /* this.annualFeeAmount = annualFeeAmount; if (annualFeeAmount != null && annualFeeOnMonthDay != null) { this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear(); this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); - } + }*/ if (accountingRuleType != null) { this.accountingRule = accountingRuleType.getValue(); @@ -213,8 +183,8 @@ public class SavingsProduct extends AbstractPersistable { } validateLockinDetails(); - validateWithdrawalFeeDetails(); - validateAnnualFeeDetails(); + //validateWithdrawalFeeDetails(); + //validateAnnualFeeDetails(); } public MonetaryCurrency currency() { @@ -257,30 +227,6 @@ public class SavingsProduct extends AbstractPersistable { return type; } - public BigDecimal withdrawalFeeAmount() { - return this.withdrawalFeeAmount; - } - - public SavingsWithdrawalFeesType withdrawalFeeType() { - SavingsWithdrawalFeesType type = null; - if (this.withdrawalFeeType != null) { - type = SavingsWithdrawalFeesType.fromInt(this.withdrawalFeeType); - } - return type; - } - - public BigDecimal annualFeeAmount() { - return this.annualFeeAmount; - } - - public MonthDay monthDayOfAnnualFee() { - MonthDay monthDay = null; - if (this.annualFeeOnMonth != null && this.annualFeeOnDay != null) { - monthDay = new MonthDay(this.annualFeeOnMonth, this.annualFeeOnDay); - } - return monthDay; - } - public Map update(final JsonCommand command) { final Map actualChanges = new LinkedHashMap(10); @@ -382,7 +328,7 @@ public class SavingsProduct extends AbstractPersistable { if (this.lockinPeriodFrequency == null) { this.lockinPeriodFrequencyType = null; } - +/* if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); actualChanges.put(withdrawalFeeAmountParamName, newValue); @@ -395,7 +341,7 @@ public class SavingsProduct extends AbstractPersistable { actualChanges.put(withdrawalFeeTypeParamName, newValue); this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue; } - +*/ if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) { final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); actualChanges.put(withdrawalFeeForTransfersParamName, newValue); @@ -403,40 +349,40 @@ public class SavingsProduct extends AbstractPersistable { } // set period type to null if frequency is null - if (this.withdrawalFeeAmount == null) { - this.withdrawalFeeType = null; - } +// if (this.withdrawalFeeAmount == null) { +// this.withdrawalFeeType = null; +// } - if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); - actualChanges.put(annualFeeAmountParamName, newValue); +/* if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(feeAmountParamName, this.annualFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(feeAmountParamName); + actualChanges.put(feeAmountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.annualFeeAmount = newValue; } - if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { - final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); - final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(feeOnMonthDayParamName, this.annualFeeOnDay)) { + final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName); final Integer newValue = monthDay != null ? monthDay.getDayOfMonth() : null; - actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(feeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnDay = newValue; } - if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { - final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); - final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(feeOnMonthDayParamName, this.annualFeeOnMonth)) { + final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName); final Integer newValue = monthDay != null ? monthDay.getMonthOfYear() : null; - actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(feeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnMonth = newValue; - } + }*/ - // set period type to null if frequency is null - if (this.annualFeeAmount == null) { - this.annualFeeOnDay = null; - this.annualFeeOnMonth = null; - } +// // set period type to null if frequency is null +// if (this.annualFeeAmount == null) { +// this.annualFeeOnDay = null; +// this.annualFeeOnMonth = null; +// } if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) { final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName); @@ -453,13 +399,13 @@ public class SavingsProduct extends AbstractPersistable { } validateLockinDetails(); - validateWithdrawalFeeDetails(); - validateAnnualFeeDetails(); + //validateWithdrawalFeeDetails(); + //validateAnnualFeeDetails(); return actualChanges; } - private void validateAnnualFeeDetails() { +/* private void validateAnnualFeeDetails() { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -468,21 +414,21 @@ public class SavingsProduct extends AbstractPersistable { if (this.annualFeeAmount == null) { if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) { - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull(); + baseDataValidator.reset().parameter(feeAmountParamName).value(this.annualFeeAmount).notNull(); } } else { if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) { - baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull(); + baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull(); } - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount(); + baseDataValidator.reset().parameter(feeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount(); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - - private void validateWithdrawalFeeDetails() { +*/ +/* private void validateWithdrawalFeeDetails() { final List dataValidationErrors = new ArrayList(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -503,7 +449,7 @@ public class SavingsProduct extends AbstractPersistable { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - +*/ private void validateLockinDetails() { final List dataValidationErrors = new ArrayList(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index efaf39812..6f7f3bb8d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -5,8 +5,6 @@ */ package org.mifosplatform.portfolio.savings.domain; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -22,15 +20,12 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; import java.util.HashSet; import java.util.Set; -import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -43,7 +38,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -107,20 +101,11 @@ public class SavingsProductAssembler { lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); } - final BigDecimal withdrawalFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); - SavingsWithdrawalFeesType withdrawalFeeType = null; - final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); - if (withdrawalFeeTypeValue != null) { - withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue); - } - boolean iswithdrawalFeeApplicableForTransfer = false; if (command.parameterExists(withdrawalFeeForTransfersParamName)) { iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); } - final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); - final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); // Savings product charges @@ -128,8 +113,7 @@ public class SavingsProductAssembler { return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, - iswithdrawalFeeApplicableForTransfer, annualFeeAmount, monthDayOfAnnualFee, accountingRuleType, charges); + lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges); } public Set assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java index a23fc8caa..a29640cc5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductChargeAssembler.java @@ -11,6 +11,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdPa import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; import java.math.BigDecimal; @@ -19,6 +20,7 @@ import java.util.Locale; import java.util.Set; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; @@ -70,7 +72,8 @@ public class SavingsProductChargeAssembler { savingsChargeElement, locale); final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement, dateFormat, locale); - + final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement); + if (id == null) { final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); final ChargeTimeType chargeTime = null; @@ -82,7 +85,7 @@ public class SavingsProductChargeAssembler { ChargeCalculationType.fromInt(chargeCalculationType); } final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount( - chargeDefinition, amount, chargeTime, chargeCalculation, dueDate); + chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay); savingsAccountCharges.add(savingsAccountCharge); } else { final Long savingsAccountChargeId = id; @@ -90,7 +93,7 @@ public class SavingsProductChargeAssembler { .findOne(savingsAccountChargeId); if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } - savingsAccountCharge.update(amount, dueDate); + savingsAccountCharge.update(amount, dueDate, null); savingsAccountCharges.add(savingsAccountCharge); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java index 1e0cf8ee6..1ffb43227 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ApplyAnnualFeeSavingsAccountCommandHandler.java @@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; @Service public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSourceHandler { + @SuppressWarnings("unused") private final SavingsAccountWritePlatformService writePlatformService; @Autowired @@ -28,8 +29,10 @@ public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSou @Override public CommandProcessingResult processCommand(final JsonCommand command) { + @SuppressWarnings("unused") final LocalDate annualFeeTransactionDate = command.localDateValueOfParameterNamed("annualFeeTransactionDate"); - return this.writePlatformService.applyAnnualFee(command.getSavingsId(), annualFeeTransactionDate); + //return this.writePlatformService.applyAnnualFee(command.getSavingsId(), annualFeeTransactionDate); + return null; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index adf756355..845cfe3be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.List; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; @@ -55,7 +56,9 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou + "sc.calculation_percentage as percentageOf, sc.calculation_on_amount as amountPercentageAppliedTo, " + "sc.charge_time_enum as chargeTime, " + "sc.is_penalty as penalty, " - + "sc.due_for_collection_as_of_date as dueAsOfDate, " + + "sc.charge_due_date as dueAsOfDate, " + + "sc.fee_on_month as feeOnMonth, " + + "sc.fee_on_day as feeOnDay, " + "sc.charge_calculation_enum as chargeCalculation, " + "c.currency_code as currencyCode, oc.name as currencyName, " + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " @@ -93,7 +96,13 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); - + MonthDay feeOnMonthDay = null; + final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth"); + final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay"); + if (feeOnDay != null) { + feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay); + } + final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); final boolean penalty = rs.getBoolean("penalty"); @@ -102,7 +111,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou return SavingsAccountChargeData.instance(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, - chargeOptions, penalty); + chargeOptions, penalty, feeOnMonthDay); } } @@ -148,7 +157,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper(); final String sql = "select " + rm.schema() + " where sc.savings_account_id=? " - + " order by sc.charge_time_enum ASC, sc.due_for_collection_as_of_date ASC, sc.is_penalty ASC"; + + " order by sc.charge_time_enum ASC, sc.charge_due_date ASC, sc.is_penalty ASC"; return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java index f09125c94..a05275a49 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java @@ -9,7 +9,6 @@ import java.util.Collection; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.portfolio.group.service.SearchParameters; -import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; @@ -27,7 +26,7 @@ public interface SavingsAccountReadPlatformService { Collection retrieveAllTransactions(Long savingsId); - Collection retrieveAccountsWithAnnualFeeDue(); + //Collection retrieveAccountsWithAnnualFeeDue(); SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 62cef05c7..8fba2e734 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -14,7 +14,6 @@ import java.util.Collection; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; -import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; @@ -40,7 +39,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; -import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData; import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData; import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; @@ -74,7 +72,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; private final SavingsAccountTransactionsMapper transactionsMapper; private final SavingAccountMapper savingAccountMapper; - private final SavingsAccountAnnualFeeMapper annualFeeMapper; + //private final SavingsAccountAnnualFeeMapper annualFeeMapper; // pagination private final PaginationHelper paginationHelper = new PaginationHelper(); @@ -95,7 +93,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); this.transactionsMapper = new SavingsAccountTransactionsMapper(); this.savingAccountMapper = new SavingAccountMapper(); - this.annualFeeMapper = new SavingsAccountAnnualFeeMapper(); + //this.annualFeeMapper = new SavingsAccountAnnualFeeMapper(); this.chargeReadPlatformService = chargeReadPlatformService; } @@ -222,13 +220,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,"); - sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); +// sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,"); +// sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,"); - sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, "); - sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, "); - sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, "); +// sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,"); +// sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, "); +// sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, "); +// sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, "); sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); @@ -351,17 +349,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); } - final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); + /*final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) { withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); - } + }*/ final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); - final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); + /*final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); @@ -371,7 +369,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead } final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate"); - +*/ final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); @@ -390,8 +388,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary); } } @@ -554,13 +551,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, new Object[] { savingsId, transactionId }); } - @Override + /*@Override public Collection retrieveAccountsWithAnnualFeeDue() { final String sql = "select " + this.annualFeeMapper.schema() + " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()"; return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {}); - } + }*/ private static final class SavingsAccountTransactionsMapper implements RowMapper { @@ -732,12 +729,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, "); sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); - sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); - sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); - sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); - sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); +// sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); +// sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); + sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers "); +// sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); +// sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); +// sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); @@ -789,24 +786,24 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); } - final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); +// final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); - EnumOptionData withdrawalFeeType = null; +/* EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) { withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); } - +*/ final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); - final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); + //final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); - MonthDay annualFeeOnMonthDay = null; + /*MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); if (annualFeeAmount != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay); - } + }*/ Long clientId = null; String clientName = null; @@ -825,7 +822,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Long fieldOfficerId = null; final String fieldOfficerName = null; final SavingsAccountStatusEnumData status = null; - final LocalDate annualFeeNextDueDate = null; +// final LocalDate annualFeeNextDueDate = null; final SavingsAccountSummaryData summary = null; final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); @@ -833,12 +830,11 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return SavingsAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary); } } - private static final class SavingsAccountAnnualFeeMapper implements RowMapper { + /*private static final class SavingsAccountAnnualFeeMapper implements RowMapper { private final String schemaSql; @@ -864,5 +860,5 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return SavingsAccountAnnualFeeData.instance(id, accountNo, annualFeeNextDueDate); } - } + }*/ } \ 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 7d02115aa..03c6ce906 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 @@ -20,7 +20,7 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult withdrawal(Long savingsId, JsonCommand command); - CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate); + /*CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate);*/ CommandProcessingResult calculateInterest(Long savingsId); 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 f01ef4950..288612d2d 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 @@ -57,6 +57,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.SavingsAccountTransactionType; import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeDataValidator; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO; @@ -70,7 +71,6 @@ 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.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountClosingNotAllowedException; import org.mifosplatform.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; import org.mifosplatform.portfolio.savings.exception.TransactionUpdateNotAllowedException; @@ -152,6 +152,18 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Map changes = account.activate(user, command, DateUtils.getLocalDateOfTenant()); if (!changes.isEmpty()) { + + final MathContext mc = MathContext.DECIMAL64; + if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(mc, today); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(mc, today); + } + + account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name()); + this.savingAccountRepository.save(account); } @@ -236,7 +248,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .build(); } - @Transactional + /*@Transactional @Override public CommandProcessingResult applyAnnualFee(final Long savingsId, final LocalDate annualFeeTransactionDate) { @@ -265,7 +277,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withGroupId(account.groupId()) // .withSavingsId(savingsId) // .build(); - } + }*/ @Transactional @Override @@ -356,6 +368,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction); } account.undoTransaction(transactionId); + + // undoing transaction is withdrawal then undo withdrawal fee + // transaction if any + if (savingsAccountTransaction.isWithdrawal()) { + final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository + .findOneByIdAndSavingsAccountId(transactionId + 1, savingsId); + if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) { + account.undoTransaction(transactionId + 1); + } + } + checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { @@ -604,7 +627,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(savingsAccount); final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final String format = command.dateFormat(); + final DateTimeFormatter fmt = StringUtils.isNotBlank(format) ? DateTimeFormat.forPattern(format).withLocale(locale) : DateTimeFormat.forPattern("dd MM yyyy"); final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); @@ -810,19 +834,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi // always use current date as transaction date for batch job final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); final List pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate()); - // final List paidCharges = new - // ArrayList(pendingCharges.size()); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); for (SavingsAccountCharge savingsAccountCharge : pendingCharges) { try { - payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); - } catch (InsufficientAccountBalanceException isab) { + payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt); + } catch (Exception exception) { // TODO: AA - provide a meaningful error with failed // transactions list } } - - // this.savingsAccountChargeRepository.save(paidCharges); } @Transactional @@ -845,7 +865,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi account.calculateInterestUsing(mc, today); } - account.validateAccountBalanceDoesNotBecomeNegative(amountPaid); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name()); this.savingAccountRepository.save(account); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 9111e6b03..c15fc8ce0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -10,7 +10,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; -import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; @@ -87,12 +86,12 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, "); sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); - sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); +// sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); +// sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); - sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); - sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay, "); +// sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); +// sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); +// sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay, "); sqlBuilder.append("sp.accounting_type as accountingType "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); @@ -150,17 +149,17 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue); } - final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); + /*final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount"); EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) { withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); } - +*/ final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); - final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); +/* final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); @@ -168,11 +167,10 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead if (annualFeeAmount != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay); } - +*/ return SavingsProductData.instance(id, name, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers, - annualFeeAmount, annualFeeOnMonthDay, accountingRuleType); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRuleType); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java index c71632ddb..4359425b2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java @@ -5,16 +5,10 @@ */ package org.mifosplatform.scheduledjobs.service; -import java.util.Collection; -import java.util.List; - -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.service.RoutingDataSourceServiceFactory; import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.service.JobName; -import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData; import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService; import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService; import org.slf4j.Logger; @@ -30,7 +24,9 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class); private final RoutingDataSourceServiceFactory dataSourceServiceFactory; + @SuppressWarnings("unused") private final SavingsAccountWritePlatformService savingsAccountWritePlatformService; + @SuppressWarnings("unused") private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; @Autowired @@ -203,7 +199,7 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService @CronTarget(jobName = JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS) public void applyAnnualFeeForSavings() { - final Collection annualFeeData = this.savingsAccountReadPlatformService + /*final Collection annualFeeData = this.savingsAccountReadPlatformService .retrieveAccountsWithAnnualFeeDue(); for (final SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) { @@ -219,6 +215,6 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService } } - logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size()); + logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());*/ } } \ No newline at end of file From be24d60b9ee079d95b635f70b7be5bc413f66819 Mon Sep 17 00:00:00 2001 From: mifoscontributer Date: Thu, 3 Oct 2013 14:48:10 +0530 Subject: [PATCH 14/15] mifosx-717 fix register/deregister datatable for constraint based approach --- .../core/data/DataValidatorBuilder.java | 21 ++++++++ ...tatableCommandFromApiJsonDeserializer.java | 8 +-- .../ReadWriteNonCoreDataServiceImpl.java | 54 ++++++++++--------- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 4768c1fe3..d8bf38f0e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -241,6 +241,27 @@ public class DataValidatorBuilder { return this; } + public DataValidatorBuilder isOneOfTheseStringValues(final Object... values) { + if (this.value == null && this.ignoreNullValue) { return this; } + + final List valuesList = Arrays.asList(values); + final String valuesListStr = StringUtils.join(valuesList, ", "); + + if (this.value == null || !valuesList.contains(this.value.toString().toLowerCase())) { + final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") + .append(this.parameter).append(".is.not.one.of.expected.enumerations"); + final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter) + .append(" must be one of [ ").append(valuesListStr).append(" ] ").append("."); + + final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), this.parameter, this.value, values); + + this.dataValidationErrors.add(error); + } + + return this; + } + public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) { if (this.value == null && this.ignoreNullValue) { return this; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index 510222fdd..1d43f2dfc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -45,7 +45,7 @@ public class DatatableCommandFromApiJsonDeserializer { private final Set supportedParametersForChangeColumns = new HashSet(Arrays.asList("name", "newName", "length", "mandatory", "after", "code", "newCode")); private final Set supportedParametersForDropColumns = new HashSet(Arrays.asList("name")); - private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" }; + private final Object[] supportedColumnTypes = { "string", "number", "decimal", "date", "text", "dropdown" }; private final Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_center", "m_office", "m_savings_product", "m_product_loan" }; @@ -58,9 +58,9 @@ public class DatatableCommandFromApiJsonDeserializer { private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) { final String type = this.fromApiJsonHelper.extractStringNamed("type", column); - baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(this.supportedColumnTypes); + baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseStringValues(this.supportedColumnTypes); - if (type != null && type.equals("String")) { + if (type != null && type.equalsIgnoreCase("String")) { if (this.fromApiJsonHelper.parameterExists("length", column)) { final String lengthStr = this.fromApiJsonHelper.extractStringNamed("length", column); if (lengthStr != null && !StringUtils.isWhitespace(lengthStr) && StringUtils.isNumeric(lengthStr) @@ -80,7 +80,7 @@ public class DatatableCommandFromApiJsonDeserializer { } final String code = this.fromApiJsonHelper.extractStringNamed("code", column); - if (type != null && type.equals("Dropdown")) { + if (type != null && type.equalsIgnoreCase("Dropdown")) { if (code != null) { baseDataValidator.reset().parameter("code").value(code).notBlank().matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); } else { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index ff54ad7c3..9e5b41a70 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -71,12 +71,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private final static HashMap apiTypeToMySQL = new HashMap() { { - put("String", "VARCHAR"); - put("Number", "INT"); - put("Decimal", "DECIMAL"); - put("Date", "DATE"); - put("Text", "TEXT"); - put("Dropdown", "INT"); + put("string", "VARCHAR"); + put("number", "INT"); + put("decimal", "DECIMAL"); + put("date", "DATE"); + put("text", "TEXT"); + put("dropdown", "INT"); } }; @@ -265,15 +265,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'"; - String[] sqlArray = null; - if (this.configurationDomainService.isConstraintApproachEnabledForDatatables()) { - final String deleteColumnCodeSql = "delete from x_table_cloumn_code_mappings where column_alias_name like'" - + datatable.toLowerCase().replaceAll("\\s", "_") + "%'"; - sqlArray = new String[4]; - sqlArray[3] = deleteColumnCodeSql; - } else { - sqlArray = new String[3]; - } + String[] sqlArray = new String[3]; sqlArray[0] = deleteRolePermissionsSql; sqlArray[1] = deletePermissionsSql; sqlArray[2] = deleteRegisteredDatatableSql; @@ -362,7 +354,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final boolean isConstraintApproach) { String name = (column.has("name")) ? column.get("name").getAsString() : null; - final String type = (column.has("type")) ? column.get("type").getAsString() : null; + final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null; final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; final String code = (column.has("code")) ? column.get("code").getAsString() : null; @@ -381,11 +373,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType); if (type != null) { - if (type.equals("String")) { + if (type.equalsIgnoreCase("String")) { sqlBuilder = sqlBuilder.append("(" + length + ")"); - } else if (type.equals("Decimal")) { + } else if (type.equalsIgnoreCase("Decimal")) { sqlBuilder = sqlBuilder.append("(19,6)"); - } else if (type.equals("Dropdown")) { + } else if (type.equalsIgnoreCase("Dropdown")) { sqlBuilder = sqlBuilder.append("(11)"); } } @@ -526,14 +518,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } else { if (code != null) { removeMappings.add(dataTableNameAlias + "_" + name); - if (newCode == null) { + if (newCode == null || !StringUtils.equalsIgnoreCase(name, newName)) { constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name) .append("` "); } } if (newCode != null) { codeMappings.put(dataTableNameAlias + "_" + newName, this.codeReadPlatformService.retriveCode(newCode).getCodeId()); - if (code == null) { + if (code == null || !StringUtils.equalsIgnoreCase(name, newName)) { constrainBuilder.append(",ADD CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(newName) .append("` ").append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `") .append(CODE_VALUES_TABLE).append("` (`id`)"); @@ -596,7 +588,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final StringBuilder constrainBuilder, final Map codeMappings, final boolean isConstraintApproach) { String name = (column.has("name")) ? column.get("name").getAsString() : null; - final String type = (column.has("type")) ? column.get("type").getAsString() : null; + final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null; final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; final String after = (column.has("after")) ? column.get("after").getAsString() : null; @@ -616,11 +608,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType); if (type != null) { - if (type.equals("String") && length != null) { + if (type.equalsIgnoreCase("String") && length != null) { sqlBuilder = sqlBuilder.append("(" + length + ")"); - } else if (type.equals("Decimal")) { + } else if (type.equalsIgnoreCase("Decimal")) { sqlBuilder = sqlBuilder.append("(19,6)"); - } else if (type.equals("Dropdown")) { + } else if (type.equalsIgnoreCase("Dropdown")) { sqlBuilder = sqlBuilder.append("(11)"); } } @@ -828,8 +820,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (!isRegisteredDataTable(datatableName)) { throw new DatatableNotFoundException(datatableName); } validateDatatableName(datatableName); deregisterDatatable(datatableName); + String[] sqlArray = null; + if (this.configurationDomainService.isConstraintApproachEnabledForDatatables()) { + final String deleteColumnCodeSql = "delete from x_table_cloumn_code_mappings where column_alias_name like'" + + datatableName.toLowerCase().replaceAll("\\s", "_") + "_%'"; + sqlArray = new String[2]; + sqlArray[1] = deleteColumnCodeSql; + }else{ + sqlArray = new String[1]; + } final String sql = "DROP TABLE `" + datatableName + "`"; - this.jdbcTemplate.execute(sql); + sqlArray[0] = sql; + this.jdbcTemplate.batchUpdate(sqlArray); } catch (final SQLGrammarException e) { final Throwable realCause = e.getCause(); final List dataValidationErrors = new ArrayList(); From c02fba1efef51617aa8e250e74e189343b78cfdd Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Thu, 3 Oct 2013 15:57:13 +0530 Subject: [PATCH 15/15] handle overpayments and refunds in accounting --- ...yments_seperately_in_loan_transactions.sql | 16 ++++++++ .../common/AccountingConstants.java | 16 ++++---- ...untingDropdownReadPlatformServiceImpl.java | 7 ++++ .../journalentry/data/LoanTransactionDTO.java | 10 ++++- .../service/AccountingProcessorHelper.java | 16 +++++--- .../CashBasedAccountingProcessorForLoan.java | 41 +++++++++++++++++++ ...ctToGLAccountMappingNotFoundException.java | 2 +- ...AccountMappingFromApiJsonDeserializer.java | 5 +++ .../LoanProductToGLAccountMappingHelper.java | 31 ++++++++++---- ...AccountMappingReadPlatformServiceImpl.java | 4 ++ ...ccountMappingWritePlatformServiceImpl.java | 13 ++++-- .../loanaccount/data/LoanTransactionData.java | 25 ++++++----- .../loanaccount/domain/LoanTransaction.java | 32 +++++++++++---- ...RepaymentScheduleTransactionProcessor.java | 1 + .../service/LoanReadPlatformServiceImpl.java | 12 +++--- .../LoanProductDataValidator.java | 14 ++++++- 16 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql diff --git a/mifosng-db/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql b/mifosng-db/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql new file mode 100644 index 000000000..81ca984df --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_8__track_overpayments_seperately_in_loan_transactions.sql @@ -0,0 +1,16 @@ +/***Set defaults for income from Penalties for existing Saving Products**/ +ALTER TABLE `m_loan_transaction` + ADD COLUMN `overpayment_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_portion_derived`; + +/**Add dummy liability account if organization already has a loan product with accounting enabled**/ +INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`, `classification_enum`,`description`) +select 'Loan Overpayments (Temp)', '.', '22000-Temp', 2,'Temporary account to track Loan overpayments Liabilities' +FROM m_product_loan WHERE accounting_type != 1 +limit 1; + + +/**Map a liability account for every loan which has accounting enabled**/ +INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`financial_account_type`) +select (select id from acc_gl_account where classification_enum=2 and account_usage=1 LIMIT 1), mapping.product_id, mapping.product_type, 11 +from acc_product_mapping mapping +where mapping.financial_account_type = 2 and mapping.product_type=1; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java index 92604063b..c2b2e6f9e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java @@ -13,7 +13,7 @@ public class AccountingConstants { /*** Accounting placeholders for cash based accounting for loan products ***/ public static enum CASH_ACCOUNTS_FOR_LOAN { FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), TRANSFERS_SUSPENSE( - 10); + 10), OVERPAYMENT(11); private final Integer value; @@ -46,7 +46,7 @@ public class AccountingConstants { /*** Accounting placeholders for accrual based accounting for loan products ***/ public static enum ACCRUAL_ACCOUNTS_FOR_LOAN { FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE( - 7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10); + 7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11); private final Integer value; @@ -83,12 +83,12 @@ public class AccountingConstants { ***/ public static enum LOAN_PRODUCT_ACCOUNTING_PARAMS { FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES( - "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE( - "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( - "receivablePenaltyAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( - "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING( - "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( - "incomeAccountId"); + "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), OVERPAYMENT( + "overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE("receivableInterestAccountId"), FEES_RECEIVABLE( + "receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( + "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( + "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java index 98306ebb8..a91d1188a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java @@ -69,6 +69,13 @@ public class AccountingDropdownReadPlatformServiceImpl implements AccountingDrop expenseAccountOptions = null; } accountOptions.put("expenseAccountOptions", expenseAccountOptions); + + List liabilityAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY); + if (liabilityAccountOptions.isEmpty()) { + liabilityAccountOptions = null; + } + accountOptions.put("liabilityAccountOptions", liabilityAccountOptions); return accountOptions; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java index 61679d171..73ea17ecf 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java @@ -27,6 +27,7 @@ public class LoanTransactionDTO { private final BigDecimal interest; private final BigDecimal fees; private final BigDecimal penalties; + private final BigDecimal overPayment; /*** Boolean values determines if the transaction is reversed ***/ private final boolean reversed; @@ -37,8 +38,8 @@ public class LoanTransactionDTO { public LoanTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate, final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest, - final BigDecimal fees, final BigDecimal penalties, final boolean reversed, final List feePayments, - final List penaltyPayments) { + final BigDecimal fees, final BigDecimal penalties, final BigDecimal overPayment, final boolean reversed, + final List feePayments, final List penaltyPayments) { this.paymentTypeId = paymentTypeId; this.transactionId = transactionId; this.transactionDate = transactionDate; @@ -51,6 +52,7 @@ public class LoanTransactionDTO { this.transactionType = transactionType; this.feePayments = feePayments; this.penaltyPayments = penaltyPayments; + this.overPayment = overPayment; this.officeId = officeId; } @@ -90,6 +92,10 @@ public class LoanTransactionDTO { return this.penalties; } + public BigDecimal getOverPayment() { + return this.overPayment; + } + public boolean isReversed() { return this.reversed; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java index 07f62a68c..3abf9f41f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java @@ -32,6 +32,7 @@ import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidEx import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.office.domain.Office; @@ -81,11 +82,12 @@ public class AccountingProcessorHelper { final BigDecimal interest = (BigDecimal) map.get("interestPortion"); final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion"); final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion"); + final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion"); final boolean reversed = (Boolean) map.get("reversed"); final Long paymentTypeId = (Long) map.get("paymentTypeId"); - final List feePayments = new ArrayList(); - final List penaltyPayments = new ArrayList(); + final List feePaymentDetails = new ArrayList(); + final List penaltyPaymentDetails = new ArrayList(); // extract charge payment details (if exists) if (map.containsKey("loanChargesPaid")) { @SuppressWarnings("unchecked") @@ -97,15 +99,16 @@ public class AccountingProcessorHelper { final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); if (isPenalty) { - penaltyPayments.add(chargePaymentDTO); + penaltyPaymentDetails.add(chargePaymentDTO); } else { - feePayments.add(chargePaymentDTO); + feePaymentDetails.add(chargePaymentDTO); } } } final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, - transactionDate, transactionType, amount, principal, interest, fees, penalties, reversed, feePayments, penaltyPayments); + transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed, + feePaymentDetails, penaltyPaymentDetails); newLoanTransactions.add(transaction); @@ -513,6 +516,9 @@ public class AccountingProcessorHelper { } } + if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString()); } + return accountMapping.getGlAccount(); } 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 66da5ee67..138f8cbbd 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 @@ -55,6 +55,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF || loanTransactionDTO.getTransactionType().isChargePayment()) { createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office); } + + /** Logic for Refunds of Overpayments **/ + else if (loanTransactionDTO.getTransactionType().isRefund()) { + createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office); + } /*** * Only principal write off affects cash based accounting (interest * and fee write off need not be considered). Debit losses written @@ -103,6 +108,35 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF isReversal); } + /** + * Debit loan Portfolio and credit Fund source for a Disbursement
+ * + * All debits are turned into credits and vice versa in case of disbursement + * reversals + * + * + * @param loanDTO + * @param loanTransactionDTO + * @param office + */ + private void createJournalEntriesForRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { + // loan properties + final Long loanProductId = loanDTO.getLoanProductId(); + final Long loanId = loanDTO.getLoanId(); + final String currencyCode = loanDTO.getCurrencyCode(); + + // transaction properties + final String transactionId = loanTransactionDTO.getTransactionId(); + final Date transactionDate = loanTransactionDTO.getTransactionDate(); + final BigDecimal refundAmount = loanTransactionDTO.getAmount(); + final boolean isReversal = loanTransactionDTO.isReversed(); + final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); + + this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, refundAmount, + isReversal); + } + /** * Create a single Debit to fund source and multiple credits if applicable * (loan portfolio for principal repayments, Interest on loans for interest @@ -125,6 +159,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF final BigDecimal interestAmount = loanTransactionDTO.getInterest(); final BigDecimal feesAmount = loanTransactionDTO.getFees(); final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); + final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); @@ -156,6 +191,12 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments()); } + if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(overPaymentAmount); + this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId, + paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal); + } + /*** create a single debit entry (or reversal) for the entire amount **/ this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java index 5a4d41207..330d0077e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/exception/ProductToGLAccountMappingNotFoundException.java @@ -15,7 +15,7 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException { public ProductToGLAccountMappingNotFoundException(final PortfolioProductType type, final Long productId, final String accountType) { - super("error.msg.productTyAccountMapping.not.found", "Mapping for product of type" + type.toString() + " with Id " + productId + super("error.msg.productToAccountMapping.not.found", "Mapping for product of type " + type.toString() + " with Id " + productId + " does not exist for an account of type " + accountType, type.toString(), productId, accountType); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 978597db2..7aa16db63 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -107,6 +107,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .notNull().integerGreaterThanZero(); + final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId) + .notNull().integerGreaterThanZero(); + final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue()) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java index 74de8f839..9f72f2fbd 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -52,6 +52,11 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.LOAN); } + public void saveLoanToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.LOAN); + } + /*** Set of abstractions for merging Savings Products to GL Account Mappings ***/ public void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, final int accountTypeId, final String accountTypeName, final Map changes) { @@ -71,6 +76,12 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi PortfolioProductType.LOAN); } + public void mergeLoanToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, + GLAccountType.LIABILITY, PortfolioProductType.LOAN); + } + /*** Abstractions for payments channel related to loan products ***/ public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, @@ -112,6 +123,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); + final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), + element); final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element); @@ -132,19 +145,13 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), overPaymentAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId); - break; + //$FALL-THROUGH$ case ACCRUAL_BASED: - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId); break; } @@ -187,6 +194,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + + // liabilities + mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes); break; case ACCRUAL_BASED: // assets (including receivables) @@ -221,6 +232,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + + // liabilities + mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes); break; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index cc59f506b..2ce50d032 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -106,6 +106,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId); + } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) { + accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), glAccountId); } } } else if (AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingType)) { @@ -126,6 +128,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId); + } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) { + accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 88e62779f..9ec60469e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -79,9 +79,11 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); // expenses - this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); + this.loanProductToGLAccountMappingHelper + .saveLoanToLiabilityAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue()); + + // liabilities // advanced accounting mappings this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); @@ -125,6 +127,11 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); + // liabilities + this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue()); + // advanced accounting mappings this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java index db19d8b74..657815a5c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java @@ -35,6 +35,7 @@ public class LoanTransactionData { private final BigDecimal interestPortion; private final BigDecimal feeChargesPortion; private final BigDecimal penaltyChargesPortion; + private final BigDecimal overpaymentPortion; private final String externalId; private final AccountTransferData transfer; @@ -46,23 +47,24 @@ public class LoanTransactionData { return new LoanTransactionData(loanTransactionData.id, loanTransactionData.officeId, loanTransactionData.officeName, loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date, loanTransactionData.amount, loanTransactionData.principalPortion, loanTransactionData.interestPortion, - loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, paymentTypeOptions, - loanTransactionData.externalId, loanTransactionData.transfer); + loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, loanTransactionData.overpaymentPortion, + paymentTypeOptions, loanTransactionData.externalId, loanTransactionData.transfer); } public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, - final BigDecimal penaltyChargesPortion, final String externalId, final AccountTransferData transfer) { - this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion, - feeChargesPortion, penaltyChargesPortion, null, externalId, transfer); - } - - public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType, - final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, - final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, - final BigDecimal penaltyChargesPortion, final Collection paymentTypeOptions, final String externalId, + final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final String externalId, final AccountTransferData transfer) { + this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion, + feeChargesPortion, penaltyChargesPortion, overpaymentPortion, null, externalId, transfer); + } + + public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType, + final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, + final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, + final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, + final Collection paymentTypeOptions, final String externalId, final AccountTransferData transfer) { this.id = id; this.officeId = officeId; this.officeName = officeName; @@ -78,6 +80,7 @@ public class LoanTransactionData { this.paymentTypeOptions = paymentTypeOptions; this.externalId = externalId; this.transfer = transfer; + this.overpaymentPortion = overpaymentPortion; } public LocalDate dateOf() { 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 212c5de8c..db8a2f419 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 @@ -82,6 +82,9 @@ public final class LoanTransaction extends AbstractPersistable { @Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true) private BigDecimal penaltyChargesPortion; + @Column(name = "overpayment_portion_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal overPaymentPortion; + @Column(name = "is_reversed", nullable = false) private boolean reversed; @@ -133,21 +136,21 @@ public final class LoanTransaction extends AbstractPersistable { return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), false, null); + .getTotalPenaltyChargesOutstanding(), null, false, null); } public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate) { return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), false, null); + .getTotalPenaltyChargesOutstanding(), null, false, null); } public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate) { return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() - .getTotalPenaltyChargesOutstanding(), false, null); + .getTotalPenaltyChargesOutstanding(), null, false, null); } public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail, @@ -158,8 +161,8 @@ public final class LoanTransaction extends AbstractPersistable { public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) { return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, - loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.reversed, - loanTransaction.paymentDetail); + loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion, + loanTransaction.reversed, loanTransaction.paymentDetail); } public static LoanTransaction applyLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate, @@ -176,13 +179,15 @@ public final class LoanTransaction extends AbstractPersistable { && (loanTransaction.getPrincipalPortion(currency).isEqualTo(newLoanTransaction.getPrincipalPortion(currency))) && (loanTransaction.getInterestPortion(currency).isEqualTo(newLoanTransaction.getInterestPortion(currency))) && (loanTransaction.getFeeChargesPortion(currency).isEqualTo(newLoanTransaction.getFeeChargesPortion(currency))) - && (loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency)))) { return true; } + && (loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency))) + && (loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency)))) { return true; } return false; } private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final Date dateOf, final BigDecimal amount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, - final BigDecimal penaltyChargesPortion, final boolean reversed, final PaymentDetail paymentDetail) { + final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed, + final PaymentDetail paymentDetail) { super(); this.loan = loan; this.typeOf = typeOf; @@ -192,6 +197,7 @@ public final class LoanTransaction extends AbstractPersistable { this.interestPortion = interestPortion; this.feeChargesPortion = feeChargesPortion; this.penaltyChargesPortion = penaltyChargesPortion; + this.overPaymentPortion = overPaymentPortion; this.reversed = reversed; this.paymentDetail = paymentDetail; this.office = office; @@ -274,6 +280,11 @@ public final class LoanTransaction extends AbstractPersistable { .plus(getPenaltyChargesPortion(currency)).getAmount(); } + public void updateOverPayments(final Money overPayment) { + final MonetaryCurrency currency = overPayment.getCurrency(); + this.overPaymentPortion = defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount()); + } + public Money getPrincipalPortion(final MonetaryCurrency currency) { return Money.of(currency, this.principalPortion); } @@ -294,6 +305,10 @@ public final class LoanTransaction extends AbstractPersistable { return Money.of(currency, this.penaltyChargesPortion); } + public Money getOverPaymentPortion(final MonetaryCurrency currency) { + return Money.of(currency, this.overPaymentPortion); + } + public Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } @@ -425,7 +440,7 @@ public final class LoanTransaction extends AbstractPersistable { } return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData, currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion, - this.penaltyChargesPortion, this.externalId, transfer); + this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer); } public Map toMapData(final CurrencyData currencyData) { @@ -444,6 +459,7 @@ public final class LoanTransaction extends AbstractPersistable { thisTransactionData.put("interestPortion", this.interestPortion); thisTransactionData.put("feeChargesPortion", this.feeChargesPortion); thisTransactionData.put("penaltyChargesPortion", this.penaltyChargesPortion); + thisTransactionData.put("overPaymentPortion", this.overPaymentPortion); if (this.paymentDetail != null) { thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); 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 4fca20850..244f87bfd 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 @@ -186,6 +186,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen if (transactionAmountUnprocessed.isGreaterThanZero()) { onLoanOverpayment(loanTransaction, transactionAmountUnprocessed); + loanTransaction.updateOverPayments(transactionAmountUnprocessed); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 4fadd17b0..2efe26092 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -330,7 +330,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection paymentOptions = this.codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - possibleNextRepaymentAmount.getAmount(), null, null, null, null, paymentOptions, null, null); + possibleNextRepaymentAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null); } @Override @@ -354,7 +354,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal amount = waiveOfInterest.getAmount(currency).getAmount(); return new LoanTransactionData(null, null, null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount, - null, null, null, null, null, null); + null, null, null, null, null, null, null); } @Override @@ -364,7 +364,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF); return new LoanTransactionData(null, null, null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null, - null, null, null, null); + null, null, null, null, null); } @Override @@ -375,7 +375,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection paymentOptions = this.codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); return new LoanTransactionData(null, null, null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDate(), null, null, - null, null, null, paymentOptions, null, null); + null, null, null, null, paymentOptions, null, null); } @Override @@ -861,6 +861,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, " + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, " + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, " + + " tr.overpayment_portion_derived as overpayment, " + " pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " + " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, " + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, " @@ -920,6 +921,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest"); final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees"); final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties"); + final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment"); final String externalId = rs.getString("externalId"); AccountTransferData transfer = null; @@ -944,7 +946,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalAmount, - principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, externalId, transfer); + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, externalId, transfer); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index fd018242d..1aa3d6ab1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -48,8 +48,8 @@ public final class LoanProductDataValidator { LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue())); @@ -282,6 +282,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .notNull().integerGreaterThanZero(); + final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId) + .notNull().integerGreaterThanZero(); + validatePaymentChannelFundSourceMappings(baseDataValidator, element); validateChargeToIncomeAccountMappings(baseDataValidator, element); @@ -520,6 +525,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .ignoreIfNull().integerGreaterThanZero(); + final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), + element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId) + .ignoreIfNull().integerGreaterThanZero(); + final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())