moving all bugf fixes and improvemnets from 1.10.3 to develop branch

This commit is contained in:
Vishwas Babu A J
2013-10-05 03:41:39 +05:30
90 changed files with 2086 additions and 1221 deletions
+4 -2
View File
@@ -9159,8 +9159,8 @@ Request Body:
"toDate": "25 October 2013",
"repaymentsRescheduledTo": "26 October 2013",
"offices": [
"officeId":"1",
"officeId":"2"
{"officeId":"1"},
{"officeId":"2"}
]
}
</code>
@@ -14345,8 +14345,10 @@ POST savingsaccounts/1/charges/2?command=paycharge
Content-Type: application/json
Request Body:
{
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"amount": "60",
"dueDate": "12 September 2013"
}
</code>
<code class="method-response">
@@ -0,0 +1 @@
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Pay Due Savings Charges', 'Pay Due Savings Charges', '0 0 12 * * ?', '2013-09-23 00:00:00', 5, NULL, NULL, NULL, 'Pay Due Savings ChargesJobDetail1 _ DEFAULT', NULL, 1, 0, 1, 0, 0);
@@ -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;
@@ -0,0 +1,25 @@
ALTER TABLE `m_savings_account_charge`
ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `waived`;
ALTER TABLE `m_savings_account`
DROP COLUMN `withdrawal_fee_amount`,
DROP COLUMN `withdrawal_fee_type_enum`;
ALTER TABLE `m_savings_account_charge`
ADD COLUMN `fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `due_for_collection_as_of_date`,
ADD COLUMN `fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `fee_on_month`;
ALTER TABLE `m_savings_account_charge`
CHANGE COLUMN `due_for_collection_as_of_date` `charge_due_date` DATE NULL DEFAULT NULL AFTER `charge_time_enum`;
ALTER TABLE `m_savings_account`
DROP COLUMN `annual_fee_amount`,
DROP COLUMN `annual_fee_on_month`,
DROP COLUMN `annual_fee_on_day`,
DROP COLUMN `annual_fee_next_due_date`;
ALTER TABLE `m_savings_product`
DROP COLUMN `annual_fee_amount`,
DROP COLUMN `annual_fee_on_month`,
DROP COLUMN `annual_fee_on_day`;
@@ -0,0 +1,16 @@
/***Set defaults for income from Penalties for existing Saving Products**/
ALTER TABLE `m_loan_transaction`
ADD COLUMN `overpayment_portion_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_portion_derived`;
/**Add dummy liability account if organization already has a loan product with accounting enabled**/
INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`, `classification_enum`,`description`)
select 'Loan Overpayments (Temp)', '.', '22000-Temp', 2,'Temporary account to track Loan overpayments Liabilities'
FROM m_product_loan WHERE accounting_type != 1
limit 1;
/**Map a liability account for every loan which has accounting enabled**/
INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`financial_account_type`)
select (select id from acc_gl_account where classification_enum=2 and account_usage=1 LIMIT 1), mapping.product_id, mapping.product_type, 11
from acc_product_mapping mapping
where mapping.financial_account_type = 2 and mapping.product_type=1;
@@ -13,7 +13,7 @@ public class AccountingConstants {
/*** Accounting placeholders for cash based accounting for loan products ***/
public static enum CASH_ACCOUNTS_FOR_LOAN {
FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), TRANSFERS_SUSPENSE(
10);
10), OVERPAYMENT(11);
private final Integer value;
@@ -46,7 +46,7 @@ public class AccountingConstants {
/*** Accounting placeholders for accrual based accounting for loan products ***/
public static enum ACCRUAL_ACCOUNTS_FOR_LOAN {
FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(
7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10);
7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11);
private final Integer value;
@@ -83,12 +83,12 @@ public class AccountingConstants {
***/
public static enum LOAN_PRODUCT_ACCOUNTING_PARAMS {
FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES(
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE(
"receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE(
"receivablePenaltyAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING(
"paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING(
"feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID(
"incomeAccountId");
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), OVERPAYMENT(
"overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE("receivableInterestAccountId"), FEES_RECEIVABLE(
"receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE(
"transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
"paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING(
"penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId");
private final String value;
@@ -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;
@@ -69,6 +69,13 @@ public class AccountingDropdownReadPlatformServiceImpl implements AccountingDrop
expenseAccountOptions = null;
}
accountOptions.put("expenseAccountOptions", expenseAccountOptions);
List<GLAccountData> liabilityAccountOptions = this.accountReadPlatformService
.retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY);
if (liabilityAccountOptions.isEmpty()) {
liabilityAccountOptions = null;
}
accountOptions.put("liabilityAccountOptions", liabilityAccountOptions);
return accountOptions;
}
@@ -27,6 +27,7 @@ public class LoanTransactionDTO {
private final BigDecimal interest;
private final BigDecimal fees;
private final BigDecimal penalties;
private final BigDecimal overPayment;
/*** Boolean values determines if the transaction is reversed ***/
private final boolean reversed;
@@ -37,8 +38,8 @@ public class LoanTransactionDTO {
public LoanTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate,
final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest,
final BigDecimal fees, final BigDecimal penalties, final boolean reversed, final List<ChargePaymentDTO> feePayments,
final List<ChargePaymentDTO> penaltyPayments) {
final BigDecimal fees, final BigDecimal penalties, final BigDecimal overPayment, final boolean reversed,
final List<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> penaltyPayments) {
this.paymentTypeId = paymentTypeId;
this.transactionId = transactionId;
this.transactionDate = transactionDate;
@@ -51,6 +52,7 @@ public class LoanTransactionDTO {
this.transactionType = transactionType;
this.feePayments = feePayments;
this.penaltyPayments = penaltyPayments;
this.overPayment = overPayment;
this.officeId = officeId;
}
@@ -90,6 +92,10 @@ public class LoanTransactionDTO {
return this.penalties;
}
public BigDecimal getOverPayment() {
return this.overPayment;
}
public boolean isReversed() {
return this.reversed;
}
@@ -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;
}
}
@@ -32,6 +32,7 @@ import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidEx
import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.office.domain.Office;
@@ -81,31 +82,33 @@ public class AccountingProcessorHelper {
final BigDecimal interest = (BigDecimal) map.get("interestPortion");
final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion");
final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion");
final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion");
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>();
final List<ChargePaymentDTO> feePaymentDetails = new ArrayList<ChargePaymentDTO>();
final List<ChargePaymentDTO> penaltyPaymentDetails = new ArrayList<ChargePaymentDTO>();
// extract charge payment details (if exists)
if (map.containsKey("loanChargesPaid")) {
@SuppressWarnings("unchecked")
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);
if (isPenalty) {
penaltyPayments.add(chargePaymentDTO);
penaltyPaymentDetails.add(chargePaymentDTO);
} else {
feePayments.add(chargePaymentDTO);
feePaymentDetails.add(chargePaymentDTO);
}
}
}
final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
transactionDate, transactionType, amount, principal, interest, fees, penalties, reversed, feePayments, penaltyPayments);
transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed,
feePaymentDetails, penaltyPaymentDetails);
newLoanTransactions.add(transaction);
@@ -135,8 +138,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 +401,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) {
@@ -438,6 +516,9 @@ public class AccountingProcessorHelper {
}
}
if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString()); }
return accountMapping.getGlAccount();
}
@@ -464,6 +545,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 +577,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);
@@ -55,6 +55,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
}
/** Logic for Refunds of Overpayments **/
else if (loanTransactionDTO.getTransactionType().isRefund()) {
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
}
/***
* Only principal write off affects cash based accounting (interest
* and fee write off need not be considered). Debit losses written
@@ -103,6 +108,35 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
isReversal);
}
/**
* Debit loan Portfolio and credit Fund source for a Disbursement <br/>
*
* All debits are turned into credits and vice versa in case of disbursement
* reversals
*
*
* @param loanDTO
* @param loanTransactionDTO
* @param office
*/
private void createJournalEntriesForRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
final String currencyCode = loanDTO.getCurrencyCode();
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final Date transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal refundAmount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT,
CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, refundAmount,
isReversal);
}
/**
* Create a single Debit to fund source and multiple credits if applicable
* (loan portfolio for principal repayments, Interest on loans for interest
@@ -125,6 +159,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
@@ -156,6 +191,12 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments());
}
if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal);
}
/*** create a single debit entry (or reversal) for the entire amount **/
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
@@ -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 **/
@@ -11,7 +11,6 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.closure.domain.GLClosure;
@@ -40,10 +39,12 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.domain.OrganisationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -67,6 +68,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
private final AccountingRuleRepository accountingRuleRepository;
private final GLAccountReadPlatformService glAccountReadPlatformService;
private final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository;
private final PlatformSecurityContext context;
@Autowired
public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
@@ -76,7 +78,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory,
final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory,
final GLAccountReadPlatformService glAccountReadPlatformService,
final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository) {
final OrganisationCurrencyRepositoryWrapper organisationCurrencyRepository,
final PlatformSecurityContext context) {
this.glClosureRepository = glClosureRepository;
this.officeRepository = officeRepository;
this.glJournalEntryRepository = glJournalEntryRepository;
@@ -88,6 +91,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
this.accountingRuleRepository = accountingRuleRepository;
this.glAccountReadPlatformService = glAccountReadPlatformService;
this.organisationCurrencyRepository = organisationCurrencyRepository;
this.context = context;
}
@Transactional
@@ -109,7 +113,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
/** Set a transaction Id and save these Journal entries **/
final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue());
final String transactionId = generateTransactionId();
final String transactionId = generateTransactionId(officeId);
final String referenceNumber = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue());
if (accountRuleId != null) {
@@ -263,10 +267,10 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
.getTransactionId());
if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); }
final String reversalTransactionId = generateTransactionId();
Long officeId = journalEntries.get(0).getOffice().getId();
final String reversalTransactionId = generateTransactionId(officeId);
final boolean manualEntry = true;
for (final JournalEntry journalEntry : journalEntries) {
JournalEntry reversalJournalEntry;
final String reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId()
@@ -377,8 +381,12 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
* TODO: Need a better implementation with guaranteed uniqueness (but not a
* long UUID)...maybe something tied to system clock..
*/
private String generateTransactionId() {
return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
private String generateTransactionId(Long officeId) {
AppUser user = this.context.authenticatedUser();
Long time = System.currentTimeMillis();
String uniqueVal = String.valueOf(time) + user.getId() + officeId;
String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
return transactionId;
}
private void handleJournalEntryDataIntegrityIssues(final DataIntegrityViolationException dve) {
@@ -15,7 +15,7 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN
public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
public ProductToGLAccountMappingNotFoundException(final PortfolioProductType type, final Long productId, final String accountType) {
super("error.msg.productTyAccountMapping.not.found", "Mapping for product of type" + type.toString() + " with Id " + productId
super("error.msg.productToAccountMapping.not.found", "Mapping for product of type " + type.toString() + " with Id " + productId
+ " does not exist for an account of type " + accountType, type.toString(), productId, accountType);
}
}
@@ -107,6 +107,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
.notNull().integerGreaterThanZero();
final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
.notNull().integerGreaterThanZero();
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue())
@@ -176,6 +181,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);
@@ -52,6 +52,11 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.LOAN);
}
public void saveLoanToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.LOAN);
}
/*** Set of abstractions for merging Savings Products to GL Account Mappings ***/
public void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
@@ -71,6 +76,12 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
PortfolioProductType.LOAN);
}
public void mergeLoanToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes,
GLAccountType.LIABILITY, PortfolioProductType.LOAN);
}
/*** Abstractions for payments channel related to loan products ***/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
@@ -112,6 +123,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
element);
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
@@ -132,19 +145,13 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), overPaymentAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
break;
//$FALL-THROUGH$
case ACCRUAL_BASED:
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
break;
}
@@ -187,6 +194,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(),
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes);
// liabilities
mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes);
break;
case ACCRUAL_BASED:
// assets (including receivables)
@@ -221,6 +232,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes);
// liabilities
mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes);
break;
}
}
@@ -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);
}
@@ -106,6 +106,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId);
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId);
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), glAccountId);
}
}
} else if (AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingType)) {
@@ -126,6 +128,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
@@ -169,6 +173,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 +231,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 +265,5 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
}
return chargeToGLAccountMappers;
}
}
@@ -79,9 +79,11 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
// expenses
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
this.loanProductToGLAccountMappingHelper
.saveLoanToLiabilityAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue());
// liabilities
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
@@ -125,6 +127,11 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
// liabilities
this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue());
// advanced accounting mappings
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
@@ -154,6 +161,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 +180,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 +248,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(),
@@ -420,9 +420,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountInterestPosting()) {
handler = this.applicationContext.getBean("postInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountApplyAnnualFee()) {
} /*else if (wrapper.isSavingsAccountApplyAnnualFee()) {
handler = this.applicationContext.getBean("applyAnnualFeeSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountUndoTransaction()) {
} */else if (wrapper.isSavingsAccountUndoTransaction()) {
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isAdjustSavingsAccountTransaction()) {
handler = this.applicationContext.getBean("savingsTransactionAdjustmentCommandHandler", NewCommandSourceHandler.class);
@@ -241,6 +241,27 @@ public class DataValidatorBuilder {
return this;
}
public DataValidatorBuilder isOneOfTheseStringValues(final Object... values) {
if (this.value == null && this.ignoreNullValue) { return this; }
final List<Object> valuesList = Arrays.asList(values);
final String valuesListStr = StringUtils.join(valuesList, ", ");
if (this.value == null || !valuesList.contains(this.value.toString().toLowerCase())) {
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
.append(this.parameter).append(".is.not.one.of.expected.enumerations");
final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
.append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
defaultEnglishMessage.toString(), this.parameter, this.value, values);
this.dataValidationErrors.add(error);
}
return this;
}
public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) {
if (this.value == null && this.ignoreNullValue) { return this; }
@@ -45,7 +45,7 @@ public class DatatableCommandFromApiJsonDeserializer {
private final Set<String> supportedParametersForChangeColumns = new HashSet<String>(Arrays.asList("name", "newName", "length",
"mandatory", "after", "code", "newCode"));
private final Set<String> supportedParametersForDropColumns = new HashSet<String>(Arrays.asList("name"));
private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" };
private final Object[] supportedColumnTypes = { "string", "number", "decimal", "date", "text", "dropdown" };
private final Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_center", "m_office",
"m_savings_product", "m_product_loan" };
@@ -58,9 +58,9 @@ public class DatatableCommandFromApiJsonDeserializer {
private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) {
final String type = this.fromApiJsonHelper.extractStringNamed("type", column);
baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(this.supportedColumnTypes);
baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseStringValues(this.supportedColumnTypes);
if (type != null && type.equals("String")) {
if (type != null && type.equalsIgnoreCase("String")) {
if (this.fromApiJsonHelper.parameterExists("length", column)) {
final String lengthStr = this.fromApiJsonHelper.extractStringNamed("length", column);
if (lengthStr != null && !StringUtils.isWhitespace(lengthStr) && StringUtils.isNumeric(lengthStr)
@@ -80,7 +80,7 @@ public class DatatableCommandFromApiJsonDeserializer {
}
final String code = this.fromApiJsonHelper.extractStringNamed("code", column);
if (type != null && type.equals("Dropdown")) {
if (type != null && type.equalsIgnoreCase("Dropdown")) {
if (code != null) {
baseDataValidator.reset().parameter("code").value(code).notBlank().matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
} else {
@@ -149,6 +149,11 @@ public class FromJsonHelper {
public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) {
return this.helperDelegator.extractMonthDayNamed(parameterName, element);
}
public MonthDay extractMonthDayNamed(final String parameterName, final JsonObject object, final String dateFormat,
final Locale clientApplicationLocale) {
return this.helperDelegator.extractMonthDayNamed(parameterName, object, dateFormat, clientApplicationLocale);
}
public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element) {
return this.helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet<String>());
@@ -216,4 +221,8 @@ public class FromJsonHelper {
public String extractDateFormatParameter(final JsonObject element) {
return this.helperDelegator.extractDateFormatParameter(element);
}
public String extractMonthDayFormatParameter(final JsonObject element) {
return this.helperDelegator.extractMonthDayFormatParameter(element);
}
}
@@ -63,4 +63,8 @@ public class DateUtils {
final String formattedSqlDate = df.format(date);
return formattedSqlDate;
}
public static boolean isDateInTheFuture(final LocalDate localDate){
return localDate.isAfter(getLocalDateOfTenant());
}
}
@@ -71,12 +71,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {
{
put("String", "VARCHAR");
put("Number", "INT");
put("Decimal", "DECIMAL");
put("Date", "DATE");
put("Text", "TEXT");
put("Dropdown", "INT");
put("string", "VARCHAR");
put("number", "INT");
put("decimal", "DECIMAL");
put("date", "DATE");
put("text", "TEXT");
put("dropdown", "INT");
}
};
@@ -265,15 +265,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
String[] sqlArray = null;
if (this.configurationDomainService.isConstraintApproachEnabledForDatatables()) {
final String deleteColumnCodeSql = "delete from x_table_cloumn_code_mappings where column_alias_name like'"
+ datatable.toLowerCase().replaceAll("\\s", "_") + "%'";
sqlArray = new String[4];
sqlArray[3] = deleteColumnCodeSql;
} else {
sqlArray = new String[3];
}
String[] sqlArray = new String[3];
sqlArray[0] = deleteRolePermissionsSql;
sqlArray[1] = deletePermissionsSql;
sqlArray[2] = deleteRegisteredDatatableSql;
@@ -362,7 +354,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
final boolean isConstraintApproach) {
String name = (column.has("name")) ? column.get("name").getAsString() : null;
final String type = (column.has("type")) ? column.get("type").getAsString() : null;
final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null;
final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
final String code = (column.has("code")) ? column.get("code").getAsString() : null;
@@ -381,11 +373,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType);
if (type != null) {
if (type.equals("String")) {
if (type.equalsIgnoreCase("String")) {
sqlBuilder = sqlBuilder.append("(" + length + ")");
} else if (type.equals("Decimal")) {
} else if (type.equalsIgnoreCase("Decimal")) {
sqlBuilder = sqlBuilder.append("(19,6)");
} else if (type.equals("Dropdown")) {
} else if (type.equalsIgnoreCase("Dropdown")) {
sqlBuilder = sqlBuilder.append("(11)");
}
}
@@ -526,14 +518,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else {
if (code != null) {
removeMappings.add(dataTableNameAlias + "_" + name);
if (newCode == null) {
if (newCode == null || !StringUtils.equalsIgnoreCase(name, newName)) {
constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name)
.append("` ");
}
}
if (newCode != null) {
codeMappings.put(dataTableNameAlias + "_" + newName, this.codeReadPlatformService.retriveCode(newCode).getCodeId());
if (code == null) {
if (code == null || !StringUtils.equalsIgnoreCase(name, newName)) {
constrainBuilder.append(",ADD CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(newName)
.append("` ").append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `")
.append(CODE_VALUES_TABLE).append("` (`id`)");
@@ -596,7 +588,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
final StringBuilder constrainBuilder, final Map<String, Long> codeMappings, final boolean isConstraintApproach) {
String name = (column.has("name")) ? column.get("name").getAsString() : null;
final String type = (column.has("type")) ? column.get("type").getAsString() : null;
final String type = (column.has("type")) ? column.get("type").getAsString().toLowerCase() : null;
final Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
final Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
final String after = (column.has("after")) ? column.get("after").getAsString() : null;
@@ -616,11 +608,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType);
if (type != null) {
if (type.equals("String") && length != null) {
if (type.equalsIgnoreCase("String") && length != null) {
sqlBuilder = sqlBuilder.append("(" + length + ")");
} else if (type.equals("Decimal")) {
} else if (type.equalsIgnoreCase("Decimal")) {
sqlBuilder = sqlBuilder.append("(19,6)");
} else if (type.equals("Dropdown")) {
} else if (type.equalsIgnoreCase("Dropdown")) {
sqlBuilder = sqlBuilder.append("(11)");
}
}
@@ -828,8 +820,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (!isRegisteredDataTable(datatableName)) { throw new DatatableNotFoundException(datatableName); }
validateDatatableName(datatableName);
deregisterDatatable(datatableName);
String[] sqlArray = null;
if (this.configurationDomainService.isConstraintApproachEnabledForDatatables()) {
final String deleteColumnCodeSql = "delete from x_table_cloumn_code_mappings where column_alias_name like'"
+ datatableName.toLowerCase().replaceAll("\\s", "_") + "_%'";
sqlArray = new String[2];
sqlArray[1] = deleteColumnCodeSql;
}else{
sqlArray = new String[1];
}
final String sql = "DROP TABLE `" + datatableName + "`";
this.jdbcTemplate.execute(sql);
sqlArray[0] = sql;
this.jdbcTemplate.batchUpdate(sqlArray);
} catch (final SQLGrammarException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -5,7 +5,8 @@ public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS(
"Transfer Fee For Loans From Savings"), ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances");
"Transfer Fee For Loans From Savings"), ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), PAY_DUE_SAVINGS_CHARGES(
"Pay Due Savings Charges");
private final String name;
@@ -1,9 +1,14 @@
package org.mifosplatform.organisation.holiday.service;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface HolidayWritePlatformService {
CommandProcessingResult createHoliday(JsonCommand command);
boolean isHoliday(Long officeId, LocalDate transactionDate);
boolean isTransactionAllowedOnHoliday();
}
@@ -1,9 +1,11 @@
package org.mifosplatform.organisation.holiday.service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -39,7 +41,7 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
private final HolidayDataValidator fromApiJsonDeserializer;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper daysRepositoryWrapper;
private final ConfigurationDomainService configurationDomainService;
private final PlatformSecurityContext context;
private final OfficeRepository officeRepository;
private final FromJsonHelper fromApiJsonHelper;
@@ -47,13 +49,14 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
@Autowired
public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer,
final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository,
final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper) {
final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper, final ConfigurationDomainService configurationDomainService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.holidayRepository = holidayRepository;
this.context = context;
this.officeRepository = officeRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
this.daysRepositoryWrapper = daysRepositoryWrapper;
this.configurationDomainService = configurationDomainService;
}
@Transactional
@@ -152,4 +155,16 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
toDate.toString(), repaymentsRescheduledTo.toString());
}
}
@Override
public boolean isHoliday(Long officeId, LocalDate transactionDate) {
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, transactionDate.toDate());
return HolidayUtil.isHoliday(transactionDate, holidays);
}
@Override
public boolean isTransactionAllowedOnHoliday() {
return this.configurationDomainService.allowTransactionsOnHolidayEnabled();
}
}
@@ -0,0 +1,10 @@
package org.mifosplatform.organisation.workingdays.service;
import org.joda.time.LocalDate;
public interface WorkingDaysWritePlatformService {
boolean isWorkingDay(LocalDate transactionDate);
boolean isTransactionAllowedOnNonWorkingDay();
}
@@ -0,0 +1,34 @@
package org.mifosplatform.organisation.workingdays.service;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService {
private final WorkingDaysRepositoryWrapper daysRepositoryWrapper;
private final ConfigurationDomainService configurationDomainService;
@Autowired
public WorkingDaysWritePlatformServiceJpaRepositoryImpl(final WorkingDaysRepositoryWrapper daysRepositoryWrapper,
final ConfigurationDomainService configurationDomainService) {
this.daysRepositoryWrapper = daysRepositoryWrapper;
this.configurationDomainService = configurationDomainService;
}
@Override
public boolean isWorkingDay(LocalDate transactionDate) {
final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
return WorkingDaysUtil.isWorkingDay(workingDays, transactionDate);
}
@Override
public boolean isTransactionAllowedOnNonWorkingDay() {
return this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
}
}
@@ -175,4 +175,9 @@ public class AccountTransfer extends AbstractPersistable<Long> {
public SavingsAccount getFromAccount() {
return this.fromAccount;
}
public void updateToLoanTransaction(LoanTransaction toLoanTransaction) {
this.toLoanTransaction = toLoanTransaction;
}
}
@@ -19,4 +19,7 @@ public interface AccountTransferRepository extends JpaRepository<AccountTransfer
@Query("from AccountTransfer at where (at.fromLoanAccount.id= :accountNumber or at.toLoanAccount.id=:accountNumber) and at.reversed=false")
List<AccountTransfer> findAllByLoanId(@Param("accountNumber") Long accountNumber);
@Query("from AccountTransfer at where at.toLoanTransaction.id= :loanTransactionId and at.reversed=false")
AccountTransfer findByToLoanTransactionId(@Param("loanTransactionId") Long loanTransactionId);
}
@@ -9,6 +9,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
public interface AccountTransfersWritePlatformService {
@@ -19,4 +20,6 @@ public interface AccountTransfersWritePlatformService {
Long transferFunds(AccountTransferDTO accountTransferDTO);
void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId);
void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction);
}
@@ -239,6 +239,16 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
return transferTransactionId;
}
@Override
@Transactional
public void updateLoanTransaction(final Long loanTransactionId,final LoanTransaction newLoanTransaction){
final AccountTransfer transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if(transferTransaction!=null){
transferTransaction.updateToLoanTransaction(newLoanTransaction);
this.accountTransferRepository.save(transferTransaction);
}
}
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
@@ -43,7 +43,8 @@ public class ChargesApiResource {
private final Set<String> CHARGES_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "name", "amount", "currency", "penalty",
"active", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions",
"chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions"));
"chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions", "loanChargeCalculationTypeOptions",
"loanChargeTimeTypeOptions", "savingsChargeCalculationTypeOptions", "savingsChargeTimeTypeOptions"));
private final String resourceNameForPermissions = "CHARGE";
@@ -11,6 +11,7 @@ import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
@@ -33,30 +34,53 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final EnumOptionData chargePaymentMode;
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;
private final List<EnumOptionData> chargeAppliesToOptions;
private final List<EnumOptionData> chargeTimeTypeOptions;
private final List<EnumOptionData> chargePaymetModeOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;//
private final List<EnumOptionData> chargeAppliesToOptions;//
private final List<EnumOptionData> chargeTimeTypeOptions;//
private final List<EnumOptionData> chargePaymetModeOptions;//
private final List<EnumOptionData> loanChargeCalculationTypeOptions;
private final List<EnumOptionData> loanChargeTimeTypeOptions;
private final List<EnumOptionData> savingsChargeCalculationTypeOptions;
private final List<EnumOptionData> savingsChargeTimeTypeOptions;
public static ChargeData template(final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions) {
final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions,
final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions) {
return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions,
chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions);
chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions,
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
}
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo,
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions,
template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
template.chargePaymetModeOptions);
template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions,
template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions);
}
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active) {
final Collection<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null;
final List<EnumOptionData> chargeAppliesToOptions = null;
final List<EnumOptionData> chargeTimeTypeOptions = null;
final List<EnumOptionData> chargePaymentModeOptions = null;
final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, null, null, null, null, null);
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
}
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
@@ -73,9 +97,15 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> chargeAppliesToOptions = null;
final List<EnumOptionData> chargeTimeTypeOptions = null;
final List<EnumOptionData> chargePaymentModeOptions = null;
final List<EnumOptionData> loansChargeCalculationTypeOptions = null;
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions);
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
}
private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
@@ -83,7 +113,9 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active,
final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
final List<EnumOptionData> chargePaymentModeOptions) {
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
final List<EnumOptionData> savingsChargeTimeTypeOptions) {
this.id = id;
this.name = name;
this.amount = amount;
@@ -99,6 +131,10 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.chargeAppliesToOptions = chargeAppliesToOptions;
this.chargeTimeTypeOptions = chargeTimeTypeOptions;
this.chargePaymetModeOptions = chargePaymentModeOptions;
this.savingsChargeCalculationTypeOptions = savingsChargeCalculationTypeOptions;
this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions;
this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions;
this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions;
}
@Override
@@ -141,9 +177,10 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> chargeOptions = null;
final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
return SavingsAccountChargeData.instance(savingsChargeId, this.id, this.name, this.currency, this.amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage,
amountPercentageAppliedTo, chargeOptions, this.penalty);
amountPercentageAppliedTo, chargeOptions, this.penalty, feeOnMonthDay);
}
}
@@ -114,6 +114,11 @@ public class Charge extends AbstractPersistable<Long> {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
}
if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal");
}
} else if (isLoanCharge()) {
@@ -277,6 +282,11 @@ public class Charge extends AbstractPersistable<Long> {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
}
if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal");
}
}
}
@@ -57,6 +57,6 @@ public enum ChargeCalculationType {
}
public boolean isAllowedSavingsChargeCalculationType() {
return isFlat();
return isFlat() || isPercentageOfAmount();
}
}
@@ -8,8 +8,12 @@ package org.mifosplatform.portfolio.charge.domain;
public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), //
DISBURSEMENT(1, "chargeTimeType.disbursement"), //
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), MONTHLY(3, "chargeTimeType.monthly"), YEARLY(4, "chargeTimeType.yearly");
DISBURSEMENT(1, "chargeTimeType.disbursement"), //only for loan charges
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and savings charges
SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for savings
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"); // only for savings
private final Integer value;
private final String code;
@@ -32,7 +36,11 @@ public enum ChargeTimeType {
}
public static int maxValue() {
return 4;
int max = 1;// Default it to one (1)
for (ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
max = (chargeTimeType.getValue() > max) ? chargeTimeType.getValue() : max;
}
return max;
}
public static ChargeTimeType fromInt(final Integer chargeTime) {
@@ -45,10 +53,16 @@ public enum ChargeTimeType {
chargeTimeType = SPECIFIED_DUE_DATE;
break;
case 3:
chargeTimeType = MONTHLY;
chargeTimeType = SAVINGS_ACTIVATION;
break;
case 4:
chargeTimeType = YEARLY;
chargeTimeType = SAVINGS_CLOSURE;
break;
case 5:
chargeTimeType = WITHDRAWAL_FEE;
break;
case 6:
chargeTimeType = ANNUAL_FEE;
break;
default:
chargeTimeType = INVALID;
@@ -65,12 +79,20 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue());
}
public boolean isMonthly() {
return this.value.equals(ChargeTimeType.MONTHLY.getValue());
public boolean isSavingsActivation() {
return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue());
}
public boolean isYearly() {
return this.value.equals(ChargeTimeType.YEARLY.getValue());
public boolean isSavingsClosure() {
return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue());
}
public boolean isWithdrawalFee() {
return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue());
}
public boolean isAnnualFee() {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
}
public boolean isAllowedLoanChargeTime() {
@@ -78,6 +100,6 @@ public enum ChargeTimeType {
}
public boolean isAllowedSavingsChargeTime() {
return isOnSpecifiedDueDate() || isMonthly() || isYearly();
return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee();
}
}
@@ -18,4 +18,13 @@ public interface ChargeDropdownReadPlatformService {
List<EnumOptionData> retrieveCollectionTimeTypes();
List<EnumOptionData> retrivePaymentModes();
List<EnumOptionData> retrieveLoanCalculationTypes();
List<EnumOptionData> retrieveLoanCollectionTimeTypes();
List<EnumOptionData> retrieveSavingsCalculationTypes();
List<EnumOptionData> retrieveSavingsCollectionTimeTypes();
}
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.charge.service;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -48,15 +49,7 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrieveCollectionTimeTypes() {
final List<EnumOptionData> chargeTimeTypes = new ArrayList<EnumOptionData>();
for (final ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
if (ChargeTimeType.INVALID.equals(chargeTimeType) || ChargeTimeType.MONTHLY.equals(chargeTimeType) // To
// be
// implemented
// for
// Savings
|| ChargeTimeType.YEARLY.equals(chargeTimeType)) {
// implemented
// for
// Savings
if (ChargeTimeType.INVALID.equals(chargeTimeType)) {
continue;
}
chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType));
@@ -68,4 +61,27 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrivePaymentModes() {
return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER));
}
@Override
public List<EnumOptionData> retrieveLoanCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT));
}
@Override
public List<EnumOptionData> retrieveLoanCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
}
@Override
public List<EnumOptionData> retrieveSavingsCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT));
}
@Override
public List<EnumOptionData> retrieveSavingsCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION),
chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE), chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE),
chargeTimeType(ChargeTimeType.ANNUAL_FEE));
}
}
@@ -28,11 +28,17 @@ public class ChargeEnumerations {
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(),
ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
case MONTHLY:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY.getValue().longValue(), ChargeTimeType.MONTHLY.getCode(), "Monthly");
case SAVINGS_ACTIVATION:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_ACTIVATION.getValue().longValue(), ChargeTimeType.SAVINGS_ACTIVATION.getCode(), "Savings Activation");
break;
case YEARLY:
optionData = new EnumOptionData(ChargeTimeType.YEARLY.getValue().longValue(), ChargeTimeType.YEARLY.getCode(), "Yearly");
case SAVINGS_CLOSURE:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_CLOSURE.getValue().longValue(), ChargeTimeType.SAVINGS_CLOSURE.getCode(), "Savings Closure");
break;
case WITHDRAWAL_FEE:
optionData = new EnumOptionData(ChargeTimeType.WITHDRAWAL_FEE.getValue().longValue(), ChargeTimeType.WITHDRAWAL_FEE.getCode(), "Withdrawal Fee");
break;
case ANNUAL_FEE:
optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(), "Annual Fee");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
@@ -82,9 +82,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final List<EnumOptionData> allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
final List<EnumOptionData> chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes();
final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveLoanCalculationTypes();
final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions,
allowedChargeTimeOptions, chargePaymentOptions);
allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
}
@Override
@@ -35,6 +35,7 @@ public class LoanTransactionData {
private final BigDecimal interestPortion;
private final BigDecimal feeChargesPortion;
private final BigDecimal penaltyChargesPortion;
private final BigDecimal overpaymentPortion;
private final String externalId;
private final AccountTransferData transfer;
@@ -46,23 +47,24 @@ public class LoanTransactionData {
return new LoanTransactionData(loanTransactionData.id, loanTransactionData.officeId, loanTransactionData.officeName,
loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date,
loanTransactionData.amount, loanTransactionData.principalPortion, loanTransactionData.interestPortion,
loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, paymentTypeOptions,
loanTransactionData.externalId, loanTransactionData.transfer);
loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, loanTransactionData.overpaymentPortion,
paymentTypeOptions, loanTransactionData.externalId, loanTransactionData.transfer);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final String externalId, final AccountTransferData transfer) {
this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, null, externalId, transfer);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final Collection<CodeValueData> paymentTypeOptions, final String externalId,
final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final String externalId,
final AccountTransferData transfer) {
this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overpaymentPortion, null, externalId, transfer);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final Collection<CodeValueData> paymentTypeOptions, final String externalId, final AccountTransferData transfer) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
@@ -78,6 +80,7 @@ public class LoanTransactionData {
this.paymentTypeOptions = paymentTypeOptions;
this.externalId = externalId;
this.transfer = transfer;
this.overpaymentPortion = overpaymentPortion;
}
public LocalDate dateOf() {
@@ -5,8 +5,8 @@
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
/**
* Stores details of {@link LoanTransaction}'s that were reversed or newly
@@ -16,16 +16,10 @@ import java.util.List;
*/
public class ChangedTransactionDetail {
private final List<LoanTransaction> newTransactions = new ArrayList<LoanTransaction>();
private final Map<Long,LoanTransaction> newTransactionMappings = new HashMap<Long, LoanTransaction>();
private final List<LoanTransaction> reversedTransactions = new ArrayList<LoanTransaction>();
public List<LoanTransaction> getNewTransactions() {
return this.newTransactions;
}
public List<LoanTransaction> getReversedTransactions() {
return this.reversedTransactions;
public Map<Long, LoanTransaction> getNewTransactionMappings() {
return this.newTransactionMappings;
}
}
@@ -444,10 +444,10 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
for (final LoanTransaction transaction : changedTransactionDetail.getNewTransactions()) {
transaction.updateLoan(this);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
mapEntry.getValue().updateLoan(this);
}
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions());
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
} else {
// just reprocess the loan schedule only for now.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
@@ -1493,7 +1493,10 @@ public class Loan extends AbstractPersistable<Long> {
return disbursementTransaction;
}
public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment) {
public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment,
final List<Long> existingTransactionIds,final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
LoanCharge charge = null;
for (final LoanCharge loanCharge : this.charges) {
if (chargeId.equals(loanCharge.getId())) {
@@ -1710,10 +1713,10 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
for (final LoanTransaction newLoanTransaction : changedTransactionDetail.getNewTransactions()) {
newLoanTransaction.updateLoan(this);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
mapEntry.getValue().updateLoan(this);
}
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactions());
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
}
updateLoanSummaryDerivedFields();
@@ -24,6 +24,8 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.account.domain.AccountTransfer;
import org.mifosplatform.portfolio.account.domain.AccountTransferRepository;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.group.domain.Group;
@@ -49,6 +51,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository;
private final AccountTransferRepository accountTransferRepository;
@Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -56,7 +59,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService) {
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final AccountTransferRepository accountTransferRepository) {
this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
@@ -66,6 +70,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.workingDaysRepository = workingDaysRepository;
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.accountTransferRepository = accountTransferRepository;
}
@Transactional
@@ -115,8 +120,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
* recorded against the loan)
***/
if (changedTransactionDetail != null) {
for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) {
this.loanTransactionRepository.save(loanTransaction);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
this.loanTransactionRepository.save(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
this.loanRepository.save(loan);
@@ -125,6 +131,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText);
this.noteRepository.save(note);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -158,7 +166,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
if (loanTransactionType.isRepaymentAtDisbursement()) {
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction);
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
} else {
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction);
@@ -247,4 +255,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loanTransaction.reverse();
this.loanTransactionRepository.save(loanTransaction);
}
private void updateLoanTransaction(final Long loanTransactionId,final LoanTransaction newLoanTransaction){
final AccountTransfer transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if(transferTransaction!=null){
transferTransaction.updateToLoanTransaction(newLoanTransaction);
this.accountTransferRepository.save(transferTransaction);
}
}
}
@@ -82,6 +82,9 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
@Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPortion;
@Column(name = "overpayment_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal overPaymentPortion;
@Column(name = "is_reversed", nullable = false)
private boolean reversed;
@@ -133,21 +136,21 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(),
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
.getTotalPenaltyChargesOutstanding(), false, null);
.getTotalPenaltyChargesOutstanding(), null, false, null);
}
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate) {
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(),
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
.getTotalPenaltyChargesOutstanding(), false, null);
.getTotalPenaltyChargesOutstanding(), null, false, null);
}
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate) {
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(),
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary()
.getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary()
.getTotalPenaltyChargesOutstanding(), false, null);
.getTotalPenaltyChargesOutstanding(), null, false, null);
}
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
@@ -158,8 +161,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.reversed,
loanTransaction.paymentDetail);
loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail);
}
public static LoanTransaction applyLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
@@ -176,13 +179,15 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
&& (loanTransaction.getPrincipalPortion(currency).isEqualTo(newLoanTransaction.getPrincipalPortion(currency)))
&& (loanTransaction.getInterestPortion(currency).isEqualTo(newLoanTransaction.getInterestPortion(currency)))
&& (loanTransaction.getFeeChargesPortion(currency).isEqualTo(newLoanTransaction.getFeeChargesPortion(currency)))
&& (loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency)))) { return true; }
&& (loanTransaction.getPenaltyChargesPortion(currency).isEqualTo(newLoanTransaction.getPenaltyChargesPortion(currency)))
&& (loanTransaction.getOverPaymentPortion(currency).isEqualTo(newLoanTransaction.getOverPaymentPortion(currency)))) { return true; }
return false;
}
private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final Date dateOf, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final boolean reversed, final PaymentDetail paymentDetail) {
final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed,
final PaymentDetail paymentDetail) {
super();
this.loan = loan;
this.typeOf = typeOf;
@@ -192,6 +197,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.interestPortion = interestPortion;
this.feeChargesPortion = feeChargesPortion;
this.penaltyChargesPortion = penaltyChargesPortion;
this.overPaymentPortion = overPaymentPortion;
this.reversed = reversed;
this.paymentDetail = paymentDetail;
this.office = office;
@@ -274,6 +280,11 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
.plus(getPenaltyChargesPortion(currency)).getAmount();
}
public void updateOverPayments(final Money overPayment) {
final MonetaryCurrency currency = overPayment.getCurrency();
this.overPaymentPortion = defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
}
public Money getPrincipalPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.principalPortion);
}
@@ -294,6 +305,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return Money.of(currency, this.penaltyChargesPortion);
}
public Money getOverPaymentPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.overPaymentPortion);
}
public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
@@ -425,7 +440,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData,
currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion,
this.penaltyChargesPortion, this.externalId, transfer);
this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer);
}
public Map<String, Object> toMapData(final CurrencyData currencyData) {
@@ -444,6 +459,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
thisTransactionData.put("interestPortion", this.interestPortion);
thisTransactionData.put("feeChargesPortion", this.feeChargesPortion);
thisTransactionData.put("penaltyChargesPortion", this.penaltyChargesPortion);
thisTransactionData.put("overPaymentPortion", this.overPaymentPortion);
if (this.paymentDetail != null) {
thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
@@ -118,8 +118,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
**/
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
loanTransaction.reverse();
changedTransactionDetail.getReversedTransactions().add(loanTransaction);
changedTransactionDetail.getNewTransactions().add(newLoanTransaction);
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
}
}
@@ -187,6 +186,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (transactionAmountUnprocessed.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
}
}
@@ -542,7 +542,7 @@ public final class LoanApplicationTerms {
switch (this.interestCalculationPeriodMethod) {
case DAILY:
final BigDecimal dailyInterestRate = this.annualNominalInterestRate.divide(BigDecimal.valueOf(Long.valueOf(365)), mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(this.repaymentEvery));
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc);
final BigDecimal equivalentInterestRateForPeriod = dailyInterestRate
.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
@@ -115,8 +115,17 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null);
final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveLoanCalculationTypes();
final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions);
}
@Override
@@ -330,7 +330,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
possibleNextRepaymentAmount.getAmount(), null, null, null, null, paymentOptions, null, null);
possibleNextRepaymentAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null);
}
@Override
@@ -354,7 +354,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal amount = waiveOfInterest.getAmount(currency).getAmount();
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount,
null, null, null, null, null, null);
null, null, null, null, null, null, null);
}
@Override
@@ -364,7 +364,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
return new LoanTransactionData(null, null, null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null,
null, null, null, null);
null, null, null, null, null);
}
@Override
@@ -375,7 +375,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDate(), null, null,
null, null, null, paymentOptions, null, null);
null, null, null, null, paymentOptions, null, null);
}
@Override
@@ -861,6 +861,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+ " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
+ " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
+ " tr.overpayment_portion_derived as overpayment, "
+ " pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "
+ " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, "
@@ -920,6 +921,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest");
final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment");
final String externalId = rs.getString("externalId");
AccountTransferData transfer = null;
@@ -944,7 +946,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalAmount,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, externalId, transfer);
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, externalId, transfer);
}
}
@@ -523,8 +523,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* payment recorded against the loan)
***/
if (changedTransactionDetail != null) {
for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) {
this.loanTransactionRepository.save(loanTransaction);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
@@ -598,8 +599,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* recorded against the loan)
***/
if (changedTransactionDetail != null) {
for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) {
this.loanTransactionRepository.save(loanTransaction);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
@@ -819,8 +821,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* latest payment recorded against the loan)
***/
if (changedTransactionDetail != null) {
for (final LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) {
this.loanTransactionRepository.save(loanTransaction);
for(Map.Entry<Long,LoanTransaction> mapEntry:changedTransactionDetail.getNewTransactionMappings().entrySet()){
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
@@ -48,8 +48,8 @@ public final class LoanProductDataValidator {
LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue()));
@@ -282,6 +282,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
.notNull().integerGreaterThanZero();
final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
.notNull().integerGreaterThanZero();
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
validateChargeToIncomeAccountMappings(baseDataValidator, element);
@@ -520,6 +525,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
.ignoreIfNull().integerGreaterThanZero();
final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue()).value(overpaymentAccountId)
.ignoreIfNull().integerGreaterThanZero();
final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
@@ -19,7 +19,7 @@ public enum SavingsAccountTransactionType {
INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), //
WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), //
ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), //
WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharges"), //
WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), //
PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), //
INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
@@ -105,9 +105,13 @@ public enum SavingsAccountTransactionType {
return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue());
}
public boolean isCharge() {
public boolean isPayCharge() {
return this.value.equals(SavingsAccountTransactionType.PAY_CHARGE.getValue());
}
public boolean isChargeTransaction(){
return isPayCharge() || isWithdrawalFee() || isAnnualFee();
}
public boolean isWaiveCharge() {
return this.value.equals(SavingsAccountTransactionType.WAIVE_CHARGES.getValue());
@@ -130,7 +134,7 @@ public enum SavingsAccountTransactionType {
}
public boolean isDebit() {
return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isCharge();
return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge();
}
public boolean isCredit() {
@@ -34,6 +34,7 @@ public class SavingsApiConstants {
public static String adjustTransactionAction = ".adjusttransaction";
public static String closeAction = ".close";
public static String payChargeTransactionAction = ".paycharge";
public static String waiveChargeTransactionAction = ".waivecharge";
// command
public static String COMMAND_UNDO_TRANSACTION = "undo";
@@ -80,8 +81,8 @@ public class SavingsApiConstants {
public static final String withdrawalFeeAmountParamName = "withdrawalFeeAmount";
public static final String withdrawalFeeTypeParamName = "withdrawalFeeType";
public static final String withdrawalFeeForTransfersParamName = "withdrawalFeeForTransfers";
public static final String annualFeeAmountParamName = "annualFeeAmount";
public static final String annualFeeOnMonthDayParamName = "annualFeeOnMonthDay";
public static final String feeAmountParamName = "feeAmount";//to be deleted
public static final String feeOnMonthDayParamName = "feeOnMonthDay";
public static final String accountingRuleParamName = "accountingRule";
public static final String paymentTypeIdParamName = "paymentTypeId";
public static final String transactionAccountNumberParamName = "accountNumber";
@@ -117,21 +118,30 @@ public class SavingsApiConstants {
public static final String amountParamName = "amount";
public static final String amountPaidParamName = "amountPaid";
public static final String chargeOptionsParamName = "chargeOptions";
public static final String chargePaymentModeParamName = "chargePaymentMode";
public static final String noteParamName = "note";
//Savings account associations
public static final String transactions = "transactions";
public static final String charges = "charges";
public static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName,
inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName,
feeOnMonthDayParamName, 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
@@ -143,7 +153,7 @@ public class SavingsApiConstants {
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, "currencyOptions",
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, "currencyOptions",
"interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
"interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions"));
@@ -152,8 +162,10 @@ public class SavingsApiConstants {
productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, chargesParamName));
lockinPeriodFrequencyTypeParamName,
//withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
//withdrawalFeeForTransfersParamName,
feeAmountParamName, feeOnMonthDayParamName, chargesParamName));
/**
* These parameters will match the class level parameters of
@@ -165,10 +177,10 @@ public class SavingsApiConstants {
groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", nominalAnnualInterestRateParamName,
interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
annualFeeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions",
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName,
feeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions",
"interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions"));
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee"));
public static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
@@ -191,5 +203,8 @@ public class SavingsApiConstants {
amountPaidParamName, chargeOptionsParamName));
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName));
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
}
@@ -155,7 +155,7 @@ public class SavingsAccountChargesApiResource {
@Path("{savingsAccountChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String waiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
public String payOrWaiveSavingsAccountCharge(@PathParam("savingsAccountId") final Long savingsAccountId,
@PathParam("savingsAccountChargeId") final Long savingsAccountChargeId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) {
@@ -154,11 +154,11 @@ public class SavingsAccountsApiResource {
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("transactions", "charges"));
associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges));
}
if (associationParameters.contains("transactions")) {
mandatoryResponseParameters.add("transactions");
if (associationParameters.contains(SavingsApiConstants.transactions)) {
mandatoryResponseParameters.add(SavingsApiConstants.transactions);
final Collection<SavingsAccountTransactionData> currentTransactions = this.savingsAccountReadPlatformService
.retrieveAllTransactions(accountId);
if (!CollectionUtils.isEmpty(currentTransactions)) {
@@ -166,8 +166,8 @@ public class SavingsAccountsApiResource {
}
}
if (associationParameters.contains("charges")) {
mandatoryResponseParameters.add("charges");
if (associationParameters.contains(SavingsApiConstants.charges)) {
mandatoryResponseParameters.add(SavingsApiConstants.charges);
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId);
if (!CollectionUtils.isEmpty(currentCharges)) {
@@ -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()) {
@@ -9,9 +9,11 @@ import java.math.BigDecimal;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
/**
* Immutable data object for Savings Account charge data.
@@ -27,12 +29,14 @@ public class SavingsAccountChargeData {
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused")
private final LocalDate dueDate;
@SuppressWarnings("unused")
private final MonthDay feeOnMonthDay;
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
@@ -82,28 +86,30 @@ public class SavingsAccountChargeData {
final BigDecimal percentage = BigDecimal.ZERO;
final boolean penalty = false;
final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty);
penalty, feeOnMonthDay);
}
public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final Collection<ChargeData> chargeOptions, final boolean penalty) {
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) {
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty);
penalty, feeOnMonthDay);
}
private SavingsAccountChargeData(final Long id, final Long chargeId, final String name, final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid,
final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding,
final Collection<ChargeData> chargeOptions, final boolean penalty) {
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
@@ -121,10 +127,18 @@ public class SavingsAccountChargeData {
this.amountOrPercentage = getAmountOrPercentage();
this.chargeOptions = chargeOptions;
this.penalty = penalty;
this.feeOnMonthDay = feeOnMonthDay;
}
private BigDecimal getAmountOrPercentage() {
return (this.chargeCalculationType != null) && (this.chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount;
}
public boolean isWithdrawalFee(){
return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isWithdrawalFee();
}
public boolean isAnnualFee(){
return ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isAnnualFee();
}
}
@@ -6,10 +6,12 @@
package org.mifosplatform.portfolio.savings.data;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -18,6 +20,8 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
@@ -57,9 +61,15 @@ public class SavingsAccountChargeDataValidator {
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -79,12 +89,34 @@ public class SavingsAccountChargeDataValidator {
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validatePayCharge(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@@ -7,11 +7,10 @@ package org.mifosplatform.portfolio.savings.data;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashSet;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
@@ -44,18 +43,13 @@ public class SavingsAccountData {
private final BigDecimal minRequiredOpeningBalance;
private final Integer lockinPeriodFrequency;
private final EnumOptionData lockinPeriodFrequencyType;
private final BigDecimal withdrawalFeeAmount;
private final EnumOptionData withdrawalFeeType;
private final boolean withdrawalFeeForTransfers;
private final BigDecimal annualFeeAmount;
private final MonthDay annualFeeOnMonthDay;
private final LocalDate annualFeeNextDueDate;
// associations
private final SavingsAccountSummaryData summary;
@SuppressWarnings("unused")
private final Collection<SavingsAccountTransactionData> transactions;
@SuppressWarnings("unused")
private final Collection<SavingsAccountChargeData> charges;
// template
@@ -69,6 +63,11 @@ public class SavingsAccountData {
private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
private final Collection<ChargeData> chargeOptions;
@SuppressWarnings("unused")
private final SavingsAccountChargeData withdrawalFee;
@SuppressWarnings("unused")
private final SavingsAccountChargeData annualFee;
public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId,
final String groupName, final Long clientId, final String clientName, final Long productId, final String productName,
final Long fieldOfficerId, final String fieldOfficerName, final SavingsAccountStatusEnumData status,
@@ -76,9 +75,7 @@ public class SavingsAccountData {
final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType,
final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType,
final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary) {
final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary) {
final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null;
@@ -96,15 +93,44 @@ public class SavingsAccountData {
return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges,
chargeOptions);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions);
}
public static SavingsAccountData lookup(final Long accountId, final String accountNo) {
final String externalId = null;
final Long productId = null;
final Long groupId = null;
final Long clientId = null;
final String clientName = null;
final String groupName = null;
final String productName = null;
final Long fieldOfficerId = null;
final String fieldOfficerName = null;
final SavingsAccountStatusEnumData status = null;
final SavingsAccountApplicationTimelineData timeline = null;
final CurrencyData currency = null;
final BigDecimal nominalAnnualInterestRate = null;
final EnumOptionData interestPeriodType = null;
final EnumOptionData interestPostingPeriodType = null;
final EnumOptionData interestCalculationType = null;
final EnumOptionData interestCalculationDaysInYearType = null;
final BigDecimal minRequiredOpeningBalance = null;
final Integer lockinPeriodFrequency = null;
final EnumOptionData lockinPeriodFrequencyType = null;
// final BigDecimal withdrawalFeeAmount = null;
// final EnumOptionData withdrawalFeeType = null;
final boolean withdrawalFeeForTransfers = false;
// final BigDecimal annualFeeAmount = null;
// final MonthDay annualFeeOnMonthDay = null;
// final LocalDate annualFeeNextDueDate = null;
final SavingsAccountSummaryData summary = null;
final Collection<SavingsAccountTransactionData> transactions = null;
final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null;
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
@@ -114,12 +140,13 @@ public class SavingsAccountData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<SavingsAccountTransactionData> transactions = null;
final Collection<SavingsAccountChargeData> charges = null;
final Collection<ChargeData> chargeOptions = null;
return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, false, null, null, null, null, transactions, productOptions,
return new SavingsAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId , fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions);
@@ -149,12 +176,11 @@ public class SavingsAccountData {
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
account.lockinPeriodFrequencyType, account.withdrawalFeeAmount, account.withdrawalFeeType,
account.withdrawalFeeForTransfers, account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate,
account.summary, transactions, template.productOptions, template.fieldOfficerOptions,
template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions,
template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions,
template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions);
account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, transactions,
template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions,
template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions,
template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions,
template.withdrawalFeeTypeOptions, charges, template.chargeOptions);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -172,11 +198,10 @@ public class SavingsAccountData {
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
account.lockinPeriodFrequencyType, account.withdrawalFeeAmount, account.withdrawalFeeType,
account.withdrawalFeeForTransfers, account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate,
account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions);
account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions);
}
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -200,12 +225,12 @@ public class SavingsAccountData {
final BigDecimal minRequiredOpeningBalance = null;
final Integer lockinPeriodFrequency = null;
final EnumOptionData lockinPeriodFrequencyType = null;
final BigDecimal withdrawalFeeAmount = null;
final EnumOptionData withdrawalFeeType = null;
// final BigDecimal withdrawalFeeAmount = null;
// final EnumOptionData withdrawalFeeType = null;
final boolean withdrawalFeeForTransfers = false;
final BigDecimal annualFeeAmount = null;
final MonthDay annualFeeOnMonthDay = null;
final LocalDate annualFeeNextDueDate = null;
// final BigDecimal annualFeeAmount = null;
// final MonthDay annualFeeOnMonthDay = null;
// final LocalDate annualFeeNextDueDate = null;
final SavingsAccountSummaryData summary = null;
final Collection<SavingsAccountTransactionData> transactions = null;
@@ -225,11 +250,10 @@ public class SavingsAccountData {
return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges,
chargeOptions);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions);
}
private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
@@ -238,11 +262,10 @@ public class SavingsAccountData {
final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount,
final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount,
final MonthDay annualFeeOnMonthDay, final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary,
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsProductData> productOptions,
final Collection<StaffData> fieldOfficerOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
final SavingsAccountSummaryData summary, final Collection<SavingsAccountTransactionData> transactions,
final Collection<SavingsProductData> productOptions, final Collection<StaffData> fieldOfficerOptions,
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
@@ -270,12 +293,12 @@ public class SavingsAccountData {
this.minRequiredOpeningBalance = minRequiredOpeningBalance;
this.lockinPeriodFrequency = lockinPeriodFrequency;
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
this.withdrawalFeeAmount = withdrawalFeeAmount;
this.withdrawalFeeType = withdrawalFeeType;
// this.withdrawalFeeAmount = withdrawalFeeAmount;
// this.withdrawalFeeType = withdrawalFeeType;
this.withdrawalFeeForTransfers = withdrawalFeeForTransfers;
this.annualFeeAmount = annualFeeAmount;
this.annualFeeOnMonthDay = annualFeeOnMonthDay;
this.annualFeeNextDueDate = annualFeeNextDueDate;
// this.annualFeeAmount = annualFeeAmount;
// this.annualFeeOnMonthDay = annualFeeOnMonthDay;
// this.annualFeeNextDueDate = annualFeeNextDueDate;
this.summary = summary;
this.transactions = transactions;
@@ -292,8 +315,26 @@ public class SavingsAccountData {
this.charges = charges;// charges associated with Savings account
// charges available for adding to Savings account
this.chargeOptions = chargeOptions;
this.withdrawalFee = getWithdrawalFee();
this.annualFee = getAnnualFee();
}
private SavingsAccountChargeData getWithdrawalFee() {
for (SavingsAccountChargeData charge : this.charges()) {
if (charge.isWithdrawalFee()) return charge;
}
return null;
}
private SavingsAccountChargeData getAnnualFee() {
for (SavingsAccountChargeData charge : this.charges()) {
if (charge.isAnnualFee()) return charge;
}
return null;
}
public Long id() {
return this.id;
}
@@ -328,4 +369,8 @@ public class SavingsAccountData {
public int hashCode() {
return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).toHashCode();
}
public Collection<SavingsAccountChargeData> charges() {
return (this.charges == null) ? new HashSet<SavingsAccountChargeData>() : this.charges;
}
}
@@ -6,10 +6,13 @@
package org.mifosplatform.portfolio.savings.data;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
@@ -22,14 +25,13 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequire
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
@@ -45,11 +47,12 @@ import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
@Component
@@ -176,32 +179,6 @@ public class SavingsAccountDataValidator {
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName,
element);
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).zeroOrPositiveAmount();
if (withdrawalFeeAmount != null) {
final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType)
.isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues());
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) {
final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull()
.isOneOfTheseValues(1, 2);
if (withdrawalFeeType != null) {
final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
withdrawalFeeAmountParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull()
.zeroOrPositiveAmount();
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
withdrawalFeeForTransfersParamName, element);
@@ -209,29 +186,34 @@ public class SavingsAccountDataValidator {
.ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount();
if (annualFeeAmount != null) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull();
}
}
if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
if (monthDayOfAnnualFee != null) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName,
element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount();
}
}
validateSavingsCharges(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateSavingsCharges(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject savingsChargeElement = array.get(i).getAsJsonObject();
//final Long id = this.fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).longGreaterThanZero();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
}
}
}
}
public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -359,19 +341,6 @@ public class SavingsAccountDataValidator {
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName,
element);
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull()
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) {
final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull()
.isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues());
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
withdrawalFeeForTransfersParamName, element);
@@ -379,14 +348,14 @@ public class SavingsAccountDataValidator {
.ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -1,7 +1,6 @@
package org.mifosplatform.portfolio.savings.data;
import java.math.BigDecimal;
import java.util.List;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
@@ -12,18 +11,13 @@ public class SavingsAccountTransactionDTO {
private final DateTimeFormatter formatter;
private final LocalDate transactionDate;
private final BigDecimal transactionAmount;
private final List<Long> existingTransactionIds;
private final List<Long> existingReversedTransactionIds;
private final PaymentDetail paymentDetail;
public SavingsAccountTransactionDTO(final DateTimeFormatter formatter, final LocalDate transactionDate,
final BigDecimal transactionAmount, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final PaymentDetail paymentDetail) {
final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
this.formatter = formatter;
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.existingTransactionIds = existingTransactionIds;
this.existingReversedTransactionIds = existingReversedTransactionIds;
this.paymentDetail = paymentDetail;
}
@@ -39,14 +33,6 @@ public class SavingsAccountTransactionDTO {
return this.transactionAmount;
}
public List<Long> getExistingTransactionIds() {
return this.existingTransactionIds;
}
public List<Long> getExistingReversedTransactionIds() {
return this.existingReversedTransactionIds;
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
@@ -10,9 +10,9 @@ import java.util.Collection;
import java.util.List;
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;
@@ -36,16 +36,14 @@ public class SavingsProductData {
private final BigDecimal minRequiredOpeningBalance;
private final Integer lockinPeriodFrequency;
private final EnumOptionData lockinPeriodFrequencyType;
private final BigDecimal withdrawalFeeAmount;
private final EnumOptionData withdrawalFeeType;
private final boolean withdrawalFeeForTransfers;
private final BigDecimal annualFeeAmount;
private final MonthDay annualFeeOnMonthDay;
// accounting
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;
@@ -83,35 +81,34 @@ public class SavingsProductData {
final BigDecimal minRequiredOpeningBalance = null;
final Integer lockinPeriodFrequency = null;
final EnumOptionData lockinPeriodFrequencyType = null;
final BigDecimal withdrawalFeeAmount = null;
final EnumOptionData withdrawalFeeType = null;
final boolean withdrawalFeeForTransfers = false;
final BigDecimal annualFeeAmount = null;
final MonthDay annualFeeMonthDay = null;
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,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRule, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings);
}
public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
return new SavingsProductData(product.id, product.name, product.description, product.currency, product.nominalAnnualInterestRate,
product.interestCompoundingPeriodType, product.interestPostingPeriodType, product.interestCalculationType,
product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance, product.lockinPeriodFrequency,
product.lockinPeriodFrequencyType, product.withdrawalFeeAmount, product.withdrawalFeeType,
product.withdrawalFeeForTransfers, product.accountingRule, product.annualFeeAmount, product.annualFeeOnMonthDay,
product.accountingMappings, product.paymentChannelToFundSourceMappings, product.currencyOptions,
product.interestCompoundingPeriodTypeOptions, product.interestPostingPeriodTypeOptions,
product.interestCalculationTypeOptions, product.interestCalculationDaysInYearTypeOptions,
product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions,
product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions);
product.lockinPeriodFrequencyType, product.withdrawalFeeForTransfers, product.accountingRule, product.accountingMappings,
product.paymentChannelToFundSourceMappings, product.currencyOptions, product.interestCompoundingPeriodTypeOptions,
product.interestPostingPeriodTypeOptions, product.interestCalculationTypeOptions,
product.interestCalculationDaysInYearTypeOptions, product.lockinPeriodFrequencyTypeOptions,
product.withdrawalFeeTypeOptions, product.paymentTypeOptions, product.accountingRuleOptions,
product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions,
product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings);
}
/**
@@ -132,17 +129,20 @@ public class SavingsProductData {
existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance,
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount,
existingProduct.withdrawalFeeType, existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule,
existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, existingProduct.accountingMappings,
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, existingProduct.accountingMappings,
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;
@@ -161,22 +161,21 @@ public class SavingsProductData {
existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance,
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount,
existingProduct.withdrawalFeeType, existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule,
existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, accountingMappings,
existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType,
existingProduct.withdrawalFeeForTransfers, existingProduct.accountingRule, accountingMappings,
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,
final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount,
final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final BigDecimal annualFeeAmount,
final MonthDay annualFeeOnMonthDay, final EnumOptionData accountingType) {
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
final EnumOptionData accountingType) {
final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -194,14 +193,17 @@ 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,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
accountingType, annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingType, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings);
}
public static SavingsProductData lookup(final Long id, final String name) {
@@ -216,12 +218,8 @@ public class SavingsProductData {
final BigDecimal minRequiredOpeningBalance = null;
final Integer lockinPeriodFrequency = null;
final EnumOptionData lockinPeriodFrequencyType = null;
final BigDecimal withdrawalFeeAmount = null;
final EnumOptionData withdrawalFeeType = null;
final boolean withdrawalFeeForTransfers = false;
final EnumOptionData accountingType = null;
final BigDecimal annualFeeAmount = null;
final MonthDay annualFeeMonthDay = null;
final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -238,23 +236,25 @@ 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,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
accountingType, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
accountingType, 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,
final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount,
final EnumOptionData withdrawalFeeType, final boolean withdrawalFeeForTransfers, final EnumOptionData accountingType,
final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final Map<String, Object> accountingMappings,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers,
final EnumOptionData accountingType, final Map<String, Object> accountingMappings,
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
@@ -263,7 +263,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;
@@ -277,11 +279,7 @@ public class SavingsProductData {
this.minRequiredOpeningBalance = minRequiredOpeningBalance;
this.lockinPeriodFrequency = lockinPeriodFrequency;
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType;
this.withdrawalFeeAmount = withdrawalFeeAmount;
this.withdrawalFeeType = withdrawalFeeType;
this.withdrawalFeeForTransfers = withdrawalFeeForTransfers;
this.annualFeeAmount = annualFeeAmount;
this.annualFeeOnMonthDay = annualFeeOnMonthDay;
this.currencyOptions = currencyOptions;
this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions;
@@ -306,6 +304,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() {
@@ -7,8 +7,8 @@ package org.mifosplatform.portfolio.savings.data;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
@@ -22,9 +22,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -46,7 +44,6 @@ import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -151,7 +148,7 @@ public class SavingsProductDataValidator {
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
/*if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName,
element);
@@ -176,7 +173,7 @@ public class SavingsProductDataValidator {
.zeroOrPositiveAmount();
}
}
*/
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
withdrawalFeeForTransfersParamName, element);
@@ -184,23 +181,23 @@ public class SavingsProductDataValidator {
.ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount();
if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount();
if (annualFeeAmount != null) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull();
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull();
}
}
if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) {
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
if (monthDayOfAnnualFee != null) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName,
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName,
element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount();
baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount();
}
}
@@ -235,7 +232,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);
@@ -325,19 +328,6 @@ public class SavingsProductDataValidator {
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName,
element);
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull()
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) {
final Integer withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull()
.isOneOfTheseValues(1, 2);
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(
withdrawalFeeForTransfersParamName, element);
@@ -345,14 +335,14 @@ public class SavingsProductDataValidator {
.ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element);
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
}
final Long savingsControlAccountId = this.fromApiJsonHelper.extractLongNamed(
@@ -380,7 +370,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 +421,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,14 +6,10 @@
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -48,7 +44,6 @@ import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
@@ -75,7 +70,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO;
import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
@@ -222,28 +216,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@Column(name = "lockedin_until_date_derived", nullable = true)
private Date lockedInUntilDate;
@Column(name = "withdrawal_fee_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal withdrawalFeeAmount;
@Column(name = "withdrawal_fee_type_enum", nullable = true)
private Integer withdrawalFeeType;
@Column(name = "withdrawal_fee_for_transfer", nullable = true)
private boolean withdrawalFeeApplicableForTransfer;
@Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal annualFeeAmount;
@Column(name = "annual_fee_on_month", nullable = true)
private Integer annualFeeOnMonth;
@Column(name = "annual_fee_on_day", nullable = true)
private Integer annualFeeOnDay;
@Temporal(TemporalType.DATE)
@Column(name = "annual_fee_next_due_date", nullable = true)
private Date annualFeeNextDueDate;
@Embedded
private SavingsAccountSummary summary;
@@ -274,16 +249,13 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final Set<SavingsAccountCharge> savingsAccountCharges) {
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges) {
final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate,
interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay,
savingsAccountCharges);
withdrawalFeeApplicableForTransfer, savingsAccountCharges);
}
private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer,
@@ -293,9 +265,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final Set<SavingsAccountCharge> savingsAccountCharges) {
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> savingsAccountCharges) {
this.client = client;
this.group = group;
this.product = product;
@@ -321,23 +291,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (lockinPeriodFrequencyType != null) {
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
}
this.withdrawalFeeAmount = withdrawalFeeAmount;
if (withdrawalFeeType != null) {
this.withdrawalFeeType = withdrawalFeeType.getValue();
}
this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
this.annualFeeAmount = annualFeeAmount;
if (annualFeeOnMonthDay != null) {
this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear();
this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth();
}
if (!CollectionUtils.isEmpty(savingsAccountCharges)) {
this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges);
// TODO:Add charges details to savings summary
// this.summary =
// updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
this.summary = new SavingsAccountSummary();
@@ -378,16 +335,13 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return SavingsAccountStatusType.fromInt(this.status).isClosed();
}
public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate) {
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate);
Money interestPostedToDate = Money.zero(this.currency);
boolean recalucateDailyBalanceDetails = false;
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
@@ -610,9 +564,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_DEPOSIT, transactionDTO.getTransactionDate());
transactionDTO.getExistingTransactionIds().addAll(findExistingTransactionIds());
transactionDTO.getExistingReversedTransactionIds().addAll(findExistingReversedTransactionIds());
final Money amount = Money.of(this.currency, transactionDTO.getTransactionAmount());
final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(),
@@ -624,7 +575,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return transaction;
}
private LocalDate getActivationLocalDate() {
public LocalDate getActivationLocalDate() {
LocalDate activationLocalDate = null;
if (this.activatedOnDate != null) {
activationLocalDate = new LocalDate(this.activatedOnDate);
@@ -685,41 +636,25 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_WITHDRAWAL, transactionDTO.getTransactionDate());
transactionDTO.getExistingTransactionIds().addAll(findExistingTransactionIds());
transactionDTO.getExistingReversedTransactionIds().addAll(findExistingReversedTransactionIds());
final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount());
final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(),
transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney);
this.transactions.add(transaction);
if (applyWithdrawFee && isAutomaticWithdrawalFee()) {
SavingsAccountTransaction withdrawalFeeTransaction = null;
Money feeAmount = Money.zero(this.currency);
switch (SavingsWithdrawalFeesType.fromInt(this.withdrawalFeeType)) {
case INVALID:
break;
case FLAT:
feeAmount = Money.of(this.currency, this.withdrawalFeeAmount);
withdrawalFeeTransaction = SavingsAccountTransaction
.fee(this, office(), transactionDTO.getTransactionDate(), feeAmount);
this.transactions.add(withdrawalFeeTransaction);
break;
case PERCENT_OF_AMOUNT:
final BigDecimal feeAmountDecimal = transactionDTO.getTransactionAmount().multiply(this.withdrawalFeeAmount)
.divide(BigDecimal.valueOf(100l));
feeAmount = Money.of(this.currency, feeAmountDecimal);
withdrawalFeeTransaction = SavingsAccountTransaction
.fee(this, office(), transactionDTO.getTransactionDate(), feeAmount);
this.transactions.add(withdrawalFeeTransaction);
break;
if (applyWithdrawFee){
//auto pay withdrawal fee
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate());
}
return transaction;
}
private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate){
for (SavingsAccountCharge charge : this.charges()) {
if (charge.isWithdrawalFee()){
charge.updateWithdralFeeAmount(transactionAmoount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate);
}
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
return transaction;
}
public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) {
@@ -779,132 +714,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
}
}
public SavingsAccountTransaction addAnnualFee(final MathContext mc, final DateTimeFormatter formatter,
final LocalDate annualFeeTransactionDate, final LocalDate today, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.applyAnnualFeeTransactionAction);
if (isNotActive()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
final LocalDate nextAnnualFeeDueDate = getNextAnnualFeeDueDate();
if (nextAnnualFeeDueDate == null || annualFeeSettingsNotSet()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (nextAnnualFeeDueDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter("annualFeeTransactionDate").value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (isDateInTheFuture(annualFeeTransactionDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.in.the.future");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (isNotValidAnnualFeeTransactionDate(annualFeeTransactionDate, today)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(annualFeeTransactionDate)) {
baseDataValidator.reset().parameter("annualFeeTransactionDate").value(annualFeeTransactionDate.toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_PAY_CHARGE, annualFeeTransactionDate);
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Money annualFee = Money.of(this.currency, this.annualFeeAmount);
final SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, office(),
annualFeeTransactionDate, annualFee);
this.transactions.add(annualFeeTransaction);
validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.applyAnnualFeeTransactionAction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
calculateInterestUsing(mc, today);
currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
if (currentAnnualFeeNextDueDate != null) {
final LocalDate newAnnualFeeNextDueDate = new LocalDate(currentAnnualFeeNextDueDate).withMonthOfYear(this.annualFeeOnMonth)
.withDayOfMonth(this.annualFeeOnDay).plusYears(1);
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
} else {
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
}
return annualFeeTransaction;
}
private boolean isNotValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) {
return !isValidAnnualFeeTransactionDate(annualFeeTransactionDate, today);
}
private boolean isValidAnnualFeeTransactionDate(final LocalDate annualFeeTransactionDate, final LocalDate today) {
LocalDate startingDate = getActivationLocalDate();
boolean isValid = false;
while (!startingDate.isAfter(today) && !isValid) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay);
if (startingDate.isAfter(nextDueLocalDate)) {
nextDueLocalDate = nextDueLocalDate.plusYears(1);
}
isValid = nextDueLocalDate.isEqual(annualFeeTransactionDate);
startingDate = nextDueLocalDate.plusYears(1);
}
return isValid;
}
private void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay);
if (startingDate.isAfter(nextDueLocalDate)) {
nextDueLocalDate = nextDueLocalDate.plusYears(1);
}
this.annualFeeNextDueDate = nextDueLocalDate.toDate();
}
private LocalDate getNextAnnualFeeDueDate() {
LocalDate nextAnnualFeeDueDate = null;
if (this.annualFeeNextDueDate != null) {
nextAnnualFeeDueDate = new LocalDate(this.annualFeeNextDueDate);
}
return nextAnnualFeeDueDate;
}
private boolean annualFeeSettingsNotSet() {
return !annualFeeSettingsSet();
}
private boolean annualFeeSettingsSet() {
return this.annualFeeOnDay != null && this.annualFeeOnMonth != null;
}
private boolean isAutomaticWithdrawalFee() {
return this.withdrawalFeeType != null;
}
private boolean isAccountLocked(final LocalDate transactionDate) {
boolean isLocked = false;
final boolean accountHasLockedInSetting = this.lockedInUntilDate != null;
@@ -1052,55 +862,12 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.lockinPeriodFrequencyType = null;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName);
actualChanges.put(withdrawalFeeAmountParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.withdrawalFeeAmount = newValue;
}
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(withdrawalFeeTypeParamName, this.withdrawalFeeType)) {
final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeTypeParamName);
actualChanges.put(withdrawalFeeTypeParamName, newValue);
this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue;
}
if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
this.withdrawalFeeApplicableForTransfer = newValue;
}
// set period type to null if frequency is null
if (this.withdrawalFeeAmount == null) {
this.withdrawalFeeType = null;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName);
actualChanges.put(annualFeeAmountParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeAmount = newValue;
}
if (command.hasParameter(annualFeeOnMonthDayParamName)) {
final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName);
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
if (this.annualFeeOnDay != dayOfMonthValue) {
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeOnDay = dayOfMonthValue;
}
final Integer monthOfYear = monthDay.getMonthOfYear();
if (this.annualFeeOnMonth != monthOfYear) {
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeOnMonth = monthOfYear;
}
}
// charges
final String chargesParamName = "charges";
if (command.hasParameter(chargesParamName)) {
@@ -1110,60 +877,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
}
// set period type to null if frequency is null
if (this.annualFeeAmount == null) {
this.annualFeeOnDay = null;
this.annualFeeOnMonth = null;
}
validateLockinDetails();
validateWithdrawalFeeDetails();
validateAnnualFeeDetails();
}
private void validateAnnualFeeDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (this.annualFeeAmount == null) {
if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) {
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull();
}
} else {
if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) {
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull();
}
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount();
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateWithdrawalFeeDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (this.withdrawalFeeAmount == null) {
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).ignoreIfNull()
.isOneOfTheseValues(1, 2);
if (this.withdrawalFeeType != null) {
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).notNull();
}
} else {
baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).zeroOrPositiveAmount();
baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).notNull()
.isOneOfTheseValues(1, 2);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateLockinDetails() {
@@ -1190,8 +905,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds) {
final Map<String, Object> accountingBridgeData = new LinkedHashMap<String, Object>();
accountingBridgeData.put("savingsId", getId());
@@ -1326,8 +1041,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
public void validateNewApplicationState(final LocalDate todayDateOfTenant) {
validateLockinDetails();
validateWithdrawalFeeDetails();
validateAnnualFeeDetails();
//validateWithdrawalFeeDetails();
//validateAnnualFeeDetails();
final LocalDate submittedOn = getSubmittedOnLocalDate();
@@ -1468,7 +1183,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return actualChanges;
}
public void undoTransaction(final Long transactionId, final List<Long> reversedTransactionIds) {
public void undoTransaction(final Long transactionId) {
SavingsAccountTransaction transactionToUndo = null;
for (final SavingsAccountTransaction transaction : this.transactions) {
@@ -1485,26 +1200,21 @@ public class SavingsAccount extends AbstractPersistable<Long> {
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION,
transactionToUndo.transactionLocalDate());
transactionToUndo.reverse();
reversedTransactionIds.add(transactionId);
if (transactionToUndo.isAnnualFee()) {
this.annualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
if (this.annualFeeNextDueDate == null) {
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
} else {
final LocalDate newAnnualFeeNextDueDate = new LocalDate(this.annualFeeNextDueDate)
.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1);
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
}
} else if (transactionToUndo.isCharge()) {
if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) {
// undo charge
final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid();
for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
chargeToUndo.resetPaidAmount(this.currency);
if (transactionToUndo.isChargeTransaction()) {
chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
if(chargeToUndo.isAnnualFee()){
chargeToUndo.updateToPreviousAnnualFeeDueDate();
}
} else if (transactionToUndo.isWaiveCharge()) {
chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
}
}
}
}
}
@@ -1649,8 +1359,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return actualChanges;
}
public Map<String, Object> activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
public Map<String, Object> activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -1687,10 +1396,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.activatedBy = currentUser;
this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate());
if (annualFeeSettingsSet()) {
/*if (annualFeeSettingsSet()) {
updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
}
*/
if (this.client != null && this.client.isActivatedAfter(activationDate)) {
final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
final String dateAsString = formatter.print(this.client.getActivationLocalDate());
@@ -1733,7 +1442,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance);
if (minRequiredOpeningBalance.isGreaterThanZero()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, activationDate,
minRequiredOpeningBalance.getAmount(), existingTransactionIds, existingReversedTransactionIds, null);
minRequiredOpeningBalance.getAmount(), null);
deposit(transactionDTO);
// no openingBalance concept supported yet but probably will to
@@ -1745,9 +1454,27 @@ public class SavingsAccount extends AbstractPersistable<Long> {
// correct.
recalculateDailyBalances(openingAccountBalance);
}
//update annual fee due date
for (SavingsAccountCharge charge : this.charges()) {
charge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
}
//auto pay the activation time charges
this.payActivationCharges();
//TODO : AA add activation charges to actual changes list
return actualChanges;
}
private void payActivationCharges() {
for (SavingsAccountCharge savingsAccountCharge : this.charges()) {
if (savingsAccountCharge.isSavingsActivation()) {
payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate());
}
}
}
public Map<String, Object> close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) {
@@ -1893,21 +1620,33 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
// TODO AA: revisit
if (this.currency == null) { return false; }
return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode);
}
public void removeCharge(final SavingsAccountCharge charge) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (isClosed()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.closed");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (isActive() || isApproved()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("delete.transaction.invalid.account.is.active");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.charges.remove(charge);
}
public void waiveCharge(final Long savingsAccountChargeId, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
public void waiveCharge(final Long savingsAccountChargeId) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (isClosed()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
@@ -1921,6 +1660,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId);
if (savingsAccountCharge.isWithdrawalFee()){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.waiver.of.withdrawal.fee.not.supported");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
// validate charge is not already paid or waived
if (savingsAccountCharge.isWaived()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived");
@@ -1930,46 +1674,23 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
// waive charge
final Money amountWaived = savingsAccountCharge.waive(getCurrency());
// TODO : AA revisit the code
/*
* Money feeChargesWaived = amountWaived; Money penaltyChargesWaived =
* Money.zero(this.getCurrency()); if
* (savingsAccountCharge.isPenaltyCharge()) { penaltyChargesWaived =
* amountWaived; feeChargesWaived = Money.zero(this.getCurrency()); }
*/
// persist an entry in transaction details
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(),
savingsAccountCharge.getDueLocalDate(), amountWaived);
this.transactions.add(chargeTransaction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final MathContext mc = MathContext.DECIMAL64;
calculateInterestUsing(mc, savingsAccountCharge.getDueLocalDate());
handleWaiverChargeTransactions(savingsAccountCharge, amountWaived);
}
public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (isClosed()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (isNotActive()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.not.active");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (!hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"transaction.invalid.account.currency.and.charge.currency.not.same");
@@ -1978,25 +1699,72 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final LocalDate chargeDueDate = savingsAccountCharge.getDueLocalDate();
if (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter("dueDate").value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.activationDate");
if (savingsAccountCharge.isOnSpecifiedDueDate()) {
if (getActivationLocalDate() != null && chargeDueDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
} else if (getSubmittedOnLocalDate() != null && chargeDueDate.isBefore(getSubmittedOnLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.submittedOnDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
if (savingsAccountCharge.isSavingsActivation() && !(isSubmittedAndPendingApproval() || (isApproved() && isNotActive()))) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"not.valid.account.status.cannot.add.activation.time.charge");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate);
//Only one withdrawal fee is supported per account
if(savingsAccountCharge.isWithdrawalFee()){
if(this.isWithDrawalFeeExists()){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"multiple.withdrawal.fee.per.account.not.supported");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
//Only one annual fee is supported per account
if(savingsAccountCharge.isAnnualFee()){
if(this.isAnnualFeeExists()){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"multiple.annual.fee.per.account.not.supported");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
//activation charge and withdrawal charges not required this validation
if(savingsAccountCharge.isOnSpecifiedDueDate()){
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_APPLY_CHARGE, chargeDueDate);
}
// add new charge to savings account
getCharges().add(savingsAccountCharge);
charges().add(savingsAccountCharge);
}
public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
private boolean isWithDrawalFeeExists() {
for (SavingsAccountCharge charge : this.charges()) {
if(charge.isWithdrawalFee()) return true;
}
return false;
}
private boolean isAnnualFeeExists() {
for (SavingsAccountCharge charge : this.charges()) {
if(charge.isAnnualFee()) return true;
}
return false;
}
public void payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, final LocalDate transactionDate,
final DateTimeFormatter formatter) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
if (isClosed()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.is.closed");
@@ -2008,8 +1776,45 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
final SavingsAccountCharge savingsAccountCharge = getCharge(savingsAccountChargeId);
if (getActivationLocalDate() != null && transactionDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (DateUtils.isDateInTheFuture(transactionDate)) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(getSubmittedOnLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (savingsAccountCharge.isSavingsActivation()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"transaction.not.valid.cannot.pay.activation.time.charge.is.automated");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (savingsAccountCharge.isAnnualFee()){
final LocalDate annualFeeDueDate = savingsAccountCharge.getDueLocalDate();
if (annualFeeDueDate == null) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.annualfee.settings");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (!annualFeeDueDate.equals(transactionDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("invalid.date");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
Date currentAnnualFeeNextDueDate = findLatestAnnualFeeTransactionDueDate();
if (currentAnnualFeeNextDueDate != null && new LocalDate(currentAnnualFeeNextDueDate).isEqual(transactionDate)) {
baseDataValidator.reset().parameter("dueDate").value(transactionDate.toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("transaction.exists.on.date");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
// validate charge is not already paid or waived
if (savingsAccountCharge.isWaived()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.already.waived");
@@ -2019,36 +1824,51 @@ public class SavingsAccount extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Money chargePaid = savingsAccountCharge.pay(getCurrency(), amountPaid);
// If null reset to current date
final LocalDate transactionDate = (savingsAccountCharge.getDueLocalDate() == null) ? DateUtils.getLocalDateOfTenant()
: savingsAccountCharge.getDueLocalDate();
// persist an entry in transaction details
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, chargePaid);
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge,
amountPaid);
chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy);
this.transactions.add(chargeTransaction);
final MathContext mc = MathContext.DECIMAL64;
if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(mc, today);
final Money chargePaid = Money.of(currency, amountPaid);
if (!savingsAccountCharge.getAmountOutstanding(getCurrency()).isGreaterThanOrEqualTo(chargePaid)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("transaction.invalid.charge.amount.paid.in.access");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
this.payCharge(savingsAccountCharge, chargePaid, transactionDate);
//update next annual fee due date
savingsAccountCharge.updateToNextAnnualFeeDueDate();
}
validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.payChargeTransactionAction);
public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate) {
savingsAccountCharge.pay(getCurrency(), amountPaid);
handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate);
}
private void handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount,
final LocalDate transactionDate) {
SavingsAccountTransaction chargeTransaction = null;
if (savingsAccountCharge.isWithdrawalFee()){
chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount);
}else if (savingsAccountCharge.isAnnualFee()){
chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount);
}else {
chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount);
}
handleChargeTransactions(savingsAccountCharge, chargeTransaction);
}
private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount) {
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(),
DateUtils.getLocalDateOfTenant(), transactionAmount);
handleChargeTransactions(savingsAccountCharge, chargeTransaction);
}
private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction) {
// Provide a link between transaction and savings charge for which
// amount is waived.
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, transaction
.getAmount(this.getCurrency()).getAmount());
transaction.getSavingsAccountChargesPaid().add(chargePaidBy);
this.getTransactions().add(transaction);
}
private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) {
@@ -2065,7 +1885,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return charge;
}
private Set<SavingsAccountCharge> getCharges() {
public Set<SavingsAccountCharge> charges() {
return (this.charges == null) ? new HashSet<SavingsAccountCharge>() : this.charges;
}
}
@@ -6,8 +6,6 @@
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
@@ -22,15 +20,12 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequire
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.math.BigDecimal;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.core.service.DateUtils;
@@ -50,7 +45,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -199,50 +193,18 @@ public class SavingsAccountAssembler {
} else {
lockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
}
BigDecimal withdrawalFeeAmount = null;
if (command.parameterExists(withdrawalFeeAmountParamName)) {
withdrawalFeeAmount = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName);
} else {
withdrawalFeeAmount = product.withdrawalFeeAmount();
}
SavingsWithdrawalFeesType withdrawalFeeType = null;
if (command.parameterExists(withdrawalFeeAmountParamName)) {
final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName);
if (withdrawalFeeTypeValue != null) {
withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue);
}
} else {
withdrawalFeeType = product.withdrawalFeeType();
}
boolean iswithdrawalFeeApplicableForTransfer = false;
if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
}
BigDecimal annualFeeAmount = null;
if (command.parameterExists(annualFeeAmountParamName)) {
annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName);
} else {
annualFeeAmount = product.annualFeeAmount();
}
MonthDay monthDayOfAnnualFee = null;
if (command.parameterExists(annualFeeOnMonthDayParamName)) {
monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
} else {
monthDayOfAnnualFee = product.monthDayOfAnnualFee();
}
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(element);
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode());
final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo,
externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer, annualFeeAmount,
monthDayOfAnnualFee, charges);
lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -9,6 +9,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountPara
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -28,6 +29,7 @@ import javax.persistence.TemporalType;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
@@ -53,15 +55,22 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
private Integer chargeTime;
@Temporal(TemporalType.DATE)
@Column(name = "due_for_collection_as_of_date")
@Column(name = "charge_due_date")
private Date dueDate;
@Column(name = "fee_on_month", nullable = true)
private Integer feeOnMonth;
@Column(name = "fee_on_day", nullable = true)
private Integer feeOnDay;
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
private BigDecimal percentage;
// TODO AA: This field may not require for savings charges
@Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPercentageAppliedTo;
@@ -89,29 +98,38 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
@Column(name = "waived", nullable = false)
private boolean waived = false;
@Column(name = "is_active", nullable = false)
private boolean status = true;
public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition,
final JsonCommand command) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
final boolean status = true;
return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status,
feeOnMonthDay);
}
// TODO AA: refactor method signature
public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
return new SavingsAccountCharge(null, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
final MonthDay feeOnMonthDay) {
return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status,
feeOnMonthDay);
}
protected SavingsAccountCharge() {
//
}
public SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
final MonthDay feeOnMonthDay) {
this.savingsAccount = savingsAccount;
this.charge = chargeDefinition;
@@ -127,6 +145,16 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
}
if (isAnnualFee()) {
if (feeOnMonthDay == null) {
final String defaultUserMessage = "Savings Account charge is missing due date.";
throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
}
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
}
this.dueDate = (dueDate == null) ? null : dueDate.toDate();
this.chargeCalculation = chargeDefinition.getChargeCalculation();
@@ -139,13 +167,18 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
chargeAmount = amount;
}
final BigDecimal transactionAmount = new BigDecimal(0);// percentage
// based fee is
// not yet
// supported for
// savings
final BigDecimal transactionAmount = new BigDecimal(0);
populateDerivedFields(transactionAmount, chargeAmount);
resetOutstandingAmount();// reset only if it's withdrawal or annual fee
this.paid = determineIfFullyPaid();
this.status = status;
}
private void resetOutstandingAmount() {
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
}
}
private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) {
@@ -214,31 +247,42 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.waived = false;
}
public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
public void undoPayment(final MonetaryCurrency currency, final Money transactionAmount) {
Money amountPaid = getAmountPaid(currency);
amountPaid = amountPaid.minus(transactionAmount);
this.amountPaid = amountPaid.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency);
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
}
this.paid = false;
}
public Money waive(final MonetaryCurrency currency) {
this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
return getAmountWaived(currency);
}
public Money pay(final MonetaryCurrency currency, final BigDecimal chargePaid) {
public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) {
Money amountWaived = getAmountWaived(currency);
amountWaived = amountWaived.minus(transactionAmount);
this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency);
this.waived = false;
}
public Money pay(final MonetaryCurrency currency, final Money amountPaid) {
Money amountPaidToDate = Money.of(currency, this.amountPaid);
final Money amountPaid = Money.of(currency, chargePaid);
Money amountOutstanding = Money.of(currency, this.amountOutstanding);
amountPaidToDate = amountPaidToDate.plus(amountPaid);
amountOutstanding = amountOutstanding.minus(chargePaid);
amountOutstanding = amountOutstanding.minus(amountPaid);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount();
this.paid = determineIfFullyPaid();
return amountPaid;
return amountOutstanding;
}
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
@@ -249,16 +293,17 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.savingsAccount = savingsAccount;
}
public void update(final BigDecimal amount, final LocalDate dueDate) {
final BigDecimal transactionAmount = BigDecimal.ZERO;// percentage based
// fee calculation
// is not yet
// supported for
// Savings
public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay) {
final BigDecimal transactionAmount = BigDecimal.ZERO;
if (dueDate != null) {
this.dueDate = dueDate.toDate();
}
if (feeOnMonthDay != null) {
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
}
if (amount != null) {
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
case INVALID:
@@ -305,6 +350,24 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.dueDate = newValue.toDate();
}
if (command.hasParameter(feeOnMonthDayParamName)) {
final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName);
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
if (this.feeOnDay != dayOfMonthValue) {
actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.feeOnDay = dayOfMonthValue;
}
final Integer monthOfYear = monthDay.getMonthOfYear();
if (this.feeOnMonth != monthOfYear) {
actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.feeOnMonth = monthOfYear;
}
}
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
actualChanges.put(amountParamName, newValue);
@@ -314,6 +377,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
break;
case FLAT:
this.amount = newValue;
this.amountOutstanding = calculateOutstanding();
break;
case PERCENT_OF_AMOUNT:
this.percentage = newValue;
@@ -339,10 +403,6 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return actualChanges;
}
public boolean isSpecifiedDueDate() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE);
}
private boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1;
}
@@ -398,6 +458,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return this.amount;
}
public BigDecimal amoutOutstanding() {
return this.amountOutstanding;
}
public boolean isFeeCharge() {
return !this.penaltyCharge;
}
@@ -440,6 +504,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return Money.of(currency, this.amountWrittenOff);
}
public Money getAmountOutstanding(final MonetaryCurrency currency) {
return Money.of(currency, this.amountOutstanding);
}
/**
* @param incrementBy
* Amount used to pay off this charge
@@ -482,10 +550,35 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return this.charge;
}
public SavingsAccount savingsAccount() {
return this.savingsAccount;
}
public boolean isOnSpecifiedDueDate() {
return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate();
}
public boolean isSavingsActivation() {
return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation();
}
public boolean isSavingsClosure() {
return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure();
}
public boolean isWithdrawalFee() {
return ChargeTimeType.fromInt(this.chargeTime).isWithdrawalFee();
}
public boolean isAnnualFee() {
return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee();
}
public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; }
return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode);
}
@Override
public boolean equals(final Object obj) {
if (obj == null) { return false; }
@@ -508,4 +601,47 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
.append(this.amount).append(getDueLocalDate()) //
.toHashCode();
}
public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) {
BigDecimal amountPaybale = BigDecimal.ZERO;
if (ChargeCalculationType.fromInt(this.chargeCalculation).isFlat()) {
amountPaybale = this.amount;
} else if (ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount()) {
amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100l));
}
this.amountOutstanding = amountPaybale;
return amountPaybale;
}
public void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) {
if (isAnnualFee()) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay);
if (startingDate.isAfter(nextDueLocalDate)) {
nextDueLocalDate = nextDueLocalDate.plusYears(1);
}
this.dueDate = nextDueLocalDate.toDate();
}
}
public void updateToNextAnnualFeeDueDate() {
if (isAnnualFee()) {
LocalDate nextDueLocalDate = new LocalDate(dueDate);
this.dueDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).plusYears(1).toDate();
}
}
public void updateToPreviousAnnualFeeDueDate() {
if (isAnnualFee()) {
LocalDate previousDueLocalDate = new LocalDate(dueDate);
this.dueDate = previousDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).minusYears(1).toDate();
}
}
public boolean feeSettingsNotSet() {
return !feeSettingsSet();
}
public boolean feeSettingsSet() {
return this.feeOnDay != null && this.feeOnMonth != null;
}
}
@@ -5,20 +5,28 @@
*/
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
@@ -48,13 +56,14 @@ public class SavingsAccountChargeAssembler {
this.savingsAccountChargeRepository = savingsAccountChargeRepository;
}
public Set<SavingsAccountCharge> fromParsedJson(final JsonElement element) {
public Set<SavingsAccountCharge> fromParsedJson(final JsonElement element, final String productCurrencyCode) {
final Set<SavingsAccountCharge> savingsAccountCharges = new HashSet<SavingsAccountCharge>();
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
@@ -72,6 +81,8 @@ public class SavingsAccountChargeAssembler {
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -89,8 +100,10 @@ public class SavingsAccountChargeAssembler {
if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType);
}
final boolean status = true;
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay);
savingsAccountCharges.add(savingsAccountCharge);
} else {
final Long savingsAccountChargeId = id;
@@ -98,7 +111,7 @@ public class SavingsAccountChargeAssembler {
.findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate);
savingsAccountCharge.update(amount, dueDate, feeOnMonthDay);
savingsAccountCharges.add(savingsAccountCharge);
}
@@ -106,6 +119,36 @@ public class SavingsAccountChargeAssembler {
}
}
this.validateSavingsCharges(savingsAccountCharges, productCurrencyCode);
return savingsAccountCharges;
}
private void validateSavingsCharges(final Set<SavingsAccountCharge> charges, final String productCurrencyCode) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
boolean isOneWithdrawalPresent = false;
boolean isOneAnnualPresent = false;
for (SavingsAccountCharge charge : charges) {
if(!charge.hasCurrencyCodeOf(productCurrencyCode)){
baseDataValidator.reset().parameter("currency").value(charge.getCharge().getId())
.failWithCodeNoParameterAddedToErrorCode("currency.and.charge.currency.not.same");
}
if (charge.isWithdrawalFee()) {
if (isOneWithdrawalPresent) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.withdrawal.fee.per.account.not.supported");
}
isOneWithdrawalPresent = true;
}
if (charge.isAnnualFee()) {
if (isOneAnnualPresent) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("multiple.annual.fee.per.account.not.supported");
}
isOneAnnualPresent = true;
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -5,11 +5,19 @@
*/
package org.mifosplatform.portfolio.savings.domain;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface SavingsAccountChargeRepository extends JpaRepository<SavingsAccountCharge, Long>,
JpaSpecificationExecutor<SavingsAccountCharge> {
SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId);
@Query("from SavingsAccountCharge sac where sac.dueDate <=:transactionDate and sac.waived = 0 and sac.paid=0 order by sac.dueDate")
List<SavingsAccountCharge> findPendingCharges(@Param("transactionDate") Date transactionDate);
}
@@ -5,6 +5,9 @@
*/
package org.mifosplatform.portfolio.savings.domain;
import java.util.Date;
import java.util.List;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -36,11 +39,19 @@ public class SavingsAccountChargeRepositoryWrapper {
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(id); }
return savingsAccountCharge;
}
public List<SavingsAccountCharge> findPendingCharges(final Date transactionDate){
return this.repository.findPendingCharges(transactionDate);
}
public void save(final SavingsAccountCharge savingsAccountCharge) {
this.repository.save(savingsAccountCharge);
}
public void save(final Iterable<SavingsAccountCharge> savingsAccountCharges){
this.repository.save(savingsAccountCharges);
}
public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) {
this.repository.saveAndFlush(savingsAccountCharge);
}
@@ -7,9 +7,9 @@ package org.mifosplatform.portfolio.savings.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
@@ -49,16 +49,17 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean applyWithdrawFee) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
existingTransactionIds, existingReversedTransactionIds, paymentDetail);
paymentDetail);
final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, applyWithdrawFee);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
@@ -77,16 +78,17 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
existingTransactionIds, existingReversedTransactionIds, paymentDetail);
paymentDetail);
final SavingsAccountTransaction deposit = account.deposit(transactionDTO);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
@@ -106,8 +108,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
return transaction.getId();
}
private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
Set<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(account.findExistingTransactionIds());
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
}
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
@@ -10,6 +10,7 @@ import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Transient;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
@@ -49,6 +50,13 @@ public final class SavingsAccountSummary {
@Column(name = "account_balance_derived", scale = 6, precision = 19)
private BigDecimal accountBalance = BigDecimal.ZERO;
//TODO: AA do we need this data to be persisted.
@Transient
private BigDecimal totalFeeChargesWaived = BigDecimal.ZERO;
@Transient
private BigDecimal totalPenaltyChargesWaived = BigDecimal.ZERO;
protected SavingsAccountSummary() {
//
}
@@ -63,6 +71,8 @@ public final class SavingsAccountSummary {
this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions);
this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions);
this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions);
this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, transactions);
this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions);
this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals)
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge)
@@ -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;
@@ -114,7 +116,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
amount, isReversed);
}
public static SavingsAccountTransaction fee(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
final Money amount) {
final boolean isReversed = false;
return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount,
@@ -208,6 +210,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawal();
}
public boolean isPostInterestCalculationRequired(){
return this.isDeposit() || this.isWithdrawal() || this.isPayCharge();
}
public boolean isInterestPostingAndNotReversed() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isInterestPosting() && isNotReversed();
}
@@ -215,6 +221,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
public boolean isWithdrawalFeeAndNotReversed() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee() && isNotReversed();
}
public boolean isWithdrawalFee() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee();
}
public boolean isAnnualFeeAndNotReversed() {
return isAnnualFee() && isNotReversed();
@@ -315,6 +325,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;
}
@@ -431,21 +460,29 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
}
public boolean isDebit() {
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isCharge();
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isPayCharge();
}
public boolean isCharge() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge();
public boolean isPayCharge() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isPayCharge();
}
public boolean isChargeTransaction(){
return SavingsAccountTransactionType.fromInt(this.typeOf).isChargeTransaction();
}
public boolean isWaiveCharge(){
return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge();
}
public boolean isFeeCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
}
public boolean isPenaltyCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
}
public boolean isFeeChargeAndNotReversed() {
@@ -456,6 +493,24 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return isPenaltyCharge() && isNotReversed();
}
public boolean isWaiveFeeCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
}
public boolean isWaivePenaltyCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
}
public boolean isWaiveFeeChargeAndNotReversed() {
return isWaiveFeeCharge() && isNotReversed();
}
public boolean isWaivePenaltyChargeAndNotReversed() {
return isWaivePenaltyCharge() && isNotReversed();
}
private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy() {
if (!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)) { return this.savingsAccountChargesPaid.iterator().next(); }
return null;
@@ -72,17 +72,37 @@ public final class SavingsAccountTransactionSummaryWrapper {
public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isFeeChargeAndNotReversed() && transaction.isNotReversed()) {
if (transaction.isFeeChargeAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isWaiveFeeChargeAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isPenaltyChargeAndNotReversed() && transaction.isNotReversed()) {
if (transaction.isPenaltyChargeAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalPenaltyChargeWaived(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isWaivePenaltyChargeAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
@@ -7,8 +7,6 @@ package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName;
@@ -24,9 +22,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -44,7 +40,6 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.joda.time.MonthDay;
import org.mifosplatform.accounting.common.AccountingRuleType;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
@@ -58,7 +53,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.gson.JsonArray;
@@ -123,24 +117,9 @@ public class SavingsProduct extends AbstractPersistable<Long> {
@Column(name = "accounting_type", nullable = false)
private Integer accountingRule;
@Column(name = "withdrawal_fee_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal withdrawalFeeAmount;
@Column(name = "withdrawal_fee_type_enum", nullable = true)
private Integer withdrawalFeeType;
@Column(name = "withdrawal_fee_for_transfer")
private boolean withdrawalFeeApplicableForTransfer;
@Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal annualFeeAmount;
@Column(name = "annual_fee_on_month", nullable = true)
private Integer annualFeeOnMonth;
@Column(name = "annual_fee_on_day", nullable = true)
private Integer annualFeeOnDay;
@ManyToMany
@JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
private Set<Charge> charges;
@@ -150,14 +129,11 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount,
annualFeeOnMonthDay, accountingRuleType, charges);
lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges);
}
protected SavingsProduct() {
@@ -170,9 +146,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
this.name = name;
this.description = description;
@@ -192,17 +166,13 @@ public class SavingsProduct extends AbstractPersistable<Long> {
if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) {
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
}
this.withdrawalFeeAmount = withdrawalFeeAmount;
if (withdrawalFeeAmount != null && withdrawalFeeType != null) {
this.withdrawalFeeType = withdrawalFeeType.getValue();
}
this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
this.annualFeeAmount = annualFeeAmount;
/* this.annualFeeAmount = annualFeeAmount;
if (annualFeeAmount != null && annualFeeOnMonthDay != null) {
this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear();
this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth();
}
}*/
if (accountingRuleType != null) {
this.accountingRule = accountingRuleType.getValue();
@@ -213,8 +183,8 @@ public class SavingsProduct extends AbstractPersistable<Long> {
}
validateLockinDetails();
validateWithdrawalFeeDetails();
validateAnnualFeeDetails();
//validateWithdrawalFeeDetails();
//validateAnnualFeeDetails();
}
public MonetaryCurrency currency() {
@@ -257,30 +227,6 @@ public class SavingsProduct extends AbstractPersistable<Long> {
return type;
}
public BigDecimal withdrawalFeeAmount() {
return this.withdrawalFeeAmount;
}
public SavingsWithdrawalFeesType withdrawalFeeType() {
SavingsWithdrawalFeesType type = null;
if (this.withdrawalFeeType != null) {
type = SavingsWithdrawalFeesType.fromInt(this.withdrawalFeeType);
}
return type;
}
public BigDecimal annualFeeAmount() {
return this.annualFeeAmount;
}
public MonthDay monthDayOfAnnualFee() {
MonthDay monthDay = null;
if (this.annualFeeOnMonth != null && this.annualFeeOnDay != null) {
monthDay = new MonthDay(this.annualFeeOnMonth, this.annualFeeOnDay);
}
return monthDay;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(10);
@@ -382,7 +328,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
if (this.lockinPeriodFrequency == null) {
this.lockinPeriodFrequencyType = null;
}
/*
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName);
actualChanges.put(withdrawalFeeAmountParamName, newValue);
@@ -395,7 +341,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
actualChanges.put(withdrawalFeeTypeParamName, newValue);
this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue;
}
*/
if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
@@ -403,40 +349,40 @@ public class SavingsProduct extends AbstractPersistable<Long> {
}
// set period type to null if frequency is null
if (this.withdrawalFeeAmount == null) {
this.withdrawalFeeType = null;
}
// if (this.withdrawalFeeAmount == null) {
// this.withdrawalFeeType = null;
// }
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName);
actualChanges.put(annualFeeAmountParamName, newValue);
/* if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(feeAmountParamName, this.annualFeeAmount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(feeAmountParamName);
actualChanges.put(feeAmountParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeAmount = newValue;
}
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) {
final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName);
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(feeOnMonthDayParamName, this.annualFeeOnDay)) {
final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName);
final Integer newValue = monthDay != null ? monthDay.getDayOfMonth() : null;
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeOnDay = newValue;
}
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) {
final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName);
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(feeOnMonthDayParamName, this.annualFeeOnMonth)) {
final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName);
final Integer newValue = monthDay != null ? monthDay.getMonthOfYear() : null;
actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered);
actualChanges.put(feeOnMonthDayParamName, actualValueEntered);
actualChanges.put(localeParamName, localeAsInput);
this.annualFeeOnMonth = newValue;
}
}*/
// set period type to null if frequency is null
if (this.annualFeeAmount == null) {
this.annualFeeOnDay = null;
this.annualFeeOnMonth = null;
}
// // set period type to null if frequency is null
// if (this.annualFeeAmount == null) {
// this.annualFeeOnDay = null;
// this.annualFeeOnMonth = null;
// }
if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName);
@@ -453,13 +399,13 @@ public class SavingsProduct extends AbstractPersistable<Long> {
}
validateLockinDetails();
validateWithdrawalFeeDetails();
validateAnnualFeeDetails();
//validateWithdrawalFeeDetails();
//validateAnnualFeeDetails();
return actualChanges;
}
private void validateAnnualFeeDetails() {
/* private void validateAnnualFeeDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -468,21 +414,21 @@ public class SavingsProduct extends AbstractPersistable<Long> {
if (this.annualFeeAmount == null) {
if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) {
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull();
baseDataValidator.reset().parameter(feeAmountParamName).value(this.annualFeeAmount).notNull();
}
} else {
if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) {
baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull();
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull();
}
baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount();
baseDataValidator.reset().parameter(feeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount();
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateWithdrawalFeeDetails() {
*/
/* private void validateWithdrawalFeeDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -503,7 +449,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
*/
private void validateLockinDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -5,8 +5,6 @@
*/
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName;
@@ -22,15 +20,12 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeri
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.MonthDay;
import org.mifosplatform.accounting.common.AccountingRuleType;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -43,7 +38,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -107,20 +101,11 @@ public class SavingsProductAssembler {
lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
}
final BigDecimal withdrawalFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName);
SavingsWithdrawalFeesType withdrawalFeeType = null;
final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName);
if (withdrawalFeeTypeValue != null) {
withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue);
}
boolean iswithdrawalFeeApplicableForTransfer = false;
if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
}
final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName);
final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
// Savings product charges
@@ -128,8 +113,7 @@ public class SavingsProductAssembler {
return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,
iswithdrawalFeeApplicableForTransfer, annualFeeAmount, monthDayOfAnnualFee, accountingRuleType, charges);
lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges);
}
public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) {
@@ -11,6 +11,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdPa
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal;
@@ -19,6 +20,7 @@ import java.util.Locale;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
@@ -70,7 +72,8 @@ public class SavingsProductChargeAssembler {
savingsChargeElement, locale);
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
final ChargeTimeType chargeTime = null;
@@ -82,7 +85,7 @@ public class SavingsProductChargeAssembler {
ChargeCalculationType.fromInt(chargeCalculationType);
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay);
savingsAccountCharges.add(savingsAccountCharge);
} else {
final Long savingsAccountChargeId = id;
@@ -90,7 +93,7 @@ public class SavingsProductChargeAssembler {
.findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate);
savingsAccountCharge.update(amount, dueDate, null);
savingsAccountCharges.add(savingsAccountCharge);
}
@@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSourceHandler {
@SuppressWarnings("unused")
private final SavingsAccountWritePlatformService writePlatformService;
@Autowired
@@ -28,8 +29,10 @@ public class ApplyAnnualFeeSavingsAccountCommandHandler implements NewCommandSou
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
@SuppressWarnings("unused")
final LocalDate annualFeeTransactionDate = command.localDateValueOfParameterNamed("annualFeeTransactionDate");
return this.writePlatformService.applyAnnualFee(command.getSavingsId(), annualFeeTransactionDate);
//return this.writePlatformService.applyAnnualFee(command.getSavingsId(), annualFeeTransactionDate);
return null;
}
}
@@ -27,6 +27,6 @@ public class WaiveSavingsAccountChargeCommandHandler implements NewCommandSource
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId(), command);
return this.writePlatformService.waiveCharge(command.getSavingsId(), command.entityId());
}
}
@@ -12,6 +12,7 @@ import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
@@ -55,7 +56,9 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
+ "sc.calculation_percentage as percentageOf, sc.calculation_on_amount as amountPercentageAppliedTo, "
+ "sc.charge_time_enum as chargeTime, "
+ "sc.is_penalty as penalty, "
+ "sc.due_for_collection_as_of_date as dueAsOfDate, "
+ "sc.charge_due_date as dueAsOfDate, "
+ "sc.fee_on_month as feeOnMonth, "
+ "sc.fee_on_day as feeOnDay, "
+ "sc.charge_calculation_enum as chargeCalculation, "
+ "c.currency_code as currencyCode, oc.name as currencyName, "
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
@@ -93,7 +96,13 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
MonthDay feeOnMonthDay = null;
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
if (feeOnDay != null) {
feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
}
final int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final boolean penalty = rs.getBoolean("penalty");
@@ -102,7 +111,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
return SavingsAccountChargeData.instance(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff,
amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo,
chargeOptions, penalty);
chargeOptions, penalty, feeOnMonthDay);
}
}
@@ -112,9 +121,18 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
final List<EnumOptionData> loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveLoanCalculationTypes();
final List<EnumOptionData> loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes();
final List<EnumOptionData> savingsChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
// TODO AA : revisit for merge conflict - Not sure method signature
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null);
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions);
}
@Override
@@ -139,7 +157,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper();
final String sql = "select " + rm.schema() + " where sc.savings_account_id=? "
+ " order by sc.charge_time_enum ASC, sc.due_for_collection_as_of_date ASC, sc.is_penalty ASC";
+ " order by sc.charge_time_enum ASC, sc.charge_due_date ASC, sc.is_penalty ASC";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
@@ -9,7 +9,6 @@ import java.util.Collection;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData;
@@ -27,7 +26,7 @@ public interface SavingsAccountReadPlatformService {
Collection<SavingsAccountTransactionData> retrieveAllTransactions(Long savingsId);
Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue();
//Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue();
SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId);
}
@@ -14,7 +14,6 @@ import java.util.Collection;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
@@ -40,7 +39,6 @@ import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearT
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
@@ -74,7 +72,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
private final SavingsAccountTransactionsMapper transactionsMapper;
private final SavingAccountMapper savingAccountMapper;
private final SavingsAccountAnnualFeeMapper annualFeeMapper;
//private final SavingsAccountAnnualFeeMapper annualFeeMapper;
// pagination
private final PaginationHelper<SavingsAccountData> paginationHelper = new PaginationHelper<SavingsAccountData>();
@@ -95,7 +93,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper();
this.transactionsMapper = new SavingsAccountTransactionsMapper();
this.savingAccountMapper = new SavingAccountMapper();
this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
//this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
this.chargeReadPlatformService = chargeReadPlatformService;
}
@@ -222,13 +220,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,");
sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
// sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,");
// sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,");
sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, ");
sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, ");
sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, ");
// sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,");
// sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, ");
// sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, ");
// sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, ");
sqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
@@ -351,17 +349,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
}
final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
/*final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
EnumOptionData withdrawalFeeType = null;
final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum");
if (withdrawalFeeTypeValue != null) {
withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue);
}
}*/
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
/*final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
MonthDay annualFeeOnMonthDay = null;
final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth");
@@ -371,7 +369,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
}
final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
*/
final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
@@ -390,8 +388,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary);
}
}
@@ -554,13 +551,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, new Object[] { savingsId, transactionId });
}
@Override
/*@Override
public Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue() {
final String sql = "select " + this.annualFeeMapper.schema()
+ " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()";
return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {});
}
}*/
private static final class SavingsAccountTransactionsMapper implements RowMapper<SavingsAccountTransactionData> {
@@ -732,12 +729,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,");
sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, ");
sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay ");
// sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,");
// sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers ");
// sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
// sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, ");
// sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay ");
sqlBuilder.append("from m_savings_product sp ");
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
@@ -789,24 +786,24 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
}
final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
// final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
EnumOptionData withdrawalFeeType = null;
/* EnumOptionData withdrawalFeeType = null;
final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum");
if (withdrawalFeeTypeValue != null) {
withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue);
}
*/
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
//final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
MonthDay annualFeeOnMonthDay = null;
/*MonthDay annualFeeOnMonthDay = null;
final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth");
final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay");
if (annualFeeAmount != null && annualFeeOnDay != null) {
annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay);
}
}*/
Long clientId = null;
String clientName = null;
@@ -825,7 +822,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Long fieldOfficerId = null;
final String fieldOfficerName = null;
final SavingsAccountStatusEnumData status = null;
final LocalDate annualFeeNextDueDate = null;
// final LocalDate annualFeeNextDueDate = null;
final SavingsAccountSummaryData summary = null;
final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault();
@@ -833,12 +830,11 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return SavingsAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary);
}
}
private static final class SavingsAccountAnnualFeeMapper implements RowMapper<SavingsAccountAnnualFeeData> {
/*private static final class SavingsAccountAnnualFeeMapper implements RowMapper<SavingsAccountAnnualFeeData> {
private final String schemaSql;
@@ -864,5 +860,5 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return SavingsAccountAnnualFeeData.instance(id, accountNo, annualFeeNextDueDate);
}
}
}*/
}
@@ -20,7 +20,7 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate);
/*CommandProcessingResult applyAnnualFee(Long savingsId, LocalDate annualFeeTransactionDate);*/
CommandProcessingResult calculateInterest(Long savingsId);
@@ -48,7 +48,9 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId);
CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
void processDueCharges();
}
@@ -8,15 +8,18 @@ package org.mifosplatform.portfolio.savings.service;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
@@ -34,11 +37,13 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysWritePlatformService;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
@@ -52,6 +57,7 @@ import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.mifosplatform.portfolio.savings.SavingsAccountTransactionType;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeDataValidator;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO;
@@ -91,6 +97,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final ChargeRepositoryWrapper chargeRepository;
private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository;
private final HolidayWritePlatformService holidayWritePlatformService;
private final WorkingDaysWritePlatformService workingDaysWritePlatformService;
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -105,7 +113,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository) {
final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository,
final HolidayWritePlatformService holidayWritePlatformService,
final WorkingDaysWritePlatformService workingDaysWritePlatformService) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
@@ -121,6 +131,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
this.chargeRepository = chargeRepository;
this.savingsAccountChargeRepository = savingsAccountChargeRepository;
this.holidayWritePlatformService = holidayWritePlatformService;
this.workingDaysWritePlatformService = workingDaysWritePlatformService;
}
@Transactional
@@ -134,12 +146,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant(),
existingReversedTransactionIds, existingReversedTransactionIds);
final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
if (!changes.isEmpty()) {
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name());
this.savingAccountRepository.save(account);
}
@@ -224,7 +248,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build();
}
@Transactional
/*@Transactional
@Override
public CommandProcessingResult applyAnnualFee(final Long savingsId, final LocalDate annualFeeTransactionDate) {
@@ -233,14 +257,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final MathContext mc = MathContext.DECIMAL64;
final LocalDate today = DateUtils.getLocalDateOfTenant();
final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today, existingTransactionIds,
existingReversedTransactionIds);
final SavingsAccountTransaction annualFee = account.addAnnualFee(mc, fmt, annualFeeTransactionDate, today);
final Long transactionId = saveTransactionToGenerateTransactionId(annualFee);
this.savingAccountRepository.save(account);
@@ -253,7 +277,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.build();
}
}*/
@Transactional
@Override
@@ -296,12 +320,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional
private void postInterest(final SavingsAccount account) {
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today);
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -322,8 +347,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId,
final boolean allowAccountTransferModification) {
final List<Long> newTransactionIds = new ArrayList<Long>();
final List<Long> reversedTransactionIds = new ArrayList<Long>();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
.findOneByIdAndSavingsAccountId(transactionId, savingsId);
@@ -337,21 +364,31 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
if (account.isNotActive()) {
throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction);
}
account.undoTransaction(transactionId, reversedTransactionIds);
account.undoTransaction(transactionId);
// undoing transaction is withdrawal then undo withdrawal fee
// transaction if any
if (savingsAccountTransaction.isWithdrawal()) {
final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository
.findOneByIdAndSavingsAccountId(transactionId + 1, savingsId);
if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) {
account.undoTransaction(transactionId + 1);
}
}
checkClientOrGroupActive(account);
if ((savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal())
if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
account.postInterest(mc, today, newTransactionIds, reversedTransactionIds);
account.postInterest(mc, today);
} else {
account.calculateInterestUsing(mc, today);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction);
account.activateAccountBasedOnBalance();
postJournalEntries(account, newTransactionIds, reversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withEntityId(savingsId) //
@@ -384,41 +421,42 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (account.isNotActive()) {
throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
}
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN);
account.undoTransaction(transactionId, existingReversedTransactionIds);
account.undoTransaction(transactionId);
// for undo withdrawal fee
final SavingsAccountTransaction nextSavingsAccountTransaction = this.savingsAccountTransactionRepository
.findOneByIdAndSavingsAccountId(transactionId + 1, savingsId);
if (nextSavingsAccountTransaction != null && nextSavingsAccountTransaction.isWithdrawalFeeAndNotReversed()) {
account.undoTransaction(transactionId + 1, existingReversedTransactionIds);
account.undoTransaction(transactionId + 1);
}
SavingsAccountTransaction transaction = null;
if (savingsAccountTransaction.isDeposit()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
existingTransactionIds, existingReversedTransactionIds, paymentDetail);
paymentDetail);
transaction = account.deposit(transactionDTO);
} else {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
existingTransactionIds, existingReversedTransactionIds, paymentDetail);
paymentDetail);
transaction = account.withdraw(transactionDTO, true);
}
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
if (account.isBeforeLastPostingPeriod(transactionDate)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
account.postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
account.postInterest(mc, today);
} else {
account.calculateInterestUsing(mc, today);
}
@@ -446,17 +484,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
throw new PlatformApiDataValidationException(dataValidationErrors);
}
private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
private void checkClientOrGroupActive(final SavingsAccount account) {
final Client client = account.getClient();
if (client != null) {
@@ -507,8 +534,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public SavingsAccountTransaction initiateSavingsTransfer(final Long accountId, final LocalDate transferDate) {
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
final List<Long> existingTransactionIds = new ArrayList<Long>(savingsAccount.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(savingsAccount.findExistingReversedTransactionIds());
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransaction newTransferTransaction = SavingsAccountTransaction.initiateTransfer(savingsAccount,
savingsAccount.office(), transferDate);
@@ -529,8 +557,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public SavingsAccountTransaction withdrawSavingsTransfer(final Long accountId, final LocalDate transferDate) {
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
final List<Long> existingTransactionIds = new ArrayList<Long>(savingsAccount.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(savingsAccount.findExistingReversedTransactionIds());
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransaction withdrawtransferTransaction = SavingsAccountTransaction.withdrawTransfer(savingsAccount,
savingsAccount.office(), transferDate);
@@ -559,8 +588,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Office acceptedInOffice, final Staff fieldOfficer) {
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(accountId);
final List<Long> existingTransactionIds = new ArrayList<Long>(savingsAccount.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(savingsAccount.findExistingReversedTransactionIds());
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
final SavingsAccountTransaction acceptTransferTransaction = SavingsAccountTransaction.approveTransfer(savingsAccount,
acceptedInOffice, transferDate);
@@ -569,7 +599,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (fieldOfficer != null) {
savingsAccount.update(fieldOfficer);
}
final MathContext mc = MathContext.DECIMAL64;
savingsAccount.calculateInterestUsing(mc, transferDate);
@@ -586,39 +616,65 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) {
this.context.authenticatedUser();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
final Long savingsAccountId = command.getSavingsId();
this.savingsAccountChargeDataValidator.validateAdd(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
final Locale locale = command.extractLocale();
final String format = command.dateFormat();
final DateTimeFormatter fmt = StringUtils.isNotBlank(format) ? DateTimeFormat.forPattern(format).withLocale(locale) : DateTimeFormat.forPattern("dd MM yyyy");
final Long chargeDefinitionId = command.longValueOfParameterNamed(chargeIdParamName);
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command);
if (savingsAccountCharge.getDueLocalDate() != null) {
// transaction date should not be on a holiday or non working day
if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday()
&& this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay()
&& !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition);
this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withLoanId(savingsAccountId) //
.withSavingsId(savingsAccountId) //
.build();
}
@Transactional
@Override
public CommandProcessingResult updateSavingsAccountCharge(final JsonCommand command) {
this.context.authenticatedUser();
this.context.authenticatedUser();
this.savingsAccountChargeDataValidator.validateUpdate(command.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
final Long savingsAccountId = command.getSavingsId();
// SavingsAccount Charge entity
final Long savingsChargeId = command.entityId();
@@ -630,51 +686,78 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
savingsAccountId);
final Map<String, Object> changes = savingsAccountCharge.update(command);
if (savingsAccountCharge.getDueLocalDate() != null) {
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
// transaction date should not be on a holiday or non working day
if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday()
&& this.holidayWritePlatformService.isHoliday(savingsAccount.officeId(), savingsAccountCharge.getDueLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.on.holiday");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay()
&& !this.workingDaysWritePlatformService.isWorkingDay(savingsAccountCharge.getDueLocalDate())) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(savingsAccountCharge.getDueLocalDate().toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("charge.due.date.is.a.nonworking.day");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge);
// TODO AA: revisit the code
/*
* // Charges may be edited only when the loan associated with them are
* // yet to be approved (are in submitted and pending status) if
* (!loan.status().isSubmittedAndPendingApproval()) { throw new
* LoanChargeCannotBeUpdatedException(
* LOAN_CHARGE_CANNOT_BE_UPDATED_REASON
* .LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE,
* loanCharge.getId()); }
*/
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsChargeId) //
// .withOfficeId(savings) //
// .withClientId(loan.getClientId()) //
// .withGroupId(loan.getGroupId()) //
// .withLoanId(savingsAccountId) //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
.withClientId(savingsAccountCharge.savingsAccount().clientId()) //
.withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
.withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
.with(changes) //
.build();
}
@Transactional
@Override
public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) {
public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Long savingsAccountChargeId) {
this.context.authenticatedUser();
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
// Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
savingsAccount.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds);
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepository.saveAndFlush(savingsAccount);
account.waiveCharge(savingsAccountChargeId);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction);
this.savingAccountRepository.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(savingsAccountId) //
.build();
}
@@ -690,8 +773,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId);
// TODO AA: validate before deleting a charge
savingsAccount.removeCharge(savingsAccountCharge);
this.savingAccountRepository.saveAndFlush(savingsAccount);
@@ -704,31 +785,107 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build();
}
@Transactional
@Override
public CommandProcessingResult payCharge(final Long savingsAccountId, final Long savingsAccountChargeId, final JsonCommand command) {
this.context.authenticatedUser();
this.savingsAccountChargeDataValidator.validatePayCharge(command.json());
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(
savingsAccountChargeId, savingsAccountId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, existingTransactionIds, existingReversedTransactionIds);
// transaction date should not be on a holiday or non working day
if (!this.holidayWritePlatformService.isTransactionAllowedOnHoliday()
&& this.holidayWritePlatformService.isHoliday(savingsAccountCharge.savingsAccount().officeId(), transactionDate)) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.on.holiday");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.savingAccountRepository.saveAndFlush(savingsAccount);
if (!this.workingDaysWritePlatformService.isTransactionAllowedOnNonWorkingDay()
&& !this.workingDaysWritePlatformService.isWorkingDay(transactionDate)) {
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate.toString(fmt))
.failWithCodeNoParameterAddedToErrorCode("transaction.not.allowed.transaction.date.is.a.nonworking.day");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withSavingsId(savingsAccountId) //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
.withClientId(savingsAccountCharge.savingsAccount().clientId()) //
.withGroupId(savingsAccountCharge.savingsAccount().groupId()) //
.withSavingsId(savingsAccountCharge.savingsAccount().getId()) //
.build();
}
@Override
@CronTarget(jobName = JobName.PAY_DUE_SAVINGS_CHARGES)
public void processDueCharges() {
// always use current date as transaction date for batch job
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
final List<SavingsAccountCharge> pendingCharges = this.savingsAccountChargeRepository.findPendingCharges(transactionDate.toDate());
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
for (SavingsAccountCharge savingsAccountCharge : pendingCharges) {
try {
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt);
} catch (Exception exception) {
// TODO: AA - provide a meaningful error with failed
// transactions list
}
}
}
@Transactional
private void payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, final BigDecimal amountPaid,
final DateTimeFormatter formatter) {
// Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final Set<Long> existingTransactionIds = new HashSet<Long>();
final Set<Long> existingReversedTransactionIds = new HashSet<Long>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter);
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name());
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
}
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
Set<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(account.findExistingTransactionIds());
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
}
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
}
@@ -14,7 +14,6 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
@@ -24,9 +23,6 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
@@ -75,8 +71,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
private final NoteRepository noteRepository;
private final StaffRepositoryWrapper staffRepository;
private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
@Autowired
@@ -87,8 +81,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository,
final NoteRepository noteRepository, final StaffRepositoryWrapper staffRepository,
final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final SavingsAccountChargeAssembler savingsAccountChargeAssembler) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
@@ -101,8 +93,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.noteRepository = noteRepository;
this.staffRepository = staffRepository;
this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator;
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
}
@@ -140,8 +130,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
try {
this.savingsAccountDataValidator.validateForSubmit(command.json());
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command);
this.savingAccountRepository.save(account);
@@ -153,8 +141,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingAccountRepository.save(account);
}
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
final Long savingsId = account.getId();
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -233,10 +219,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
}
if (changes.containsKey("charges")) {
// final Set<Charge> savingsProductCharges =
// this.savi.assembleListOfSavingsProductCharges(command,
// product.currency().getCode());
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson());
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson(),
account.getCurrency().getCode());
final boolean updated = account.update(charges);
if (!updated) {
changes.remove("charges");
@@ -429,17 +413,6 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
.build();
}
private void postJournalEntries(final SavingsAccount savingsAccount, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
private void checkClientOrGroupActive(final SavingsAccount account) {
final Client client = account.getClient();
if (client != null) {
@@ -10,7 +10,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.joda.time.MonthDay;
import org.mifosplatform.accounting.common.AccountingEnumerations;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
@@ -87,12 +86,12 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,");
sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
// sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,");
// sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, ");
sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, ");
sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay, ");
// sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
// sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, ");
// sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay, ");
sqlBuilder.append("sp.accounting_type as accountingType ");
sqlBuilder.append("from m_savings_product sp ");
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
@@ -150,17 +149,17 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue);
}
final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
/*final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
EnumOptionData withdrawalFeeType = null;
final Integer withdrawalFeeTypeValue = JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum");
if (withdrawalFeeTypeValue != null) {
withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue);
}
*/
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
/* final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
MonthDay annualFeeOnMonthDay = null;
final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth");
@@ -168,11 +167,10 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
if (annualFeeAmount != null && annualFeeOnDay != null) {
annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay);
}
*/
return SavingsProductData.instance(id, name, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, accountingRuleType);
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRuleType);
}
}
@@ -5,16 +5,10 @@
*/
package org.mifosplatform.scheduledjobs.service;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.RoutingDataSourceServiceFactory;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.slf4j.Logger;
@@ -30,7 +24,9 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
@SuppressWarnings("unused")
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
@SuppressWarnings("unused")
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
@Autowired
@@ -203,7 +199,7 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
@CronTarget(jobName = JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS)
public void applyAnnualFeeForSavings() {
final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountReadPlatformService
/*final Collection<SavingsAccountAnnualFeeData> annualFeeData = this.savingsAccountReadPlatformService
.retrieveAccountsWithAnnualFeeDue();
for (final SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) {
@@ -219,6 +215,6 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
}
}
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Savings accounts affected by update: " + annualFeeData.size());*/
}
}