From 5776ee3b534d028ba104025cbf769ca6f21b7a90 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Tue, 24 Sep 2013 23:49:10 +0530 Subject: [PATCH] handle penalties/fees separately for savings and add ability to map penalties/fees to separate income accounts --- ...ome_from_penalties_for savings_product.sql | 5 + .../common/AccountingConstants.java | 11 +- .../data/SavingsTransactionDTO.java | 18 ++- .../service/AccountingProcessorHelper.java | 104 +++++++++++++++++- ...ashBasedAccountingProcessorForSavings.java | 17 ++- ...AccountMappingFromApiJsonDeserializer.java | 5 + ...ToGLAccountMappingReadPlatformService.java | 4 + ...AccountMappingReadPlatformServiceImpl.java | 13 +++ ...ccountMappingWritePlatformServiceImpl.java | 6 + ...avingsProductToGLAccountMappingHelper.java | 21 ++++ .../savings/SavingsApiConstants.java | 5 +- .../api/SavingsProductsApiResource.java | 7 +- .../savings/data/SavingsProductData.java | 39 +++++-- .../data/SavingsProductDataValidator.java | 49 +++++++++ .../domain/SavingsAccountTransaction.java | 21 ++++ ...WritePlatformServiceJpaRepositoryImpl.java | 2 + 16 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql diff --git a/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql b/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql new file mode 100644 index 000000000..54d237239 --- /dev/null +++ b/mifosng-db/migrations/core_db/V118_6__defaults_for_income_from_penalties_for savings_product.sql @@ -0,0 +1,5 @@ +/***Set defaults for income from Penalties for existing Saving Products**/ +INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`payment_type`,`charge_id`,`financial_account_type`) +select mapping.gl_account_id,mapping.product_id,mapping.product_type,mapping.payment_type,mapping.charge_id, 5 +from acc_product_mapping mapping +where mapping.financial_account_type = 4 and mapping.product_type=2; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java index 1ccaabab3..92604063b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java @@ -108,7 +108,8 @@ public class AccountingConstants { /*** Accounting placeholders for cash based accounting for savings products ***/ public static enum CASH_ACCOUNTS_FOR_SAVINGS { - SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), TRANSFERS_SUSPENSE(10); + SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), TRANSFERS_SUSPENSE( + 10); private final Integer value; @@ -144,9 +145,11 @@ public class AccountingConstants { ***/ public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS { SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES( - "incomeFromFeeAccountId"), INTEREST_ON_SAVINGS("interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( - "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE( - "transfersInSuspenseAccountId"); + "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), INTEREST_ON_SAVINGS( + "interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( + "paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java index 00a4de949..dc0f50683 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java @@ -7,6 +7,7 @@ package org.mifosplatform.accounting.journalentry.data; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; @@ -23,14 +24,21 @@ public class SavingsTransactionDTO { /*** Boolean values determines if the transaction is reversed ***/ private final boolean reversed; + /** Breakdowns of fees and penalties this Transaction pays **/ + private final List penaltyPayments; + private final List feePayments; + public SavingsTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate, - final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed) { + final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed, + final List feePayments, final List penaltyPayments) { this.paymentTypeId = paymentTypeId; this.transactionId = transactionId; this.transactionDate = transactionDate; this.amount = amount; this.reversed = reversed; this.transactionType = transactionType; + this.feePayments = feePayments; + this.penaltyPayments = penaltyPayments; this.officeId = officeId; } @@ -62,4 +70,12 @@ public class SavingsTransactionDTO { return this.reversed; } + public List getPenaltyPayments() { + return this.penaltyPayments; + } + + public List getFeePayments() { + return this.feePayments; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java index 6dc54bf5e..07f62a68c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java @@ -92,7 +92,7 @@ public class AccountingProcessorHelper { final List> loanChargesPaidData = (List>) map.get("loanChargesPaid"); for (final Map loanChargePaid : loanChargesPaidData) { final Long chargeId = (Long) loanChargePaid.get("chargeId"); - final Long loanChargeId = (Long) loanChargePaid.get("chargeId"); + final Long loanChargeId = (Long) loanChargePaid.get("loanChargeId"); final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); @@ -135,8 +135,28 @@ public class AccountingProcessorHelper { final boolean reversed = (Boolean) map.get("reversed"); final Long paymentTypeId = (Long) map.get("paymentTypeId"); + final List feePayments = new ArrayList(); + final List penaltyPayments = new ArrayList(); + // extract charge payment details (if exists) + if (map.containsKey("savingsChargesPaid")) { + @SuppressWarnings("unchecked") + final List> savingsChargesPaidData = (List>) map.get("savingsChargesPaid"); + for (final Map loanChargePaid : savingsChargesPaidData) { + final Long chargeId = (Long) loanChargePaid.get("chargeId"); + final Long loanChargeId = (Long) loanChargePaid.get("savingsChargeId"); + final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); + final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); + final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); + if (isPenalty) { + penaltyPayments.add(chargePaymentDTO); + } else { + feePayments.add(chargePaymentDTO); + } + } + } + final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, - transactionDate, transactionType, amount, reversed); + transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments); newSavingsTransactions.add(transaction); @@ -378,6 +398,61 @@ public class AccountingProcessorHelper { totalCreditedAmount, totalAmount); } } + /** + * Convenience method that creates a pair of related Debits and Credits for + * Cash Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * @param paymentTypeId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode, + final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeDebited, final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeCredited, + final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, + final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal, + final List chargePaymentDTOs) { + // TODO Vishwas: Remove this validation, as and when appropriate Junit + // tests are written for accounting + /** + * Accounting module currently supports a single charge per transaction, + * throw an error if this is not the case here so any developers + * changing the expected portfolio behavior would also take care of + * modifying the accounting code appropriately + **/ + if (chargePaymentDTOs.size() != 1) { throw new PlatformDataIntegrityException( + "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code", + "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); } + ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); + + final GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), + chargePaymentDTO.getChargeId()); + final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), + paymentTypeId); + if (isReversal) { + createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + } else { + createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + } + } + private void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId, final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { @@ -464,6 +539,29 @@ public class AccountingProcessorHelper { return accountMapping.getGlAccount(); } + private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, final Long chargeId) { + ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId); + /***** + * Get more specific mappings for Charges and penalties (based on the + * actual charge /penalty coupled with the loan product). Note the + * income from fees and income from penalties placeholder ID would be + * the same for both cash and accrual based accounts + *****/ + + // Vishwas TODO: remove this condition as it should always be true + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue() + || accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) { + final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId, chargeId); + if (chargeSpecificIncomeAccountMapping != null) { + accountMapping = chargeSpecificIncomeAccountMapping; + } + } + return accountMapping.getGlAccount(); + } + private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId, final Long paymentTypeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, @@ -473,7 +571,7 @@ public class AccountingProcessorHelper { * channels). Note that fund source placeholder ID would be same for * both cash and accrual accounts ***/ - if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()) { + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue()) { final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId, PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java index 718e85ab3..07991cbd4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java @@ -7,9 +7,11 @@ package org.mifosplatform.accounting.journalentry.service; import java.math.BigDecimal; import java.util.Date; +import java.util.List; import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; +import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO; import org.mifosplatform.accounting.journalentry.data.SavingsDTO; import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO; import org.mifosplatform.organisation.office.domain.Office; @@ -39,6 +41,8 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId(); final boolean isReversal = savingsTransactionDTO.isReversed(); final BigDecimal amount = savingsTransactionDTO.getAmount(); + final List feePayments = savingsTransactionDTO.getFeePayments(); + final List penaltyPayments = savingsTransactionDTO.getPenaltyPayments(); this.helper.checkForBranchClosures(latestGLClosure, transactionDate); @@ -68,9 +72,16 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess /** Handle Fees Deductions and reversals of Fees Deductions **/ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { - this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, - CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId, - paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + // Is the Charge a penalty? + if (penaltyPayments.size() > 0) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments); + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); + } } /** Handle Transfers proposal **/ diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 5f08d7c58..978597db2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -176,6 +176,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .notNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()) + .value(incomeFromPenaltyId).notNull().integerGreaterThanZero(); + } throwExceptionIfValidationWarningsExist(dataValidationErrors); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java index 0cfa464b1..8c56d01ff 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java @@ -24,5 +24,9 @@ public interface ProductToGLAccountMappingReadPlatformService { public Map fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType); public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId); + + public List fetchFeeToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId); + + public List fetchPenaltyToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index c4e494969..cc59f506b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -169,6 +169,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) { accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), glAccountId); + } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE)) { accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS)) { @@ -225,6 +227,16 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true); } + @Override + public List fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, false); + } + + @Override + public List fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, true); + } + private List fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType, final Long loanProductId, final boolean penalty) { final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); @@ -249,4 +261,5 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product } return chargeToGLAccountMappers; } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 5ac062f57..88e62779f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -154,6 +154,10 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue()); + this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue()); + // expenses this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId, @@ -169,6 +173,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc // advanced accounting mappings this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); + this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null); break; /*** Do not have accrual based accounting for now ***/ case ACCRUAL_BASED: @@ -236,6 +241,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc element, accountingRuleType); this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId, changes); + this.savingsProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, savingsProductId, changes); } return changes; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java index 30825cc97..02b426f91 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -94,6 +94,20 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING); } + public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + final Map changes) { + // save both fee and penalty charges + saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); + saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); + } + + public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + final Map changes) { + // update both fee and penalty charges + updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true); + updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false); + } + public Map populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element, final AccountingRuleType accountingRuleType) { final Map changes = new HashMap(); @@ -102,6 +116,8 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed( SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); + final Long incomeFromPenaltiesId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); final Long interestOnSavingsId = this.fromApiJsonHelper.extractLongNamed( SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), @@ -117,6 +133,7 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingsReferenceId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeesId); + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId); break; case ACCRUAL_BASED: @@ -150,6 +167,10 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.toString(), changes); + mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_PENALTIES.toString(), changes); + // expenses mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(), diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index 5a77b82ea..b669f9672 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -128,10 +128,13 @@ public class SavingsApiConstants { withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), - SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())); + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue())); /** * These parameters will match the class level parameters of diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java index d9cebb180..3d416d574 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java @@ -26,6 +26,7 @@ import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService; import org.mifosplatform.commands.domain.CommandWrapper; @@ -159,8 +160,12 @@ public class SavingsProductsApiResource { .fetchAccountMappingDetailsForSavingsProduct(productId, savingProductData.accountingRuleTypeId()); final Collection paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId); + Collection feeToGLAccountMappings = this.accountMappingReadPlatformService + .fetchFeeToIncomeAccountMappingsForSavingsProduct(productId); + Collection penaltyToGLAccountMappings = this.accountMappingReadPlatformService + .fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId); savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings, - paymentChannelToFundSourceMappings); + paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings); } if (settings.isTemplate()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index 73221a561..a00e21f2e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -13,6 +13,7 @@ import java.util.Map; import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -46,6 +47,8 @@ public class SavingsProductData { private final EnumOptionData accountingRule; private final Map accountingMappings; private final Collection paymentChannelToFundSourceMappings; + private final Collection feeToIncomeAccountMappings; + private final Collection penaltyToIncomeAccountMappings; // charges private final Collection charges; @@ -91,6 +94,8 @@ public class SavingsProductData { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; final Collection charges = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -98,7 +103,8 @@ public class SavingsProductData { accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData withCharges(final SavingsProductData product, final Collection charges) { @@ -111,7 +117,8 @@ public class SavingsProductData { product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions, product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions, product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, - product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions); + product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, + product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings); } /** @@ -138,11 +145,15 @@ public class SavingsProductData { existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); + accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, + existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings); } public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, - final Map accountingMappings, final Collection paymentChannelToFundSourceMappings) { + final Map accountingMappings, + final Collection paymentChannelToFundSourceMappings, + final Collection feeToIncomeAccountMappings, + final Collection penaltyToIncomeAccountMappings) { final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -167,7 +178,8 @@ public class SavingsProductData { paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions); + accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, @@ -194,6 +206,8 @@ public class SavingsProductData { final Collection chargeOptions = null; final Collection penaltyOptions = null; final Collection charges = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -201,7 +215,8 @@ public class SavingsProductData { accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } public static SavingsProductData lookup(final Long id, final String name) { @@ -238,6 +253,8 @@ public class SavingsProductData { final Collection charges = null; final Collection chargeOptions = null; final Collection penaltyOptions = null; + final Collection feeToIncomeAccountMappings = null; + final Collection penaltyToIncomeAccountMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -245,7 +262,8 @@ public class SavingsProductData { accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, - accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions); + accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, + penaltyToIncomeAccountMappings); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, @@ -263,7 +281,9 @@ public class SavingsProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection charges, - final Collection chargeOptions, final Collection penaltyOptions) { + final Collection chargeOptions, final Collection penaltyOptions, + final Collection feeToIncomeAccountMappings, + final Collection penaltyToIncomeAccountMappings) { this.id = id; this.name = name; this.description = description; @@ -306,6 +326,9 @@ public class SavingsProductData { // Savings product this.penaltyOptions = penaltyOptions;// penalties available for adding // to Savings product + + this.feeToIncomeAccountMappings = feeToIncomeAccountMappings; + this.penaltyToIncomeAccountMappings = penaltyToIncomeAccountMappings; } public boolean hasAccountingEnabled() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index 0cadc5f04..72a5077fe 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -235,7 +235,13 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .notNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()) + .value(incomeFromPenaltyId).notNull().integerGreaterThanZero(); + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + validateChargeToIncomeAccountMappings(baseDataValidator, element); } throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -380,7 +386,13 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) .ignoreIfNull().integerGreaterThanZero(); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) + .ignoreIfNull().integerGreaterThanZero(); + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + validateChargeToIncomeAccountMappings(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -425,4 +437,41 @@ public class SavingsProductDataValidator { } } } + + private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + // validate for both fee and penalty charges + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + } + + private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element, + final boolean isPenalty) { + String parameterName; + if (isPenalty) { + parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); + } else { + parameterName = SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(); + } + + if (this.fromApiJsonHelper.parameterExists(parameterName, element)) { + final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element); + if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) { + int i = 0; + do { + final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); + final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue(), + jsonObject); + final Long incomeAccountId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), jsonObject); + baseDataValidator.reset() + .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()) + .value(chargeId).notNull().integerGreaterThanZero(); + baseDataValidator.reset() + .parameter(parameterName + "[" + i + "]." + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()) + .value(incomeAccountId).notNull().integerGreaterThanZero(); + i++; + } while (i < chargeToIncomeAccountMappingArray.size()); + } + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 29005cbb5..a9df3aa95 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -6,9 +6,11 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -315,6 +317,25 @@ public final class SavingsAccountTransaction extends AbstractPersistable { thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); } + /*** + * Sending data in a map, though in savings we currently expect a transaction to + * always repay a single charge (or may repay a part of a single charge + * too) + ***/ + if (!this.savingsAccountChargesPaid.isEmpty()) { + final List> savingsChargesPaidData = new ArrayList>(); + for (final SavingsAccountChargePaidBy chargePaidBy : this.savingsAccountChargesPaid) { + final Map savingChargePaidData = new LinkedHashMap(); + savingChargePaidData.put("chargeId", chargePaidBy.getSavingsAccountCharge().getCharge().getId()); + savingChargePaidData.put("isPenalty", chargePaidBy.getSavingsAccountCharge().getCharge().isPenalty()); + savingChargePaidData.put("savingsChargeId", chargePaidBy.getSavingsAccountCharge().getId()); + savingChargePaidData.put("amount", chargePaidBy.getAmount()); + + savingsChargesPaidData.add(savingChargePaidData); + } + thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData); + } + return thisTransactionData; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 63fb352f3..ff5489ac0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -787,6 +787,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.saveAndFlush(savingsAccount); + + postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountChargeId) //