Merge pull request #854 from vishwasbabu/develop

MIFOSX-865: initial work around adding support for accrual accounting
This commit is contained in:
Vishwas Babu A J
2014-04-11 14:28:40 -07:00
10 changed files with 135 additions and 71 deletions
@@ -12,7 +12,8 @@ public enum AccountingRuleType {
NONE(1, "accountingRuleType.none"), // NONE(1, "accountingRuleType.none"), //
CASH_BASED(2, "accountingRuleType.cash"), // CASH_BASED(2, "accountingRuleType.cash"), //
ACCRUAL_BASED(3, "accountingRuleType.accrual"); ACCRUAL_PERIODIC(3, "accountingRuleType.accrual.periodic"), //
ACCRUAL_UPFRONT(4, "accountingRuleType.accrual.upfront"); //
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -198,8 +198,8 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
final Long writtenOff = this.fromApiJsonHelper.extractLongNamed( final Long writtenOff = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()) baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenOff)
.value(writtenOff).notNull().integerGreaterThanZero(); .notNull().integerGreaterThanZero();
} }
@@ -211,7 +211,8 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
} }
private boolean isAccrualBasedAccounting(final Integer accountingRuleType) { private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingRuleType); return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType)
|| AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -139,6 +139,41 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
case NONE: case NONE:
break; break;
case CASH_BASED: case CASH_BASED:
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId);
break;
case ACCRUAL_PERIODIC:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break;
case ACCRUAL_UPFRONT:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break;
}
return changes;
}
private void populateChangesForAccrualBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,
final Long receivableInterestAccountId, final Long receivableFeeAccountId, final Long receivablePenaltyAccountId) {
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);
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId,
incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId);
}
private void populateChangesForCashBasedAccounting(final Map<String, Object> changes, final Long fundAccountId,
final Long loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId) {
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); 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.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId);
@@ -147,15 +182,6 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); 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.OVERPAYMENT.getValue(), overPaymentAccountId);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
//$FALL-THROUGH$
case ACCRUAL_BASED:
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);
break;
}
return changes;
} }
/** /**
@@ -199,7 +225,9 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId, mergeLoanToLiabilityAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes); CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.toString(), changes);
break; break;
case ACCRUAL_BASED: case ACCRUAL_UPFRONT:
// fall through to periodic accrual
case ACCRUAL_PERIODIC:
// assets (including receivables) // assets (including receivables)
mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes);
@@ -50,8 +50,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
+ " charge.name as chargeName " + " charge.name as chargeName "
+ " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id " + " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id "
+ " left join acc_gl_account as glaccount on mapping.gl_account_id = glaccount.id" + " left join acc_gl_account as glaccount on mapping.gl_account_id = glaccount.id"
+ " left join m_code_value cv on mapping.payment_type=cv.id" + " left join m_code_value cv on mapping.payment_type=cv.id" + " where mapping.product_type= ? ";
+ " where mapping.product_type= ? ";
} }
@Override @Override
@@ -128,7 +127,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData);
} }
} }
} else if (AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingType)) { } else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
|| AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
@@ -191,7 +191,6 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
final String glCode = (String) productToGLAccountMap.get("glCode"); final String glCode = (String) productToGLAccountMap.get("glCode");
final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode); final GLAccountData gLAccountData = new GLAccountData(glAccountId, glAccountName, glCode);
if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE)) { if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE)) {
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.SAVINGS_REFERENCE.getValue(), gLAccountData); accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.SAVINGS_REFERENCE.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL)) { } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL)) {
@@ -92,8 +92,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null); this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
break; break;
case ACCRUAL_BASED: case ACCRUAL_UPFRONT:
// Fall Through
case ACCRUAL_PERIODIC:
// assets (including receivables) // assets (including receivables)
this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, this.loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId,
@@ -175,7 +176,6 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), savingProductId, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), savingProductId,
CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.getValue()); CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST.getValue());
// expenses // expenses
this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId,
@@ -197,8 +197,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null); this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
break; break;
/*** Do not have accrual based accounting for now ***/ default:
case ACCRUAL_BASED:
break; break;
} }
@@ -124,10 +124,10 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
element); element);
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed( final Long transfersInSuspenseAccountId = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element); SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element);
final Long overdraftControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), final Long overdraftControlId = this.fromApiJsonHelper.extractLongNamed(
element); SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), final Long incomeFromInterest = this.fromApiJsonHelper.extractLongNamed(
element); SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), element);
final Long writeOffId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), final Long writeOffId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
element); element);
switch (accountingRuleType) { switch (accountingRuleType) {
@@ -144,7 +144,11 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), incomeFromInterest); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), incomeFromInterest);
changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffId); changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
break; break;
case ACCRUAL_BASED: case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
default:
break; break;
} }
return changes; return changes;
@@ -203,7 +207,11 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(), savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(),
CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.toString(), changes); CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.toString(), changes);
break; break;
case ACCRUAL_BASED: case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
default:
break; break;
} }
} }
@@ -3238,7 +3238,8 @@ public class Loan extends AbstractPersistable<Long> {
accountingBridgeData.put("currency", currencyData); accountingBridgeData.put("currency", currencyData);
accountingBridgeData.put("calculatedInterest", this.summary.getTotalInterestCharged()); accountingBridgeData.put("calculatedInterest", this.summary.getTotalInterestCharged());
accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct()); accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct());
accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnLoanProduct()); accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnLoanProduct());
accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnLoanProduct());
final List<Map<String, Object>> newLoanTransactions = new ArrayList<Map<String, Object>>(); final List<Map<String, Object>> newLoanTransactions = new ArrayList<Map<String, Object>>();
for (final LoanTransaction transaction : this.loanTransactions) { for (final LoanTransaction transaction : this.loanTransactions) {
@@ -614,7 +614,15 @@ public class LoanProduct extends AbstractPersistable<Long> {
} }
public boolean isAccrualBasedAccountingEnabled() { public boolean isAccrualBasedAccountingEnabled() {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule); return isUpfrontAccrualAccounting() || isPeriodicAccrualAccounting();
}
public boolean isUpfrontAccrualAccounting() {
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(this.accountingRule);
}
public boolean isPeriodicAccrualAccounting() {
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(this.accountingRule);
} }
public Money getPrincipalAmount() { public Money getPrincipalAmount() {
@@ -254,7 +254,8 @@ public final class LoanProductDataValidator {
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
LoanProductConstants.graceOnArrearsAgeingParameterName, element); LoanProductConstants.graceOnArrearsAgeingParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing).zeroOrPositiveAmount(); baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
.zeroOrPositiveAmount();
// accounting related data validation // accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
@@ -973,7 +974,15 @@ public final class LoanProductDataValidator {
} }
private boolean isAccrualBasedAccounting(final Integer accountingRuleType) { private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(accountingRuleType); return isUpfrontAccrualAccounting(accountingRuleType) || isPeriodicAccounting(accountingRuleType);
}
private boolean isUpfrontAccrualAccounting(final Integer accountingRuleType) {
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
}
private boolean isPeriodicAccounting(final Integer accountingRuleType) {
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType);
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -538,8 +538,18 @@ public class SavingsProduct extends AbstractPersistable<Long> {
return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule); return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule);
} }
// TODO this entire block is currently unnecessary as Savings does not have
// accrual accounting
public boolean isAccrualBasedAccountingEnabled() { public boolean isAccrualBasedAccountingEnabled() {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule); return isUpfrontAccrualAccounting() || isPeriodicAccrualAccounting();
}
public boolean isPeriodicAccrualAccounting() {
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(this.accountingRule);
}
public boolean isUpfrontAccrualAccounting() {
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(this.accountingRule);
} }
public Integer getAccountingType() { public Integer getAccountingType() {