Merge pull request #608 from vishwasbabu/temp

Temp
This commit is contained in:
Vishwas Babu A J
2013-09-24 14:24:28 -07:00
16 changed files with 306 additions and 21 deletions
@@ -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;
@@ -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;
@@ -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<ChargePaymentDTO> penaltyPayments;
private final List<ChargePaymentDTO> 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<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> 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<ChargePaymentDTO> getPenaltyPayments() {
return this.penaltyPayments;
}
public List<ChargePaymentDTO> getFeePayments() {
return this.feePayments;
}
}
@@ -92,7 +92,7 @@ public class AccountingProcessorHelper {
final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid");
for (final Map<String, Object> 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<ChargePaymentDTO> feePayments = new ArrayList<ChargePaymentDTO>();
final List<ChargePaymentDTO> penaltyPayments = new ArrayList<ChargePaymentDTO>();
// extract charge payment details (if exists)
if (map.containsKey("savingsChargesPaid")) {
@SuppressWarnings("unchecked")
final List<Map<String, Object>> savingsChargesPaidData = (List<Map<String, Object>>) map.get("savingsChargesPaid");
for (final Map<String, Object> 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<ChargePaymentDTO> 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);
@@ -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<ChargePaymentDTO> feePayments = savingsTransactionDTO.getFeePayments();
final List<ChargePaymentDTO> 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 **/
@@ -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);
@@ -24,5 +24,9 @@ public interface ProductToGLAccountMappingReadPlatformService {
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType);
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId);
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
}
@@ -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<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, false);
}
@Override
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(Long savingsProductId) {
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SAVING, savingsProductId, true);
}
private List<ChargeToGLAccountMapper> 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;
}
}
@@ -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;
}
@@ -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<String, Object> 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<String, Object> 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<String, Object> populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element,
final AccountingRuleType accountingRuleType) {
final Map<String, Object> changes = new HashMap<String, Object>();
@@ -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(),
@@ -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
@@ -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<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
.fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = this.accountMappingReadPlatformService
.fetchFeeToIncomeAccountMappingsForSavingsProduct(productId);
Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = this.accountMappingReadPlatformService
.fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId);
savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings,
paymentChannelToFundSourceMappings);
paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings);
}
if (settings.isTemplate()) {
@@ -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<String, Object> accountingMappings;
private final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
private final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
private final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
// charges
private final Collection<ChargeData> charges;
@@ -91,6 +94,8 @@ public class SavingsProductData {
final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
final Collection<ChargeData> charges = null;
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> 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<ChargeData> 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<String, Object> accountingMappings, final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings) {
final Map<String, Object> accountingMappings,
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings) {
final Collection<CurrencyData> currencyOptions = null;
final Collection<EnumOptionData> 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<ChargeData> chargeOptions = null;
final Collection<ChargeData> penaltyOptions = null;
final Collection<ChargeData> charges = null;
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> 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<ChargeData> charges = null;
final Collection<ChargeData> chargeOptions = null;
final Collection<ChargeData> penaltyOptions = null;
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> 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<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions) {
final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions,
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
final Collection<ChargeToGLAccountMapper> 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() {
@@ -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());
}
}
}
}
@@ -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<Long> {
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<Map<String, Object>> savingsChargesPaidData = new ArrayList<Map<String, Object>>();
for (final SavingsAccountChargePaidBy chargePaidBy : this.savingsAccountChargesPaid) {
final Map<String, Object> savingChargePaidData = new LinkedHashMap<String, Object>();
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;
}
@@ -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) //