cleaning up recovery payments functionality
This commit is contained in:
@@ -923,6 +923,14 @@
|
||||
<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>
|
||||
<td></td>
|
||||
@@ -7342,7 +7350,61 @@ Request Body:
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<!------recovery payment -->
|
||||
<a id="loans_transaction_recovery_payment" name="loans_transaction_recovery_payment" class="old-syle-anchor"> </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"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
@@ -29138,6 +29200,14 @@ No Request Body:
|
||||
<td></td>
|
||||
<td></td>
|
||||
</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>
|
||||
<td></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;
|
||||
|
||||
+6
-4
@@ -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), OVERPAYMENT(11);
|
||||
10), OVERPAYMENT(11),INCOME_FROM_RECOVERY(12);
|
||||
|
||||
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), OVERPAYMENT(11);
|
||||
7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11),INCOME_FROM_RECOVERY(12);
|
||||
|
||||
private final Integer value;
|
||||
|
||||
@@ -88,7 +88,8 @@ public class AccountingConstants {
|
||||
"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");
|
||||
"penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"),
|
||||
INCOME_FROM_RECOVERY("incomeFromRecoveryAccountId");
|
||||
|
||||
private final String value;
|
||||
|
||||
@@ -110,7 +111,8 @@ public class AccountingConstants {
|
||||
FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS("interestOnLoanAccount"), INCOME_FROM_FEES(
|
||||
"incomeFromFeeAccount"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), OVERPAYMENT(
|
||||
"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;
|
||||
|
||||
|
||||
+32
@@ -56,6 +56,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
|
||||
/** Logic for handling recovery payments **/
|
||||
else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
|
||||
createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
|
||||
/** Logic for Refunds of Overpayments **/
|
||||
else if (loanTransactionDTO.getTransactionType().isRefund()) {
|
||||
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
|
||||
@@ -202,6 +207,33 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
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
|
||||
* Initiation. A Transfer acceptance would be treated the opposite i.e Debit
|
||||
|
||||
+5
@@ -102,6 +102,11 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
|
||||
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
|
||||
.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(
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
|
||||
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
|
||||
|
||||
+18
-6
@@ -121,6 +121,10 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
|
||||
element);
|
||||
final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed(
|
||||
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(
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
|
||||
final Long overPaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.OVERPAYMENT.getValue(),
|
||||
@@ -140,16 +144,16 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
|
||||
break;
|
||||
case CASH_BASED:
|
||||
populateChangesForCashBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId);
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId);
|
||||
break;
|
||||
case ACCRUAL_PERIODIC:
|
||||
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId,
|
||||
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
|
||||
break;
|
||||
case ACCRUAL_UPFRONT:
|
||||
populateChangesForAccrualBasedAccounting(changes, fundAccountId, loanPortfolioAccountId, incomeFromInterestId,
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,
|
||||
incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId, overPaymentAccountId, transfersInSuspenseAccountId,incomeFromRecoveryAccountId,
|
||||
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
|
||||
break;
|
||||
}
|
||||
@@ -159,7 +163,7 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
|
||||
|
||||
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 writeOffAccountId, final Long overPaymentAccountId, final Long transfersInSuspenseAccountId,final Long incomeFromRecoveryAccountId,
|
||||
final Long receivableInterestAccountId, final Long receivableFeeAccountId, final Long receivablePenaltyAccountId) {
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
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.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId);
|
||||
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.OVERPAYMENT.getValue(), overPaymentAccountId);
|
||||
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(),
|
||||
loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
|
||||
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
|
||||
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(),
|
||||
loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
|
||||
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
|
||||
mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
|
||||
|
||||
+4
@@ -125,6 +125,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
|
||||
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT)) {
|
||||
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)
|
||||
@@ -161,6 +163,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_DATA_PARAMS.FEES_RECEIVABLE.getValue(), gLAccountData);
|
||||
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -77,6 +77,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
|
||||
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
|
||||
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
|
||||
@@ -125,6 +128,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId,
|
||||
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
|
||||
this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element,
|
||||
|
||||
Regular → Executable
+3
@@ -385,6 +385,9 @@ public class CommandWrapper {
|
||||
public boolean isLoanRepayment() {
|
||||
return this.actionName.equalsIgnoreCase("REPAYMENT") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
public boolean isLoanRecoveryPayment() {
|
||||
return this.actionName.equalsIgnoreCase("RECOVERYPAYMENT") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
|
||||
public boolean isLoanRepaymentAdjustment() {
|
||||
return this.actionName.equalsIgnoreCase("ADJUST") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
|
||||
Regular → Executable
+9
@@ -576,6 +576,15 @@ public class CommandWrapperBuilder {
|
||||
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) {
|
||||
this.actionName = "WAIVEINTERESTPORTION";
|
||||
this.entityName = "LOAN";
|
||||
|
||||
Regular → Executable
+5
-3
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
package org.mifosplatform.commands.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.mifosplatform.commands.domain.CommandSource;
|
||||
import org.mifosplatform.commands.domain.CommandSourceRepository;
|
||||
@@ -26,6 +24,8 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class SynchronousCommandProcessingService implements CommandProcessingService {
|
||||
|
||||
@@ -341,7 +341,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isLoanRepayment()) {
|
||||
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);
|
||||
} else if (wrapper.isWaiveInterestPortionOnLoan()) {
|
||||
handler = this.applicationContext.getBean("waiveInterestPortionOnLoanCommandHandler", NewCommandSourceHandler.class);
|
||||
|
||||
+5
-2
@@ -128,8 +128,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
final boolean isRecoveryRepayment = false;
|
||||
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,
|
||||
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
|
||||
@@ -253,9 +255,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
accountTransferDTO.getLoanInstallmentNumber());
|
||||
|
||||
} else {
|
||||
final boolean isRecoveryRepayment = false;
|
||||
loanTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccount, new CommandProcessingResultBuilder(),
|
||||
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
||||
accountTransferDTO.getPaymentDetail(), null, null);
|
||||
accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment);
|
||||
}
|
||||
|
||||
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
|
||||
|
||||
Regular → Executable
+12
-16
@@ -5,22 +5,6 @@
|
||||
*/
|
||||
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.mifosplatform.commands.domain.CommandWrapper;
|
||||
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.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")
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@@ -155,6 +148,9 @@ public class LoanTransactionsApiResource {
|
||||
} else if (is(commandParam, "undowriteoff")) {
|
||||
final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build();
|
||||
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); }
|
||||
|
||||
Regular → Executable
+73
-13
@@ -303,6 +303,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
|
||||
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,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
@@ -991,6 +994,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final Money overpaidBy = calculateTotalOverpayment();
|
||||
this.totalOverpaid = overpaidBy.getAmountDefaultedToNullIfZero();
|
||||
|
||||
final Money recoveredAmount = calculateTotalRecoveredPayments();
|
||||
this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
|
||||
|
||||
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
|
||||
isDisbursed());
|
||||
@@ -2148,7 +2154,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(waiveInterestTransaction,
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
|
||||
loanLifecycleStateMachine, null);
|
||||
|
||||
return changedTransactionDetail;
|
||||
@@ -2157,10 +2163,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
|
||||
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);
|
||||
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, repaymentTransaction.getTransactionDate());
|
||||
LoanEvent event = null;
|
||||
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);
|
||||
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
|
||||
@@ -2168,7 +2181,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(repaymentTransaction,
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
|
||||
loanLifecycleStateMachine, null);
|
||||
|
||||
return changedTransactionDetail;
|
||||
@@ -2233,13 +2246,19 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction,
|
||||
private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
|
||||
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
LoanStatus statusEnum = null;
|
||||
|
||||
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();
|
||||
|
||||
loanTransaction.updateLoan(this);
|
||||
@@ -2250,9 +2269,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.loanTransactions.add(loanTransaction);
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver()) {
|
||||
final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
|
||||
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.transaction", errorMessage);
|
||||
if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver() && loanTransaction.isNotRecoveryRepayment()) {
|
||||
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.or.recovery.transaction",
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
|
||||
@@ -2311,7 +2331,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
/**
|
||||
* FIXME: Vishwas, skipping post loan transaction checks for Loan
|
||||
* recoveries
|
||||
**/
|
||||
if (loanTransaction.isNotRecoveryRepayment()) {
|
||||
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
}
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
@@ -2520,7 +2546,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
|
||||
changedTransactionDetail = handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
|
||||
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
|
||||
transactionForAdjustment);
|
||||
}
|
||||
|
||||
@@ -2589,6 +2615,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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() {
|
||||
return this.loanRepaymentScheduleDetail.getCurrency();
|
||||
}
|
||||
@@ -2930,6 +2963,14 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return disbursementDate;
|
||||
}
|
||||
|
||||
public LocalDate getWrittenOffDate() {
|
||||
LocalDate writtenOffDate = null;
|
||||
if (this.writtenOffOnDate != null) {
|
||||
writtenOffDate = new LocalDate(this.writtenOffOnDate);
|
||||
}
|
||||
return writtenOffDate;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedDisbursedOnLocalDateForTemplate() {
|
||||
|
||||
LocalDate expectedDisbursementDate = null;
|
||||
@@ -2998,6 +3039,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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() {
|
||||
Money cumulativeInterest = Money.zero(loanCurrency());
|
||||
|
||||
@@ -3682,6 +3734,14 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
Regular → Executable
+2
-1
@@ -14,7 +14,8 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
public interface LoanAccountDomainService {
|
||||
|
||||
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,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
Regular → Executable
+11
-4
@@ -82,7 +82,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
@Override
|
||||
public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
|
||||
final String txnExternalId) {
|
||||
final String txnExternalId, final boolean isRecoveryRepayment) {
|
||||
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
@@ -103,8 +103,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
|
||||
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail,
|
||||
transactionDate, txnExternalId);
|
||||
LoanTransaction newRepaymentTransaction = null;
|
||||
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 List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
|
||||
transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
@@ -113,7 +120,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay);
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay, isRecoveryRepayment);
|
||||
|
||||
try {
|
||||
this.loanTransactionRepository.save(newRepaymentTransaction);
|
||||
|
||||
Regular → Executable
+5
-1
@@ -15,6 +15,7 @@ public enum LoanEvent {
|
||||
LOAN_WITHDRAWN, //
|
||||
LOAN_APPROVED, //
|
||||
LOAN_APPROVAL_UNDO, //
|
||||
LOAN_RECOVERY_PAYMENT, //
|
||||
LOAN_DISBURSED, //
|
||||
LOAN_DISBURSAL_UNDO, //
|
||||
LOAN_REPAYMENT_OR_WAIVER, //
|
||||
@@ -23,5 +24,8 @@ public enum LoanEvent {
|
||||
WRITE_OFF_OUTSTANDING_UNDO, //
|
||||
LOAN_RESCHEDULE, //
|
||||
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;
|
||||
}
|
||||
|
||||
Regular → Executable
+10
@@ -112,6 +112,12 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
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,
|
||||
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) {
|
||||
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();
|
||||
}
|
||||
|
||||
public boolean isNotRecoveryRepayment() {
|
||||
return !isRecoveryRepayment();
|
||||
}
|
||||
|
||||
public boolean isRecoveryRepayment() {
|
||||
return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
Regular → Executable
+24
-19
@@ -75,14 +75,14 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
|
||||
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
|
||||
transferCharges.add(loanCharge);
|
||||
if(loanCharge.isInstalmentFee()){
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if(loanCharge.isFeeCharge()){
|
||||
if (loanCharge.isFeeCharge()) {
|
||||
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
|
||||
}else{
|
||||
} else {
|
||||
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
}
|
||||
if(installment !=null){
|
||||
if (installment != null) {
|
||||
chargePaymentInstallments.add(installment);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
|
||||
|
||||
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) {
|
||||
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||
// pass through for new transactions
|
||||
if (loanTransaction.getId() == null) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
@@ -186,19 +186,19 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
||||
Integer installmentNumber = null;
|
||||
if(loanTransaction.isChargePayment() && installments.size() == 1){
|
||||
if (loanTransaction.isChargePayment() && installments.size() == 1) {
|
||||
installmentNumber = installments.get(0).getInstallmentNumber();
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotWaiver()) {
|
||||
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||
if (feeCharges.isGreaterThanZero()) {
|
||||
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees,installmentNumber);
|
||||
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
|
||||
}
|
||||
|
||||
final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
||||
if (penaltyCharges.isGreaterThanZero()) {
|
||||
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties,installmentNumber);
|
||||
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,16 +228,17 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
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;
|
||||
while(amountRemaining.isGreaterThanZero()) {
|
||||
while (amountRemaining.isGreaterThanZero()) {
|
||||
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
|
||||
Money feeAmount = feeCharges.zero();
|
||||
if(loanTransaction.isChargePayment()){
|
||||
Money feeAmount = feeCharges.zero();
|
||||
if (loanTransaction.isChargePayment()) {
|
||||
feeAmount = feeCharges;
|
||||
}
|
||||
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining,installmentNumber, feeAmount);
|
||||
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
|
||||
if (!amountPaidTowardsCharge.isZero()) {
|
||||
if (!loanTransaction.isChargePayment()) {
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
|
||||
@@ -247,7 +248,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
|
||||
@@ -256,18 +257,22 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
LoanInstallmentCharge chargePerInstallment = null;
|
||||
for (final LoanCharge loanCharge : charges) {
|
||||
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
|
||||
if(loanCharge.isInstalmentFee()){
|
||||
LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
|
||||
if(chargePerInstallment == null || chargePerInstallment.getRepaymentInstallment().getDueDate().isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())){
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanInstallmentCharge unpaidLoanChargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
|
||||
if (chargePerInstallment == null
|
||||
|| chargePerInstallment.getRepaymentInstallment().getDueDate()
|
||||
.isAfter(unpaidLoanChargePerInstallment.getRepaymentInstallment().getDueDate())) {
|
||||
installemntCharge = loanCharge;
|
||||
chargePerInstallment = unpaidLoanChargePerInstallment;
|
||||
}
|
||||
}else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
|
||||
} else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -26,7 +26,7 @@ public class LoanRepaymentCommandHandler implements NewCommandSourceHandler {
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.makeLoanRepayment(command.getLoanId(), command);
|
||||
boolean isRecoveryRepayment = false;
|
||||
return this.writePlatformService.makeLoanRepayment(command.getLoanId(), command, isRecoveryRepayment);
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
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.loanaccount.domain.LoanTransaction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public interface LoanWritePlatformService {
|
||||
|
||||
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 makeLoanRepayment(Long loanId, JsonCommand command);
|
||||
CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command, boolean isRecoveryRepayment);
|
||||
|
||||
Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
|
||||
|
||||
|
||||
+6
-3
@@ -560,7 +560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
@Transactional
|
||||
@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();
|
||||
|
||||
@@ -585,7 +585,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
|
||||
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount,
|
||||
paymentDetail, noteText, txnExternalId);
|
||||
paymentDetail, noteText, txnExternalId, isRecoveryRepayment);
|
||||
|
||||
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
|
||||
.withLoanId(loanId) //
|
||||
@@ -600,6 +600,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.context.authenticatedUser();
|
||||
final SingleRepaymentCommand[] repaymentCommand = bulkRepaymentCommand.getLoanTransactions();
|
||||
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||
final boolean isRecoveryRepayment = false;
|
||||
|
||||
if (repaymentCommand == null) { return changes; }
|
||||
|
||||
@@ -612,7 +613,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final PaymentDetail paymentDetail = null;
|
||||
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
|
||||
this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(),
|
||||
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null);
|
||||
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null,
|
||||
isRecoveryRepayment);
|
||||
|
||||
changes.put("bulkTransations", singleLoanRepaymentCommand);
|
||||
}
|
||||
@@ -2046,4 +2048,5 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransferRepository.save(transferTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Regular → Executable
+11
-1
@@ -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.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.FEE_INCOME_ACCOUNT_MAPPING.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_RECOVERY.getValue(),
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductConstants.useBorrowerCycleParameterName,
|
||||
LoanProductConstants.principalVariationsForBorrowerCycleParameterName,
|
||||
LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
|
||||
@@ -299,6 +299,11 @@ public final class LoanProductDataValidator {
|
||||
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
|
||||
.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(
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
|
||||
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)
|
||||
.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(
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
|
||||
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId)
|
||||
|
||||
Reference in New Issue
Block a user