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 8ff140857..7d94e281a 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 @@ -223,7 +223,8 @@ public class AccountingConstants { public static enum FINANCIAL_ACTIVITY { ASSET_TRANSFER(100, "assetTransfer", GLAccountType.ASSET), LIABILITY_TRANSFER(200, "liabilityTransfer", GLAccountType.LIABILITY), CASH_AT_MAINVAULT (101, "cashAtMainVault", GLAccountType.ASSET), - CASH_AT_TELLER (102, "cashAtTeller", GLAccountType.ASSET),OPENING_BALANCES_TRANSFER_CONTRA (300,"openingBalancesTransferContra",GLAccountType.EQUITY);; + CASH_AT_TELLER (102, "cashAtTeller", GLAccountType.ASSET),OPENING_BALANCES_TRANSFER_CONTRA (300,"openingBalancesTransferContra",GLAccountType.EQUITY), + ASSET_FUND_SOURCE(103, "fundSource", GLAccountType.ASSET); private final Integer value; private final String code; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java index b9f11ca12..4ccf8d9e7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java @@ -25,6 +25,8 @@ public interface AccountingDropdownReadPlatformService { public Map> retrieveAccountMappingOptionsForSavingsProducts(); + public Map> retrieveAccountMappingOptionsForCharges(); + public Map> retrieveAccountMappingOptions(); } \ No newline at end of file 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 942b3ada9..54c131d73 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 @@ -58,42 +58,79 @@ public class AccountingDropdownReadPlatformServiceImpl implements AccountingDrop return retrieveAccountMappingOptions(); } + @Override + public Map> retrieveAccountMappingOptionsForCharges() { + // only get income and liability accounts + boolean includeIncomeAccounts = true; + boolean includeLiabilityAccounts = true; + + boolean includeAssetAccounts = false; + boolean includeExpenseAccounts = false; + boolean includeEquityAccounts = false; + + return retrieveAccountMappingOptions(includeAssetAccounts, includeIncomeAccounts, includeExpenseAccounts, includeLiabilityAccounts, + includeEquityAccounts); + } + @Override public Map> retrieveAccountMappingOptions() { + boolean includeAssetAccounts = true; + boolean includeIncomeAccounts = true; + boolean includeExpenseAccounts = true; + boolean includeLiabilityAccounts = true; + boolean includeEquityAccounts = true; + return retrieveAccountMappingOptions(includeAssetAccounts, includeIncomeAccounts, includeExpenseAccounts, includeLiabilityAccounts, + includeEquityAccounts); + } + + private Map> retrieveAccountMappingOptions(boolean includeAssetAccounts, boolean includeIncomeAccounts, + boolean includeExpenseAccounts, boolean includeLiabilityAccounts, boolean includeEquityAccounts) { final Map> accountOptions = new HashMap<>(); - List assetAccountOptions = this.accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET); - if (assetAccountOptions.isEmpty()) { - assetAccountOptions = null; - } - accountOptions.put("assetAccountOptions", assetAccountOptions); - List incomeAccountOptions = this.accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.INCOME); - if (incomeAccountOptions.isEmpty()) { - incomeAccountOptions = null; + if (includeAssetAccounts) { + List assetAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET); + if (assetAccountOptions.isEmpty()) { + assetAccountOptions = null; + } + accountOptions.put("assetAccountOptions", assetAccountOptions); } - accountOptions.put("incomeAccountOptions", incomeAccountOptions); - List expenseAccountOptions = this.accountReadPlatformService - .retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE); - if (expenseAccountOptions.isEmpty()) { - expenseAccountOptions = null; + if (includeIncomeAccounts) { + List incomeAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.INCOME); + if (incomeAccountOptions.isEmpty()) { + incomeAccountOptions = null; + } + accountOptions.put("incomeAccountOptions", incomeAccountOptions); } - accountOptions.put("expenseAccountOptions", expenseAccountOptions); - List liabilityAccountOptions = this.accountReadPlatformService - .retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY); - if (liabilityAccountOptions.isEmpty()) { - liabilityAccountOptions = null; + if (includeExpenseAccounts) { + List expenseAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE); + if (expenseAccountOptions.isEmpty()) { + expenseAccountOptions = null; + } + accountOptions.put("expenseAccountOptions", expenseAccountOptions); + } + + if (includeLiabilityAccounts) { + List liabilityAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY); + if (liabilityAccountOptions.isEmpty()) { + liabilityAccountOptions = null; + } + accountOptions.put("liabilityAccountOptions", liabilityAccountOptions); + } + + if (includeEquityAccounts) { + List equityAccountOptions = this.accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.EQUITY); + if (equityAccountOptions.isEmpty()) { + equityAccountOptions = null; + } + accountOptions.put("equityAccountOptions", equityAccountOptions); } - accountOptions.put("liabilityAccountOptions", liabilityAccountOptions); - - List equityAccountOptions = this.accountReadPlatformService - .retrieveAllEnabledDetailGLAccounts(GLAccountType.EQUITY); - if (equityAccountOptions.isEmpty()) { - equityAccountOptions = null; - } - accountOptions.put("equityAccountOptions",equityAccountOptions); - return accountOptions; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientChargePaymentDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientChargePaymentDTO.java new file mode 100644 index 000000000..55315797c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientChargePaymentDTO.java @@ -0,0 +1,42 @@ +package org.mifosplatform.accounting.journalentry.data; + +import java.math.BigDecimal; + +public class ClientChargePaymentDTO { + + private final Long chargeId; + private final BigDecimal amount; + private final Long clientChargeId; + private final boolean isPenalty; + private final Long incomeAccountId; + + public ClientChargePaymentDTO(Long chargeId, BigDecimal amount, Long clientChargeId, boolean isPenalty, Long incomeAccountId) { + super(); + this.chargeId = chargeId; + this.amount = amount; + this.clientChargeId = clientChargeId; + this.isPenalty = isPenalty; + this.incomeAccountId = incomeAccountId; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public Long getClientChargeId() { + return this.clientChargeId; + } + + public boolean isPenalty() { + return this.isPenalty; + } + + public Long getChargeId() { + return chargeId; + } + + public Long getIncomeAccountId() { + return this.incomeAccountId; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientTransactionDTO.java new file mode 100644 index 000000000..01323d777 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ClientTransactionDTO.java @@ -0,0 +1,100 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.client.domain.ClientTransactionType; + +public class ClientTransactionDTO { + + private final Long officeId; + private final Long clientId; + private String currencyCode; + + private final Long transactionId; + private final Date transactionDate; + private final Long paymentTypeId; + private final EnumOptionData transactionType; + + private final BigDecimal amount; + + private final Boolean accountingEnabled; + + /*** Boolean values determines if the transaction is reversed ***/ + private final boolean reversed; + + /** Breakdowns of fees this Transaction pays **/ + private final List chargePayments; + + public ClientTransactionDTO(final Long clientId, final Long officeId, final Long paymentTypeId, final Long transactionId, + final Date transactionDate, final EnumOptionData transactionType, final String currencyCode, final BigDecimal amount, + final boolean reversed, final boolean accountingEnabled, final List clientChargePayments) { + this.clientId = clientId; + this.paymentTypeId = paymentTypeId; + this.transactionId = transactionId; + this.transactionDate = transactionDate; + this.amount = amount; + this.reversed = reversed; + this.transactionType = transactionType; + this.chargePayments = clientChargePayments; + this.officeId = officeId; + this.accountingEnabled = accountingEnabled; + this.currencyCode = currencyCode; + } + + public Long getOfficeId() { + return this.officeId; + } + + public Date getTransactionDate() { + return this.transactionDate; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public boolean isReversed() { + return this.reversed; + } + + public Long getPaymentTypeId() { + return this.paymentTypeId; + } + + public EnumOptionData getTransactionType() { + return transactionType; + } + + public boolean isChargePayment() { + return ClientTransactionType.PAY_CHARGE.getValue().equals(new Integer(this.transactionType.getId().intValue())); + } + + public String getCurrencyCode() { + return this.currencyCode; + } + + public List getChargePayments() { + return this.chargePayments; + } + + public Long getTransactionId() { + return this.transactionId; + } + + public Boolean getAccountingEnabled() { + return this.accountingEnabled; + } + + public Long getClientId() { + return this.clientId; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java index 912e0523f..9edf8103f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/domain/JournalEntry.java @@ -21,6 +21,7 @@ import org.apache.commons.lang.StringUtils; import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.portfolio.client.domain.ClientTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; @@ -60,6 +61,10 @@ public class JournalEntry extends AbstractAuditableCustom { @JoinColumn(name = "savings_transaction_id", nullable = false) private SavingsAccountTransaction savingsTransaction; + @ManyToOne + @JoinColumn(name = "client_transaction_id", nullable = false) + private ClientTransaction clientTransaction; + @Column(name = "reversed", nullable = false) private boolean reversed = false; @@ -92,10 +97,10 @@ public class JournalEntry extends AbstractAuditableCustom { final String currencyCode, final String transactionId, final boolean manualEntry, final Date transactionDate, final JournalEntryType journalEntryType, final BigDecimal amount, final String description, final Integer entityType, final Long entityId, final String referenceNumber, final LoanTransaction loanTransaction, - final SavingsAccountTransaction savingsTransaction) { + final SavingsAccountTransaction savingsTransaction, final ClientTransaction clientTransaction) { return new JournalEntry(office, paymentDetail, glAccount, currencyCode, transactionId, manualEntry, transactionDate, journalEntryType.getValue(), amount, description, entityType, entityId, referenceNumber, loanTransaction, - savingsTransaction); + savingsTransaction, clientTransaction); } protected JournalEntry() { @@ -105,7 +110,8 @@ public class JournalEntry extends AbstractAuditableCustom { public JournalEntry(final Office office, final PaymentDetail paymentDetail, final GLAccount glAccount, final String currencyCode, final String transactionId, final boolean manualEntry, final Date transactionDate, final Integer type, final BigDecimal amount, final String description, final Integer entityType, final Long entityId, final String referenceNumber, - final LoanTransaction loanTransaction, final SavingsAccountTransaction savingsTransaction) { + final LoanTransaction loanTransaction, final SavingsAccountTransaction savingsTransaction, + final ClientTransaction clientTransaction) { this.office = office; this.glAccount = glAccount; this.reversalJournalEntry = null; @@ -122,6 +128,7 @@ public class JournalEntry extends AbstractAuditableCustom { this.currencyCode = currencyCode; this.loanTransaction = loanTransaction; this.savingsTransaction = savingsTransaction; + this.clientTransaction = clientTransaction; this.paymentDetail = paymentDetail; } @@ -181,4 +188,8 @@ public class JournalEntry extends AbstractAuditableCustom { return transactionId; } + public ClientTransaction getClientTransaction() { + return this.clientTransaction; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForClientTransactions.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForClientTransactions.java new file mode 100644 index 000000000..b53b8ac7d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForClientTransactions.java @@ -0,0 +1,8 @@ +package org.mifosplatform.accounting.journalentry.service; + +import org.mifosplatform.accounting.journalentry.data.ClientTransactionDTO; + +public interface AccountingProcessorForClientTransactions { + + void createJournalEntriesForClientTransaction(ClientTransactionDTO clientTransactionDTO); +} 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 aa4179f58..dfb76c296 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 @@ -23,7 +23,10 @@ import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVIT import org.mifosplatform.accounting.financialactivityaccount.domain.FinancialActivityAccount; import org.mifosplatform.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; import org.mifosplatform.accounting.glaccount.domain.GLAccount; +import org.mifosplatform.accounting.glaccount.domain.GLAccountRepositoryWrapper; import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO; +import org.mifosplatform.accounting.journalentry.data.ClientChargePaymentDTO; +import org.mifosplatform.accounting.journalentry.data.ClientTransactionDTO; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.accounting.journalentry.data.SavingsDTO; @@ -37,12 +40,15 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProd 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.data.EnumOptionData; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.portfolio.account.PortfolioAccountType; import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService; +import org.mifosplatform.portfolio.client.domain.ClientTransaction; +import org.mifosplatform.portfolio.client.domain.ClientTransactionRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; @@ -58,12 +64,15 @@ public class AccountingProcessorHelper { public static final String LOAN_TRANSACTION_IDENTIFIER = "L"; public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S"; + public static final String CLIENT_TRANSACTION_IDENTIFIER = "C"; private final JournalEntryRepository glJournalEntryRepository; private final ProductToGLAccountMappingRepository accountMappingRepository; private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository; private final GLClosureRepository closureRepository; + private final GLAccountRepositoryWrapper accountRepositoryWrapper; private final OfficeRepository officeRepository; private final LoanTransactionRepository loanTransactionRepository; + private final ClientTransactionRepositoryWrapper clientTransactionRepository; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; @@ -73,7 +82,9 @@ public class AccountingProcessorHelper { final OfficeRepository officeRepository, final LoanTransactionRepository loanTransactionRepository, final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository, - final AccountTransfersReadPlatformService accountTransfersReadPlatformService) { + final AccountTransfersReadPlatformService accountTransfersReadPlatformService, + final GLAccountRepositoryWrapper accountRepositoryWrapper, + final ClientTransactionRepositoryWrapper clientTransactionRepositoryWrapper) { this.glJournalEntryRepository = glJournalEntryRepository; this.accountMappingRepository = accountMappingRepository; this.closureRepository = closureRepository; @@ -82,6 +93,8 @@ public class AccountingProcessorHelper { this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.financialActivityAccountRepository = financialActivityAccountRepository; this.accountTransfersReadPlatformService = accountTransfersReadPlatformService; + this.accountRepositoryWrapper = accountRepositoryWrapper; + this.clientTransactionRepository = clientTransactionRepositoryWrapper; } public LoanDTO populateLoanDtoFromMap(final Map accountingBridgeData, final boolean cashBasedAccountingEnabled, @@ -202,6 +215,45 @@ public class AccountingProcessorHelper { accrualBasedAccountingEnabled, newSavingsTransactions); } + public ClientTransactionDTO populateClientTransactionDtoFromMap(final Map accountingBridgeData) { + + final Long transactionOfficeId = (Long) accountingBridgeData.get("officeId"); + final Long clientId = (Long) accountingBridgeData.get("clientId"); + final Long transactionId = (Long) accountingBridgeData.get("id"); + final Date transactionDate = ((LocalDate) accountingBridgeData.get("date")).toDate(); + final EnumOptionData transactionType = (EnumOptionData) accountingBridgeData.get("type"); + final BigDecimal amount = (BigDecimal) accountingBridgeData.get("amount"); + final boolean reversed = (Boolean) accountingBridgeData.get("reversed"); + final Long paymentTypeId = (Long) accountingBridgeData.get("paymentTypeId"); + final String currencyCode = (String) accountingBridgeData.get("currencyCode"); + final Boolean accountingEnabled = (Boolean) accountingBridgeData.get("accountingEnabled"); + + final List clientChargePaymentDTOs = new ArrayList<>(); + // extract client charge payment details (if exists) + if (accountingBridgeData.containsKey("clientChargesPaid")) { + @SuppressWarnings("unchecked") + final List> clientChargesPaidData = (List>) accountingBridgeData + .get("clientChargesPaid"); + for (final Map clientChargePaid : clientChargesPaidData) { + final Long chargeId = (Long) clientChargePaid.get("chargeId"); + final Long clientChargeId = (Long) clientChargePaid.get("clientChargeId"); + final boolean isPenalty = (Boolean) clientChargePaid.get("isPenalty"); + final BigDecimal chargeAmountPaid = (BigDecimal) clientChargePaid.get("amount"); + final Long incomeAccountId = (Long) clientChargePaid.get("incomeAccountId"); + final ClientChargePaymentDTO clientChargePaymentDTO = new ClientChargePaymentDTO(chargeId, chargeAmountPaid, clientChargeId, + isPenalty, incomeAccountId); + clientChargePaymentDTOs.add(clientChargePaymentDTO); + } + } + + final ClientTransactionDTO clientTransactionDTO = new ClientTransactionDTO(clientId, transactionOfficeId, paymentTypeId, + transactionId, transactionDate, transactionType, currencyCode, amount, reversed, accountingEnabled, + clientChargePaymentDTOs); + + return clientTransactionDTO; + + } + /** * Convenience method that creates a pair of related Debits and Credits for * Accrual Based accounting. @@ -223,9 +275,8 @@ public class AccountingProcessorHelper { * @param isReversal */ public void createAccrualBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode, - final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, - final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, - final Boolean isReversal) { + final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, + final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { int accountTypeToDebitId = accountTypeToBeDebited; int accountTypeToCreditId = accountTypeToBeCredited; // reverse debits and credits for reversals @@ -233,8 +284,8 @@ public class AccountingProcessorHelper { accountTypeToDebitId = accountTypeToBeCredited; accountTypeToCreditId = accountTypeToBeDebited; } - createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, - loanId, transactionId, transactionDate, amount); + createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId, + transactionId, transactionDate, amount); } /** @@ -354,9 +405,8 @@ public class AccountingProcessorHelper { * @param isReversal */ public void createCashBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode, - final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, - final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, - final Boolean isReversal) { + final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, + final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { int accountTypeToDebitId = accountTypeToBeDebited; int accountTypeToCreditId = accountTypeToBeCredited; // reverse debits and credits for reversals @@ -364,8 +414,8 @@ public class AccountingProcessorHelper { accountTypeToDebitId = accountTypeToBeCredited; accountTypeToCreditId = accountTypeToBeDebited; } - createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, - loanId, transactionId, transactionDate, amount); + createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId, + transactionId, transactionDate, amount); } public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, @@ -394,8 +444,9 @@ public class AccountingProcessorHelper { * transaction Date **/ if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } + if (latestGLClosure.getClosingDate().after(transactionDate) + || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } } @@ -553,21 +604,21 @@ public class AccountingProcessorHelper { } } - private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, - final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) { + private void createCreditJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account, + final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount) { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; + ClientTransaction clientTransaction = null; final PaymentDetail paymentDetail = null; - String modifiedTransactionId = transactionId; - if (StringUtils.isNumeric(transactionId)) { - long id = Long.parseLong(transactionId); - loanTransaction = this.loanTransactionRepository.findOne(id); - modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId; - } + + clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId); + + String modifiedTransactionId = transactionId.toString(); + modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId; final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null, - loanTransaction, savingsAccountTransaction); + manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null, + loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -576,6 +627,7 @@ public class AccountingProcessorHelper { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; + ClientTransaction clientTransaction = null; final PaymentDetail paymentDetail = null; String modifiedTransactionId = transactionId; if (StringUtils.isNumeric(transactionId)) { @@ -585,7 +637,26 @@ public class AccountingProcessorHelper { } final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, - null, loanTransaction, savingsAccountTransaction); + null, loanTransaction, savingsAccountTransaction, clientTransaction); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + + private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId, + final String transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + LoanTransaction loanTransaction = null; + SavingsAccountTransaction savingsAccountTransaction = null; + ClientTransaction clientTransaction = null; + final PaymentDetail paymentDetail = null; + String modifiedTransactionId = transactionId; + if (StringUtils.isNumeric(transactionId)) { + long id = Long.parseLong(transactionId); + loanTransaction = this.loanTransactionRepository.findOne(id); + modifiedTransactionId = LOAN_TRANSACTION_IDENTIFIER + transactionId; + } + final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, + manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null, + loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -594,6 +665,7 @@ public class AccountingProcessorHelper { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; + ClientTransaction clientTransaction = null; final PaymentDetail paymentDetail = null; String modifiedTransactionId = transactionId; if (StringUtils.isNumeric(transactionId)) { @@ -603,7 +675,7 @@ public class AccountingProcessorHelper { } final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId, null, - loanTransaction, savingsAccountTransaction); + loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -612,6 +684,7 @@ public class AccountingProcessorHelper { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; + ClientTransaction clientTransaction = null; final PaymentDetail paymentDetail = null; String modifiedTransactionId = transactionId; if (StringUtils.isNumeric(transactionId)) { @@ -620,8 +693,26 @@ public class AccountingProcessorHelper { modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; } final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, - null, loanTransaction, savingsAccountTransaction); + manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, + loanTransaction, savingsAccountTransaction, clientTransaction); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + + private void createDebitJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account, + final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + ClientTransaction clientTransaction = null; + LoanTransaction loanTransaction = null; + SavingsAccountTransaction savingsAccountTransaction = null; + final PaymentDetail paymentDetail = null; + + clientTransaction = this.clientTransactionRepository.findOneWithNotFoundDetection(clientId, transactionId); + String modifiedTransactionId = transactionId.toString(); + modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId; + + final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, + manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null, + loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -659,12 +750,13 @@ public class AccountingProcessorHelper { private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, final int accountMappingTypeId, final Long chargeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId, PortfolioProductType.LOAN.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 - *****/ + /***** + * 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_LOAN.INCOME_FROM_FEES.getValue() @@ -679,15 +771,17 @@ public class AccountingProcessorHelper { return accountMapping.getGlAccount(); } - private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, final Long chargeId) { + 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 - *****/ + /***** + * 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() @@ -737,4 +831,56 @@ public class AccountingProcessorHelper { } return isOrganizationAccount; } + + public BigDecimal createCreditJournalEntryOrReversalForClientPayments(final Office office, final String currencyCode, + final Long clientId, final Long transactionId, final Date transactionDate, final Boolean isReversal, + final List clientChargePaymentDTOs) { + /*** + * Map to track each account affected and the net credit to be made for + * a particular account + ***/ + final Map creditDetailsMap = new LinkedHashMap<>(); + for (final ClientChargePaymentDTO clientChargePaymentDTO : clientChargePaymentDTOs) { + if (clientChargePaymentDTO.getIncomeAccountId() != null) { + final GLAccount chargeSpecificAccount = getGLAccountById(clientChargePaymentDTO.getIncomeAccountId()); + BigDecimal chargeSpecificAmount = clientChargePaymentDTO.getAmount(); + + // adjust net credit amount if the account is already present in + // the map + if (creditDetailsMap.containsKey(chargeSpecificAccount)) { + final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); + chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); + } + creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); + } + } + + BigDecimal totalCreditedAmount = BigDecimal.ZERO; + for (final Map.Entry entry : creditDetailsMap.entrySet()) { + final GLAccount account = entry.getKey(); + final BigDecimal amount = entry.getValue(); + totalCreditedAmount = totalCreditedAmount.add(amount); + if (isReversal) { + createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); + } else { + createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); + } + } + return totalCreditedAmount; + } + + public void createDebitJournalEntryOrReversalForClientChargePayments(final Office office, final String currencyCode, + final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + final GLAccount account = financialActivityAccountRepository + .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue()).getGlAccount(); + if (isReversal) { + createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); + } else { + createDebitJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); + } + } + + private GLAccount getGLAccountById(final Long accountId) { + return this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountId); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java new file mode 100644 index 000000000..42214e96c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForClientTransactions.java @@ -0,0 +1,70 @@ +package org.mifosplatform.accounting.journalentry.service; + +import java.math.BigDecimal; +import java.util.Date; + +import org.mifosplatform.accounting.closure.domain.GLClosure; +import org.mifosplatform.accounting.journalentry.data.ClientTransactionDTO; +import org.mifosplatform.organisation.office.domain.Office; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class CashBasedAccountingProcessorForClientTransactions implements AccountingProcessorForClientTransactions { + + AccountingProcessorHelper helper; + + @Autowired + public CashBasedAccountingProcessorForClientTransactions(final AccountingProcessorHelper accountingProcessorHelper) { + this.helper = accountingProcessorHelper; + } + + @Override + public void createJournalEntriesForClientTransaction(ClientTransactionDTO clientTransactionDTO) { + if (clientTransactionDTO.getAccountingEnabled()) { + final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(clientTransactionDTO.getOfficeId()); + final Date transactionDate = clientTransactionDTO.getTransactionDate(); + final Office office = this.helper.getOfficeById(clientTransactionDTO.getOfficeId()); + this.helper.checkForBranchClosures(latestGLClosure, transactionDate); + + /** Handle client payments **/ + if (clientTransactionDTO.isChargePayment()) { + createJournalEntriesForChargePayments(clientTransactionDTO, office); + } + } + } + + /** + * Create a single debit to fund source and multiple credits for the income + * account mapped with each charge this payment pays off + * + * In case the loan transaction is a reversal, all debits are turned into + * credits and vice versa + */ + private void createJournalEntriesForChargePayments(final ClientTransactionDTO clientTransactionDTO, final Office office) { + // client properties + final Long clientId = clientTransactionDTO.getClientId(); + + // transaction properties + final String currencyCode = clientTransactionDTO.getCurrencyCode(); + final Long transactionId = clientTransactionDTO.getTransactionId(); + final Date transactionDate = clientTransactionDTO.getTransactionDate(); + final BigDecimal amount = clientTransactionDTO.getAmount(); + final boolean isReversal = clientTransactionDTO.isReversed(); + + if (amount != null && !(amount.compareTo(BigDecimal.ZERO) == 0)) { + BigDecimal totalCreditedAmount = this.helper.createCreditJournalEntryOrReversalForClientPayments(office, currencyCode, clientId, + transactionId, transactionDate, isReversal, clientTransactionDTO.getChargePayments()); + + /*** + * create a single Debit entry (or reversal) for the entire amount + * that was credited (accounting is turned on at the level of for + * each charge that has been paid by this transaction) + **/ + this.helper.createDebitJournalEntryOrReversalForClientChargePayments(office, currencyCode, clientId, transactionId, + transactionDate, totalCreditedAmount, isReversal); + } + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java index 1fd51a739..384896984 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java @@ -20,6 +20,8 @@ public interface JournalEntryWritePlatformService { void createJournalEntriesForSavings(Map accountingBridgeData); + void createJournalEntriesForClientTransactions(Map accountingBridgeData); + CommandProcessingResult defineOpeningBalance(JsonCommand command); - + } 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 60591fd17..d6c6f295d 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 @@ -27,6 +27,7 @@ import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformServi import org.mifosplatform.accounting.journalentry.api.JournalEntryJsonInputParams; import org.mifosplatform.accounting.journalentry.command.JournalEntryCommand; import org.mifosplatform.accounting.journalentry.command.SingleDebitOrCreditEntryCommand; +import org.mifosplatform.accounting.journalentry.data.ClientTransactionDTO; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.SavingsDTO; import org.mifosplatform.accounting.journalentry.domain.JournalEntry; @@ -52,6 +53,7 @@ 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.portfolio.client.domain.ClientTransaction; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.mifosplatform.useradministration.domain.AppUser; @@ -82,6 +84,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private final PlatformSecurityContext context; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper; + private final CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions; @Autowired public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, @@ -93,7 +96,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final GLAccountReadPlatformService glAccountReadPlatformService, final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository, final PlatformSecurityContext context, final PaymentDetailWritePlatformService paymentDetailWritePlatformService, - final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper) { + final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, + final CashBasedAccountingProcessorForClientTransactions accountingProcessorForClientTransactions) { this.glClosureRepository = glClosureRepository; this.officeRepository = officeRepository; this.glJournalEntryRepository = glJournalEntryRepository; @@ -108,6 +112,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa this.context = context; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; this.financialActivityAccountRepositoryWrapper = financialActivityAccountRepositoryWrapper; + this.accountingProcessorForClientTransactions = accountingProcessorForClientTransactions; } @Transactional @@ -248,18 +253,21 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } } - private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits) { + private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, + final SingleDebitOrCreditEntryCommand[] debits) { // sum of all debits must be = sum of all credits BigDecimal creditsSum = BigDecimal.ZERO; BigDecimal debitsSum = BigDecimal.ZERO; for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) { - if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } + if (creditEntryCommand.getAmount() == null + || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } creditsSum = creditsSum.add(creditEntryCommand.getAmount()); } for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) { - if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } + if (debitEntryCommand.getAmount() == null + || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); } debitsSum = debitsSum.add(debitEntryCommand.getAmount()); } if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException( @@ -283,8 +291,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa @Override public CommandProcessingResult revertJournalEntry(final JsonCommand command) { // is the transaction Id valid - final List journalEntries = this.glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(command - .getTransactionId()); + final List journalEntries = this.glJournalEntryRepository + .findUnReversedManualJournalEntriesByTransactionId(command.getTransactionId()); String reversalComment = command.stringValueOfParameterNamed("comments"); if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); } @@ -311,12 +319,14 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(), journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, manualEntry, journalEntry.getTransactionDate(), JournalEntryType.CREDIT, journalEntry.getAmount(), reversalComment, null, null, - journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction()); + journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), + journalEntry.getClientTransaction()); } else { reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(), journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, manualEntry, journalEntry.getTransactionDate(), JournalEntryType.DEBIT, journalEntry.getAmount(), reversalComment, null, null, - journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction()); + journalEntry.getReferenceNumber(), journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), + journalEntry.getClientTransaction()); } // save the reversal entry this.glJournalEntryRepository.saveAndFlush(reversalJournalEntry); @@ -384,8 +394,9 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa // shouldn't be before an accounting closure final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId()); if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } + if (latestGLClosure.getClosingDate().after(transactionDate) + || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } /*** check if credits and debits are valid **/ @@ -393,16 +404,16 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); // atleast one debit or credit must be present - if (credits == null || credits.length <= 0 || debits == null || debits.length <= 0) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); } + if (credits == null || credits.length <= 0 || debits == null + || debits.length <= 0) { throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, + null, null); } checkDebitAndCreditAmounts(credits, debits); } private void saveAllDebitOrCreditEntries(final JournalEntryCommand command, final Office office, final PaymentDetail paymentDetail, - final String currencyCode, final Date transactionDate, - final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId, - final JournalEntryType type, final String referenceNumber) { + final String currencyCode, final Date transactionDate, final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, + final String transactionId, final JournalEntryType type, final String referenceNumber) { final boolean manualEntry = true; for (final SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) { final GLAccount glAccount = this.glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId()); @@ -418,9 +429,10 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa /** Validate current code is appropriate **/ this.organisationCurrencyRepository.findOneWithNotFoundDetection(currencyCode); + final ClientTransaction clientTransaction = null; final JournalEntry glJournalEntry = JournalEntry.createNew(office, paymentDetail, glAccount, currencyCode, transactionId, manualEntry, transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, referenceNumber, - null, null); + null, null, clientTransaction); this.glJournalEntryRepository.saveAndFlush(glJournalEntry); } } @@ -499,9 +511,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } private void saveAllDebitOrCreditOpeningBalanceEntries(final JournalEntryCommand command, final Office office, - final String currencyCode, final Date transactionDate, - final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, final String transactionId, - final JournalEntryType type, final Long contraAccountId) { + final String currencyCode, final Date transactionDate, final SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, + final String transactionId, final JournalEntryType type, final Long contraAccountId) { final boolean manualEntry = true; final GLAccount contraAccount = this.glAccountRepository.findOne(contraAccountId); @@ -526,12 +537,15 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa comments = singleDebitOrCreditEntryCommand.getComments(); } + final ClientTransaction clientTransaction = null; final JournalEntry glJournalEntry = JournalEntry.createNew(office, null, glAccount, currencyCode, transactionId, manualEntry, - transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null); + transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null, + clientTransaction); this.glJournalEntryRepository.saveAndFlush(glJournalEntry); final JournalEntry contraEntry = JournalEntry.createNew(office, null, contraAccount, currencyCode, transactionId, manualEntry, - transactionDate, contraType, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null); + transactionDate, contraType, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, null, null, null, + clientTransaction); this.glJournalEntryRepository.saveAndFlush(contraEntry); } } @@ -553,4 +567,10 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa "Defining Opening balances not allowed after journal entries posted", transactionIds); } } + @Override + public void createJournalEntriesForClientTransactions(Map accountingBridgeData) { + final ClientTransactionDTO clientTransactionDTO = this.helper.populateClientTransactionDtoFromMap(accountingBridgeData); + accountingProcessorForClientTransactions.createJournalEntriesForClientTransaction(clientTransactionDTO); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/PortfolioProductType.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/PortfolioProductType.java index e162c9b99..056b23609 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/PortfolioProductType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/PortfolioProductType.java @@ -9,7 +9,7 @@ import java.util.HashMap; import java.util.Map; public enum PortfolioProductType { - LOAN(1, "productType.loan"), SAVING(2, "productType.saving"); + LOAN(1, "productType.loan"), SAVING(2, "productType.saving"), CLIENT(2, "productType.client"); private final Integer value; private final String code; @@ -33,6 +33,7 @@ public enum PortfolioProductType { } private static final Map intToEnumMap = new HashMap<>(); + static { for (final PortfolioProductType type : PortfolioProductType.values()) { intToEnumMap.put(type.value, type); @@ -52,4 +53,8 @@ public enum PortfolioProductType { return this.value.equals(PortfolioProductType.LOAN.getValue()); } + public boolean isClient() { + return this.value.equals(PortfolioProductType.CLIENT.getValue()); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java index 6eb9a4c5d..c70e27f52 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/teller/service/TellerWritePlatformServiceJpaImpl.java @@ -39,6 +39,7 @@ import org.mifosplatform.organisation.teller.exception.CashierExistForTellerExce import org.mifosplatform.organisation.teller.exception.CashierNotFoundException; import org.mifosplatform.organisation.teller.exception.TellerNotFoundException; import org.mifosplatform.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer; +import org.mifosplatform.portfolio.client.domain.ClientTransaction; import org.mifosplatform.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -177,8 +178,8 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer Set isTellerIdPresentInCashier = teller.getCashiers(); for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) { - if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) { throw new CashierExistForTellerException( - tellerId); } + if (tellerIdInCashier.getTeller().getId().toString() + .equalsIgnoreCase(tellerId.toString())) { throw new CashierExistForTellerException(tellerId); } } tellerRepository.delete(teller); @@ -407,6 +408,7 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer final Long time = System.currentTimeMillis(); final String uniqueVal = String.valueOf(time) + currentUser.getId() + cashierOffice.getId(); final String transactionId = Long.toHexString(Long.parseLong(uniqueVal)); + ClientTransaction clientTransaction = null; final JournalEntry debitJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment // detail @@ -415,7 +417,8 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer transactionId, false, // manual entry cashierTxn.getTxnDate(), JournalEntryType.DEBIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description null, null, null, // entity Type, entityId, reference number - null, null); // Loan and Savings Txn + null, null, clientTransaction); // Loan and Savings Txn + final JournalEntry creditJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment // detail creditAccount, "USD", // FIXME: Take currency code from the @@ -423,7 +426,7 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer transactionId, false, // manual entry cashierTxn.getTxnDate(), JournalEntryType.CREDIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description null, null, null, // entity Type, entityId, reference number - null, null); // Loan and Savings Txn + null, null, clientTransaction); // Loan and Savings Txn this.glJournalEntryRepository.saveAndFlush(debitJournalEntry); this.glJournalEntryRepository.saveAndFlush(creditJournalEntry); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiConstants.java new file mode 100644 index 000000000..0bfdb4631 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/api/ChargesApiConstants.java @@ -0,0 +1,7 @@ +package org.mifosplatform.portfolio.charge.api; + +public class ChargesApiConstants { + + public static final String glAccountIdParamName = "incomeAccountId"; + +} 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 e52f8e27c..65f820564 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 @@ -41,10 +41,11 @@ import org.springframework.stereotype.Component; @Scope("singleton") 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", "loanChargeCalculationTypeOptions", - "loanChargeTimeTypeOptions", "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions")); + private final Set CHARGES_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "amount", "currency", "penalty", "active", + "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "chargeAppliesToOptions", + "chargeTimeTypeOptions", "currencyOptions", "loanChargeCalculationTypeOptions", "loanChargeTimeTypeOptions", + "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions", "incomeAccount", "clientChargeCalculationTypeOptions", + "clientChargeTimeTypeOptions")); 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 5776a2758..765432ddd 100755 --- 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 @@ -9,9 +9,11 @@ import java.io.Serializable; import java.math.BigDecimal; import java.util.Collection; import java.util.List; +import java.util.Map; import org.joda.time.LocalDate; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; @@ -38,6 +40,7 @@ public class ChargeData implements Comparable, Serializable { private final BigDecimal minCap; private final BigDecimal maxCap; private final EnumOptionData feeFrequency; + private final GLAccountData incomeOrLiabilityAccount; private final Collection currencyOptions; private final List chargeCalculationTypeOptions;// @@ -53,18 +56,21 @@ public class ChargeData implements Comparable, Serializable { private final List clientChargeTimeTypeOptions; private final List feeFrequencyOptions; + private final Map> incomeOrLiabilityAccountOptions; + public static ChargeData template(final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, final List chargeTimeTypeOptions, final List chargePaymentModeOptions, final List loansChargeCalculationTypeOptions, final List loansChargeTimeTypeOptions, final List savingsChargeCalculationTypeOptions, final List savingsChargeTimeTypeOptions, final List clientChargeCalculationTypeOptions, final List clientChargeTimeTypeOptions, - final List feeFrequencyOptions) { - + final List feeFrequencyOptions, final Map> incomeOrLiabilityAccountOptions) { + final GLAccountData account = null; return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, - clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, null, feeFrequencyOptions); + clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, null, feeFrequencyOptions, account, + incomeOrLiabilityAccountOptions); } public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) { @@ -74,13 +80,15 @@ public class ChargeData implements Comparable, Serializable { template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions, template.clientChargeCalculationTypeOptions, template.clientChargeTimeTypeOptions, charge.feeOnMonthDay, charge.feeInterval, - charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions); + charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions, charge.incomeOrLiabilityAccount, + template.incomeOrLiabilityAccountOptions); } 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 MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty, - final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency) { + final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency, + final GLAccountData accountData) { final Collection currencyOptions = null; final List chargeCalculationTypeOptions = null; @@ -94,12 +102,14 @@ public class ChargeData implements Comparable, Serializable { final List feeFrequencyOptions = null; final List clientChargeCalculationTypeOptions = null; final List clientChargeTimeTypeOptions = null; + final Map> incomeOrLiabilityAccountOptions = null; return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, - clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions); + clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData, + incomeOrLiabilityAccountOptions); } public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) { @@ -128,12 +138,15 @@ public class ChargeData implements Comparable, Serializable { final List clientChargeTimeTypeOptions = null; final EnumOptionData feeFrequency = null; final List feeFrequencyOptions = null; + final GLAccountData account = null; + final Map> incomeOrLiabilityAccountOptions = null; return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, - clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions); + clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, account, + incomeOrLiabilityAccountOptions); } private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, @@ -146,7 +159,8 @@ public class ChargeData implements Comparable, Serializable { final List savingsChargeTimeTypeOptions, final List clientChargeCalculationTypeOptions, final List clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency, - final List feeFrequencyOptions) { + final List feeFrequencyOptions, final GLAccountData account, + final Map> incomeOrLiabilityAccountOptions) { this.id = id; this.name = name; this.amount = amount; @@ -174,6 +188,8 @@ public class ChargeData implements Comparable, Serializable { this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions; this.feeFrequency = feeFrequency; this.feeFrequencyOptions = feeFrequencyOptions; + this.incomeOrLiabilityAccount = account; + this.incomeOrLiabilityAccountOptions = incomeOrLiabilityAccountOptions; } @Override 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 d2311a746..cd4e0a041 100755 --- 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 @@ -13,18 +13,24 @@ import java.util.Map; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.charge.api.ChargesApiConstants; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException; import org.mifosplatform.portfolio.charge.exception.ChargeMustBePenaltyException; @@ -85,7 +91,11 @@ public class Charge extends AbstractPersistable { @Column(name = "fee_frequency", nullable = true) private Integer feeFrequency; - public static Charge fromJson(final JsonCommand command) { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "income_or_liability_account_id") + private GLAccount account; + + public static Charge fromJson(final JsonCommand command, final GLAccount account) { final String name = command.stringValueOfParameterNamed("name"); final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount"); @@ -108,7 +118,7 @@ public class Charge extends AbstractPersistable { final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency"); return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode, - feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency); + feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, account); } protected Charge() { @@ -118,7 +128,7 @@ public class Charge extends AbstractPersistable { private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active, final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap, - final BigDecimal maxCap, final Integer feeFrequency) { + final BigDecimal maxCap, final Integer feeFrequency, final GLAccount account) { this.name = name; this.amount = amount; this.currencyCode = currencyCode; @@ -127,6 +137,7 @@ public class Charge extends AbstractPersistable { this.chargeCalculation = chargeCalculationType.getValue(); this.penalty = penalty; this.active = active; + this.account = account; this.chargePaymentMode = paymentMode == null ? null : paymentMode.getValue(); final List dataValidationErrors = new ArrayList<>(); @@ -461,6 +472,11 @@ public class Charge extends AbstractPersistable { .isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name); } if (!penalty && ChargeTimeType.fromInt(this.chargeTime).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } + if (command.isChangeInLongParameterNamed(ChargesApiConstants.glAccountIdParamName, getIncomeAccountId())) { + final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName); + actualChanges.put(ChargesApiConstants.glAccountIdParamName, newValue); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } return actualChanges; @@ -484,10 +500,14 @@ public class Charge extends AbstractPersistable { final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation); final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode); final EnumOptionData feeFrequencyType = ChargeEnumerations.chargePaymentMode(this.feeFrequency); + GLAccountData accountData = null; + if (account != null) { + accountData = new GLAccountData(account.getId(), account.getName(), account.getGlCode()); + } final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null); return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap, - feeFrequencyType); + feeFrequencyType, accountData); } public Integer getChargePaymentMode() { @@ -525,4 +545,20 @@ public class Charge extends AbstractPersistable { public Integer feeFrequency() { return this.feeFrequency; } + + public GLAccount getAccount() { + return this.account; + } + + public void setAccount(GLAccount account) { + this.account = account; + } + + private Long getIncomeAccountId() { + Long incomeAccountId = null; + if (this.account != null) { + incomeAccountId = this.account.getId(); + } + return incomeAccountId; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java index efdd90b99..eccf23bf6 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java @@ -23,6 +23,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.api.ChargesApiConstants; import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; @@ -39,9 +40,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { /** * The parameters supported for this command. */ - private final Set supportedParameters = new HashSet<>(Arrays.asList("name", "amount", "locale", "currencyCode", - "currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty", - "active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency")); + private final Set supportedParameters = new HashSet<>( + Arrays.asList("name", "amount", "locale", "currencyCode", "currencyOptions", "chargeAppliesTo", "chargeTimeType", + "chargeCalculationType", "chargeCalculationTypeOptions", "penalty", "active", "chargePaymentMode", "feeOnMonthDay", + "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency", ChargesApiConstants.glAccountIdParamName)); private final FromJsonHelper fromApiJsonHelper; @@ -145,13 +147,18 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { .isOneOfTheseValues(ChargeTimeType.validClientValues()); } - // final ChargeTimeType ctt = - // ChargeTimeType.fromInt(chargeTimeType); - if (chargeCalculationType != null) { baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType) - .isOneOfTheseValues(ChargeCalculationType.validValuesForSavings()); + .isOneOfTheseValues(ChargeCalculationType.validValuesForClients()); } + + // GL Account can be linked to clients + if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.glAccountIdParamName, element)) { + final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.glAccountIdParamName, element); + baseDataValidator.reset().parameter(ChargesApiConstants.glAccountIdParamName).value(glAccountId).notNull() + .longGreaterThanZero(); + } + } final String name = this.fromApiJsonHelper.extractStringNamed("name", element); @@ -286,6 +293,12 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3); } + if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.glAccountIdParamName, element)) { + final Long glAccountId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.glAccountIdParamName, element); + baseDataValidator.reset().parameter(ChargesApiConstants.glAccountIdParamName).value(glAccountId).notNull() + .longGreaterThanZero(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } 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 0d4a9245a..6ecc1f4f2 100755 --- 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 @@ -11,8 +11,11 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; @@ -45,16 +48,19 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService; private final DropdownReadPlatformService dropdownReadPlatformService; private final MifosEntityAccessUtil mifosEntityAccessUtil; + private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; @Autowired public ChargeReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService, final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource, - final DropdownReadPlatformService dropdownReadPlatformService, final MifosEntityAccessUtil mifosEntityAccessUtil) { + final DropdownReadPlatformService dropdownReadPlatformService, final MifosEntityAccessUtil mifosEntityAccessUtil, + final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); this.currencyReadPlatformService = currencyReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; this.mifosEntityAccessUtil = mifosEntityAccessUtil; + this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; } @Override @@ -118,11 +124,13 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService .retrieveClientCalculationTypes(); final List clientChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveClientCollectionTimeTypes(); final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); + final Map> incomeOrLiabilityAccountOptions = this.accountingDropdownReadPlatformService + .retrieveAccountMappingOptionsForCharges(); return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions, allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, - clientChargeTimeTypeOptions, feeFrequencyOptions); + clientChargeTimeTypeOptions, feeFrequencyOptions, incomeOrLiabilityAccountOptions); } @Override @@ -245,8 +253,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService + "c.is_active as active, oc.name as currencyName, oc.decimal_places as currencyDecimalPlaces, " + "oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode, c.fee_on_day as feeOnDay, c.fee_on_month as feeOnMonth, " - + "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap " - + "from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code"; + + "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap, " + + "c.income_or_liability_account_id as glAccountId , acc.name as glAccountName, acc.gl_code as glCode " + + "from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code " + + " LEFT JOIN acc_gl_account acc on acc.id = c.income_or_liability_account_id "; } public String loanProductChargeSchema() { @@ -303,8 +313,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final BigDecimal minCap = rs.getBigDecimal("minCap"); final BigDecimal maxCap = rs.getBigDecimal("maxCap"); + // extract GL Account + final Long glAccountId = JdbcSupport.getLong(rs, "glAccountId"); + final String glAccountName = rs.getString("glAccountName"); + final String glCode = rs.getString("glCode"); + GLAccountData glAccountData = null; + if (glAccountId != null) { + glAccountData = new GLAccountData(glAccountId, glAccountName, glCode); + } + return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, - chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType); + chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType, glAccountData); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java index 48018856d..0b4ab63e9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java @@ -10,6 +10,8 @@ import java.util.Map; import javax.sql.DataSource; +import org.mifosplatform.accounting.glaccount.domain.GLAccount; +import org.mifosplatform.accounting.glaccount.domain.GLAccountRepositoryWrapper; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -19,6 +21,7 @@ import org.mifosplatform.infrastructure.entityaccess.domain.MifosEntityAccessTyp import org.mifosplatform.infrastructure.entityaccess.domain.MifosEntityType; import org.mifosplatform.infrastructure.entityaccess.service.MifosEntityAccessUtil; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.charge.api.ChargesApiConstants; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeRepository; import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeDeletedException; @@ -47,20 +50,21 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP private final ChargeRepository chargeRepository; private final LoanProductRepository loanProductRepository; private final MifosEntityAccessUtil mifosEntityAccessUtil; + private final GLAccountRepositoryWrapper gLAccountRepository; @Autowired public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, - final ChargeRepository chargeRepository, final LoanProductRepository loanProductRepository, final RoutingDataSource dataSource, - final MifosEntityAccessUtil mifosEntityAccessUtil - ) { - this.context = context; + final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ChargeRepository chargeRepository, + final LoanProductRepository loanProductRepository, final RoutingDataSource dataSource, + final MifosEntityAccessUtil mifosEntityAccessUtil, final GLAccountRepositoryWrapper glAccountRepository) { + this.context = context; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.dataSource = dataSource; this.jdbcTemplate = new JdbcTemplate(this.dataSource); this.chargeRepository = chargeRepository; this.loanProductRepository = loanProductRepository; this.mifosEntityAccessUtil = mifosEntityAccessUtil; + this.gLAccountRepository = glAccountRepository; } @Transactional @@ -68,18 +72,25 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP @CacheEvict(value = "charges", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')") public CommandProcessingResult createCharge(final JsonCommand command) { try { - this.context.authenticatedUser(); + this.context.authenticatedUser(); this.fromApiJsonDeserializer.validateForCreate(command.json()); - final Charge charge = Charge.fromJson(command); + // Retrieve linked GLAccount for Client charges (if present) + final Long glAccountId = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName); + + GLAccount glAccount = null; + if (glAccountId != null) { + glAccount = this.gLAccountRepository.findOneWithNotFoundDetection(glAccountId); + } + + final Charge charge = Charge.fromJson(command, glAccount); this.chargeRepository.save(charge); - // check if the office specific products are enabled. If yes, then save this savings product against a specific office + // check if the office specific products are enabled. If yes, then + // save this savings product against a specific office // i.e. this savings product is specific for this office. - mifosEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice( - MifosEntityAccessType.OFFICE_ACCESS_TO_CHARGES, - MifosEntityType.CHARGE, - charge.getId()); + mifosEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(MifosEntityAccessType.OFFICE_ACCESS_TO_CHARGES, + MifosEntityType.CHARGE, charge.getId()); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(charge.getId()).build(); } catch (final DataIntegrityViolationException dve) { @@ -114,10 +125,21 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP if (isChargeExistWithLoans || isChargeExistWithSavings) { throw new ChargeCannotBeUpdatedException( "error.msg.charge.cannot.be.updated.it.is.used.in.loan", "This charge cannot be updated, it is used in loan"); } } - }else if((changes.containsKey("feeFrequency") || changes.containsKey("feeInterval")) && chargeForUpdate.isLoanCharge()){ + } else if ((changes.containsKey("feeFrequency") || changes.containsKey("feeInterval")) && chargeForUpdate.isLoanCharge()) { final Boolean isChargeExistWithLoans = isAnyLoanProductsAssociateWithThisCharge(chargeId); if (isChargeExistWithLoans) { throw new ChargeCannotBeUpdatedException( - "error.msg.charge.frequency.cannot.be.updated.it.is.used.in.loan", "This charge frequency cannot be updated, it is used in loan"); } + "error.msg.charge.frequency.cannot.be.updated.it.is.used.in.loan", + "This charge frequency cannot be updated, it is used in loan"); } + } + + // Has account Id been changed ? + if (changes.containsKey(ChargesApiConstants.glAccountIdParamName)) { + final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName); + GLAccount newIncomeAccount = null; + if (newValue != null) { + newIncomeAccount = this.gLAccountRepository.findOneWithNotFoundDetection(newValue); + } + chargeForUpdate.setAccount(newIncomeAccount); } if (!changes.isEmpty()) { @@ -144,9 +166,10 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP final Boolean isChargeExistWithSavings = isAnySavingsAssociateWithThisCharge(chargeId); // TODO: Change error messages around: - if (!loanProducts.isEmpty() || isChargeExistWithLoans || isChargeExistWithSavings) { throw new ChargeCannotBeDeletedException( - "error.msg.charge.cannot.be.deleted.it.is.already.used.in.loan", - "This charge cannot be deleted, it is already used in loan"); } + if (!loanProducts.isEmpty() || isChargeExistWithLoans + || isChargeExistWithSavings) { throw new ChargeCannotBeDeletedException( + "error.msg.charge.cannot.be.deleted.it.is.already.used.in.loan", + "This charge cannot be deleted, it is already used in loan"); } chargeForDelete.delete(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java index 31e4bfcba..ca2e1a977 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientChargePaidBy.java @@ -59,6 +59,11 @@ public class ClientChargePaidBy extends AbstractPersistable { public void setClientCharge(ClientCharge clientCharge) { this.clientCharge = clientCharge; + + } + + public BigDecimal getAmount() { + return this.amount; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java index 63e8abd45..88c9f9d08 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientTransaction.java @@ -6,8 +6,12 @@ package org.mifosplatform.portfolio.client.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; import javax.persistence.CascadeType; @@ -25,6 +29,8 @@ import javax.persistence.UniqueConstraint; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; +import org.mifosplatform.accounting.glaccount.domain.GLAccount; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -123,6 +129,54 @@ public class ClientTransaction extends AbstractPersistable { this.reversed = true; } + /** + * Converts the content of this Client Transaction to a map which can be + * passed to the accounting module + * + * @param currencyData + * @return + */ + public Map toMapData() { + final Map thisTransactionData = new LinkedHashMap<>(); + + final EnumOptionData transactionType = ClientEnumerations.clientTransactionType(this.typeOf); + Boolean accountingEnabledForAtleastOneCharge = false; + + thisTransactionData.put("id", getId()); + thisTransactionData.put("officeId", this.office.getId()); + thisTransactionData.put("type", transactionType); + thisTransactionData.put("reversed", Boolean.valueOf(this.reversed)); + thisTransactionData.put("date", getTransactionDate()); + thisTransactionData.put("currencyCode", this.currencyCode); + thisTransactionData.put("amount", this.amount); + + if (this.paymentDetail != null) { + thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); + } + + if (!this.clientChargePaidByCollection.isEmpty()) { + final List> clientChargesPaidData = new ArrayList<>(); + for (final ClientChargePaidBy clientChargePaidBy : this.clientChargePaidByCollection) { + final Map clientChargePaidData = new LinkedHashMap<>(); + clientChargePaidData.put("chargeId", clientChargePaidBy.getClientCharge().getCharge().getId()); + clientChargePaidData.put("isPenalty", clientChargePaidBy.getClientCharge().getCharge().isPenalty()); + clientChargePaidData.put("clientChargeId", clientChargePaidBy.getClientCharge().getId()); + clientChargePaidData.put("amount", clientChargePaidBy.getAmount()); + GLAccount glAccount = clientChargePaidBy.getClientCharge().getCharge().getAccount(); + if (glAccount != null) { + accountingEnabledForAtleastOneCharge = true; + clientChargePaidData.put("incomeAccountId", glAccount.getId()); + } + clientChargesPaidData.add(clientChargePaidData); + } + thisTransactionData.put("clientChargesPaid", clientChargesPaidData); + } + + thisTransactionData.put("accountingEnabled", accountingEnabledForAtleastOneCharge); + + return thisTransactionData; + } + public boolean isPayChargeTransaction() { return ClientTransactionType.PAY_CHARGE.getValue().equals(this.typeOf); } @@ -159,4 +213,8 @@ public class ClientTransaction extends AbstractPersistable { return this.reversed; } + public LocalDate getTransactionDate() { + return new LocalDate(this.dateOf); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java index 44f9eb85e..45aee37ed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientChargeWritePlatformServiceJpaRepositoryImpl.java @@ -15,6 +15,7 @@ import java.util.Map; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -58,6 +59,7 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client private final ClientChargeRepositoryWrapper clientChargeRepository; private final ClientTransactionRepository clientTransactionRepository; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; @Autowired public ClientChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -65,7 +67,8 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client final ClientRepositoryWrapper clientRepository, final HolidayRepositoryWrapper holidayRepositoryWrapper, final ConfigurationDomainService configurationDomainService, final ClientChargeRepositoryWrapper clientChargeRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, final ClientTransactionRepository clientTransactionRepository, - final PaymentDetailWritePlatformService paymentDetailWritePlatformService) { + final PaymentDetailWritePlatformService paymentDetailWritePlatformService, + final JournalEntryWritePlatformService journalEntryWritePlatformService) { this.context = context; this.chargeRepository = chargeRepository; this.clientChargeDataValidator = clientChargeDataValidator; @@ -76,6 +79,7 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client this.workingDaysRepository = workingDaysRepository; this.clientTransactionRepository = clientTransactionRepository; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; } @Override @@ -134,12 +138,15 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client ClientTransaction clientTransaction = ClientTransaction.payCharge(client, client.getOffice(), paymentDetail, transactionDate, chargePaid, clientCharge.getCurrency().getCode(), getAppUserIfPresent()); - this.clientTransactionRepository.save(clientTransaction); + this.clientTransactionRepository.saveAndFlush(clientTransaction); // update charge paid by associations final ClientChargePaidBy chargePaidBy = ClientChargePaidBy.instance(clientTransaction, clientCharge, amountPaid); clientTransaction.getClientChargePaidByCollection().add(chargePaidBy); + // generate accounting entries + generateAccountingEntries(clientTransaction); + return new CommandProcessingResultBuilder() // .withEntityId(clientCharge.getId()) // .withOfficeId(clientCharge.getClient().getOffice().getId()) // @@ -148,6 +155,11 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client } + private void generateAccountingEntries(ClientTransaction clientTransaction) { + Map accountingBridgeData = clientTransaction.toMapData(); + journalEntryWritePlatformService.createJournalEntriesForClientTransactions(accountingBridgeData); + } + @Override public CommandProcessingResult waiveCharge(Long clientId, Long clientChargeId) { final Client client = this.clientRepository.getActiveClient(clientId); @@ -294,7 +306,7 @@ public class ClientChargeWritePlatformServiceJpaRepositoryImpl implements Client */ @Override public CommandProcessingResult updateCharge(Long clientId, JsonCommand command) { - this.clientChargeDataValidator.validateAdd(command.json()); + // this.clientChargeDataValidator.validateAdd(command.json()); return null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java index 6496294c5..e49530a62 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientTransactionWritePlatformServiceJpaRepositoryImpl.java @@ -5,8 +5,10 @@ */ package org.mifosplatform.portfolio.client.service; +import java.util.Map; import java.util.Set; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.organisation.office.domain.OrganisationCurrencyRepositoryWrapper; @@ -27,14 +29,17 @@ public class ClientTransactionWritePlatformServiceJpaRepositoryImpl implements C private final ClientRepositoryWrapper clientRepository; private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; @Autowired public ClientTransactionWritePlatformServiceJpaRepositoryImpl(final ClientTransactionRepositoryWrapper clientTransactionRepository, final ClientRepositoryWrapper clientRepositoryWrapper, - final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepositoryWrapper) { + final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepositoryWrapper, + JournalEntryWritePlatformService journalEntryWritePlatformService) { this.clientTransactionRepository = clientTransactionRepository; this.clientRepository = clientRepositoryWrapper; this.organisationCurrencyRepository = organisationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; } @Override @@ -66,6 +71,10 @@ public class ClientTransactionWritePlatformServiceJpaRepositoryImpl implements C } } + // generate accounting entries + this.clientTransactionRepository.saveAndFlush(clientTransaction); + generateAccountingEntries(clientTransaction); + return new CommandProcessingResultBuilder() // .withEntityId(transactionId) // .withOfficeId(client.officeId()) // @@ -73,4 +82,9 @@ public class ClientTransactionWritePlatformServiceJpaRepositoryImpl implements C .build(); } + private void generateAccountingEntries(ClientTransaction clientTransaction) { + Map accountingBridgeData = clientTransaction.toMapData(); + journalEntryWritePlatformService.createJournalEntriesForClientTransactions(accountingBridgeData); + } + } 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 dee038e86..6d9dbabba 100755 --- 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 @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import org.joda.time.LocalDate; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; @@ -133,10 +134,13 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final List clientChargeTimeTypeOptions = null; final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); + // this field is applicable only for client charges + final Map> incomeOrLiabilityAccountOptions = null; return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, - savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions); + savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions, + incomeOrLiabilityAccountOptions); } @Override 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 5e27b508c..bd216addd 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 @@ -10,9 +10,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Map; import org.joda.time.LocalDate; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; @@ -142,11 +144,14 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final List clientChargeTimeTypeOptions = null; final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); + // this field is applicable only for client charges + final Map> incomeOrLiabilityAccountOptions = null; // TODO AA : revisit for merge conflict - Not sure method signature return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, - savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions); + savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions, + incomeOrLiabilityAccountOptions); } @Override diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V270__accounting_for_client_charges.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V270__accounting_for_client_charges.sql new file mode 100644 index 000000000..2c4071f11 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V270__accounting_for_client_charges.sql @@ -0,0 +1,9 @@ +-- Charges have a reference to an Income or Liability account that gets credited when a payment is made +ALTER TABLE `m_charge` + ADD COLUMN `income_or_liability_account_id` BIGINT(20) NULL AFTER `fee_frequency`, + ADD CONSTRAINT `FK_m_charge_acc_gl_account` FOREIGN KEY (`income_or_liability_account_id`) REFERENCES `acc_gl_account` (`id`); + +--Journal entries also refer to Client transactions +ALTER TABLE `acc_gl_journal_entry` + ADD COLUMN `client_transaction_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_transaction_id`, + ADD CONSTRAINT `FK_acc_gl_journal_entry_m_client_transaction` FOREIGN KEY (`client_transaction_id`) REFERENCES `m_client_transaction` (`id`);