cleaning up recovery payments functionality

This commit is contained in:
Vishwas Babu A J
2014-05-04 18:02:59 +05:30
24 changed files with 378 additions and 80 deletions
+70
View File
@@ -923,6 +923,14 @@
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr>
<tr>
<td></td>
<td>loans/{loanId}/transactions?command=recoverypayment</td>
<td><a href="#loans_transaction_recovery_payment">Make a Recovery Payment</a></td>
<td></td>
<td></td>
<td></td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
@@ -7342,7 +7350,61 @@ Request Body:
</code> </code>
</div> </div>
</div> </div>
<!------recovery payment -->
<a id="loans_transaction_recovery_payment" name="loans_transaction_recovery_payment" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Make Recovery Payment </h4>
<p>only applies to written-off loans</p>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}/transactions?command=recoverypayment
</code>
<code class="method-request">
POST loans/5/transactions?command=recoverypayment
Content-Type: application/json
Request Body:
{
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"transactionDate": "14 May 2013",
"transactionAmount": "500.00",
"paymentTypeId": "12",
"note": "check payment",
"accountNumber": "acc123",
"checkNumber": "che123",
"routingCode": "rou123",
"receiptNumber": "rec123",
"bankNumber": "ban123"
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 5,
"resourceId": 564,
"changes": {
"transactionDate": "14 May 2013",
"transactionAmount": "500.00",
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"note": "check payment",
"accountNumber": "acc123",
"checkNumber": "che123",
"routingCode": "rou123",
"receiptNumber": "rec123",
"bankNumber": "ban123"
}
}
</code>
</div>
</div>
<!--- end of recovery payment -->
<a id="loans_transaction_undo_write-off_loan" name="loans_transaction_undo_write-off_loan" class="old-syle-anchor">&nbsp;</a> <a id="loans_transaction_undo_write-off_loan" name="loans_transaction_undo_write-off_loan" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
@@ -29138,6 +29200,14 @@ No Request Body:
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
</tr><tr>
<td></td>
<td>loans/{loanId}/transactions?command=recoverypayment</td>
<td><a href="#loans_transaction_recovery_payment">Make Recovery Payment</a></td>
<td></td>
<td></td>
<td></td>
</tr>
</tr><tr> </tr><tr>
<td></td> <td></td>
<td>loans/{loanId}/transactions?command=undowriteoff</td> <td>loans/{loanId}/transactions?command=undowriteoff</td>
@@ -0,0 +1,28 @@
INSERT INTO `m_permission` (
`id` ,
`grouping` ,
`code` ,
`entity_name` ,
`action_name` ,
`can_maker_checker`
)
VALUES (
NULL , 'transaction_loan', 'RECOVERYPAYMENT_LOAN', 'LOAN', 'RECOVERYPAYMENT', '0'
);
Alter table m_loan
add column total_recovered_derived Decimal(19,6);
INSERT INTO `acc_gl_account` (`name`, `hierarchy`, `gl_code`,`account_usage`, `classification_enum`,`description`)
select 'Loan Recovery (Temp)', '.', '220002-Temp', 1, 4,'Temporary account to track income from Loan recovery'
FROM m_product_loan WHERE accounting_type != 1
limit 1;
INSERT INTO `acc_product_mapping` (`gl_account_id`,`product_id`,`product_type`,`financial_account_type`)
select (select max(id) from acc_gl_account where classification_enum=4 and account_usage=1 LIMIT 1), mapping.product_id, mapping.product_type, 12
from acc_product_mapping mapping
where mapping.financial_account_type = 4 and mapping.product_type=1
group by mapping.product_id;
@@ -13,7 +13,7 @@ public class AccountingConstants {
/*** Accounting placeholders for cash based accounting for loan products ***/ /*** Accounting placeholders for cash based accounting for loan products ***/
public static enum CASH_ACCOUNTS_FOR_LOAN { 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( 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), OVERPAYMENT(11); 10), OVERPAYMENT(11),INCOME_FROM_RECOVERY(12);
private final Integer value; private final Integer value;
@@ -46,7 +46,7 @@ public class AccountingConstants {
/*** Accounting placeholders for accrual based accounting for loan products ***/ /*** Accounting placeholders for accrual based accounting for loan products ***/
public static enum ACCRUAL_ACCOUNTS_FOR_LOAN { 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( 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), OVERPAYMENT(11); 7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11),INCOME_FROM_RECOVERY(12);
private final Integer value; private final Integer value;
@@ -88,7 +88,8 @@ public class AccountingConstants {
"receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE( "receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE(
"transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
"paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING(
"penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"); "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"),
INCOME_FROM_RECOVERY("incomeFromRecoveryAccountId");
private final String value; private final String value;
@@ -110,7 +111,8 @@ public class AccountingConstants {
FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS("interestOnLoanAccount"), INCOME_FROM_FEES( FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS("interestOnLoanAccount"), INCOME_FROM_FEES(
"incomeFromFeeAccount"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), OVERPAYMENT( "incomeFromFeeAccount"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), OVERPAYMENT(
"overpaymentLiabilityAccount"), INTEREST_RECEIVABLE("receivableInterestAccount"), FEES_RECEIVABLE("receivableFeeAccount"), PENALTIES_RECEIVABLE( "overpaymentLiabilityAccount"), INTEREST_RECEIVABLE("receivableInterestAccount"), FEES_RECEIVABLE("receivableFeeAccount"), PENALTIES_RECEIVABLE(
"receivablePenaltyAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), INCOME_ACCOUNT_ID("incomeAccount"); "receivablePenaltyAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), INCOME_ACCOUNT_ID("incomeAccount"),
INCOME_FROM_RECOVERY("incomeFromRecoveryAccount");
private final String value; private final String value;
@@ -56,6 +56,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office); createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
} }
/** Logic for handling recovery payments **/
else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
}
/** Logic for Refunds of Overpayments **/ /** Logic for Refunds of Overpayments **/
else if (loanTransactionDTO.getTransactionType().isRefund()) { else if (loanTransactionDTO.getTransactionType().isRefund()) {
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office); createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
@@ -202,6 +207,33 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
} }
/**
* Create a single Debit to fund source and a single credit to
* "Income from Recovery"
*
* In case the loan transaction is a reversal, all debits are turned into
* credits and vice versa
*/
private void createJournalEntriesForRecoveryRepayments(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 amount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE,
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, amount,
isReversal);
}
/** /**
* Credit loan Portfolio and Debit Suspense Account for a Transfer * Credit loan Portfolio and Debit Suspense Account for a Transfer
* Initiation. A Transfer acceptance would be treated the opposite i.e Debit * Initiation. A Transfer acceptance would be treated the opposite i.e Debit
@@ -102,6 +102,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.notNull().integerGreaterThanZero(); .notNull().integerGreaterThanZero();
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue()).value(incomeFromRecoveryAccountId)
.notNull().integerGreaterThanZero();
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
@@ -121,6 +121,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
element); element);
final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
@@ -140,16 +144,16 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
break; break;
case CASH_BASED: case CASH_BASED:
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId); incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId);
break; break;
case ACCRUAL_PERIODIC: case ACCRUAL_PERIODIC:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId, incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId,
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId); receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break; break;
case ACCRUAL_UPFRONT: case ACCRUAL_UPFRONT:
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId, incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId,
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId); receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
break; break;
} }
@@ -159,7 +163,7 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
private void populateChangesForAccrualBasedAccounting(final Map<String, Object> changes, final Long fundAccountId, 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 loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId, final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,final Long incomeFromRecoveryAccountId,
final Long receivableInterestAccountId, final Long receivableFeeAccountId, final Long receivablePenaltyAccountId) { 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.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId);
@@ -167,13 +171,13 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId);
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId, populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId, incomeFromFeeId,
incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId); incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId);
} }
private void populateChangesForCashBasedAccounting(final Map<String, Object> changes, final Long fundAccountId, 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 loanPortfolioAccountId, final Long incomeFromInterestId, final Long incomeFromFeeId, final Long incomeFromPenaltyId,
final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId) { final Long writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,final Long incomeFromRecoveryAccountId) {
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);
@@ -182,6 +186,8 @@ 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);
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), incomeFromRecoveryAccountId);
} }
/** /**
@@ -215,6 +221,9 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(),
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.toString(), changes);
// expenses // expenses
mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
@@ -255,6 +264,9 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes);
mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.toString(), changes);
// expenses // expenses
mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
@@ -125,6 +125,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) { } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.OVERPAYMENT.getValue(), gLAccountData);
} else if(glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
} }
} }
} else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType) } else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
@@ -161,6 +163,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FEES_RECEIVABLE.getValue(), gLAccountData); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FEES_RECEIVABLE.getValue(), gLAccountData);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE)) { } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.PENALTIES_RECEIVABLE.getValue(), gLAccountData); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.PENALTIES_RECEIVABLE.getValue(), gLAccountData);
} else if((glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY))){
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_RECOVERY.getValue(), gLAccountData);
} }
} }
@@ -77,6 +77,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue());
// expenses // expenses
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
@@ -125,6 +128,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue());
// expenses // expenses
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
@@ -385,6 +385,9 @@ public class CommandWrapper {
public boolean isLoanRepayment() { public boolean isLoanRepayment() {
return this.actionName.equalsIgnoreCase("REPAYMENT") && this.entityName.equalsIgnoreCase("LOAN"); return this.actionName.equalsIgnoreCase("REPAYMENT") && this.entityName.equalsIgnoreCase("LOAN");
} }
public boolean isLoanRecoveryPayment() {
return this.actionName.equalsIgnoreCase("RECOVERYPAYMENT") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isLoanRepaymentAdjustment() { public boolean isLoanRepaymentAdjustment() {
return this.actionName.equalsIgnoreCase("ADJUST") && this.entityName.equalsIgnoreCase("LOAN"); return this.actionName.equalsIgnoreCase("ADJUST") && this.entityName.equalsIgnoreCase("LOAN");
@@ -576,6 +576,15 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder loanRecoveryPaymentTransaction(final Long loanId){
this.actionName = "RECOVERYPAYMENT";
this.entityName = "LOAN";
this.entityId = null;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/transactions/template?command=recoverypayment";
return this;
}
public CommandWrapperBuilder waiveInterestPortionTransaction(final Long loanId) { public CommandWrapperBuilder waiveInterestPortionTransaction(final Long loanId) {
this.actionName = "WAIVEINTERESTPORTION"; this.actionName = "WAIVEINTERESTPORTION";
this.entityName = "LOAN"; this.entityName = "LOAN";
@@ -5,8 +5,6 @@
*/ */
package org.mifosplatform.commands.service; package org.mifosplatform.commands.service;
import java.util.Map;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.mifosplatform.commands.domain.CommandSource; import org.mifosplatform.commands.domain.CommandSource;
import org.mifosplatform.commands.domain.CommandSourceRepository; import org.mifosplatform.commands.domain.CommandSourceRepository;
@@ -26,6 +24,8 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service @Service
public class SynchronousCommandProcessingService implements CommandProcessingService { public class SynchronousCommandProcessingService implements CommandProcessingService {
@@ -341,7 +341,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isLoanRepayment()) { } else if (wrapper.isLoanRepayment()) {
handler = this.applicationContext.getBean("loanRepaymentCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("loanRepaymentCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isLoanRepaymentAdjustment()) { } else if (wrapper.isLoanRecoveryPayment()){
handler = this.applicationContext.getBean("loanRecoveryPaymentCommandHandler", NewCommandSourceHandler.class);
}else if (wrapper.isLoanRepaymentAdjustment()) {
handler = this.applicationContext.getBean("loanRepaymentAdjustmentCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("loanRepaymentAdjustmentCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isWaiveInterestPortionOnLoan()) { } else if (wrapper.isWaiveInterestPortionOnLoan()) {
handler = this.applicationContext.getBean("waiveInterestPortionOnLoanCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("waiveInterestPortionOnLoanCommandHandler", NewCommandSourceHandler.class);
@@ -128,8 +128,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId); final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
final boolean isRecoveryRepayment = false;
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount,
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null,
isRecoveryRepayment);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command, final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction); fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
@@ -253,9 +255,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
accountTransferDTO.getLoanInstallmentNumber()); accountTransferDTO.getLoanInstallmentNumber());
} else { } else {
final boolean isRecoveryRepayment = false;
loanTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(), loanTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(),
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), null, null); accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment);
} }
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount, accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
@@ -5,22 +5,6 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.api; package org.mifosplatform.portfolio.loanaccount.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
@@ -40,6 +24,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@Path("/loans/{loanId}/transactions") @Path("/loans/{loanId}/transactions")
@Component @Component
@Scope("singleton") @Scope("singleton")
@@ -155,6 +148,9 @@ public class LoanTransactionsApiResource {
} else if (is(commandParam, "undowriteoff")) { } else if (is(commandParam, "undowriteoff")) {
final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build(); final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if(is(commandParam,"recoverypayment")){
final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} }
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); } if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
@@ -303,6 +303,9 @@ public class Loan extends AbstractPersistable<Long> {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private final Set<LoanTermVariations> loanTermVariations = new HashSet<LoanTermVariations>(); private final Set<LoanTermVariations> loanTermVariations = new HashSet<LoanTermVariations>();
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
private BigDecimal totalRecovered;
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -991,6 +994,9 @@ public class Loan extends AbstractPersistable<Long> {
final Money overpaidBy = calculateTotalOverpayment(); final Money overpaidBy = calculateTotalOverpayment();
this.totalOverpaid = overpaidBy.getAmountDefaultedToNullIfZero(); this.totalOverpaid = overpaidBy.getAmountDefaultedToNullIfZero();
final Money recoveredAmount = calculateTotalRecoveredPayments();
this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal(); final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper, this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
isDisbursed()); isDisbursed());
@@ -2148,7 +2154,7 @@ public class Loan extends AbstractPersistable<Long> {
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
loanLifecycleStateMachine, null); loanLifecycleStateMachine, null);
return changedTransactionDetail; return changedTransactionDetail;
@@ -2157,10 +2163,17 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, boolean isRecoveryRepayment) {
validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); LoanEvent event = null;
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, repaymentTransaction.getTransactionDate()); if (isRecoveryRepayment) {
event = LoanEvent.LOAN_RECOVERY_PAYMENT;
} else {
event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER;
}
validateAccountStatus(event);
validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
@@ -2168,7 +2181,7 @@ public class Loan extends AbstractPersistable<Long> {
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(repaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
loanLifecycleStateMachine, null); loanLifecycleStateMachine, null);
return changedTransactionDetail; return changedTransactionDetail;
@@ -2233,13 +2246,19 @@ public class Loan extends AbstractPersistable<Long> {
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
} }
private ChangedTransactionDetail handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction, private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus statusEnum = null;
LoanStatus.fromInt(this.loanStatus));
if (loanTransaction.isRecoveryRepayment()) {
statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, LoanStatus.fromInt(this.loanStatus));
} else {
statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus));
}
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
loanTransaction.updateLoan(this); loanTransaction.updateLoan(this);
@@ -2250,9 +2269,10 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(loanTransaction); this.loanTransactions.add(loanTransaction);
} }
if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver()) { if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver() && loanTransaction.isNotRecoveryRepayment()) {
final String errorMessage = "A transaction of type repayment or waiver was expected but not received."; final String errorMessage = "A transaction of type repayment or recovery repayment or waiver was expected but not received.";
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.transaction", errorMessage); throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.or.recovery.transaction",
errorMessage);
} }
final LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
@@ -2311,7 +2331,13 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); /**
* FIXME: Vishwas, skipping post loan transaction checks for Loan
* recoveries
**/
if (loanTransaction.isNotRecoveryRepayment()) {
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
}
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -2520,7 +2546,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
changedTransactionDetail = handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment); transactionForAdjustment);
} }
@@ -2589,6 +2615,13 @@ public class Loan extends AbstractPersistable<Long> {
return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments); return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
} }
public Money calculateTotalRecoveredPayments() {
Money totalRecoveredPayments = getTotalRecoveredPayments();
// in case logic for reversing recovered payment is implemented handle
// subtraction from totalRecoveredPayments
return totalRecoveredPayments;
}
private MonetaryCurrency loanCurrency() { private MonetaryCurrency loanCurrency() {
return this.loanRepaymentScheduleDetail.getCurrency(); return this.loanRepaymentScheduleDetail.getCurrency();
} }
@@ -2930,6 +2963,14 @@ public class Loan extends AbstractPersistable<Long> {
return disbursementDate; return disbursementDate;
} }
public LocalDate getWrittenOffDate() {
LocalDate writtenOffDate = null;
if (this.writtenOffOnDate != null) {
writtenOffDate = new LocalDate(this.writtenOffOnDate);
}
return writtenOffDate;
}
public LocalDate getExpectedDisbursedOnLocalDateForTemplate() { public LocalDate getExpectedDisbursedOnLocalDateForTemplate() {
LocalDate expectedDisbursementDate = null; LocalDate expectedDisbursementDate = null;
@@ -2998,6 +3039,17 @@ public class Loan extends AbstractPersistable<Long> {
return cumulativePaid; return cumulativePaid;
} }
public Money getTotalRecoveredPayments() {
Money cumulativePaid = Money.zero(loanCurrency());
for (final LoanTransaction recoveredPayment : this.loanTransactions) {
if (recoveredPayment.isRecoveryRepayment()) {
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(loanCurrency()));
}
}
return cumulativePaid;
}
private Money getTotalInterestOutstandingOnLoan() { private Money getTotalInterestOutstandingOnLoan() {
Money cumulativeInterest = Money.zero(loanCurrency()); Money cumulativeInterest = Money.zero(loanCurrency());
@@ -3682,6 +3734,14 @@ public class Loan extends AbstractPersistable<Long> {
dataValidationErrors.add(error); dataValidationErrors.add(error);
} }
break; break;
case LOAN_RECOVERY_PAYMENT:
if (!isClosedWrittenOff()) {
final String defaultUserMessage = "Recovery repayments may only be made on loans which are written off";
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.account.is.not.written.off",
defaultUserMessage);
dataValidationErrors.add(error);
}
break;
default: default:
break; break;
} }
@@ -14,7 +14,8 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
public interface LoanAccountDomainService { public interface LoanAccountDomainService {
LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId,
final boolean isRecoveryRepayment);
LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
@@ -82,7 +82,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult, public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final String txnExternalId) { final String txnExternalId, final boolean isRecoveryRepayment) {
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
@@ -103,8 +103,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail, LoanTransaction newRepaymentTransaction = null;
transactionDate, txnExternalId); if (isRecoveryRepayment) {
newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
txnExternalId);
} else {
newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
txnExternalId);
}
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
@@ -113,7 +120,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
holidays, workingDays, allowTransactionsOnNonWorkingDay); holidays, workingDays, allowTransactionsOnNonWorkingDay, isRecoveryRepayment);
try { try {
this.loanTransactionRepository.save(newRepaymentTransaction); this.loanTransactionRepository.save(newRepaymentTransaction);
@@ -15,6 +15,7 @@ public enum LoanEvent {
LOAN_WITHDRAWN, // LOAN_WITHDRAWN, //
LOAN_APPROVED, // LOAN_APPROVED, //
LOAN_APPROVAL_UNDO, // LOAN_APPROVAL_UNDO, //
LOAN_RECOVERY_PAYMENT, //
LOAN_DISBURSED, // LOAN_DISBURSED, //
LOAN_DISBURSAL_UNDO, // LOAN_DISBURSAL_UNDO, //
LOAN_REPAYMENT_OR_WAIVER, // LOAN_REPAYMENT_OR_WAIVER, //
@@ -23,5 +24,8 @@ public enum LoanEvent {
WRITE_OFF_OUTSTANDING_UNDO, // WRITE_OFF_OUTSTANDING_UNDO, //
LOAN_RESCHEDULE, // LOAN_RESCHEDULE, //
INTERST_REBATE_OWED, // INTERST_REBATE_OWED, //
LOAN_OVERPAYMENT, LOAN_CHARGE_PAYMENT, LOAN_CLOSED, LOAN_EDIT_MULTI_DISBURSE_DATE; LOAN_OVERPAYMENT, //
LOAN_CHARGE_PAYMENT, //
LOAN_CLOSED, //
LOAN_EDIT_MULTI_DISBURSE_DATE;
} }
@@ -112,6 +112,12 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId); return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
} }
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) {
return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate,
externalId);
}
public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) { final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) {
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId); return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId);
@@ -364,6 +370,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
} }
public boolean isNotRecoveryRepayment() {
return !isRecoveryRepayment();
}
public boolean isRecoveryRepayment() { public boolean isRecoveryRepayment() {
return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed();
} }
@@ -75,14 +75,14 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
LoanCharge loanCharge = chargePaidBy.getLoanCharge(); LoanCharge loanCharge = chargePaidBy.getLoanCharge();
transferCharges.add(loanCharge); transferCharges.add(loanCharge);
if(loanCharge.isInstalmentFee()){ if (loanCharge.isInstalmentFee()) {
LoanRepaymentScheduleInstallment installment = null; LoanRepaymentScheduleInstallment installment = null;
if(loanCharge.isFeeCharge()){ if (loanCharge.isFeeCharge()) {
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency)); installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
}else{ } else {
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency)); installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
} }
if(installment !=null){ if (installment != null) {
chargePaymentInstallments.add(installment); chargePaymentInstallments.add(installment);
} }
} }
@@ -106,7 +106,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) { for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) { if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
// pass through for new transactions // pass through for new transactions
if (loanTransaction.getId() == null) { if (loanTransaction.getId() == null) {
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
@@ -186,19 +186,19 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final Set<LoanCharge> loanFees = extractFeeCharges(charges); final Set<LoanCharge> loanFees = extractFeeCharges(charges);
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges); final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
Integer installmentNumber = null; Integer installmentNumber = null;
if(loanTransaction.isChargePayment() && installments.size() == 1){ if (loanTransaction.isChargePayment() && installments.size() == 1) {
installmentNumber = installments.get(0).getInstallmentNumber(); installmentNumber = installments.get(0).getInstallmentNumber();
} }
if (loanTransaction.isNotWaiver()) { if (loanTransaction.isNotWaiver()) {
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency); final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
if (feeCharges.isGreaterThanZero()) { if (feeCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees,installmentNumber); updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
} }
final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (penaltyCharges.isGreaterThanZero()) { if (penaltyCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties,installmentNumber); updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
} }
} }
@@ -228,16 +228,17 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return penaltyCharges; return penaltyCharges;
} }
private void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,final Integer installmentNumber) { private void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
final Integer installmentNumber) {
Money amountRemaining = feeCharges; Money amountRemaining = feeCharges;
while(amountRemaining.isGreaterThanZero()) { while (amountRemaining.isGreaterThanZero()) {
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges); final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
Money feeAmount = feeCharges.zero(); Money feeAmount = feeCharges.zero();
if(loanTransaction.isChargePayment()){ if (loanTransaction.isChargePayment()) {
feeAmount = feeCharges; feeAmount = feeCharges;
} }
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining,installmentNumber, feeAmount); final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) { if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) { if (!loanTransaction.isChargePayment()) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
@@ -247,7 +248,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge); amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
} }
} }
} }
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) { private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
@@ -256,18 +257,22 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
LoanInstallmentCharge chargePerInstallment = null; LoanInstallmentCharge chargePerInstallment = null;
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
if(loanCharge.isInstalmentFee()){ if (loanCharge.isInstalmentFee()) {
LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge(); LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
if(chargePerInstallment == null || chargePerInstallment.getRepaymentInstallment().getDueDate().isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())){ if (chargePerInstallment == null
|| chargePerInstallment.getRepaymentInstallment().getDueDate()
.isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())) {
installemntCharge = loanCharge; installemntCharge = loanCharge;
chargePerInstallment = unpaidLoanChargePerInstallment; chargePerInstallment = unpaidLoanChargePerInstallment;
} }
}else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) { } else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
earliestUnpaidCharge = loanCharge; earliestUnpaidCharge = loanCharge;
} }
} }
} }
if(earliestUnpaidCharge == null || (chargePerInstallment!=null && earliestUnpaidCharge.getDueLocalDate().isAfter(chargePerInstallment.getRepaymentInstallment().getDueDate()))){ if (earliestUnpaidCharge == null
|| (chargePerInstallment != null && earliestUnpaidCharge.getDueLocalDate().isAfter(
chargePerInstallment.getRepaymentInstallment().getDueDate()))) {
earliestUnpaidCharge = installemntCharge; earliestUnpaidCharge = installemntCharge;
} }
@@ -0,0 +1,25 @@
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoanRecoveryPaymentCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public LoanRecoveryPaymentCommandHandler(LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
final boolean isRecoveryRepayment = true;
return writePlatformService.makeLoanRepayment(command.getLoanId(), command, isRecoveryRepayment);
}
}
@@ -26,7 +26,7 @@ public class LoanRepaymentCommandHandler implements NewCommandSourceHandler {
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
boolean isRecoveryRepayment = false;
return this.writePlatformService.makeLoanRepayment(command.getLoanId(), command); return this.writePlatformService.makeLoanRepayment(command.getLoanId(), command, isRecoveryRepayment);
} }
} }
@@ -5,6 +5,9 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import java.util.Collection;
import java.util.Map;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
@@ -17,18 +20,16 @@ import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDi
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import java.util.Collection;
import java.util.Map;
public interface LoanWritePlatformService { public interface LoanWritePlatformService {
CommandProcessingResult disburseLoan(Long loanId, JsonCommand command, Boolean isAccountTransfer); CommandProcessingResult disburseLoan(Long loanId, JsonCommand command, Boolean isAccountTransfer);
Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand, Boolean isAccountTransfer); Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand,
Boolean isAccountTransfer);
CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command); CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command);
CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command); CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command, boolean isRecoveryRepayment);
Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand); Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
@@ -560,7 +560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command) { public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command, final boolean isRecoveryRepayment) {
this.context.authenticatedUser(); this.context.authenticatedUser();
@@ -585,7 +585,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount, this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount,
paymentDetail, noteText, txnExternalId); paymentDetail, noteText, txnExternalId, isRecoveryRepayment);
return commandProcessingResultBuilder.withCommandId(command.commandId()) // return commandProcessingResultBuilder.withCommandId(command.commandId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
@@ -600,6 +600,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.context.authenticatedUser(); this.context.authenticatedUser();
final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions(); final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions();
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
final boolean isRecoveryRepayment = false;
if (repaymentCommand == null) { return changes; } if (repaymentCommand == null) { return changes; }
@@ -612,7 +613,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(), this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(),
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null); singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null,
isRecoveryRepayment);
changes.put("bulkTransations", singleLoanRepaymentCommand); changes.put("bulkTransations", singleLoanRepaymentCommand);
} }
@@ -2046,4 +2048,5 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransferRepository.save(transferTransaction); this.accountTransferRepository.save(transferTransaction);
} }
} }
} }
@@ -54,7 +54,7 @@ public final class LoanProductDataValidator {
LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.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.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.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductConstants.useBorrowerCycleParameterName, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductConstants.useBorrowerCycleParameterName,
LoanProductConstants.principalVariationsForBorrowerCycleParameterName, LoanProductConstants.principalVariationsForBorrowerCycleParameterName,
LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName, LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
@@ -299,6 +299,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.notNull().integerGreaterThanZero(); .notNull().integerGreaterThanZero();
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue()).value(incomeFromRecoveryAccountId)
.notNull().integerGreaterThanZero();
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
@@ -600,6 +605,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.ignoreIfNull().integerGreaterThanZero(); .ignoreIfNull().integerGreaterThanZero();
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue()).value(incomeFromRecoveryAccountId)
.notNull().integerGreaterThanZero();
final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)