mifosx-281 done
This commit is contained in:
@@ -707,6 +707,14 @@
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<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 class="alt">
|
||||
<td></td>
|
||||
@@ -6971,7 +6979,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">
|
||||
@@ -24105,6 +24167,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>
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
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);
|
||||
|
||||
Alter table m_loan_transaction
|
||||
add column recovered_portion_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;
|
||||
|
||||
|
||||
+7
-1
@@ -28,6 +28,7 @@ public class LoanTransactionDTO {
|
||||
private final BigDecimal fees;
|
||||
private final BigDecimal penalties;
|
||||
private final BigDecimal overPayment;
|
||||
private final BigDecimal recoveryPayment;
|
||||
|
||||
/*** Boolean values determines if the transaction is reversed ***/
|
||||
private final boolean reversed;
|
||||
@@ -38,7 +39,7 @@ public class LoanTransactionDTO {
|
||||
|
||||
public LoanTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate,
|
||||
final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest,
|
||||
final BigDecimal fees, final BigDecimal penalties, final BigDecimal overPayment, final boolean reversed,
|
||||
final BigDecimal fees, final BigDecimal penalties, final BigDecimal overPayment, final BigDecimal recoveryPayment, final boolean reversed,
|
||||
final List<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> penaltyPayments) {
|
||||
this.paymentTypeId = paymentTypeId;
|
||||
this.transactionId = transactionId;
|
||||
@@ -53,6 +54,7 @@ public class LoanTransactionDTO {
|
||||
this.feePayments = feePayments;
|
||||
this.penaltyPayments = penaltyPayments;
|
||||
this.overPayment = overPayment;
|
||||
this.recoveryPayment = recoveryPayment;
|
||||
this.officeId = officeId;
|
||||
}
|
||||
|
||||
@@ -96,6 +98,10 @@ public class LoanTransactionDTO {
|
||||
return this.overPayment;
|
||||
}
|
||||
|
||||
public BigDecimal getRecoveryPayment() {
|
||||
return this.recoveryPayment;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return this.reversed;
|
||||
}
|
||||
|
||||
+2
-1
@@ -96,6 +96,7 @@ public class AccountingProcessorHelper {
|
||||
final BigDecimal fees = (BigDecimal) map.get("feeChargesPortion");
|
||||
final BigDecimal penalties = (BigDecimal) map.get("penaltyChargesPortion");
|
||||
final BigDecimal overPayments = (BigDecimal) map.get("overPaymentPortion");
|
||||
final BigDecimal recoveryPayments = (BigDecimal) map.get("recoveryPaymentPortion");
|
||||
final boolean reversed = (Boolean) map.get("reversed");
|
||||
final Long paymentTypeId = (Long) map.get("paymentTypeId");
|
||||
|
||||
@@ -120,7 +121,7 @@ public class AccountingProcessorHelper {
|
||||
}
|
||||
|
||||
final LoanTransactionDTO transaction = new LoanTransactionDTO(transactionOfficeId, paymentTypeId, transactionId,
|
||||
transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, reversed,
|
||||
transactionDate, transactionType, amount, principal, interest, fees, penalties, overPayments, recoveryPayments, reversed,
|
||||
feePaymentDetails, penaltyPaymentDetails);
|
||||
|
||||
newLoanTransactions.add(transaction);
|
||||
|
||||
+44
-1
@@ -56,6 +56,12 @@ 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);
|
||||
@@ -142,7 +148,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
* (loan portfolio for principal repayments, Interest on loans for interest
|
||||
* repayments, Income from fees for fees payment and Income from penalties
|
||||
* for penalty payment)
|
||||
*
|
||||
*
|
||||
* In case the loan transaction is a reversal, all debits are turned into
|
||||
* credits and vice versa
|
||||
*/
|
||||
@@ -202,6 +208,43 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single Debit to fund source and multiple credits if applicable
|
||||
* (loan portfolio for principal repayments, Interest on loans for interest
|
||||
* repayments, Income from fees for fees payment and Income from penalties
|
||||
* for penalty payment)
|
||||
*
|
||||
* 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 recoveryPaymentAmount = loanTransactionDTO.getRecoveryPayment();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
if (recoveryPaymentAmount != null && !(recoveryPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(recoveryPaymentAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY, loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, recoveryPaymentAmount, isReversal);
|
||||
}
|
||||
|
||||
/*** create a single debit entry (or reversal) for the entire amount **/
|
||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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
@@ -381,6 +381,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 {
|
||||
|
||||
@@ -339,7 +339,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);
|
||||
|
||||
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")
|
||||
@@ -152,6 +145,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
+141
-48
@@ -5,36 +5,9 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.OrderBy;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
@@ -74,21 +47,8 @@ import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.*;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.*;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
@@ -98,9 +58,12 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
|
||||
@@ -304,6 +267,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,
|
||||
@@ -992,6 +958,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());
|
||||
@@ -2172,6 +2141,26 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
public ChangedTransactionDetail makeRecoveryRepayment(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) {
|
||||
|
||||
validateAccountStatus(LoanEvent.LOAN_WRITTEN_OFF);
|
||||
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.WRITE_OFF_OUTSTANDING, repaymentTransaction.getTransactionDate());
|
||||
|
||||
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
|
||||
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRecoveryRepayment(repaymentTransaction,
|
||||
loanLifecycleStateMachine, null);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
|
||||
public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
|
||||
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
@@ -2232,6 +2221,77 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail handleRecoveryRepayment(final LoanTransaction loanTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction){
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
|
||||
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WRITTEN_OFF,
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
this.loanStatus = statusEnum.getValue();
|
||||
|
||||
loanTransaction.updateLoan(this);
|
||||
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions);
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
this.loanTransactions.add(loanTransaction);
|
||||
}
|
||||
|
||||
if (!loanTransaction.isRecoveryRepayment()) {
|
||||
final String errorMessage = "A transaction of type recovery repayment was expected but not received.";
|
||||
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.recovery.repayment.transaction", errorMessage);
|
||||
}
|
||||
|
||||
final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
|
||||
if (loanTransactionDate.isBefore(getWrittenOffDate())) {
|
||||
final String errorMessage = "The transaction date cannot be before the loan written-off date: "
|
||||
+ getWrittenOffDate().toString();
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.written-off.date", errorMessage,
|
||||
loanTransactionDate, getWrittenOffDate());
|
||||
}
|
||||
|
||||
if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) {
|
||||
final String errorMessage = "The transaction date cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate);
|
||||
}
|
||||
|
||||
if (this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null) {
|
||||
BigDecimal totalDisbursed = getDisbursedAmount();
|
||||
if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0) {
|
||||
final String errorMessage = "The transaction cannot be done before the loan disbursement: "
|
||||
+ getApprovedOnDate().toString();
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
|
||||
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
|
||||
charges());
|
||||
} else {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
/***
|
||||
* Commented since throwing exception if external id present for one
|
||||
* of the transactions. for this need to save the reversed
|
||||
* transactions first and then new transactions.
|
||||
*/
|
||||
// this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
}
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
// doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
|
||||
return changedTransactionDetail;
|
||||
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
|
||||
|
||||
@@ -2588,6 +2648,12 @@ 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();
|
||||
}
|
||||
@@ -2929,6 +2995,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;
|
||||
@@ -2997,6 +3071,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.getRecoveredPaymentPortion(loanCurrency())) ;
|
||||
}
|
||||
}
|
||||
return cumulativePaid;
|
||||
}
|
||||
|
||||
private Money getTotalInterestOutstandingOnLoan() {
|
||||
Money cumulativeInterest = Money.zero(loanCurrency());
|
||||
|
||||
@@ -3681,6 +3766,14 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
break;
|
||||
case LOAN_WRITTEN_OFF:
|
||||
if(!isClosedWrittenOff()){
|
||||
final String defaultUserMessage = "Active Loan Account is not allowed for recovery repayment. Loan Account is Active.";
|
||||
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.close.account.is.not.active",
|
||||
defaultUserMessage);
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Regular → Executable
+5
-2
@@ -5,17 +5,20 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface LoanAccountDomainService {
|
||||
|
||||
LoanTransaction makeRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
LoanTransaction makeLoanRecoveryRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
|
||||
Regular → Executable
+88
-6
@@ -5,12 +5,6 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
@@ -43,6 +37,12 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
@@ -284,4 +284,86 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public LoanTransaction makeLoanRecoveryRepayment(Loan loan, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
// TODO: Is it required to validate transaction date with meeting dates
|
||||
// if repayments is synced with meeting?
|
||||
/*
|
||||
* if(loan.isSyncDisbursementWithMeeting()){ // validate actual
|
||||
* disbursement date against meeting date CalendarInstance
|
||||
* calendarInstance =
|
||||
* this.calendarInstanceRepository.findCalendarInstaneByLoanId
|
||||
* (loan.getId(), CalendarEntityType.LOANS.getValue());
|
||||
* this.loanEventApiJsonValidator
|
||||
* .validateRepaymentDateWithMeetingDate(transactionDate,
|
||||
* calendarInstance); }
|
||||
*/
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
|
||||
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction newRepaymentTransaction = LoanTransaction.recoveryRepayment(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());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.makeRecoveryRepayment(newRepaymentTransaction,
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
try {
|
||||
this.loanTransactionRepository.save(newRepaymentTransaction);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
final Throwable realCause = e.getCause();
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
|
||||
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
|
||||
baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId())
|
||||
.failWithCode("value.must.be.unique");
|
||||
}
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
/***
|
||||
* TODO Vishwas Batch save is giving me a
|
||||
* HibernateOptimisticLockingFailureException, looping and saving for
|
||||
* the time being, not a major issue for now as this loop is entered
|
||||
* only in edge cases (when a payment is made before the latest payment
|
||||
* recorded against the loan)
|
||||
***/
|
||||
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
|
||||
if (changedTransactionDetail != null) {
|
||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
// update loan with references to the newly created transactions
|
||||
loan.getLoanTransactions().add(mapEntry.getValue());
|
||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
builderResult.withEntityId(newRepaymentTransaction.getId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()); //
|
||||
|
||||
return newRepaymentTransaction;
|
||||
}
|
||||
}
|
||||
Regular → Executable
+1
@@ -15,6 +15,7 @@ public enum LoanEvent {
|
||||
LOAN_WITHDRAWN, //
|
||||
LOAN_APPROVED, //
|
||||
LOAN_APPROVAL_UNDO, //
|
||||
LOAN_WRITTEN_OFF,
|
||||
LOAN_DISBURSED, //
|
||||
LOAN_DISBURSAL_UNDO, //
|
||||
LOAN_REPAYMENT_OR_WAIVER, //
|
||||
|
||||
Regular → Executable
+21
-20
@@ -5,26 +5,6 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.joda.time.LocalDate;
|
||||
@@ -40,6 +20,10 @@ import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* All monetary transactions against a loan are modelled through this entity.
|
||||
* Disbursements, Repayments, Waivers, Write-off etc
|
||||
@@ -95,6 +79,9 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true)
|
||||
private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<LoanChargePaidBy>();
|
||||
|
||||
@Column(name = "recovered_portion_derived", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal recoveredPaymentPortion;
|
||||
|
||||
protected LoanTransaction() {
|
||||
this.loan = null;
|
||||
this.dateOf = null;
|
||||
@@ -111,6 +98,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
final LocalDate paymentDate, final String 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,
|
||||
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) {
|
||||
@@ -296,6 +287,11 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
this.overPaymentPortion = defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
|
||||
}
|
||||
|
||||
public void updateRecoveredPayments(final Money recoveredPayment){
|
||||
final MonetaryCurrency currency = recoveredPayment.getCurrency();
|
||||
this.recoveredPaymentPortion = defaultToNullIfZero(getRecoveredPaymentPortion(currency).plus(recoveredPayment).getAmount());
|
||||
}
|
||||
|
||||
public Money getPrincipalPortion(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.principalPortion);
|
||||
}
|
||||
@@ -320,6 +316,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
return Money.of(currency, this.overPaymentPortion);
|
||||
}
|
||||
|
||||
public Money getRecoveredPaymentPortion(final MonetaryCurrency currency){
|
||||
return Money.of(currency,this.recoveredPaymentPortion) ;
|
||||
}
|
||||
|
||||
public Money getAmount(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.amount);
|
||||
}
|
||||
@@ -471,6 +471,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
thisTransactionData.put("feeChargesPortion", this.feeChargesPortion);
|
||||
thisTransactionData.put("penaltyChargesPortion", this.penaltyChargesPortion);
|
||||
thisTransactionData.put("overPaymentPortion", this.overPaymentPortion);
|
||||
thisTransactionData.put("recoveryPaymentPortion", this.recoveredPaymentPortion);
|
||||
|
||||
if (this.paymentDetail != null) {
|
||||
thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId());
|
||||
|
||||
Regular → Executable
+5
-2
@@ -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();
|
||||
@@ -202,7 +202,10 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||
if(loanTransaction.isRecoveryRepayment()){
|
||||
loanTransaction.updateRecoveredPayments(transactionAmountUnprocessed);
|
||||
System.out.println(loanTransaction.getRecoveredPaymentPortion(currency)) ;
|
||||
}else if(transactionAmountUnprocessed.isGreaterThanZero() && !loanTransaction.isRecoveryRepayment()) {
|
||||
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
|
||||
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: andrew
|
||||
* Date: 2-4-14
|
||||
* Time: 15:21
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
@Service
|
||||
public class LoanRecoveryPaymentCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public LoanRecoveryPaymentCommandHandler(LoanWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(JsonCommand command) {
|
||||
return writePlatformService.makeLoanRecoveryPayment(command.getLoanId(), command);
|
||||
}
|
||||
}
|
||||
+2
@@ -30,6 +30,8 @@ public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult makeLoanRecoveryPayment(Long loanId, JsonCommand command);
|
||||
|
||||
Map<String, Object> makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand);
|
||||
|
||||
CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command);
|
||||
|
||||
Regular → Executable
+40
-34
@@ -5,17 +5,7 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
@@ -63,16 +53,11 @@ import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
|
||||
import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.*;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBePayedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBePayedException.LOAN_CHARGE_CANNOT_BE_PAYED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
|
||||
import org.mifosplatform.portfolio.client.domain.Client;
|
||||
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
|
||||
@@ -85,22 +70,7 @@ import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
|
||||
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.*;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||
@@ -130,7 +100,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
@@ -1951,4 +1922,39 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransferRepository.save(transferTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult makeLoanRecoveryPayment(Long loanId, JsonCommand command) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
|
||||
this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json());
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
|
||||
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
|
||||
changes.put("locale", command.locale());
|
||||
changes.put("dateFormat", command.dateFormat());
|
||||
changes.put("paymentTypeId",command.stringValueOfParameterNamed("paymentTypeId"));
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
}
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
|
||||
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
|
||||
this.loanAccountDomainService.makeLoanRecoveryRepayment(loan, commandProcessingResultBuilder, transactionDate, transactionAmount,
|
||||
paymentDetail, noteText, txnExternalId);
|
||||
|
||||
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
|
||||
.withLoanId(loanId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
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