diff --git a/mifosng-db/migrations/core_db/V54__charge-to-income-account-mappings.sql b/mifosng-db/migrations/core_db/V54__charge-to-income-account-mappings.sql new file mode 100755 index 000000000..ef63134cb --- /dev/null +++ b/mifosng-db/migrations/core_db/V54__charge-to-income-account-mappings.sql @@ -0,0 +1,3 @@ +ALTER TABLE `acc_product_mapping` + ADD COLUMN `charge_id` BIGINT(20) NULL DEFAULT NULL AFTER `payment_type`, + ADD CONSTRAINT `FK_acc_product_mapping_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`); \ 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 f9b47b31f..35de2586a 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 @@ -85,7 +85,8 @@ public class AccountingConstants { "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE( "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( "receivablePenaltyAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( - "paymentTypeId"); + "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( + "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ChargePaymentDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ChargePaymentDTO.java new file mode 100755 index 000000000..7dbde9413 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/ChargePaymentDTO.java @@ -0,0 +1,29 @@ +package org.mifosplatform.accounting.journalentry.data; + +import java.math.BigDecimal; + +public class ChargePaymentDTO { + + private final Long chargeId; + private final BigDecimal amount; + private final Long loanChargeId; + + public ChargePaymentDTO(Long chargeId, Long loanChargeId, BigDecimal amount) { + this.chargeId = chargeId; + this.amount = amount; + this.loanChargeId = loanChargeId; + } + + public Long getChargeId() { + return this.chargeId; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public Long getLoanChargeId() { + return this.loanChargeId; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java index e277c3a3a..cd17861a0 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/LoanTransactionDTO.java @@ -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.loanaccount.data.LoanTransactionEnumData; @@ -28,9 +29,14 @@ public class LoanTransactionDTO { /*** 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 LoanTransactionDTO(final Long paymentTypeId, final String transactionId, final Date transactionDate, final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest, - final BigDecimal fees, final BigDecimal penalties, final boolean reversed) { + final BigDecimal fees, final BigDecimal penalties, final boolean reversed, final List feePayments, + final List penaltyPayments) { this.paymentTypeId = paymentTypeId; this.transactionId = transactionId; this.transactionDate = transactionDate; @@ -41,6 +47,8 @@ public class LoanTransactionDTO { this.penalties = penalties; this.reversed = reversed; this.transactionType = transactionType; + this.feePayments = feePayments; + this.penaltyPayments = penaltyPayments; } public String getTransactionId() { @@ -83,4 +91,12 @@ public class LoanTransactionDTO { return this.paymentTypeId; } + 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 dc23811a0..7245e7949 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 @@ -8,6 +8,7 @@ package org.mifosplatform.accounting.journalentry.service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_ import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.glaccount.domain.GLAccount; +import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.accounting.journalentry.data.SavingsDTO; @@ -30,6 +32,7 @@ import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidEx import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; @@ -77,8 +80,28 @@ public class AccountingProcessorHelper { final boolean reversed = (Boolean) map.get("reversed"); final Long paymentTypeId = (Long) map.get("paymentTypeId"); + List feePayments = new ArrayList(); + List penaltyPayments = new ArrayList(); + // extract charge payment details (if exists) + if (map.containsKey("loanChargesPaid")) { + @SuppressWarnings("unchecked") + 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 boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty"); + final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount"); + ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid); + if (isPenalty) { + penaltyPayments.add(chargePaymentDTO); + } else { + feePayments.add(chargePaymentDTO); + } + } + } + final LoanTransactionDTO transaction = new LoanTransactionDTO(paymentTypeId, transactionId, transactionDate, transactionType, - amount, principal, interest, fees, penalties, reversed); + amount, principal, interest, fees, penalties, reversed, feePayments, penaltyPayments); newLoanTransactions.add(transaction); @@ -304,6 +327,48 @@ public class AccountingProcessorHelper { } } + public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final int accountMappingTypeId, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, + final BigDecimal totalAmount, final Boolean isReversal, final List chargePaymentDTOs) { + /*** + * Map to track each account and the net credit to be made for a + * particular account + ***/ + Map creditDetailsMap = new LinkedHashMap(); + for (ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { + Long chargeId = chargePaymentDTO.getChargeId(); + GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId); + BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); + + // adjust net credit amount if the account is already present in the + // map + if (creditDetailsMap.containsKey(chargeSpecificAccount)) { + BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); + chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); + } + creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); + } + + BigDecimal totalCreditedAmount = BigDecimal.ZERO; + for (Map.Entry entry : creditDetailsMap.entrySet()) { + GLAccount account = entry.getKey(); + BigDecimal amount = entry.getValue(); + totalCreditedAmount = totalCreditedAmount.add(amount); + if (isReversal) { + createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } else { + createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } + } + + // TODO: Vishwas Temporary validation to be removed before moving to + // release branch + if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException( + "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", + "Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction", + totalCreditedAmount, totalAmount); } + } + private void createCreditJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { @@ -363,6 +428,30 @@ public class AccountingProcessorHelper { accountMapping = paymentChannelSpecificAccountMapping; } } + + return accountMapping.getGlAccount(); + } + + 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 + *****/ + + // Vishwas TODO: remove this condition as it should always be true + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue() + || accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) { + ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(loanProductId, PortfolioProductType.LOAN.getValue(), + accountMappingTypeId, chargeId); + if (chargeSpecificIncomeAccountMapping != null) { + accountMapping = chargeSpecificIncomeAccountMapping; + } + } return accountMapping.getGlAccount(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index e22aa54e8..ed207651b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -134,14 +134,15 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); - helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES, loanProductId, paymentTypeId, - loanId, transactionId, transactionDate, feesAmount, isReversal); + helper.createCreditJournalEntryOrReversalForLoanCharges(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), + loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversal, loanTransactionDTO.getFeePayments()); } if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); - helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal); + helper.createCreditJournalEntryOrReversalForLoanCharges(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), + loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal, + loanTransactionDTO.getPenaltyPayments()); } /*** create a single debit entry (or reversal) for the entire amount **/ diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.java new file mode 100755 index 000000000..8558ab1de --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/data/ChargeToGLAccountMapper.java @@ -0,0 +1,20 @@ +/** + * 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.producttoaccountmapping.data; + +public class ChargeToGLAccountMapper { + + @SuppressWarnings("unused") + private final long chargeId; + @SuppressWarnings("unused") + private final long incomeAccountId; + + public ChargeToGLAccountMapper(long chargeId, long incomeAccountId) { + this.chargeId = chargeId; + this.incomeAccountId = incomeAccountId; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java index 40146f0de..1341dcc7a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java @@ -15,6 +15,7 @@ import javax.persistence.UniqueConstraint; import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.infrastructure.codes.domain.CodeValue; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -33,6 +34,10 @@ public class ProductToGLAccountMapping extends AbstractPersistable { @JoinColumn(name = "payment_type", nullable = true) private CodeValue paymentType; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "charge_id", nullable = true) + private Charge charge; + @Column(name = "product_type", nullable = false) private int productType; @@ -49,16 +54,27 @@ public class ProductToGLAccountMapping extends AbstractPersistable { } public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, final int financialAccountType) { - this(glAccount, productId, productType, financialAccountType, null); + this(glAccount, productId, productType, financialAccountType, null, null); + } + + public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, + final int financialAccountType, final Charge charge) { + this(glAccount, productId, productType, financialAccountType, null, charge); } public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, final int financialAccountType, final CodeValue paymentType) { + this(glAccount, productId, productType, financialAccountType, paymentType, null); + } + + private ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, + final int financialAccountType, final CodeValue paymentType, final Charge charge) { this.glAccount = glAccount; this.productId = productId; this.productType = productType; this.financialAccountType = financialAccountType; this.paymentType = paymentType; + this.charge = charge; } public GLAccount getGlAccount() { @@ -101,4 +117,12 @@ public class ProductToGLAccountMapping extends AbstractPersistable { this.paymentType = paymentType; } + public Charge getCharge() { + return this.charge; + } + + public void setCharge(Charge charge) { + this.charge = charge; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java index 0012b5061..537b38c44 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java @@ -18,7 +18,10 @@ public interface ProductToGLAccountMappingRepository extends JpaRepository findAllPaymentTypeToFundSourceMappings(@Param("productId") Long productId, @Param("productType") int productType); + /*** The financial Account Type for income from interest will always be 4 ***/ + @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=4 and mapping.charge is not NULL") + List findAllFeeToIncomeAccountMappings(@Param("productId") Long productId, + @Param("productType") int productType); + + /*** The financial Account Type for income from interest will always be 5 ***/ + @Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=5 and mapping.charge is not NULL") + List findAllPenaltyToIncomeAccountMappings(@Param("productId") Long productId, + @Param("productType") int productType); + List findByProductIdAndProductType(Long productId, int productType); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java index a34977867..1dcb5ca36 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -19,6 +19,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAc import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,8 +31,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi @Autowired public LoanProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository, final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper, - final CodeValueRepositoryWrapper codeValueRepositoryWrapper) { - super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper); + final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final ChargeRepositoryWrapper chargeRepositoryWrapper) { + super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper, chargeRepositoryWrapper); } /*** Set of abstractions for saving Loan Products to GL Account Mappings ***/ @@ -82,6 +83,20 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN); } + 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.LOAN, true); + saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, 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.LOAN, true); + updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false); + } + public Map populateChangesForNewLoanProductToGLAccountMappingCreation(final JsonElement element, final AccountingRuleType accountingRuleType) { final Map changes = new HashMap(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java index f34d959ad..46b266811 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -26,6 +26,8 @@ import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -41,15 +43,17 @@ public class ProductToGLAccountMappingHelper { protected final ProductToGLAccountMappingRepository accountMappingRepository; protected final FromJsonHelper fromApiJsonHelper; private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; + private final ChargeRepositoryWrapper chargeRepositoryWrapper; @Autowired public ProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository, final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper, - final CodeValueRepositoryWrapper codeValueRepositoryWrapper) { + final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final ChargeRepositoryWrapper chargeRepositoryWrapper) { this.accountRepository = glAccountRepository; this.accountMappingRepository = glAccountMappingRepository; this.fromApiJsonHelper = fromApiJsonHelper; this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; + this.chargeRepositoryWrapper = chargeRepositoryWrapper; } @@ -110,6 +114,128 @@ public class ProductToGLAccountMappingHelper { } } + /** + * Saves the Charge to Income account mappings for a particular + * product/product type (also populates the changes array if passed in) + * + * @param command + * @param element + * @param productId + * @param changes + */ + public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) { + String arrayName; + if (isPenalty) { + arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); + } else { + arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(); + } + + JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(arrayName, element); + if (chargeToIncomeAccountMappingArray != null) { + if (changes != null) { + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), + command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue())); + } + for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) { + final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); + final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong(); + final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong(); + saveChargeToFundSourceMapping(productId, chargeId, incomeAccountId, portfolioProductType, isPenalty); + } + } + } + + /** + * @param command + * @param element + * @param productId + * @param changes + */ + public void updateChargeToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes, final PortfolioProductType portfolioProductType, boolean isPenalty) { + // find all existing payment Channel to Fund source Mappings + List existingChargeToIncomeAccountMappings; + String arrayFragmentName; + + if (isPenalty) { + existingChargeToIncomeAccountMappings = accountMappingRepository.findAllPenaltyToIncomeAccountMappings(productId, + portfolioProductType.getValue()); + arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); + } else { + existingChargeToIncomeAccountMappings = accountMappingRepository.findAllFeeToIncomeAccountMappings(productId, + portfolioProductType.getValue()); + arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(); + } + + JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element); + /** + * Variable stores a map representation of charges (key) and their + * associated income Id's (value) extracted from the passed in + * Jsoncommand + **/ + Map inputChargeToIncomeAccountMap = new HashMap(); + /*** + * Variable stores all charges which have already been mapped to Income + * Accounts in the system + **/ + Set existingCharges = new HashSet(); + if (chargeToIncomeAccountMappingArray != null) { + if (changes != null) { + changes.put(arrayFragmentName, command.jsonFragment(arrayFragmentName)); + } + + for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) { + final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); + final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong(); + final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong(); + inputChargeToIncomeAccountMap.put(chargeId, incomeAccountId); + } + + // If input map is empty, delete all existing mappings + if (inputChargeToIncomeAccountMap.size() == 0) { + accountMappingRepository.deleteInBatch(existingChargeToIncomeAccountMappings); + }/** + * Else,
+ * update existing mappings OR
+ * delete old mappings (which are already present, but not passed in + * as a part of Jsoncommand)
+ * Create new mappings for charges that are passed in as a part of + * the Jsoncommand but not already present + * + **/ + else { + for (ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) { + Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId(); + existingCharges.add(currentCharge); + // update existing mappings (if required) + if (inputChargeToIncomeAccountMap.containsKey(currentCharge)) { + Long newGLAccountId = inputChargeToIncomeAccountMap.get(currentCharge); + if (newGLAccountId != chargeToIncomeAccountMapping.getGlAccount().getId()) { + final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), + GLAccountType.INCOME, newGLAccountId); + chargeToIncomeAccountMapping.setGlAccount(glAccount); + accountMappingRepository.save(chargeToIncomeAccountMapping); + } + }// deleted payment type + else { + accountMappingRepository.delete(chargeToIncomeAccountMapping); + } + } + // create new mappings + Set incomingCharges = inputChargeToIncomeAccountMap.keySet(); + incomingCharges.removeAll(existingCharges); + // incomingPaymentTypes now only contains the newly added + // payment Type mappings + for (Long newCharge : incomingCharges) { + Long newGLAccountId = inputChargeToIncomeAccountMap.get(newCharge); + saveChargeToFundSourceMapping(productId, newCharge, newGLAccountId, portfolioProductType, isPenalty); + } + } + } + } + /** * @param command * @param element @@ -204,6 +330,34 @@ public class ProductToGLAccountMappingHelper { this.accountMappingRepository.save(accountMapping); } + /** + * @param productId + * @param jsonObject + */ + private void saveChargeToFundSourceMapping(final Long productId, final Long chargeId, final Long incomeAccountId, + final PortfolioProductType portfolioProductType, final boolean isPenalty) { + Charge charge = chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId); + + // TODO Vishwas: Need to validate if given charge is fee or Penalty + // based on input condition + + final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(), + GLAccountType.INCOME, incomeAccountId); + /** + * Both CASH and Accrual placeholders have the same value for income + * from Interest and penalties + **/ + CASH_ACCOUNTS_FOR_LOAN placeHolderAccountType; + if (isPenalty) { + placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES; + } else { + placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES; + } + final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, + portfolioProductType.getValue(), placeHolderAccountType.getValue(), charge); + this.accountMappingRepository.save(accountMapping); + } + /** * Fetches account with a particular Id and throws and Exception it is not * of the expected Account Category ('ASSET','liability' etc) 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 389bf6904..0cfa464b1 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 @@ -8,6 +8,7 @@ package org.mifosplatform.accounting.producttoaccountmapping.service; import java.util.List; import java.util.Map; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; public interface ProductToGLAccountMappingReadPlatformService { @@ -16,7 +17,12 @@ public interface ProductToGLAccountMappingReadPlatformService { public List fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId); + public List fetchFeeToIncomeAccountMappingsForLoanProduct(final Long loanProductId); + + public List fetchPenaltyToIncomeAccountMappingsForLoanProduct(final Long loanProductId); + public Map fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType); public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(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 6a069ebc2..5d6561a63 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 @@ -18,8 +18,10 @@ import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.common.AccountingRuleType; +import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; @@ -39,8 +41,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product private static final class ProductToGLAccountMappingMapper implements RowMapper> { public String schema() { - return " id as id, gl_account_id as glAccountId,product_id as productId,product_type as productType,financial_account_type as financialAccountType," - + " payment_type as paymentTypeId from acc_product_mapping " + " where product_type= ? "; + return " mapping.id as id, mapping.gl_account_id as glAccountId, mapping.product_id as productId, " + + " mapping.product_type as productType,mapping.financial_account_type as financialAccountType, " + + " mapping.payment_type as paymentTypeId, mapping.charge_id as chargeId, charge.is_penalty as penalty " + + " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id " + + " where mapping.product_type= ? "; } @Override @@ -49,7 +54,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product final Long id = rs.getLong("id"); final Long glAccountId = rs.getLong("glAccountId"); final Long productId = rs.getLong("productId"); - final Long paymentTypeId = rs.getLong("paymentTypeId"); + final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId"); + final Long chargeId = rs.getLong("chargeId"); final Integer productType = rs.getInt("productType"); final Integer financialAccountType = rs.getInt("financialAccountType"); @@ -60,6 +66,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product loanProductToGLAccountMap.put("productType", productType); loanProductToGLAccountMap.put("financialAccountType", financialAccountType); loanProductToGLAccountMap.put("paymentTypeId", paymentTypeId); + loanProductToGLAccountMap.put("chargeId", chargeId); return loanProductToGLAccountMap; } @@ -71,7 +78,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product final Map accountMappingDetails = new LinkedHashMap(8); final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null "; + final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null"; final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] { PortfolioProductType.LOAN.getValue(), loanProductId }); @@ -138,7 +145,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product final Map accountMappingDetails = new LinkedHashMap(8); final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null "; + final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null "; final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] { PortfolioProductType.SAVING.getValue(), savingsProductId }); @@ -168,14 +175,12 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product @Override public List fetchPaymentTypeToFundSourceMappingsForLoanProduct(Long loanProductId) { - List paymentTypeToGLAccountMappers = null; - return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId, paymentTypeToGLAccountMappers); + return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId); } @Override public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(Long savingsProductId) { - List paymentTypeToGLAccountMappers = null; - return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId, paymentTypeToGLAccountMappers); + return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId); } /** @@ -184,13 +189,14 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product * @return */ private List fetchPaymentTypeToFundSourceMappings(PortfolioProductType portfolioProductType, - Long loanProductId, List paymentTypeToGLAccountMappers) { + Long loanProductId) { final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null"; final List> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] { portfolioProductType.getValue(), loanProductId }); + List paymentTypeToGLAccountMappers = null; for (final Map productToGLAccountMap : paymentTypeToFundSourceMappingsList) { if (paymentTypeToGLAccountMappers == null) { paymentTypeToGLAccountMappers = new ArrayList(); @@ -203,4 +209,38 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product return paymentTypeToGLAccountMappers; } + @Override + public List fetchFeeToIncomeAccountMappingsForLoanProduct(Long loanProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, false); + } + + @Override + public List fetchPenaltyToIncomeAccountMappingsForLoanProduct(Long loanProductId) { + return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true); + } + + private List fetchChargeToIncomeAccountMappings(PortfolioProductType portfolioProductType, Long loanProductId, + boolean penalty) { + final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); + String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_id is not null and charge.is_penalty="; + if (penalty) { + sql = sql + " 1"; + } else { + sql = sql + " 0"; + } + + final List> chargeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] { + portfolioProductType.getValue(), loanProductId }); + List chargeToGLAccountMappers = null; + for (final Map chargeToIncomeAccountMap : chargeToFundSourceMappingsList) { + if (chargeToGLAccountMappers == null) { + chargeToGLAccountMappers = new ArrayList(); + } + final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId"); + final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId"); + ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper(chargeId, glAccountId); + chargeToGLAccountMappers.add(chargeToGLAccountMapper); + } + 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 def760091..cb6df514a 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 @@ -81,6 +81,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc // advanced accounting mappings loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); + loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null); break; case ACCRUAL_BASED: @@ -119,6 +120,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc // advanced accounting mappings loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); + loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null); break; } } 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 a32857d5a..43d535d12 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 @@ -18,6 +18,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAc import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -29,8 +30,8 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa @Autowired public SavingsProductToGLAccountMappingHelper(GLAccountRepository glAccountRepository, ProductToGLAccountMappingRepository glAccountMappingRepository, FromJsonHelper fromApiJsonHelper, - CodeValueRepositoryWrapper codeValueRepositoryWrapper) { - super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper); + CodeValueRepositoryWrapper codeValueRepositoryWrapper, ChargeRepositoryWrapper chargeRepositoryWrapper) { + super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper, chargeRepositoryWrapper); } /*** Set of abstractions for saving Saving Products to GL Account Mappings ***/ diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 918076ea6..519167349 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1374,8 +1374,8 @@ public class Loan extends AbstractPersistable { if (charge.isDueAtDisbursement()) { charge.markAsFullyPaid(); // Add "Loan Charge Paid By" details to this transaction - LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(disbursementTransaction, charge, charge.amount()); - disbursementTransaction.getLoanChargesPaid().add(loanChargePaidBy); + LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount()); + chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); } else { handleChargeAppliedTransaction(charge, disbursedOn); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index a1a8ff9e7..c6000a1e0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -487,6 +487,10 @@ public class LoanCharge extends AbstractPersistable { return this.charge.getCurrencyCode(); } + public Charge getCharge() { + return this.charge; + } + @Override public boolean equals(final Object obj) { if (obj == null) { return false; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 3e6a856a9..56b549ea0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -6,9 +6,11 @@ package org.mifosplatform.portfolio.loanaccount.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; @@ -374,6 +376,20 @@ public final class LoanTransaction extends AbstractPersistable { thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); } + if (!this.loanChargesPaid.isEmpty()) { + final List> loanChargesPaidData = new ArrayList>(); + for (LoanChargePaidBy chargePaidBy : this.loanChargesPaid) { + final Map loanChargePaidData = new LinkedHashMap(); + loanChargePaidData.put("chargeId", chargePaidBy.getLoanCharge().getCharge().getId()); + loanChargePaidData.put("isPenalty", chargePaidBy.getLoanCharge().getCharge().isPenalty()); + loanChargePaidData.put("loanChargeId", chargePaidBy.getLoanCharge().getId()); + loanChargePaidData.put("amount", chargePaidBy.getAmount()); + + loanChargesPaidData.add(loanChargePaidData); + } + thisTransactionData.put("loanChargesPaid", loanChargesPaidData); + } + return thisTransactionData; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index cce082ab1..0cf9dfb2e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -7,7 +7,6 @@ package org.mifosplatform.portfolio.loanproduct.api; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -26,6 +25,7 @@ import javax.ws.rs.core.UriInfo; import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService; 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; @@ -143,7 +143,7 @@ public class LoanProductsApiResource { context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveNewLoanProductDetails(); - loanProduct = handleTemplate(loanProduct, new HashMap(), null); + loanProduct = handleTemplate(loanProduct); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, loanProduct, LOAN_PRODUCT_DATA_PARAMETERS); @@ -163,16 +163,21 @@ public class LoanProductsApiResource { Map accountingMappings = null; Collection paymentChannelToFundSourceMappings = null; + Collection feeToGLAccountMappings = null; + Collection penaltyToGLAccountMappings = null; if (loanProduct.hasAccountingEnabled()) { accountingMappings = accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId, loanProduct .accountingRuleType().getId().intValue()); paymentChannelToFundSourceMappings = accountMappingReadPlatformService .fetchPaymentTypeToFundSourceMappingsForLoanProduct(productId); - loanProduct = LoanProductData.withAccountingDetails(loanProduct, accountingMappings, paymentChannelToFundSourceMappings); + feeToGLAccountMappings = accountMappingReadPlatformService.fetchFeeToIncomeAccountMappingsForLoanProduct(productId); + penaltyToGLAccountMappings = accountMappingReadPlatformService.fetchFeeToIncomeAccountMappingsForLoanProduct(productId); + loanProduct = LoanProductData.withAccountingDetails(loanProduct, accountingMappings, paymentChannelToFundSourceMappings, + feeToGLAccountMappings, penaltyToGLAccountMappings); } if (settings.isTemplate()) { - loanProduct = handleTemplate(loanProduct, accountingMappings, paymentChannelToFundSourceMappings); + loanProduct = handleTemplate(loanProduct); } return this.toApiJsonSerializer.serialize(settings, loanProduct, LOAN_PRODUCT_DATA_PARAMETERS); } @@ -191,8 +196,7 @@ public class LoanProductsApiResource { return this.toApiJsonSerializer.serialize(result); } - private LoanProductData handleTemplate(final LoanProductData productData, final Map accountingMappings, - final Collection paymentChannelToFundSourceMappings) { + private LoanProductData handleTemplate(final LoanProductData productData) { final boolean feeChargesOnly = true; Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly); @@ -225,7 +229,6 @@ public class LoanProductsApiResource { return new LoanProductData(productData, chargeOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, - fundOptions, transactionProcessingStrategyOptions, accountOptions, accountingRuleTypeOptions, accountingMappings, - paymentChannelToFundSourceMappings); + fundOptions, transactionProcessingStrategyOptions, accountOptions, accountingRuleTypeOptions); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index 5791981c5..f9b31a577 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -14,6 +14,7 @@ import java.util.Map; 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.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -68,11 +69,11 @@ public class LoanProductData { private final Collection charges; // accounting - private final EnumOptionData accountingRule; - @SuppressWarnings("unused") - private final Map accountingMappings; - @SuppressWarnings("unused") - private final Collection paymentChannelToFundSourceMappings; + private EnumOptionData accountingRule; + private Map accountingMappings; + private Collection paymentChannelToFundSourceMappings; + private Collection feeToIncomeAccountMappings; + private Collection penaltyToIncomeAccountMappings; // template related private final Collection fundOptions; @@ -212,13 +213,14 @@ public class LoanProductData { } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, - final Collection paymentChannelToFundSourceMappings) { - - return new LoanProductData(productData, productData.chargeOptions, productData.paymentTypeOptions, productData.currencyOptions, - productData.amortizationTypeOptions, productData.interestTypeOptions, productData.interestCalculationPeriodTypeOptions, - productData.repaymentFrequencyTypeOptions, productData.interestRateFrequencyTypeOptions, productData.fundOptions, - productData.transactionProcessingStrategyOptions, productData.accountingMappingOptions, productData.accountingRuleOptions, - accountingMappings, paymentChannelToFundSourceMappings); + final Collection paymentChannelToFundSourceMappings, + final Collection feeToGLAccountMappings, + final Collection penaltyToGLAccountMappings) { + productData.accountingMappings = accountingMappings; + productData.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings; + productData.feeToIncomeAccountMappings = feeToGLAccountMappings; + productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings; + return productData; } public LoanProductData(final Long id, final String name, final String description, final CurrencyData currency, @@ -277,6 +279,8 @@ public class LoanProductData { this.accountingRuleOptions = null; this.accountingMappings = null; this.paymentChannelToFundSourceMappings = null; + this.feeToIncomeAccountMappings = null; + this.penaltyToIncomeAccountMappings = null; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -285,8 +289,7 @@ public class LoanProductData { final List interestCalculationPeriodTypeOptions, final List repaymentFrequencyTypeOptions, final List interestRateFrequencyTypeOptions, final Collection fundOptions, final Collection transactionStrategyOptions, - final Map> accountingMappingOptions, final List accountingRuleOptions, - final Map accountingMappings, final Collection paymentChannelToFundSourceMappings) { + final Map> accountingMappingOptions, final List accountingRuleOptions) { this.id = productData.id; this.name = productData.name; this.description = productData.description; @@ -313,6 +316,10 @@ public class LoanProductData { this.charges = nullIfEmpty(productData.charges()); this.accountingRule = productData.accountingRule; + this.accountingMappings = productData.accountingMappings; + this.paymentChannelToFundSourceMappings = productData.paymentChannelToFundSourceMappings; + this.feeToIncomeAccountMappings = productData.feeToIncomeAccountMappings; + this.penaltyToIncomeAccountMappings = productData.penaltyToIncomeAccountMappings; this.chargeOptions = chargeOptions; this.paymentTypeOptions = paymentTypeOptions; @@ -344,13 +351,7 @@ public class LoanProductData { this.accountingMappingOptions = accountingMappingOptions; this.accountingRuleOptions = accountingRuleOptions; - if (accountingMappings == null || accountingMappings.isEmpty()) { - this.accountingMappings = null; - } else { - this.accountingMappings = accountingMappings; - } - - this.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings; + } private Collection nullIfEmpty(final Collection charges) {