+5
@@ -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;
|
||||||
+7
-4
@@ -108,7 +108,8 @@ public class AccountingConstants {
|
|||||||
|
|
||||||
/*** Accounting placeholders for cash based accounting for savings products ***/
|
/*** Accounting placeholders for cash based accounting for savings products ***/
|
||||||
public static enum CASH_ACCOUNTS_FOR_SAVINGS {
|
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;
|
private final Integer value;
|
||||||
|
|
||||||
@@ -144,9 +145,11 @@ public class AccountingConstants {
|
|||||||
***/
|
***/
|
||||||
public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS {
|
public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS {
|
||||||
SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES(
|
SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES(
|
||||||
"incomeFromFeeAccountId"), INTEREST_ON_SAVINGS("interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING(
|
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), INTEREST_ON_SAVINGS(
|
||||||
"paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE(
|
"interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
|
||||||
"transfersInSuspenseAccountId");
|
"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;
|
private final String value;
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -7,6 +7,7 @@ package org.mifosplatform.accounting.journalentry.data;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData;
|
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData;
|
||||||
|
|
||||||
@@ -23,14 +24,21 @@ public class SavingsTransactionDTO {
|
|||||||
/*** Boolean values determines if the transaction is reversed ***/
|
/*** Boolean values determines if the transaction is reversed ***/
|
||||||
private final boolean 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,
|
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.paymentTypeId = paymentTypeId;
|
||||||
this.transactionId = transactionId;
|
this.transactionId = transactionId;
|
||||||
this.transactionDate = transactionDate;
|
this.transactionDate = transactionDate;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.reversed = reversed;
|
this.reversed = reversed;
|
||||||
this.transactionType = transactionType;
|
this.transactionType = transactionType;
|
||||||
|
this.feePayments = feePayments;
|
||||||
|
this.penaltyPayments = penaltyPayments;
|
||||||
this.officeId = officeId;
|
this.officeId = officeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,4 +70,12 @@ public class SavingsTransactionDTO {
|
|||||||
return this.reversed;
|
return this.reversed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ChargePaymentDTO> getPenaltyPayments() {
|
||||||
|
return this.penaltyPayments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChargePaymentDTO> getFeePayments() {
|
||||||
|
return this.feePayments;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-3
@@ -92,7 +92,7 @@ public class AccountingProcessorHelper {
|
|||||||
final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid");
|
final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid");
|
||||||
for (final Map<String, Object> loanChargePaid : loanChargesPaidData) {
|
for (final Map<String, Object> loanChargePaid : loanChargesPaidData) {
|
||||||
final Long chargeId = (Long) loanChargePaid.get("chargeId");
|
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 boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty");
|
||||||
final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount");
|
final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount");
|
||||||
final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid);
|
final ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid);
|
||||||
@@ -135,8 +135,28 @@ public class AccountingProcessorHelper {
|
|||||||
final boolean reversed = (Boolean) map.get("reversed");
|
final boolean reversed = (Boolean) map.get("reversed");
|
||||||
final Long paymentTypeId = (Long) map.get("paymentTypeId");
|
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,
|
final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
|
||||||
transactionDate, transactionType, amount, reversed);
|
transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments);
|
||||||
|
|
||||||
newSavingsTransactions.add(transaction);
|
newSavingsTransactions.add(transaction);
|
||||||
|
|
||||||
@@ -378,6 +398,61 @@ public class AccountingProcessorHelper {
|
|||||||
totalCreditedAmount, totalAmount); }
|
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,
|
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 Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate,
|
||||||
final BigDecimal amount, final Boolean isReversal) {
|
final BigDecimal amount, final Boolean isReversal) {
|
||||||
@@ -464,6 +539,29 @@ public class AccountingProcessorHelper {
|
|||||||
return accountMapping.getGlAccount();
|
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,
|
private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId,
|
||||||
final Long paymentTypeId) {
|
final Long paymentTypeId) {
|
||||||
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
|
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
|
||||||
@@ -473,7 +571,7 @@ public class AccountingProcessorHelper {
|
|||||||
* channels). Note that fund source placeholder ID would be same for
|
* channels). Note that fund source placeholder ID would be same for
|
||||||
* both cash and accrual accounts
|
* 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
|
final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository
|
||||||
.findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId,
|
.findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId,
|
||||||
PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId);
|
PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId);
|
||||||
|
|||||||
+14
-3
@@ -7,9 +7,11 @@ package org.mifosplatform.accounting.journalentry.service;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
|
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.SavingsDTO;
|
||||||
import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO;
|
import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO;
|
||||||
import org.mifosplatform.organisation.office.domain.Office;
|
import org.mifosplatform.organisation.office.domain.Office;
|
||||||
@@ -39,6 +41,8 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
|
|||||||
final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId();
|
final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId();
|
||||||
final boolean isReversal = savingsTransactionDTO.isReversed();
|
final boolean isReversal = savingsTransactionDTO.isReversed();
|
||||||
final BigDecimal amount = savingsTransactionDTO.getAmount();
|
final BigDecimal amount = savingsTransactionDTO.getAmount();
|
||||||
|
final List<ChargePaymentDTO> feePayments = savingsTransactionDTO.getFeePayments();
|
||||||
|
final List<ChargePaymentDTO> penaltyPayments = savingsTransactionDTO.getPenaltyPayments();
|
||||||
|
|
||||||
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
|
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
|
||||||
|
|
||||||
@@ -68,9 +72,16 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
|
|||||||
|
|
||||||
/** Handle Fees Deductions and reversals of Fees Deductions **/
|
/** Handle Fees Deductions and reversals of Fees Deductions **/
|
||||||
else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) {
|
else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) {
|
||||||
this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
|
// Is the Charge a penalty?
|
||||||
CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId,
|
if (penaltyPayments.size() > 0) {
|
||||||
paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
|
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 **/
|
/** Handle Transfers proposal **/
|
||||||
|
|||||||
+5
@@ -176,6 +176,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
|
|||||||
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
||||||
.notNull().integerGreaterThanZero();
|
.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);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
|||||||
+4
@@ -24,5 +24,9 @@ public interface ProductToGLAccountMappingReadPlatformService {
|
|||||||
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType);
|
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType);
|
||||||
|
|
||||||
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId);
|
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId);
|
||||||
|
|
||||||
|
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
|
||||||
|
|
||||||
|
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForSavingsProduct(final Long savingsProductId);
|
||||||
|
|
||||||
}
|
}
|
||||||
+13
@@ -169,6 +169,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
|||||||
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), glAccountId);
|
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), glAccountId);
|
||||||
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) {
|
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) {
|
||||||
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), glAccountId);
|
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)) {
|
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE)) {
|
||||||
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId);
|
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId);
|
||||||
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS)) {
|
} 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);
|
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,
|
private List<ChargeToGLAccountMapper> fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType,
|
||||||
final Long loanProductId, final boolean penalty) {
|
final Long loanProductId, final boolean penalty) {
|
||||||
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
||||||
@@ -249,4 +261,5 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
|||||||
}
|
}
|
||||||
return chargeToGLAccountMappers;
|
return chargeToGLAccountMappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+6
@@ -154,6 +154,10 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId,
|
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId,
|
||||||
CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue());
|
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
|
// expenses
|
||||||
this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
|
this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
|
||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId,
|
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId,
|
||||||
@@ -169,6 +173,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
|
|
||||||
// advanced accounting mappings
|
// advanced accounting mappings
|
||||||
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
|
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
|
||||||
|
this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
|
||||||
break;
|
break;
|
||||||
/*** Do not have accrual based accounting for now ***/
|
/*** Do not have accrual based accounting for now ***/
|
||||||
case ACCRUAL_BASED:
|
case ACCRUAL_BASED:
|
||||||
@@ -236,6 +241,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
|||||||
element, accountingRuleType);
|
element, accountingRuleType);
|
||||||
this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId,
|
this.savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId,
|
||||||
changes);
|
changes);
|
||||||
|
this.savingsProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, savingsProductId, changes);
|
||||||
}
|
}
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -94,6 +94,20 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
|
|||||||
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
|
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,
|
public Map<String, Object> populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element,
|
||||||
final AccountingRuleType accountingRuleType) {
|
final AccountingRuleType accountingRuleType) {
|
||||||
final Map<String, Object> changes = new HashMap<String, Object>();
|
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);
|
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element);
|
||||||
final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed(
|
final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed(
|
||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
|
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(
|
final Long interestOnSavingsId = this.fromApiJsonHelper.extractLongNamed(
|
||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
|
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element);
|
||||||
final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(),
|
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.SAVINGS_REFERENCE.getValue(), savingsReferenceId);
|
||||||
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId);
|
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_FEES.getValue(), incomeFromFeesId);
|
||||||
|
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId);
|
||||||
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
|
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
|
||||||
break;
|
break;
|
||||||
case ACCRUAL_BASED:
|
case ACCRUAL_BASED:
|
||||||
@@ -150,6 +167,10 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
|
|||||||
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(),
|
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(),
|
||||||
CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.toString(), changes);
|
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
|
// expenses
|
||||||
mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
|
mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(),
|
||||||
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(),
|
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(),
|
||||||
|
|||||||
+4
-1
@@ -128,10 +128,13 @@ public class SavingsApiConstants {
|
|||||||
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
|
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
|
||||||
annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
|
annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
|
||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
|
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.INTEREST_ON_SAVINGS.getValue(),
|
||||||
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.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_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
|
* These parameters will match the class level parameters of
|
||||||
|
|||||||
+6
-1
@@ -26,6 +26,7 @@ import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService
|
|||||||
import org.mifosplatform.accounting.common.AccountingEnumerations;
|
import org.mifosplatform.accounting.common.AccountingEnumerations;
|
||||||
import org.mifosplatform.accounting.common.AccountingRuleType;
|
import org.mifosplatform.accounting.common.AccountingRuleType;
|
||||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
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.data.PaymentTypeToGLAccountMapper;
|
||||||
import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
|
import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
|
||||||
import org.mifosplatform.commands.domain.CommandWrapper;
|
import org.mifosplatform.commands.domain.CommandWrapper;
|
||||||
@@ -159,8 +160,12 @@ public class SavingsProductsApiResource {
|
|||||||
.fetchAccountMappingDetailsForSavingsProduct(productId, savingProductData.accountingRuleTypeId());
|
.fetchAccountMappingDetailsForSavingsProduct(productId, savingProductData.accountingRuleTypeId());
|
||||||
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
|
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = this.accountMappingReadPlatformService
|
||||||
.fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
|
.fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId);
|
||||||
|
Collection<ChargeToGLAccountMapper> feeToGLAccountMappings = this.accountMappingReadPlatformService
|
||||||
|
.fetchFeeToIncomeAccountMappingsForSavingsProduct(productId);
|
||||||
|
Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings = this.accountMappingReadPlatformService
|
||||||
|
.fetchPenaltyToIncomeAccountMappingsForSavingsProduct(productId);
|
||||||
savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings,
|
savingProductData = SavingsProductData.withAccountingDetails(savingProductData, accountingMappings,
|
||||||
paymentChannelToFundSourceMappings);
|
paymentChannelToFundSourceMappings, feeToGLAccountMappings, penaltyToGLAccountMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.isTemplate()) {
|
if (settings.isTemplate()) {
|
||||||
|
|||||||
+31
-8
@@ -13,6 +13,7 @@ import java.util.Map;
|
|||||||
import org.joda.time.MonthDay;
|
import org.joda.time.MonthDay;
|
||||||
import org.mifosplatform.accounting.common.AccountingRuleType;
|
import org.mifosplatform.accounting.common.AccountingRuleType;
|
||||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
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.data.PaymentTypeToGLAccountMapper;
|
||||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||||
@@ -46,6 +47,8 @@ public class SavingsProductData {
|
|||||||
private final EnumOptionData accountingRule;
|
private final EnumOptionData accountingRule;
|
||||||
private final Map<String, Object> accountingMappings;
|
private final Map<String, Object> accountingMappings;
|
||||||
private final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
|
private final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
|
||||||
|
private final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
|
||||||
|
private final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
|
||||||
|
|
||||||
// charges
|
// charges
|
||||||
private final Collection<ChargeData> charges;
|
private final Collection<ChargeData> charges;
|
||||||
@@ -91,6 +94,8 @@ public class SavingsProductData {
|
|||||||
final Map<String, Object> accountingMappings = null;
|
final Map<String, Object> accountingMappings = null;
|
||||||
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
|
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
|
||||||
final Collection<ChargeData> charges = 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,
|
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
|
||||||
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
||||||
@@ -98,7 +103,8 @@ public class SavingsProductData {
|
|||||||
accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
||||||
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
||||||
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
|
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) {
|
public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
|
||||||
@@ -111,7 +117,8 @@ public class SavingsProductData {
|
|||||||
product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
|
product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
|
||||||
product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions,
|
product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions,
|
||||||
product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions,
|
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,
|
existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
|
||||||
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
||||||
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
|
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,
|
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<CurrencyData> currencyOptions = null;
|
||||||
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
|
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
|
||||||
@@ -167,7 +178,8 @@ public class SavingsProductData {
|
|||||||
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
|
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
|
||||||
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
||||||
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
|
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,
|
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> chargeOptions = null;
|
||||||
final Collection<ChargeData> penaltyOptions = null;
|
final Collection<ChargeData> penaltyOptions = null;
|
||||||
final Collection<ChargeData> charges = 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,
|
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
|
||||||
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
||||||
@@ -201,7 +215,8 @@ public class SavingsProductData {
|
|||||||
accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
||||||
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
||||||
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
|
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) {
|
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> charges = null;
|
||||||
final Collection<ChargeData> chargeOptions = null;
|
final Collection<ChargeData> chargeOptions = null;
|
||||||
final Collection<ChargeData> penaltyOptions = 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,
|
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
|
||||||
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
|
||||||
@@ -245,7 +262,8 @@ public class SavingsProductData {
|
|||||||
accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
|
||||||
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
|
||||||
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
|
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,
|
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<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
|
||||||
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
|
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
|
||||||
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges,
|
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.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@@ -306,6 +326,9 @@ public class SavingsProductData {
|
|||||||
// Savings product
|
// Savings product
|
||||||
this.penaltyOptions = penaltyOptions;// penalties available for adding
|
this.penaltyOptions = penaltyOptions;// penalties available for adding
|
||||||
// to Savings product
|
// to Savings product
|
||||||
|
|
||||||
|
this.feeToIncomeAccountMappings = feeToIncomeAccountMappings;
|
||||||
|
this.penaltyToIncomeAccountMappings = penaltyToIncomeAccountMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAccountingEnabled() {
|
public boolean hasAccountingEnabled() {
|
||||||
|
|||||||
+49
@@ -235,7 +235,13 @@ public class SavingsProductDataValidator {
|
|||||||
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
||||||
.notNull().integerGreaterThanZero();
|
.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);
|
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
|
||||||
|
validateChargeToIncomeAccountMappings(baseDataValidator, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
@@ -380,7 +386,13 @@ public class SavingsProductDataValidator {
|
|||||||
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId)
|
||||||
.ignoreIfNull().integerGreaterThanZero();
|
.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);
|
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
|
||||||
|
validateChargeToIncomeAccountMappings(baseDataValidator, element);
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+21
@@ -6,9 +6,11 @@
|
|||||||
package org.mifosplatform.portfolio.savings.domain;
|
package org.mifosplatform.portfolio.savings.domain;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -315,6 +317,25 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
|||||||
thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
|
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;
|
return thisTransactionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -787,6 +787,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds);
|
savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, transactionDate, fmt, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|
||||||
this.savingAccountRepository.saveAndFlush(savingsAccount);
|
this.savingAccountRepository.saveAndFlush(savingsAccount);
|
||||||
|
|
||||||
|
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withEntityId(savingsAccountChargeId) //
|
.withEntityId(savingsAccountChargeId) //
|
||||||
|
|||||||
Reference in New Issue
Block a user