Merge branch 'develop' of git://github.com/michaeldk-miagstan/mifosx into michaeldk-miagstan-develop
Conflicts: mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/account/service/PortfolioAccountReadPlatformService.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java
This commit is contained in:
+348
-1
@@ -365,6 +365,22 @@
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>accounttransfers/templateRefundByTransfer</td>
|
||||
<td></td>
|
||||
<td><a href="#accounttransfers_retrieve_template_refund_by_transfer">Retrieve Refund of an Active Loan by Transfer Template</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>accounttransfers/refundByTransfer</td>
|
||||
<td><a href="#accounttransfers_refund_by_transfer">Refund an active loan by transfer</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1062,6 +1078,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>loans/{loanId}/transactions?command=refundByCash</td>
|
||||
<td><a href="#loans_transaction_refund_by_cash">Refund an Active Loan by Cash</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#loans_charges">Loan Charges</a></td>
|
||||
<td>loans/{loanId}/charges</td>
|
||||
<td><a href="#loans_charges_create">Add a Loan Charge</a></td>
|
||||
@@ -8914,6 +8938,11 @@ No Request Body:
|
||||
outstanding. The amount that can be waived is limited to the
|
||||
loan's inArrearsTolerance
|
||||
</dd>
|
||||
<dd>
|
||||
<b>'refundbycash'</b><br> "date" is set to the current date <br>
|
||||
"total" "amount" is set
|
||||
to the total amount paid in advance.
|
||||
</dd>
|
||||
</dl>
|
||||
<p>Example Request:</p>
|
||||
<div class=apiClick>loans/1/transactions/template?command=repayment</div>
|
||||
@@ -9000,6 +9029,45 @@ Request Body:
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="loans_transaction_refund_by_cash" name="loans_transaction_refund_by_cash" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Make a Refund of an Active Loan by Cash </h4>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/loans/{loanId}/transactions?command=refundByCash
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST loans/5/transactions?command=refundByCash
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"locale": "en",
|
||||
"transactionDate": "14 May 2013",
|
||||
"transactionAmount": "500.00",
|
||||
"paymentTypeId": "12"
|
||||
}
|
||||
</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
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<a id="loans_transaction_waiveinterest" name="loans_transaction_waiveinterest" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
@@ -9259,7 +9327,7 @@ Request Body:
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--Start of guarantors functionality-->
|
||||
|
||||
<a id="guarantors" name="guarantors" class="old-syle-anchor"> </a>
|
||||
@@ -25621,6 +25689,285 @@ No Request Body:
|
||||
"id": 1,
|
||||
"accountNo": "000000001"
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="accounttransfers_retrieve_template_refund_by_transfer" name="accounttransfers_retrieve_template_refund_by_transfer" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h2>Retrieve Refund of an Active Loan by Transfer Template:</h2>
|
||||
<p>Example Requests : </p>
|
||||
<div class=apiClick>accounttransfers/templateRefundByTransfer?fromAccountId=2&fromAccountType=1&
|
||||
toAccountId=1&toAccountType=2&toClientId=1&toOfficeId=1</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">GET https://DomainName/api/v1/accounttransfers/templateRefundByTransfer</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"currency": {
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"decimalPlaces": 0,
|
||||
"inMultiplesOf": 0,
|
||||
"displaySymbol": "$",
|
||||
"nameCode": "currency.USD",
|
||||
"displayLabel": "US Dollar ($)"
|
||||
},
|
||||
"transferAmount": 130,
|
||||
"transferDate": [
|
||||
2014,
|
||||
11,
|
||||
1
|
||||
],
|
||||
"fromOffice": {
|
||||
"id": 1,
|
||||
"name": "Head Office",
|
||||
"nameDecorated": "Head Office",
|
||||
"externalId": "1",
|
||||
"openingDate": [
|
||||
2009,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"hierarchy": "."
|
||||
},
|
||||
"fromClient": {
|
||||
"id": 1,
|
||||
"accountNo": "000000001",
|
||||
"status": {
|
||||
"id": 300,
|
||||
"code": "clientStatusType.active",
|
||||
"value": "Active"
|
||||
},
|
||||
"active": true,
|
||||
"activationDate": [
|
||||
2012,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"firstname": "Daniel",
|
||||
"lastname": "Owusu",
|
||||
"displayName": "Daniel Owusu",
|
||||
"gender": {},
|
||||
"clientType": {},
|
||||
"clientClassification": {},
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office",
|
||||
"timeline": {
|
||||
"submittedOnDate": [
|
||||
2012,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"submittedByUsername": "mifos",
|
||||
"submittedByFirstname": "App",
|
||||
"submittedByLastname": "Administrator",
|
||||
"activatedOnDate": [
|
||||
2012,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"activatedByUsername": "mifos",
|
||||
"activatedByFirstname": "App",
|
||||
"activatedByLastname": "Administrator"
|
||||
},
|
||||
"groups": []
|
||||
},
|
||||
"fromAccountType": {
|
||||
"id": 1,
|
||||
"code": "accountType.loan",
|
||||
"value": "Loan Account"
|
||||
},
|
||||
"fromAccount": {
|
||||
"id": 2,
|
||||
"accountNo": "000000002",
|
||||
"clientId": 1,
|
||||
"clientName": "Daniel Owusu",
|
||||
"productId": 1,
|
||||
"productName": "CTRL",
|
||||
"fieldOfficerId": 0,
|
||||
"currency": {
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"decimalPlaces": 0,
|
||||
"inMultiplesOf": 0,
|
||||
"displaySymbol": "$",
|
||||
"nameCode": "currency.USD",
|
||||
"displayLabel": "US Dollar ($)"
|
||||
},
|
||||
"amtForTransfer": 130
|
||||
},
|
||||
"toOffice": {
|
||||
"id": 1,
|
||||
"name": "Head Office",
|
||||
"nameDecorated": "Head Office",
|
||||
"externalId": "1",
|
||||
"openingDate": [
|
||||
2009,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"hierarchy": "."
|
||||
},
|
||||
"toClient": {
|
||||
"id": 1,
|
||||
"displayName": "Daniel Owusu",
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office"
|
||||
},
|
||||
"toAccountType": {
|
||||
"id": 2,
|
||||
"code": "accountType.savings",
|
||||
"value": "Savings Account"
|
||||
},
|
||||
"toAccount": {
|
||||
"id": 1,
|
||||
"accountNo": "000000001",
|
||||
"clientId": 1,
|
||||
"clientName": "Daniel Owusu",
|
||||
"productId": 1,
|
||||
"productName": "TEST",
|
||||
"fieldOfficerId": 0,
|
||||
"currency": {
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"decimalPlaces": 0,
|
||||
"inMultiplesOf": 0,
|
||||
"displaySymbol": "$",
|
||||
"nameCode": "currency.USD",
|
||||
"displayLabel": "US Dollar ($)"
|
||||
}
|
||||
},
|
||||
"fromOfficeOptions": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Head Office",
|
||||
"nameDecorated": "Head Office"
|
||||
}
|
||||
],
|
||||
"fromClientOptions": [
|
||||
{
|
||||
"id": 1,
|
||||
"displayName": "Daniel Owusu",
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office"
|
||||
}
|
||||
],
|
||||
"fromAccountTypeOptions": [
|
||||
{
|
||||
"id": 2,
|
||||
"code": "accountType.savings",
|
||||
"value": "Savings Account"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"code": "accountType.loan",
|
||||
"value": "Loan Account"
|
||||
}
|
||||
],
|
||||
"fromAccountOptions": [
|
||||
{
|
||||
"id": 2,
|
||||
"accountNo": "000000002",
|
||||
"clientId": 1,
|
||||
"clientName": "Daniel Owusu",
|
||||
"productId": 1,
|
||||
"productName": "CTRL",
|
||||
"fieldOfficerId": 0,
|
||||
"currency": {
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"decimalPlaces": 0,
|
||||
"inMultiplesOf": 0,
|
||||
"displaySymbol": "$",
|
||||
"nameCode": "currency.USD",
|
||||
"displayLabel": "US Dollar ($)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"toOfficeOptions": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Head Office",
|
||||
"nameDecorated": "Head Office"
|
||||
}
|
||||
],
|
||||
"toClientOptions": [
|
||||
{
|
||||
"id": 1,
|
||||
"displayName": "Daniel Owusu",
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office"
|
||||
}
|
||||
],
|
||||
"toAccountTypeOptions": [
|
||||
{
|
||||
"id": 2,
|
||||
"code": "accountType.savings",
|
||||
"value": "Savings Account"
|
||||
}
|
||||
],
|
||||
"toAccountOptions": [
|
||||
{
|
||||
"id": 1,
|
||||
"accountNo": "000000001",
|
||||
"clientId": 1,
|
||||
"clientName": "Daniel Owusu",
|
||||
"productId": 1,
|
||||
"productName": "TEST",
|
||||
"fieldOfficerId": 0,
|
||||
"currency": {
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"decimalPlaces": 0,
|
||||
"inMultiplesOf": 0,
|
||||
"displaySymbol": "$",
|
||||
"nameCode": "currency.USD",
|
||||
"displayLabel": "US Dollar ($)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<a id="accounttransfers_refund_by_transfer" name="accounttransfers_refund_by_transfer" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h2>Refund of an Active Loan by Transfer</h2>
|
||||
<p>Ability to refund an active loan by transferring to a savings account.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">POST https://Domain Name/api/v1/accounttransfers</code>
|
||||
<code class="method-request">POST refundByTransfer/
|
||||
Content-Type: application/json
|
||||
No Request Body:
|
||||
{
|
||||
"fromAccountId": "2",
|
||||
"fromAccountType": 1,
|
||||
"toOfficeId": 1,
|
||||
"toClientId": 1,
|
||||
"toAccountType": 2,
|
||||
"toAccountId": 1,
|
||||
"transferAmount": 130,
|
||||
"transferDate": "31 October 2014",
|
||||
"transferDescription": "Transfer refund to my savings account",
|
||||
"locale": "en",
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"fromClientId": 1,
|
||||
"fromOfficeId": 1
|
||||
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"savingsId": 1,
|
||||
"resourceId": 1
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
+583
@@ -35,7 +35,10 @@ import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilde
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanStatusChecker;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.mifosplatform.integrationtests.common.savings.AccountTransferHelper;
|
||||
import org.mifosplatform.integrationtests.common.savings.SavingsAccountHelper;
|
||||
import org.mifosplatform.integrationtests.common.savings.SavingsProductHelper;
|
||||
import org.mifosplatform.integrationtests.common.savings.SavingsStatusChecker;
|
||||
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||
import com.jayway.restassured.builder.ResponseSpecBuilder;
|
||||
@@ -66,6 +69,8 @@ public class ClientLoanIntegrationTest {
|
||||
private AccountHelper accountHelper;
|
||||
private SchedulerJobHelper schedulerJobHelper;
|
||||
private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper;
|
||||
private SavingsAccountHelper savingsAccountHelper;
|
||||
private AccountTransferHelper accountTransferHelper;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -891,6 +896,23 @@ public class ClientLoanIntegrationTest {
|
||||
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
}
|
||||
|
||||
private Integer createLoanProduct(final String inMultiplesOf, final String digitsAfterDecimal, final String repaymentStrategy,
|
||||
final String accountingRule, final Account... accounts) {
|
||||
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||
final String loanProductJSON = new LoanProductTestBuilder() //
|
||||
.withPrincipal("10000000.00") //
|
||||
.withNumberOfRepayments("24") //
|
||||
.withRepaymentAfterEvery("1") //
|
||||
.withRepaymentTypeAsMonth() //
|
||||
.withinterestRatePerPeriod("2") //
|
||||
.withInterestRateFrequencyTypeAsMonths() //
|
||||
.withRepaymentStrategy(repaymentStrategy) //
|
||||
.withAmortizationTypeAsEqualPrincipalPayment() //
|
||||
.withInterestTypeAsDecliningBalance() //
|
||||
.currencyDetails(digitsAfterDecimal, inMultiplesOf).withAccounting(accountingRule, accounts).build(null);
|
||||
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String graceOnPrincipalPayment) {
|
||||
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||
@@ -992,6 +1014,32 @@ public class ClientLoanIntegrationTest {
|
||||
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplicationWithPaymentStrategyAndPastMonth(final Integer clientID, final Integer loanProductID,
|
||||
List<HashMap> charges, final String savingsId, String principal, final String repaymentStrategy, final int month) {
|
||||
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
|
||||
Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, month);
|
||||
|
||||
String fourMonthsfromNow = new SimpleDateFormat("dd MMMMMM yyyy").format(fourMonthsfromNowCalendar.getTime());
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||
.withPrincipal(principal) //
|
||||
.withLoanTermFrequency("6") //
|
||||
.withLoanTermFrequencyAsMonths() //
|
||||
.withNumberOfRepayments("6") //
|
||||
.withRepaymentEveryAfter("1") //
|
||||
.withRepaymentFrequencyTypeAsMonths() //
|
||||
.withInterestRatePerPeriod("2") //
|
||||
.withAmortizationTypeAsEqualInstallments() //
|
||||
.withInterestTypeAsFlatBalance() //
|
||||
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
|
||||
.withExpectedDisbursementDate(fourMonthsfromNow) //
|
||||
.withSubmittedOnDate(fourMonthsfromNow) //
|
||||
.withwithRepaymentStrategy(repaymentStrategy) //
|
||||
.withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
|
||||
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
}
|
||||
|
||||
private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule) {
|
||||
System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
|
||||
|
||||
@@ -3820,4 +3868,539 @@ public class ClientLoanIntegrationTest {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Test case to verify Mifos Style payment strategy
|
||||
*/
|
||||
@Test
|
||||
public void testLoanRefundByCashCashBasedAccounting() {
|
||||
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
|
||||
|
||||
String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
|
||||
/***
|
||||
* Create loan product with MIFOS STYLE strategy
|
||||
*/
|
||||
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
|
||||
final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, CASH_BASED, assetAccount,
|
||||
incomeAccount, expenseAccount, overpaymentAccount);
|
||||
Assert.assertNotNull(loanProductID);
|
||||
|
||||
/***
|
||||
* Apply for loan application and verify loan status
|
||||
*/
|
||||
final String savingsId = null;
|
||||
final String principal = "12,000.00";
|
||||
|
||||
// Add charges with payment mode regular
|
||||
List<HashMap> charges = new ArrayList<>();
|
||||
|
||||
Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
|
||||
addCharges(charges, flatInstallmentFee, "50", null);
|
||||
|
||||
final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId,
|
||||
principal, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
|
||||
Assert.assertNotNull(loanID);
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
|
||||
new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
|
||||
|
||||
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #1
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #2
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT));
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("50"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
Map secondInstallment = loanSchedule.get(2);
|
||||
validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #3
|
||||
// Pay 2290 more than expected
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"), JournalEntry.TransactionType.CREDIT));
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("100"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap thirdInstallment = loanSchedule.get(3);
|
||||
validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make refund of 20
|
||||
// max 2290 to refund. Pay 20 means only principal
|
||||
// mifos style refund order(principal, interest, fees and penalties
|
||||
// paid: principal 2000, interest 240, fees 50, penalty 0
|
||||
// refund 20 means paid: principal 1980, interest 240, fees 50, penalty
|
||||
// 0
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
// Make refund of 2000
|
||||
// max 2270 to refund. Pay 2000 means only principal
|
||||
// paid: principal 1980, interest 240, fees 50, penalty 0
|
||||
// refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
|
||||
|
||||
this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
* Test case to verify Mifos style payment strategy
|
||||
*/
|
||||
@Test
|
||||
public void testLoanRefundByCashAccrualBasedAccounting() {
|
||||
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
|
||||
|
||||
String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
|
||||
/***
|
||||
* Create loan product with MIFOS STYLE strategy
|
||||
*/
|
||||
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
|
||||
final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, ACCRUAL_UPFRONT,
|
||||
assetAccount, incomeAccount, expenseAccount, overpaymentAccount);// ,
|
||||
// LoanProductTestBuilder.EQUAL_INSTALLMENTS,
|
||||
// LoanProductTestBuilder.FLAT_BALANCE);
|
||||
Assert.assertNotNull(loanProductID);
|
||||
|
||||
/***
|
||||
* Apply for loan application and verify loan status
|
||||
*/
|
||||
final String savingsId = null;
|
||||
final String principal = "12,000.00";
|
||||
|
||||
// Add charges with payment mode regular
|
||||
List<HashMap> charges = new ArrayList<>();
|
||||
|
||||
Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
|
||||
addCharges(charges, flatInstallmentFee, "50", null);
|
||||
|
||||
final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, savingsId,
|
||||
principal, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
|
||||
Assert.assertNotNull(loanID);
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("1440"), JournalEntry.TransactionType.DEBIT),
|
||||
new JournalEntry(Float.valueOf("300.00"), JournalEntry.TransactionType.DEBIT),
|
||||
new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
|
||||
new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
|
||||
|
||||
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #1
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #2
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT),
|
||||
new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2000"),
|
||||
JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
Map secondInstallment = loanSchedule.get(2);
|
||||
validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #3
|
||||
// Pay 2290 more than expected
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT),
|
||||
new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("4000"),
|
||||
JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap thirdInstallment = loanSchedule.get(3);
|
||||
validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make refund of 20
|
||||
// max 2290 to refund. Pay 20 means only principal
|
||||
// mifos style refund order(principal, interest, fees and penalties
|
||||
// paid: principal 2000, interest 240, fees 50, penalty 0
|
||||
// refund 20 means paid: principal 1980, interest 240, fees 50, penalty
|
||||
// 0
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("20"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
// Make refund of 2000
|
||||
// max 2270 to refund. Pay 2000 means only principal
|
||||
// paid: principal 1980, interest 240, fees 50, penalty 0
|
||||
// refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
|
||||
|
||||
this.loanTransactionHelper.makeRefundByCash(now, Float.valueOf("2000"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanRefundByTransferCashBasedAccounting() {
|
||||
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
this.accountTransferHelper = new AccountTransferHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
Calendar fourMonthsfromNowCalendar = Calendar.getInstance();
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, -4);
|
||||
|
||||
String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE);
|
||||
Assert.assertNotNull(savingsProductID);
|
||||
|
||||
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
|
||||
Assert.assertNotNull(savingsProductID);
|
||||
|
||||
HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
|
||||
ACCOUNT_TYPE_INDIVIDUAL);
|
||||
Assert.assertTrue(modifications.containsKey("submittedOnDate"));
|
||||
|
||||
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
|
||||
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
|
||||
|
||||
savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
|
||||
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
|
||||
|
||||
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
|
||||
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||
|
||||
/***
|
||||
* Create loan product with MIFOS STYLE strategy
|
||||
*/
|
||||
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
|
||||
final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, CASH_BASED, assetAccount,
|
||||
incomeAccount, expenseAccount, overpaymentAccount);
|
||||
Assert.assertNotNull(loanProductID);
|
||||
|
||||
/***
|
||||
* Apply for loan application and verify loan status
|
||||
*/
|
||||
|
||||
final String principal = "12,000.00";
|
||||
|
||||
// Add charges with payment mode regular
|
||||
List<HashMap> charges = new ArrayList<>();
|
||||
|
||||
Integer flatInstallmentFee = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
|
||||
addCharges(charges, flatInstallmentFee, "50", null);
|
||||
|
||||
final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, null, principal,
|
||||
LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, -4);
|
||||
Assert.assertNotNull(loanID);
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(fourMonthsfromNow, loanID);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.CREDIT),
|
||||
new JournalEntry(Float.valueOf("12000.00"), JournalEntry.TransactionType.DEBIT) };
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, fourMonthsfromNow, assetAccountInitialEntry);
|
||||
|
||||
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("2290", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #1
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String threeMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(threeMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
firstInstallment = loanSchedule.get(1);
|
||||
validateNumberForEqual("0.00", String.valueOf(firstInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #2
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String twoMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2000"), JournalEntry.TransactionType.CREDIT));
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("50"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
Map secondInstallment = loanSchedule.get(2);
|
||||
validateNumberForEqual("0.00", String.valueOf(secondInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make payment for installment #3
|
||||
// Pay 2290 more than expected
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
|
||||
final String oneMonthfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID);
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"),
|
||||
JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4000"), JournalEntry.TransactionType.CREDIT));
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("100"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap thirdInstallment = loanSchedule.get(3);
|
||||
validateNumberForEqual("0.00", String.valueOf(thirdInstallment.get("totalOutstandingForPeriod")));
|
||||
|
||||
// Make refund of 20
|
||||
// max 2290 to refund. Pay 20 means only principal
|
||||
// mifos style refund order(principal, interest, fees and penalties
|
||||
// paid: principal 2000, interest 240, fees 50, penalty 0
|
||||
// refund 20 means paid: principal 1980, interest 240, fees 50, penalty
|
||||
// 0
|
||||
|
||||
Float TRANSFER_AMOUNT = 20f;
|
||||
|
||||
fourMonthsfromNowCalendar.add(Calendar.MONTH, 1);
|
||||
final String now = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar);
|
||||
|
||||
final String FROM_LOAN_ACCOUNT_TYPE = "1";
|
||||
final String TO_SAVINGS_ACCOUNT_TYPE = "2";
|
||||
|
||||
this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
|
||||
TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
|
||||
|
||||
Float toSavingsBalance = new Float(MINIMUM_OPENING_BALANCE);
|
||||
|
||||
HashMap toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
|
||||
toSavingsBalance += TRANSFER_AMOUNT;
|
||||
|
||||
// Verifying toSavings Account Balance after Account Transfer
|
||||
assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
|
||||
toSavingsSummaryAfter.get("accountBalance"));
|
||||
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("20"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
HashMap fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
// Make refund of 2000
|
||||
// max 2270 to refund. Pay 2000 means only principal
|
||||
// paid: principal 1980, interest 240, fees 50, penalty 0
|
||||
// refund 2000 means paid: principal 0, interest 220, fees 50, penalty 0
|
||||
// final String now = Utils.convertDate(fourMonthsfromNowCalendar);
|
||||
|
||||
TRANSFER_AMOUNT = 2000f;
|
||||
|
||||
this.accountTransferHelper.refundLoanByTransfer(now, clientID, loanID, clientID, savingsId, FROM_LOAN_ACCOUNT_TYPE,
|
||||
TO_SAVINGS_ACCOUNT_TYPE, TRANSFER_AMOUNT.toString());
|
||||
|
||||
toSavingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
|
||||
toSavingsBalance += TRANSFER_AMOUNT;
|
||||
|
||||
// Verifying toSavings Account Balance after Account Transfer
|
||||
assertEquals("Verifying From Savings Account Balance after Account Transfer", toSavingsBalance,
|
||||
toSavingsSummaryAfter.get("accountBalance"));
|
||||
|
||||
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, now, new JournalEntry(Float.valueOf("2000"),
|
||||
JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("1980"), JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, now, new JournalEntry(Float.valueOf("20"),
|
||||
JournalEntry.TransactionType.DEBIT));
|
||||
|
||||
loanSchedule.clear();
|
||||
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
fourthInstallment = loanSchedule.get(4);
|
||||
validateNumberForEqual("2020.00", String.valueOf(fourthInstallment.get("totalOutstandingForPeriod")));
|
||||
validateNumberForEqual("2000.00", String.valueOf(fourthInstallment.get("principalOutstanding")));
|
||||
validateNumberForEqual("20.00", String.valueOf(fourthInstallment.get("interestOutstanding")));
|
||||
validateNumberForEqual("0.00", String.valueOf(fourthInstallment.get("feeChargesOutstanding")));
|
||||
|
||||
}
|
||||
|
||||
private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final String minOpenningBalance) {
|
||||
System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
|
||||
SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
|
||||
|
||||
final String savingsProductJSON = savingsProductHelper
|
||||
//
|
||||
.withInterestCompoundingPeriodTypeAsDaily()
|
||||
//
|
||||
.withInterestPostingPeriodTypeAsMonthly()
|
||||
//
|
||||
.withInterestCalculationPeriodTypeAsDailyBalance()
|
||||
|
||||
.withMinimumOpenningBalance(minOpenningBalance).build();
|
||||
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -13,6 +13,7 @@ import static org.junit.Assert.fail;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
@@ -115,5 +116,9 @@ public class Utils {
|
||||
public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
|
||||
return randomStringGenerator(prefix, lenOfRandomSuffix);
|
||||
}
|
||||
|
||||
public static String convertDateToURLFormat(final Calendar dateToBeConvert) {
|
||||
return new SimpleDateFormat("dd MMMMMM yyyy").format(dateToBeConvert.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+48
-3
@@ -36,6 +36,7 @@ public class LoanTransactionHelper {
|
||||
private static final String MAKE_REPAYMENT_COMMAND = "repayment";
|
||||
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
|
||||
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
|
||||
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
|
||||
|
||||
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
||||
this.requestSpec = requestSpec;
|
||||
@@ -90,9 +91,9 @@ public class LoanTransactionHelper {
|
||||
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
|
||||
return Utils.performServerGet(requestSpec, responseSpec, URL, param);
|
||||
}
|
||||
|
||||
public Object getLoanProductDetail(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanProductId,
|
||||
final String jsonAttributeToGetBack) {
|
||||
|
||||
public Object getLoanProductDetail(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer loanProductId, final String jsonAttributeToGetBack) {
|
||||
final String URL = "/mifosng-provider/api/v1/loanproducts/" + loanProductId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
|
||||
return Utils.performServerGet(requestSpec, responseSpec, URL, jsonAttributeToGetBack);
|
||||
}
|
||||
@@ -421,6 +422,10 @@ public class LoanTransactionHelper {
|
||||
return response;
|
||||
}
|
||||
|
||||
private String createLoanRefundTransferURL() {
|
||||
return "/mifosng-provider/api/v1/accounttransfers/refundByTransfer?tenantIdentifier=default";
|
||||
}
|
||||
|
||||
public void verifyRepaymentScheduleEntryFor(final int repaymentNumber, final float expectedPrincipalOutstanding, final Integer loanID) {
|
||||
System.out.println("---------------------------GETTING LOAN REPAYMENT SCHEDULE--------------------------------");
|
||||
final ArrayList<HashMap> repaymentPeriods = getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
|
||||
@@ -457,4 +462,44 @@ public class LoanTransactionHelper {
|
||||
assertTrue("No Accrual entries are posted", isTransactionFound);
|
||||
|
||||
}
|
||||
|
||||
public HashMap makeRefundByCash(final String date, final Float amountToBeRefunded, final Integer loanID) {
|
||||
return performLoanTransaction(createLoanTransactionURL(MAKE_REFUND_BY_CASH_COMMAND, loanID),
|
||||
getRefundByCashBodyAsJSON(date, amountToBeRefunded));
|
||||
}
|
||||
|
||||
public HashMap makeRefundByTransfer(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
|
||||
final Integer fromClientId, final String date, final Float amountToBeRefunded) {
|
||||
return performLoanTransaction(createLoanRefundTransferURL(),
|
||||
getRefundByTransferBodyAsJSON(fromAccountId, toClientId, toAccountId, fromClientId, date, amountToBeRefunded));
|
||||
}
|
||||
|
||||
private String getRefundByCashBodyAsJSON(final String transactionDate, final Float transactionAmount) {
|
||||
final HashMap<String, String> map = new HashMap<>();
|
||||
map.put("locale", "en");
|
||||
map.put("dateFormat", "dd MMMM yyyy");
|
||||
map.put("transactionDate", transactionDate);
|
||||
map.put("transactionAmount", transactionAmount.toString());
|
||||
map.put("note", "Refund Made!!!");
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
private String getRefundByTransferBodyAsJSON(final Integer fromAccountId, final Integer toClientId, final Integer toAccountId,
|
||||
final Integer fromClientId, final String transactionDate, final Float transactionAmount) {
|
||||
final HashMap<String, String> map = new HashMap<>();
|
||||
map.put("fromAccountId", fromAccountId.toString());
|
||||
map.put("fromAccountType", "1");
|
||||
map.put("toOfficeId", "1");
|
||||
map.put("toClientId", toClientId.toString());
|
||||
map.put("toAccountType", "2");
|
||||
map.put("toAccountId", toAccountId.toString());
|
||||
map.put("dateFormat", "dd MMMM yyyy");
|
||||
map.put("transferDate", transactionDate);
|
||||
map.put("transferAmount", transactionAmount.toString());
|
||||
map.put("transferDescription", "Refund Made!!!");
|
||||
map.put("fromClientId", fromClientId.toString());
|
||||
map.put("fromOfficeId", "1");
|
||||
map.put("locale", "en");
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
}
|
||||
+12
@@ -16,6 +16,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
|
||||
public class AccountTransferHelper {
|
||||
|
||||
private static final String ACCOUNT_TRANSFER_URL = "/mifosng-provider/api/v1/accounttransfers";
|
||||
private static final String LOAN_REFUND_BY_TRANSFER_URL = "/mifosng-provider/api/v1/accounttransfers/refundByTransfer";
|
||||
private static final String LOCALE = "en_GB";
|
||||
private static final String OFFICE_ID = "1";
|
||||
private static final String TRANSFER_DESCRIPTION = "Transfer";
|
||||
@@ -70,4 +71,15 @@ public class AccountTransferHelper {
|
||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, ACCOUNT_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
|
||||
accountTransferJSON, "savingsId");
|
||||
}
|
||||
|
||||
public Integer refundLoanByTransfer(final String date, final Integer fromClientId, final Integer fromAccountId, final Integer toClientId,
|
||||
final Integer toAccountId, final String fromAccountType, final String toAccountType, final String transferAmount) {
|
||||
System.out.println("--------------------------------ACCOUNT TRANSFER--------------------------------");
|
||||
final String accountTransferJSON = new AccountTransferHelper(this.requestSpec, this.responseSpec) //
|
||||
.withTransferOnDate(date) //
|
||||
.build(fromAccountId.toString(), fromClientId.toString(), toAccountId.toString(), toClientId.toString(), fromAccountType,
|
||||
toAccountType, transferAmount);
|
||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, LOAN_REFUND_BY_TRANSFER_URL + "?" + Utils.TENANT_IDENTIFIER,
|
||||
accountTransferJSON, "savingsId");
|
||||
}
|
||||
}
|
||||
+79
@@ -6,12 +6,15 @@
|
||||
package org.mifosplatform.accounting.journalentry.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
|
||||
import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
@@ -72,6 +75,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
.getTransactionType().isWaiveCharges())) {
|
||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
|
||||
}
|
||||
|
||||
/** Logic for Refunds of Active Loans **/
|
||||
else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
|
||||
createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,4 +347,75 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal);
|
||||
}
|
||||
}
|
||||
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
|
||||
// TODO Auto-generated method stub
|
||||
// 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 principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
|
||||
}
|
||||
|
||||
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
|
||||
}
|
||||
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
|
||||
List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
|
||||
|
||||
for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
|
||||
chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
|
||||
chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
|
||||
}
|
||||
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
|
||||
!isReversal, chargePaymentDTOs);
|
||||
}
|
||||
|
||||
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
|
||||
|
||||
for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
|
||||
chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
|
||||
chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
|
||||
}
|
||||
|
||||
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount, !isReversal, chargePaymentDTOs);
|
||||
}
|
||||
|
||||
if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
|
||||
}
|
||||
|
||||
/*** create a single debit entry (or reversal) for the entire amount **/
|
||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+79
@@ -6,11 +6,14 @@
|
||||
package org.mifosplatform.accounting.journalentry.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
|
||||
import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
@@ -85,6 +88,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
|| loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
|
||||
createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
/** Logic for Refunds of Active Loans **/
|
||||
else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
|
||||
createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,4 +300,76 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
null, loanId, transactionId, transactionDate, principalAmount, isReversal);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
|
||||
// TODO Auto-generated method stub
|
||||
// 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 principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
|
||||
}
|
||||
|
||||
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS,
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
|
||||
}
|
||||
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
|
||||
List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
|
||||
|
||||
for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) {
|
||||
chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
|
||||
chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
|
||||
}
|
||||
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
|
||||
!isReversal, chargePaymentDTOs);
|
||||
}
|
||||
|
||||
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>();
|
||||
|
||||
for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) {
|
||||
chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(),
|
||||
chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() ));
|
||||
}
|
||||
|
||||
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount, !isReversal, chargePaymentDTOs);
|
||||
}
|
||||
|
||||
if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
|
||||
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
|
||||
}
|
||||
|
||||
/*** create a single debit entry (or reversal) for the entire amount **/
|
||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,5 +943,17 @@ public class CommandWrapper {
|
||||
public boolean isInactivateSavingsAccountCharge() {
|
||||
return this.actionName.equalsIgnoreCase("INACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
|
||||
}
|
||||
|
||||
public boolean isRefundByTransfer() {
|
||||
return this.actionName.equalsIgnoreCase("REFUNDBYTRANSFER");
|
||||
}
|
||||
|
||||
public boolean isLoanRefundByCash() {
|
||||
return this.actionName.equalsIgnoreCase("REFUNDBYCASH") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
|
||||
public boolean isUndoLoanRefund() {
|
||||
return this.actionName.equalsIgnoreCase("UNDOREFUND") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -697,6 +697,15 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/loans/" + loanId + "/transactions/" + transactionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder refundLoanTransactionByCash(final Long loanId) {
|
||||
this.actionName = "REFUNDBYCASH";
|
||||
this.entityName = "LOAN";
|
||||
this.entityId = null;
|
||||
this.loanId = loanId;
|
||||
this.href = "/loans/" + loanId + "/transactions/template?command=refundbycash";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createLoanApplication() {
|
||||
this.actionName = "CREATE";
|
||||
@@ -2185,4 +2194,12 @@ public class CommandWrapperBuilder {
|
||||
this.json = "{}";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder refundByTransfer() {
|
||||
this.actionName = "REFUNDBYTRANSFER";
|
||||
this.entityName = "ACCOUNTTRANSFER";
|
||||
this.entityId = null;
|
||||
this.href = "/refundByTransfer";
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+6
@@ -130,6 +130,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
if (wrapper.isAccountTransferResource()) {
|
||||
if (wrapper.isCreate()) {
|
||||
handler = this.applicationContext.getBean("createAccountTransferCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isRefundByTransfer()) {
|
||||
handler = this.applicationContext.getBean("refundByTransferCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
@@ -396,6 +398,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("undoWriteOffLoanCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isGuaranteeRecovery()) {
|
||||
handler = this.applicationContext.getBean("recoverFromGuarantorCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isLoanRefundByCash()) {
|
||||
handler = this.applicationContext.getBean("loanRefundByCashCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isUndoLoanRefund()) {
|
||||
handler = this.applicationContext.getBean("loanRefundAdjustmentCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
|
||||
+32
@@ -119,4 +119,36 @@ public class AccountTransfersApiResource {
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("templateRefundByTransfer")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String templateRefundByTransfer(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
|
||||
@QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
|
||||
@QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
|
||||
@QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
|
||||
@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
|
||||
|
||||
final AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveRefundByTransferTemplate(fromOfficeId, fromClientId,
|
||||
fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("refundByTransfer")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String templateRefundByTransferPost(final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().refundByTransfer().withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.account.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.account.service.AccountTransfersWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class RefundByTransferCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AccountTransfersWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public RefundByTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.refundByTransfer(command);
|
||||
}
|
||||
}
|
||||
+3
@@ -26,4 +26,7 @@ public interface AccountTransfersReadPlatformService {
|
||||
Page<AccountTransferData> retrieveByStandingInstruction(Long id, SearchParameters searchParameters);
|
||||
|
||||
Collection<Long> fetchPostInterestTransactionIds(Long accountId);
|
||||
|
||||
AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
|
||||
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
|
||||
}
|
||||
+110
@@ -411,4 +411,114 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
|
||||
this.accountTransfersMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
|
||||
final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
|
||||
// TODO Auto-generated method stub
|
||||
final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
|
||||
final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
|
||||
|
||||
final Integer mostRelevantFromAccountType = fromAccountType;
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
|
||||
final Collection<EnumOptionData> toAccountTypeOptions;
|
||||
if (mostRelevantFromAccountType == 1) {
|
||||
// overpaid loan amt transfer to savings account
|
||||
toAccountTypeOptions = Arrays.asList(savingsAccountType);
|
||||
} else {
|
||||
toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
|
||||
}
|
||||
final Integer mostRelevantToAccountType = toAccountType;
|
||||
|
||||
final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
|
||||
final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
|
||||
|
||||
// from settings
|
||||
OfficeData fromOffice = null;
|
||||
ClientData fromClient = null;
|
||||
PortfolioAccountData fromAccount = null;
|
||||
|
||||
OfficeData toOffice = null;
|
||||
ClientData toClient = null;
|
||||
PortfolioAccountData toAccount = null;
|
||||
|
||||
// template
|
||||
Collection<PortfolioAccountData> fromAccountOptions = null;
|
||||
Collection<PortfolioAccountData> toAccountOptions = null;
|
||||
|
||||
Long mostRelevantFromOfficeId = fromOfficeId;
|
||||
Long mostRelevantFromClientId = fromClientId;
|
||||
|
||||
Long mostRelevantToOfficeId = toOfficeId;
|
||||
Long mostRelevantToClientId = toClientId;
|
||||
|
||||
if (fromAccountId != null) {
|
||||
Integer accountType;
|
||||
if (mostRelevantFromAccountType == 1) {
|
||||
accountType = PortfolioAccountType.LOAN.getValue();
|
||||
} else {
|
||||
accountType = PortfolioAccountType.SAVINGS.getValue();
|
||||
}
|
||||
fromAccount = this.portfolioAccountReadPlatformService.retrieveOneByPaidInAdvance(fromAccountId, accountType);
|
||||
|
||||
// override provided fromClient with client of account
|
||||
mostRelevantFromClientId = fromAccount.clientId();
|
||||
}
|
||||
|
||||
if (mostRelevantFromClientId != null) {
|
||||
fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
|
||||
mostRelevantFromOfficeId = fromClient.officeId();
|
||||
long[] loanStatus = null;
|
||||
if (mostRelevantFromAccountType == 1) {
|
||||
loanStatus = new long[] { 300, 700 };
|
||||
}
|
||||
PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
|
||||
loanStatus);
|
||||
fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
|
||||
}
|
||||
|
||||
Collection<OfficeData> fromOfficeOptions = null;
|
||||
Collection<ClientData> fromClientOptions = null;
|
||||
if (mostRelevantFromOfficeId != null) {
|
||||
fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
|
||||
fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
|
||||
fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
|
||||
}
|
||||
|
||||
// defaults
|
||||
final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
|
||||
Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
|
||||
Collection<ClientData> toClientOptions = null;
|
||||
|
||||
if (toAccountId != null && fromAccount != null) {
|
||||
toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
|
||||
fromAccount.currencyCode());
|
||||
mostRelevantToClientId = toAccount.clientId();
|
||||
}
|
||||
|
||||
if (mostRelevantToClientId != null) {
|
||||
toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
|
||||
mostRelevantToOfficeId = toClient.officeId();
|
||||
|
||||
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
|
||||
|
||||
toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
|
||||
}
|
||||
|
||||
if (mostRelevantToOfficeId != null) {
|
||||
toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
|
||||
toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
|
||||
|
||||
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
|
||||
if (toClientOptions != null && toClientOptions.size() == 1) {
|
||||
toClient = new ArrayList<>(toClientOptions).get(0);
|
||||
|
||||
toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
|
||||
}
|
||||
}
|
||||
|
||||
return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
|
||||
toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
|
||||
toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -22,4 +22,6 @@ public interface AccountTransfersWritePlatformService {
|
||||
void reverseAllTransactions(Long accountId, PortfolioAccountType accountTypeId);
|
||||
|
||||
void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction);
|
||||
|
||||
CommandProcessingResult refundByTransfer(JsonCommand command);
|
||||
}
|
||||
+57
-1
@@ -13,6 +13,7 @@ import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstan
|
||||
import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -35,7 +36,10 @@ import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.mifosplatform.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
|
||||
@@ -59,6 +63,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
private final LoanAccountDomainService loanAccountDomainService;
|
||||
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
|
||||
@@ -66,7 +71,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
|
||||
final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService,
|
||||
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
final AccountTransferDetailRepository accountTransferDetailRepository) {
|
||||
final AccountTransferDetailRepository accountTransferDetailRepository,
|
||||
final LoanReadPlatformService loanReadPlatformService) {
|
||||
this.accountTransfersDataValidator = accountTransfersDataValidator;
|
||||
this.accountTransferAssembler = accountTransferAssembler;
|
||||
this.accountTransferRepository = accountTransferRepository;
|
||||
@@ -76,6 +82,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
this.loanAccountDomainService = loanAccountDomainService;
|
||||
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
|
||||
this.accountTransferDetailRepository = accountTransferDetailRepository;
|
||||
this.loanReadPlatformService = loanReadPlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -398,5 +405,54 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult refundByTransfer(JsonCommand command) {
|
||||
// TODO Auto-generated method stub
|
||||
this.accountTransfersDataValidator.validate(command);
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
|
||||
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
|
||||
|
||||
final PaymentDetail paymentDetail = null;
|
||||
Long transferTransactionId = null;
|
||||
|
||||
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
|
||||
|
||||
BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance();
|
||||
|
||||
if (overpaid == null || overpaid.equals(BigDecimal.ZERO) || transactionAmount.floatValue() > overpaid.floatValue()) {
|
||||
if(overpaid == null)
|
||||
overpaid = BigDecimal.ZERO;
|
||||
throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
|
||||
}
|
||||
|
||||
final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(fromLoanAccountId,
|
||||
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
|
||||
|
||||
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId);
|
||||
|
||||
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
|
||||
transactionAmount, paymentDetail, true, true);
|
||||
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
|
||||
fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
|
||||
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||
transferTransactionId = accountTransferDetails.getId();
|
||||
|
||||
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId);
|
||||
|
||||
// if (fromAccountType.isSavingsAccount()) {
|
||||
|
||||
builder.withSavingsId(toSavingsAccountId);
|
||||
// }
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
+2
@@ -17,4 +17,6 @@ public interface PortfolioAccountReadPlatformService {
|
||||
PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId, String currencyCode);
|
||||
|
||||
Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO);
|
||||
|
||||
PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId);
|
||||
}
|
||||
+105
@@ -33,12 +33,14 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
||||
// mapper
|
||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||
|
||||
@Autowired
|
||||
public PortfolioAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -265,4 +267,107 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
public PortfolioLoanAccountRefundByTransferMapper() {
|
||||
|
||||
final StringBuilder amountQueryString = new StringBuilder(400);
|
||||
amountQueryString.append("(select (SUM(ifnull(mr.principal_completed_derived, 0)) +");
|
||||
amountQueryString.append("SUM(ifnull(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
|
||||
amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
|
||||
amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
|
||||
amountQueryString.append(" and mr.duedate >= CURDATE() group by ml.id having");
|
||||
amountQueryString.append(" (SUM(ifnull(mr.principal_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(ifnull(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(ifnull(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append(amountQueryString.toString());
|
||||
sqlBuilder.append(", ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
|
||||
currencyDisplaySymbol, currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
|
||||
// TODO Auto-generated method stub
|
||||
Object[] sqlParams = new Object[] { accountId , accountId};
|
||||
PortfolioAccountData accountData = null;
|
||||
//String currencyCode = null;
|
||||
try {
|
||||
String sql = null;
|
||||
//final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
|
||||
|
||||
sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
|
||||
/* if (currencyCode != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams = new Object[] {accountId , accountId,currencyCode };
|
||||
}*/
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
|
||||
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -96,7 +96,14 @@ public class LoanTransactionsApiResource {
|
||||
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
|
||||
} else if (is(commandParam, "prepayLoan")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId);
|
||||
} else {
|
||||
}
|
||||
else if (is(commandParam, "refundbycash")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
|
||||
}
|
||||
else if (is(commandParam, "refundbytransfer")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
|
||||
}
|
||||
else {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
@@ -155,6 +162,10 @@ public class LoanTransactionsApiResource {
|
||||
final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
else if (is(commandParam, "refundByCash")) {
|
||||
final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
|
||||
|
||||
|
||||
+5
-1
@@ -70,6 +70,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
|
||||
@@ -465,6 +466,7 @@ public class LoansApiResource {
|
||||
Collection<CodeValueData> loanCollateralOptions = null;
|
||||
Collection<CalendarData> calendarOptions = null;
|
||||
Collection<PortfolioAccountData> accountLinkingOptions = null;
|
||||
PaidInAdvanceData paidInAdvanceTemplate = null;
|
||||
|
||||
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
|
||||
if (template) {
|
||||
@@ -510,13 +512,15 @@ public class LoansApiResource {
|
||||
|
||||
Collection<ChargeData> overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(),
|
||||
ChargeTimeType.OVERDUE_INSTALLMENT);
|
||||
|
||||
paidInAdvanceTemplate = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId);
|
||||
|
||||
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
|
||||
charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
|
||||
null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
|
||||
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
|
||||
overdueCharges);
|
||||
overdueCharges, paidInAdvanceTemplate);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
|
||||
mandatoryResponseParameters);
|
||||
|
||||
+29
-18
@@ -177,6 +177,9 @@ public class LoanAccountData {
|
||||
private final LoanInterestRecalculationData interestRecalculationData;
|
||||
private final Boolean createStandingInstructionAtDisbursement;
|
||||
|
||||
// Paid In Advance
|
||||
private final PaidInAdvanceData paidInAdvance;
|
||||
|
||||
/**
|
||||
* Used to produce a {@link LoanAccountData} with only collateral options
|
||||
* for now.
|
||||
@@ -275,6 +278,7 @@ public class LoanAccountData {
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final LoanScheduleData originalSchedule = null;
|
||||
final Boolean createStandingInstructionAtDisbursement = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -291,7 +295,8 @@ public class LoanAccountData {
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement);
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,6 +397,7 @@ public class LoanAccountData {
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final LoanScheduleData originalSchedule = null;
|
||||
final Boolean createStandingInstructionAtDisbursement = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -408,8 +414,8 @@ public class LoanAccountData {
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement);
|
||||
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
||||
@@ -433,7 +439,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -535,6 +541,7 @@ public class LoanAccountData {
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final LoanScheduleData originalSchedule = null;
|
||||
final Boolean createStandingInstructionAtDisbursement = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -551,7 +558,7 @@ public class LoanAccountData {
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance, emiAmountVariations, memberVariations, product,
|
||||
inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement);
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
||||
@@ -575,7 +582,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||
@@ -691,6 +698,7 @@ public class LoanAccountData {
|
||||
final Boolean isNPA = null;
|
||||
final LoanScheduleData originalSchedule = null;
|
||||
final Boolean createStandingInstructionAtDisbursement = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
|
||||
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
|
||||
@@ -710,7 +718,7 @@ public class LoanAccountData {
|
||||
disbursementData, product.getMultiDisburseLoan(), fixedEmi, product.getOutstandingLoanBalance(), emiAmountVariations,
|
||||
memberVariations, product, inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA,
|
||||
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
|
||||
product.toLoanInterestRecalculationData(), originalSchedule, createStandingInstructionAtDisbursement);
|
||||
product.toLoanInterestRecalculationData(), originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||
@@ -736,6 +744,7 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
|
||||
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
|
||||
@@ -764,7 +773,7 @@ public class LoanAccountData {
|
||||
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
|
||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -823,6 +832,7 @@ public class LoanAccountData {
|
||||
final LoanProductData product = null;
|
||||
final Collection<ChargeData> overdueCharges = null;
|
||||
final LoanScheduleData originalSchedule = null;
|
||||
final PaidInAdvanceData paidInAdvance = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -839,7 +849,7 @@ public class LoanAccountData {
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations, memberVariations, product,
|
||||
inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement);
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -861,7 +871,8 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
|
||||
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
|
||||
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges) {
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
|
||||
final PaidInAdvanceData paidInAdvance) {
|
||||
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
|
||||
@@ -881,7 +892,7 @@ public class LoanAccountData {
|
||||
disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations,
|
||||
acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType,
|
||||
acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement);
|
||||
acc.createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||
@@ -893,7 +904,7 @@ public class LoanAccountData {
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
|
||||
@@ -916,7 +927,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
||||
@@ -976,7 +987,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
||||
@@ -1002,7 +1013,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
|
||||
@@ -1026,7 +1037,7 @@ public class LoanAccountData {
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
originalSchedule, acc.createStandingInstructionAtDisbursement);
|
||||
originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
private LoanAccountData(
|
||||
@@ -1087,8 +1098,7 @@ public class LoanAccountData {
|
||||
final Collection<ChargeData> overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
|
||||
final Boolean createStandingInstructionAtDisbursement) {
|
||||
|
||||
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance) {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.status = status;
|
||||
@@ -1239,6 +1249,7 @@ public class LoanAccountData {
|
||||
this.interestRecalculationData = interestRecalculationData;
|
||||
this.originalSchedule = originalSchedule;
|
||||
this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement;
|
||||
this.paidInAdvance = paidInAdvance;
|
||||
}
|
||||
|
||||
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
|
||||
|
||||
+6
@@ -30,6 +30,7 @@ public class LoanTransactionEnumData {
|
||||
private final boolean rejectTransfer;
|
||||
private final boolean chargePayment;
|
||||
private final boolean refund;
|
||||
private final boolean refundForActiveLoans;
|
||||
|
||||
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
||||
this.id = id;
|
||||
@@ -50,6 +51,7 @@ public class LoanTransactionEnumData {
|
||||
this.rejectTransfer = Long.valueOf(15).equals(this.id);
|
||||
this.refund = Long.valueOf(16).equals(this.id);
|
||||
this.chargePayment = Long.valueOf(17).equals(this.id);
|
||||
this.refundForActiveLoans = Long.valueOf(18).equals(this.id);
|
||||
}
|
||||
|
||||
public Long id() {
|
||||
@@ -130,5 +132,9 @@ public class LoanTransactionEnumData {
|
||||
public boolean isRefund() {
|
||||
return this.refund;
|
||||
}
|
||||
|
||||
public boolean isRefundForActiveLoans() {
|
||||
return this.refundForActiveLoans;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PaidInAdvanceData {
|
||||
|
||||
private final BigDecimal paidInAdvance;
|
||||
|
||||
public PaidInAdvanceData(final BigDecimal paidInAdvance) {
|
||||
this.paidInAdvance = paidInAdvance;
|
||||
}
|
||||
|
||||
public BigDecimal getPaidInAdvance() {
|
||||
return paidInAdvance;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+166
-32
@@ -85,6 +85,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanR
|
||||
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.InvalidRefundDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||
@@ -1195,9 +1196,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
if (command.isChangeInBooleanParameterNamed(createSiAtDisbursementParameterName, shouldCreateStandingInstructionAtDisbursement())) {
|
||||
final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(createSiAtDisbursementParameterName);
|
||||
actualChanges.put(createSiAtDisbursementParameterName, valueAsInput);
|
||||
this.createStandingInstructionAtDisbursement = valueAsInput;
|
||||
this.createStandingInstructionAtDisbursement = valueAsInput;
|
||||
}
|
||||
|
||||
|
||||
final String externalIdParamName = "externalId";
|
||||
if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
|
||||
@@ -3073,7 +3074,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
for (final LoanTransaction loanTransaction : this.loanTransactions) {
|
||||
if (loanTransaction.isRefund() && !loanTransaction.isReversed()) {
|
||||
if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) && !loanTransaction.isReversed()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
||||
}
|
||||
}
|
||||
@@ -3853,15 +3854,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
|
||||
final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
|
||||
|
||||
|
||||
// first repayment's from date is same as disbursement date.
|
||||
/*
|
||||
* meetingStartDate is used as seedDate Capture the seedDate from
|
||||
* user and use the seedDate as meetingStart date
|
||||
* meetingStartDate is used as seedDate Capture the seedDate from user
|
||||
* and use the seedDate as meetingStart date
|
||||
*/
|
||||
|
||||
|
||||
|
||||
LocalDate tmpFromDate = getDisbursementDate();
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
||||
final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
|
||||
@@ -3869,38 +3868,37 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
LocalDate newRepaymentDate = null;
|
||||
Boolean isFirstTime = true;
|
||||
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
|
||||
|
||||
|
||||
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
|
||||
|
||||
if ( oldDueDate.isEqual(presentMeetingDate) || oldDueDate.isAfter(presentMeetingDate) ) {
|
||||
|
||||
|
||||
if(isFirstTime){
|
||||
|
||||
|
||||
if (oldDueDate.isEqual(presentMeetingDate) || oldDueDate.isAfter(presentMeetingDate)) {
|
||||
|
||||
if (isFirstTime) {
|
||||
|
||||
isFirstTime = false;
|
||||
newRepaymentDate = newMeetingDate;
|
||||
|
||||
}else{
|
||||
|
||||
} else {
|
||||
// tmpFromDate.plusDays(1) is done to make sure
|
||||
// getNewRepaymentMeetingDate method returns next meeting
|
||||
// date and not the same as tmpFromDate
|
||||
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, tmpFromDate, tmpFromDate.plusDays(1),
|
||||
loanRepaymentInterval, frequency, workingDays);
|
||||
}
|
||||
|
||||
|
||||
if (isHolidayEnabled) {
|
||||
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
|
||||
}
|
||||
|
||||
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||
// reset from date to get actual daysInPeriod
|
||||
|
||||
if(!isFirstTime){
|
||||
|
||||
if (!isFirstTime) {
|
||||
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
|
||||
}
|
||||
|
||||
|
||||
tmpFromDate = newRepaymentDate;// update with new repayment
|
||||
// date
|
||||
} else {
|
||||
@@ -3911,7 +3909,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
|
||||
// first repayment's from date is same as disbursement date.
|
||||
LocalDate tmpFromDate = getDisbursementDate();
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
||||
@@ -3920,18 +3918,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
LocalDate newRepaymentDate = null;
|
||||
LocalDate seedDate = meetingStartDate;
|
||||
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
|
||||
|
||||
|
||||
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
|
||||
|
||||
|
||||
// FIXME: AA this won't update repayment dates before current date.
|
||||
|
||||
|
||||
if ( oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant()) ) {
|
||||
|
||||
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate,
|
||||
loanRepaymentInterval, frequency, workingDays);
|
||||
|
||||
if (oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
|
||||
|
||||
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval,
|
||||
frequency, workingDays);
|
||||
|
||||
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
|
||||
loanRepaymentInterval, tmpFromDate);
|
||||
@@ -4167,6 +4164,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
action = "charge.payment";
|
||||
postfix = "cannot.be.made.before.client.transfer.date";
|
||||
break;
|
||||
case LOAN_REFUND:
|
||||
errorMessage = "The date on which a refund is made cannot be earlier than client's transfer date to this office";
|
||||
action = "refund";
|
||||
postfix = "cannot.be.made.before.client.transfer.date";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -4372,6 +4374,14 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
break;
|
||||
case LOAN_REFUND:
|
||||
if (!isOpen()) {
|
||||
final String defaultUserMessage = "Loan Refund is not allowed. Loan Account is not active.";
|
||||
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.refund.account.is.not.active",
|
||||
defaultUserMessage);
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -4979,4 +4989,128 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public BigDecimal getGuaranteeAmount() {
|
||||
return this.guaranteeAmountDerived == null ? BigDecimal.ZERO : this.guaranteeAmountDerived;
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail makeRefundForActiveLoan(final LoanTransaction loanTransaction,
|
||||
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_REFUND);
|
||||
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REFUND, loanTransaction.getTransactionDate());
|
||||
|
||||
validateRefundDateIsAfterLastRepayment(loanTransaction.getTransactionDate());
|
||||
|
||||
validateRepaymentDateIsOnHoliday(loanTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
|
||||
validateRepaymentDateIsOnNonWorkingDay(loanTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRefundTransaction(loanTransaction, loanLifecycleStateMachine, null);
|
||||
|
||||
return changedTransactionDetail;
|
||||
|
||||
}
|
||||
|
||||
private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) {
|
||||
final LocalDate possibleNextRefundDate = possibleNextRefundDate();
|
||||
|
||||
if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(
|
||||
refundTransactionDate.toString()); }
|
||||
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction loanTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) {
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
|
||||
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, LoanStatus.fromInt(this.loanStatus));
|
||||
this.loanStatus = statusEnum.getValue();
|
||||
|
||||
loanTransaction.updateLoan(this);
|
||||
|
||||
// final boolean isTransactionChronologicallyLatest =
|
||||
// isChronologicallyLatestRefund(loanTransaction,
|
||||
// this.loanTransactions);
|
||||
|
||||
if (status().isOverpaid() || status().isClosed()) {
|
||||
|
||||
final String errorMessage = "This refund option is only for active loans ";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage, this.totalOverpaid,
|
||||
loanTransaction.getAmount(getCurrency()).getAmount());
|
||||
|
||||
} else if (this.getTotalPaidInRepayments().isZero()) {
|
||||
final String errorMessage = "Cannot refund when no payment has been made";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
this.loanTransactions.add(loanTransaction);
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotRefundForActiveLoan()) {
|
||||
final String errorMessage = "A transaction of type refund was expected but not received.";
|
||||
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.refund.transaction", errorMessage);
|
||||
}
|
||||
|
||||
final LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
|
||||
if (loanTransactionDate.isBefore(getDisbursementDate())) {
|
||||
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
|
||||
+ getApprovedOnDate().toString();
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage,
|
||||
loanTransactionDate, getDisbursementDate());
|
||||
}
|
||||
|
||||
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 is a refund
|
||||
if (adjustedTransaction == null) {
|
||||
loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
|
||||
charges());
|
||||
} else {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), null);
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
public LocalDate possibleNextRefundDate() {
|
||||
|
||||
final LocalDate now = new LocalDate();
|
||||
|
||||
LocalDate lastTransactionDate = null;
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if ((transaction.isRepayment() || transaction.isRefundForActiveLoan()) && transaction.isNonZero()) {
|
||||
lastTransactionDate = transaction.getTransactionDate();
|
||||
}
|
||||
}
|
||||
|
||||
return lastTransactionDate == null ? now : lastTransactionDate;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -31,6 +31,10 @@ public interface LoanAccountDomainService {
|
||||
PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance);
|
||||
|
||||
LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult,
|
||||
LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
/**
|
||||
* This method is to recalculate and accrue the income till the last accrued
|
||||
|
||||
+41
@@ -548,4 +548,45 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) {
|
||||
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail,
|
||||
transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser);
|
||||
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.makeRefundForActiveLoan(newRefundTransaction,
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
this.loanTransactionRepository.save(newRefundTransaction);
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
recalculateAccruals(loan);
|
||||
|
||||
builderResult.withEntityId(newRefundTransaction.getId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()); //
|
||||
|
||||
return newRefundTransaction;
|
||||
}
|
||||
}
|
||||
+44
@@ -883,4 +883,48 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
|
||||
return this.overdueInstallmentCharge;
|
||||
}
|
||||
|
||||
public Money undoPaidOrPartiallyAmountBy(final Money incrementBy,final Integer installmentNumber, final Money feeAmount) {
|
||||
Money processAmount = Money.zero(incrementBy.getCurrency());
|
||||
if(isInstalmentFee()){
|
||||
if(installmentNumber == null){
|
||||
processAmount = getLastPaidOrPartiallyPaidInstallmentLoanCharge(incrementBy.getCurrency()).undoPaidAmountBy(incrementBy, feeAmount);
|
||||
}else{
|
||||
processAmount = getInstallmentLoanCharge(installmentNumber).undoPaidAmountBy(incrementBy, feeAmount);
|
||||
}
|
||||
}else{
|
||||
processAmount = incrementBy;
|
||||
}
|
||||
Money amountPaidToDate = Money.of(processAmount.getCurrency(), this.amountPaid);
|
||||
|
||||
Money amountDeductedOnThisCharge = Money.zero(processAmount.getCurrency());
|
||||
if (processAmount.isGreaterThanOrEqualTo(amountPaidToDate)) {
|
||||
amountDeductedOnThisCharge = amountPaidToDate;
|
||||
amountPaidToDate = Money.zero(processAmount.getCurrency());
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
this.amountOutstanding = this.amount;
|
||||
this.paid = false;
|
||||
|
||||
} else {
|
||||
amountDeductedOnThisCharge = processAmount;
|
||||
amountPaidToDate = amountPaidToDate.minus(processAmount);
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
|
||||
}
|
||||
return amountDeductedOnThisCharge;
|
||||
}
|
||||
|
||||
public LoanInstallmentCharge getLastPaidOrPartiallyPaidInstallmentLoanCharge(MonetaryCurrency currency) {
|
||||
LoanInstallmentCharge paidChargePerInstallment = null;
|
||||
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
|
||||
Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding());
|
||||
final boolean partiallyPaid = outstanding.isGreaterThanZero() && outstanding.isLessThan(loanChargePerInstallment.getAmount(currency));
|
||||
if ((partiallyPaid || loanChargePerInstallment.isPaid())
|
||||
&& (paidChargePerInstallment == null || paidChargePerInstallment.getRepaymentInstallment().getDueDate()
|
||||
.isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
|
||||
paidChargePerInstallment = loanChargePerInstallment;
|
||||
}
|
||||
}
|
||||
return paidChargePerInstallment;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -27,5 +27,6 @@ public enum LoanEvent {
|
||||
LOAN_OVERPAYMENT, //
|
||||
LOAN_CHARGE_PAYMENT, //
|
||||
LOAN_CLOSED, //
|
||||
LOAN_EDIT_MULTI_DISBURSE_DATE;
|
||||
LOAN_EDIT_MULTI_DISBURSE_DATE, //
|
||||
LOAN_REFUND;
|
||||
}
|
||||
|
||||
+22
@@ -252,5 +252,27 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
|
||||
public void updateInstallment(LoanRepaymentScheduleInstallment installment) {
|
||||
this.installment = installment;
|
||||
}
|
||||
|
||||
public Money undoPaidAmountBy(final Money incrementBy, final Money feeAmount) {
|
||||
|
||||
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
|
||||
|
||||
Money amountToDeductOnThisCharge = Money.zero(incrementBy.getCurrency());
|
||||
if (incrementBy.isGreaterThanOrEqualTo(amountPaidToDate)) {
|
||||
amountToDeductOnThisCharge = amountPaidToDate;
|
||||
amountPaidToDate = Money.zero(incrementBy.getCurrency());
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
this.amountOutstanding = this.amount;
|
||||
} else {
|
||||
amountToDeductOnThisCharge = incrementBy;
|
||||
amountPaidToDate = amountPaidToDate.minus(incrementBy);
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
|
||||
}
|
||||
this.amountThroughChargePayment = feeAmount.getAmount();
|
||||
this.paid = determineIfFullyPaid();
|
||||
|
||||
return amountToDeductOnThisCharge;
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -563,6 +563,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
if (this.obligationsMet) {
|
||||
this.obligationsMetOnDate = transactionDate.toDate();
|
||||
}
|
||||
else {
|
||||
this.obligationsMetOnDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDueDate(final LocalDate newDueDate) {
|
||||
@@ -645,4 +648,113 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
return obligationsMetOnDate;
|
||||
}
|
||||
|
||||
/********** UNPAY COMPONENTS ****/
|
||||
|
||||
public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money penaltyPortionOfTransactionDeducted = Money.zero(currency);
|
||||
|
||||
final Money penaltyChargesCompleted = getPenaltyChargesPaid(currency);
|
||||
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesCompleted)) {
|
||||
this.penaltyChargesPaid = Money.zero(currency).getAmount();
|
||||
penaltyPortionOfTransactionDeducted = penaltyChargesCompleted;
|
||||
} else {
|
||||
this.penaltyChargesPaid = penaltyChargesCompleted.minus(transactionAmountRemaining).getAmount();
|
||||
penaltyPortionOfTransactionDeducted = transactionAmountRemaining;
|
||||
}
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return penaltyPortionOfTransactionDeducted;
|
||||
}
|
||||
|
||||
public Money unpayFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money feePortionOfTransactionDeducted = Money.zero(currency);
|
||||
|
||||
final Money feeChargesCompleted = getFeeChargesPaid(currency);
|
||||
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesCompleted)) {
|
||||
this.feeChargesPaid = Money.zero(currency).getAmount();
|
||||
feePortionOfTransactionDeducted = feeChargesCompleted;
|
||||
} else {
|
||||
this.feeChargesPaid = feeChargesCompleted.minus(transactionAmountRemaining).getAmount();
|
||||
feePortionOfTransactionDeducted = transactionAmountRemaining;
|
||||
}
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, feePortionOfTransactionDeducted);
|
||||
|
||||
return feePortionOfTransactionDeducted;
|
||||
}
|
||||
|
||||
public Money unpayInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money interestPortionOfTransactionDeducted = Money.zero(currency);
|
||||
|
||||
final Money interestCompleted = getInterestPaid(currency);
|
||||
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestCompleted)) {
|
||||
this.interestPaid = Money.zero(currency).getAmount();
|
||||
interestPortionOfTransactionDeducted = interestCompleted;
|
||||
} else {
|
||||
this.interestPaid = interestCompleted.minus(transactionAmountRemaining).getAmount();
|
||||
interestPortionOfTransactionDeducted = transactionAmountRemaining;
|
||||
}
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, interestPortionOfTransactionDeducted);
|
||||
|
||||
return interestPortionOfTransactionDeducted;
|
||||
}
|
||||
|
||||
public Money unpayPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money principalPortionOfTransactionDeducted = Money.zero(currency);
|
||||
|
||||
final Money principalCompleted = getPrincipalCompleted(currency);
|
||||
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalCompleted)) {
|
||||
this.principalCompleted = Money.zero(currency).getAmount();
|
||||
principalPortionOfTransactionDeducted = principalCompleted;
|
||||
} else {
|
||||
this.principalCompleted = principalCompleted.minus(transactionAmountRemaining).getAmount();
|
||||
principalPortionOfTransactionDeducted = transactionAmountRemaining;
|
||||
}
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
reduceAdvanceAndLateTotalsForRepaymentPeriod(transactionDate, currency, principalPortionOfTransactionDeducted);
|
||||
|
||||
return principalPortionOfTransactionDeducted;
|
||||
}
|
||||
|
||||
private void reduceAdvanceAndLateTotalsForRepaymentPeriod(final LocalDate transactionDate, final MonetaryCurrency currency,
|
||||
final Money amountDeductedInRepaymentPeriod) {
|
||||
|
||||
|
||||
if (isInAdvance(transactionDate)) {
|
||||
Money mTotalPaidInAdvance = Money.of(currency,this.totalPaidInAdvance);
|
||||
|
||||
if(mTotalPaidInAdvance.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidInAdvance.isEqualTo(amountDeductedInRepaymentPeriod))
|
||||
this.totalPaidInAdvance = Money.zero(currency).getAmount();
|
||||
else
|
||||
this.totalPaidInAdvance = mTotalPaidInAdvance.minus(amountDeductedInRepaymentPeriod).getAmount();
|
||||
} else if (isLatePayment(transactionDate)) {
|
||||
Money mTotalPaidLate = Money.of(currency,this.totalPaidLate);
|
||||
|
||||
if(mTotalPaidLate.isLessThan(amountDeductedInRepaymentPeriod) || mTotalPaidLate.isEqualTo(amountDeductedInRepaymentPeriod))
|
||||
this.totalPaidLate = Money.zero(currency).getAmount();
|
||||
else
|
||||
this.totalPaidLate = mTotalPaidLate.minus(amountDeductedInRepaymentPeriod).getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
public Money getDue(MonetaryCurrency currency) {
|
||||
return getPrincipal(currency).plus(getInterestCharged(currency)).plus(getFeeChargesCharged(currency)).plus(getPenaltyChargesCharged(currency));
|
||||
}
|
||||
}
|
||||
+15
@@ -229,6 +229,12 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
return applyCharge;
|
||||
}
|
||||
|
||||
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
|
||||
final LocalDate paymentDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
|
||||
return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(),
|
||||
paymentDate, externalId, createdDate, appUser);
|
||||
}
|
||||
|
||||
public static boolean transactionAmountsMatch(final MonetaryCurrency currency, final LoanTransaction loanTransaction,
|
||||
final LoanTransaction newLoanTransaction) {
|
||||
if (loanTransaction.getAmount(currency).isEqualTo(newLoanTransaction.getAmount(currency))
|
||||
@@ -610,6 +616,15 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
}
|
||||
|
||||
public boolean isNotRefundForActiveLoan() {
|
||||
// TODO Auto-generated method stub
|
||||
return !isRefundForActiveLoan();
|
||||
}
|
||||
|
||||
public boolean isRefundForActiveLoan() {
|
||||
return LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isManuallyAdjustedOrReversed() {
|
||||
return this.manuallyAdjustedOrReversed;
|
||||
}
|
||||
|
||||
+9
-1
@@ -37,7 +37,8 @@ public enum LoanTransactionType {
|
||||
WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), //
|
||||
REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"), //
|
||||
REFUND(16, "loanTransactionType.refund"), //
|
||||
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment");
|
||||
CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), //
|
||||
REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
@@ -109,6 +110,9 @@ public enum LoanTransactionType {
|
||||
case 17:
|
||||
loanTransactionType = LoanTransactionType.CHARGE_PAYMENT;
|
||||
break;
|
||||
case 18:
|
||||
loanTransactionType = LoanTransactionType.REFUND_FOR_ACTIVE_LOAN;
|
||||
break;
|
||||
default:
|
||||
loanTransactionType = LoanTransactionType.INVALID;
|
||||
break;
|
||||
@@ -151,4 +155,8 @@ public enum LoanTransactionType {
|
||||
public boolean isChargePayment() {
|
||||
return this.value.equals(LoanTransactionType.CHARGE_PAYMENT.getValue());
|
||||
}
|
||||
|
||||
public boolean isRefundForActiveLoan() {
|
||||
return this.value.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue());
|
||||
}
|
||||
}
|
||||
+160
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -22,6 +25,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||
@@ -136,6 +140,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
|
||||
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
|
||||
|
||||
if(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
||||
@Override
|
||||
public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
|
||||
return ord1.getDueDate().compareTo(ord2.getDueDate());
|
||||
}
|
||||
};
|
||||
Collections.sort(installments,byDate);
|
||||
}
|
||||
|
||||
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||
// pass through for new transactions
|
||||
@@ -170,6 +184,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
loanTransaction.resetDerivedComponents();
|
||||
handleWriteOff(loanTransaction, currency, installments);
|
||||
}
|
||||
else if(loanTransaction.isRefundForActiveLoan()) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
|
||||
handleRefund(loanTransaction, currency, installments,charges);
|
||||
}
|
||||
}
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
@@ -510,6 +529,104 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRefund(LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Set<LoanCharge> charges) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
||||
@Override
|
||||
public int compare(LoanRepaymentScheduleInstallment ord1,
|
||||
LoanRepaymentScheduleInstallment ord2) {
|
||||
return ord1.getDueDate().compareTo(ord2.getDueDate());
|
||||
}
|
||||
};
|
||||
Collections.sort(installments, Collections.reverseOrder(byDate));
|
||||
Money transactionAmountUnprocessed = loanTransaction
|
||||
.getAmount(currency);
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
Money outstanding = currentInstallment
|
||||
.getTotalOutstanding(currency);
|
||||
Money due = currentInstallment.getDue(currency);
|
||||
|
||||
if (outstanding.isLessThan(due)) {
|
||||
transactionAmountUnprocessed = handleRefundTransactionPaymentOfInstallment(
|
||||
currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed);
|
||||
|
||||
}
|
||||
|
||||
if (transactionAmountUnprocessed.isZero())
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
||||
Integer installmentNumber = null;
|
||||
|
||||
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||
if (feeCharges.isGreaterThanZero()) {
|
||||
undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees,
|
||||
installmentNumber);
|
||||
}
|
||||
|
||||
final Money penaltyCharges = loanTransaction
|
||||
.getPenaltyChargesPortion(currency);
|
||||
if (penaltyCharges.isGreaterThanZero()) {
|
||||
undoChargesPaidAmountBy(loanTransaction, penaltyCharges,
|
||||
loanPenalties, installmentNumber);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a there is a refund of an active loan or undo of an active
|
||||
* loan
|
||||
*
|
||||
* Undoes principal, interest, fees and charges of this transaction based on
|
||||
* the repayment strategy
|
||||
*
|
||||
*/
|
||||
protected abstract Money handleRefundTransactionPaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed);
|
||||
|
||||
private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction,
|
||||
final Money feeCharges, final Set<LoanCharge> charges,
|
||||
final Integer installmentNumber) {
|
||||
|
||||
Money amountRemaining = feeCharges;
|
||||
while (amountRemaining.isGreaterThanZero()) {
|
||||
final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(
|
||||
charges, feeCharges.getCurrency());
|
||||
|
||||
if (paidCharge != null) {
|
||||
Money feeAmount = feeCharges.zero();
|
||||
|
||||
final Money amountDeductedTowardsCharge = paidCharge
|
||||
.undoPaidOrPartiallyAmountBy(amountRemaining,
|
||||
installmentNumber, feeAmount);
|
||||
if (amountDeductedTowardsCharge.isGreaterThanZero()) {
|
||||
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(
|
||||
loanTransaction, paidCharge,
|
||||
amountDeductedTowardsCharge.getAmount().multiply(
|
||||
new BigDecimal(-1)), null);
|
||||
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
|
||||
amountRemaining = amountRemaining
|
||||
.minus(amountDeductedTowardsCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(final LocalDate disbursementDate,
|
||||
@@ -519,5 +636,48 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom,
|
||||
reprocessCharges);
|
||||
}
|
||||
|
||||
private LoanCharge findLatestPaidChargeFromUnOrderedSet(
|
||||
final Set<LoanCharge> charges, MonetaryCurrency currency) {
|
||||
LoanCharge latestPaidCharge = null;
|
||||
LoanCharge installemntCharge = null;
|
||||
LoanInstallmentCharge chargePerInstallment = null;
|
||||
for (final LoanCharge loanCharge : charges) {
|
||||
boolean isPaidOrPartiallyPaid = loanCharge
|
||||
.isPaidOrPartiallyPaid(currency);
|
||||
if (isPaidOrPartiallyPaid && !loanCharge.isDueAtDisbursement()) {
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge
|
||||
.getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency);
|
||||
if (chargePerInstallment == null
|
||||
|| (paidLoanChargePerInstallment != null && chargePerInstallment
|
||||
.getRepaymentInstallment()
|
||||
.getDueDate()
|
||||
.isBefore(
|
||||
paidLoanChargePerInstallment
|
||||
.getRepaymentInstallment()
|
||||
.getDueDate()))) {
|
||||
installemntCharge = loanCharge;
|
||||
chargePerInstallment = paidLoanChargePerInstallment;
|
||||
}
|
||||
} else if (latestPaidCharge == null
|
||||
|| (loanCharge.isPaidOrPartiallyPaid(currency))
|
||||
&& loanCharge.getDueLocalDate().isAfter(
|
||||
latestPaidCharge.getDueLocalDate())) {
|
||||
latestPaidCharge = loanCharge;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (latestPaidCharge == null
|
||||
|| (chargePerInstallment != null && latestPaidCharge
|
||||
.getDueLocalDate().isAfter(
|
||||
chargePerInstallment.getRepaymentInstallment()
|
||||
.getDueDate()))) {
|
||||
latestPaidCharge = installemntCharge;
|
||||
}
|
||||
|
||||
return latestPaidCharge;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+5
@@ -45,5 +45,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
|
||||
ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate,
|
||||
List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, LocalDate recalculateChargesFrom);
|
||||
|
||||
void handleRefund(LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Set<LoanCharge> charges);
|
||||
|
||||
}
|
||||
+33
@@ -129,4 +129,37 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
if(transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
if(transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if(transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
}
|
||||
+37
@@ -137,5 +137,42 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -181,4 +181,41 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
}
|
||||
+36
@@ -104,4 +104,40 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
}
|
||||
+34
@@ -123,4 +123,38 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
|
||||
// TODO - KW - dont do anything with loan over-payment for now
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
}
|
||||
+38
@@ -104,4 +104,42 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -209,4 +209,41 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
|
||||
public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
}
|
||||
|
||||
if (transactionAmountRemaining.isGreaterThanZero()) {
|
||||
penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
/**
|
||||
* {@link AbstractPlatformDomainRuleException} thrown an action to transition a
|
||||
* loan from one state to another violates a domain rule.
|
||||
*/
|
||||
public class InvalidPaidInAdvanceAmountException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public InvalidPaidInAdvanceAmountException(final String refundAmountString) {
|
||||
super("error.msg.loan.refund.amount.invalid", "The refund amount `" + refundAmountString + "`"
|
||||
+ "` is invalid or loan is not paid in advance.", new Object[] { refundAmountString});
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class InvalidRefundDateException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public InvalidRefundDateException(final String refundDateAsString) {
|
||||
super("error.msg.loan.refund.failed", "The refund date`" + refundDateAsString + "`"
|
||||
+ "` cannot be before the smallest repayment transaction date", new Object[] { refundDateAsString});
|
||||
}
|
||||
|
||||
public InvalidRefundDateException(final String defaultUserMessage,final String entity,final Object... defaultUserMessageArgs) {
|
||||
super("error.msg.loan." + entity , defaultUserMessage, defaultUserMessageArgs);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
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;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class LoanRefundByCashCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public LoanRefundByCashCommandHandler(final LoanWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.makeLoanRefund(command.getLoanId(), command);
|
||||
}
|
||||
}
|
||||
+28
@@ -388,5 +388,33 @@ public final class LoanEventApiJsonValidator {
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateNewRefundTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "externalId",
|
||||
"note", "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber",
|
||||
"bankNumber"));
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
}
|
||||
+10
-2
@@ -6,18 +6,20 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.service.Page;
|
||||
import org.mifosplatform.infrastructure.core.service.SearchParameters;
|
||||
import org.mifosplatform.organisation.staff.data.StaffData;
|
||||
import org.mifosplatform.portfolio.calendar.data.CalendarData;
|
||||
import org.mifosplatform.infrastructure.core.service.SearchParameters;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
@@ -45,7 +47,7 @@ public interface LoanReadPlatformService {
|
||||
LoanTransactionData retrieveNewClosureDetails();
|
||||
|
||||
LoanTransactionData retrieveDisbursalTemplate(Long loanId, boolean paymentDetailsRequired);
|
||||
|
||||
|
||||
LoanApprovalData retrieveApprovalTemplate(Long loanId);
|
||||
|
||||
LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId);
|
||||
@@ -101,4 +103,10 @@ public interface LoanReadPlatformService {
|
||||
Collection<LoanSchedulePeriodData> fetchWaiverInterestRepaymentData(Long loanId);
|
||||
|
||||
boolean isGuaranteeRequired(Long loanId);
|
||||
|
||||
Date retrieveMinimumDateOfRepaymentTransaction(Long loanId);
|
||||
|
||||
PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId);
|
||||
|
||||
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
|
||||
}
|
||||
+67
-10
@@ -11,6 +11,7 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -29,6 +30,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.infrastructure.core.service.Page;
|
||||
import org.mifosplatform.infrastructure.core.service.PaginationHelper;
|
||||
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
|
||||
import org.mifosplatform.infrastructure.core.service.SearchParameters;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
@@ -56,7 +58,6 @@ import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
|
||||
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
|
||||
import org.mifosplatform.portfolio.group.data.GroupRoleData;
|
||||
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.core.service.SearchParameters;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanApplicationTimelineData;
|
||||
@@ -68,6 +69,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
@@ -328,8 +330,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final Collection<CalendarData> calendarsData = null;
|
||||
final CalendarData collectionMeetingCalendar = null;
|
||||
final Collection<GroupRoleData> groupRoles = null;
|
||||
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers,
|
||||
groupRoles, calendarsData, collectionMeetingCalendar);
|
||||
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers, groupRoles, calendarsData,
|
||||
collectionMeetingCalendar);
|
||||
}
|
||||
|
||||
final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
|
||||
@@ -418,8 +420,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
final CurrencyData currencyData = applicationCurrency.toData();
|
||||
|
||||
final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(
|
||||
DateUtils.getLocalDateTimeOfTenant(), currentUser);
|
||||
final LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction(DateUtils.getLocalDateTimeOfTenant(),
|
||||
currentUser);
|
||||
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
|
||||
|
||||
@@ -448,7 +450,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final Loan loan = this.loanRepository.findOne(loanId);
|
||||
if (loan == null) { throw new LoanNotFoundException(loanId); }
|
||||
|
||||
return new LoanApprovalData(loan.getProposedPrincipal(),DateUtils.getLocalDateOfTenant());
|
||||
return new LoanApprovalData(loan.getProposedPrincipal(), DateUtils.getLocalDateOfTenant());
|
||||
|
||||
}
|
||||
|
||||
@@ -827,8 +829,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
|
||||
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData,
|
||||
loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName,
|
||||
loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance,
|
||||
termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
|
||||
loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid,
|
||||
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
|
||||
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName,
|
||||
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
|
||||
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
@@ -1322,8 +1324,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final Collection<CalendarData> calendarsData = null;
|
||||
final CalendarData collectionMeetingCalendar = null;
|
||||
final Collection<GroupRoleData> groupRoles = null;
|
||||
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers,
|
||||
groupRoles, calendarsData, collectionMeetingCalendar);
|
||||
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, activeClientMembers, groupRoles, calendarsData,
|
||||
collectionMeetingCalendar);
|
||||
}
|
||||
|
||||
return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
|
||||
@@ -1785,4 +1787,59 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) {
|
||||
// TODO Auto-generated method stub
|
||||
Date date = this.jdbcTemplate.queryForObject(
|
||||
"select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2",
|
||||
new Object[] { loanId }, Date.class);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId) {
|
||||
// TODO Auto-generated method stub
|
||||
try {
|
||||
final String sql = " select (SUM(ifnull(mr.principal_completed_derived, 0)) +"
|
||||
+ " + SUM(ifnull(mr.interest_completed_derived, 0)) " + " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) "
|
||||
+ " + SUM(ifnull(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived "
|
||||
+ " from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "
|
||||
+ " where ml.id=? and mr.duedate >= CURDATE() group by ml.id having "
|
||||
+ " (SUM(ifnull(mr.principal_completed_derived, 0)) " + " + SUM(ifnull(mr.interest_completed_derived, 0)) "
|
||||
+ " + SUM(ifnull(mr.fee_charges_completed_derived, 0)) "
|
||||
+ "+ SUM(ifnull(mr.penalty_charges_completed_derived, 0))) > 0";
|
||||
BigDecimal bigDecimal = this.jdbcTemplate.queryForObject(sql, BigDecimal.class, loanId);
|
||||
return new PaidInAdvanceData(bigDecimal);
|
||||
} catch (DataAccessException e) {
|
||||
// TODO Auto-generated catch block
|
||||
return new PaidInAdvanceData(new BigDecimal(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanTransactionData retrieveRefundByCashTemplate(Long loanId) {
|
||||
// TODO Auto-generated method stub
|
||||
this.context.authenticatedUser();
|
||||
|
||||
// TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
|
||||
// possible next transaction date.
|
||||
final Loan loan = this.loanRepository.findOne(loanId);
|
||||
if (loan == null) { throw new LoanNotFoundException(loanId); }
|
||||
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
final CurrencyData currencyData = applicationCurrency.toData();
|
||||
|
||||
final LocalDate earliestUnpaidInstallmentDate = new LocalDate();
|
||||
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
|
||||
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
|
||||
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
||||
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null,
|
||||
null, null, null, false);
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -86,4 +86,6 @@ public interface LoanWritePlatformService {
|
||||
|
||||
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
|
||||
|
||||
CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command);
|
||||
|
||||
}
|
||||
+52
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -122,6 +123,8 @@ 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.exception.InvalidPaidInAdvanceAmountException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||
@@ -2705,6 +2708,55 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
return installments;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
|
||||
//checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
|
||||
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
checkIfLoanIsPaidInAdvance(loanId, transactionAmount);
|
||||
|
||||
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());
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
}
|
||||
|
||||
final PaymentDetail paymentDetail = null;
|
||||
|
||||
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
|
||||
|
||||
this.loanAccountDomainService.makeRefundForActiveLoan(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount,
|
||||
paymentDetail, noteText, null);
|
||||
|
||||
return commandProcessingResultBuilder.withCommandId(command.commandId()) //
|
||||
.withLoanId(loanId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
private void checkIfLoanIsPaidInAdvance(final Long loanId,final BigDecimal transactionAmount) {
|
||||
BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId).getPaidInAdvance();
|
||||
|
||||
if(overpaid == null || overpaid.equals(new BigDecimal(0)) || transactionAmount.floatValue() > overpaid.floatValue()) {
|
||||
if(overpaid == null)
|
||||
overpaid = BigDecimal.ZERO;
|
||||
throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
|
||||
}
|
||||
}
|
||||
|
||||
private AppUser getAppUserIfPresent() {
|
||||
AppUser user = null;
|
||||
|
||||
+4
@@ -364,6 +364,10 @@ public class LoanEnumerations {
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.CHARGE_PAYMENT.getValue().longValue(),
|
||||
LoanTransactionType.CHARGE_PAYMENT.getCode(), "Charge Payment");
|
||||
break;
|
||||
case REFUND_FOR_ACTIVE_LOAN:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue().longValue(),
|
||||
LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getCode(), "Refund");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES
|
||||
('transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 0);
|
||||
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES
|
||||
( 'transaction_savings', 'REFUNDBYTRANSFER_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'REFUNDBYTRANSFER', 1);
|
||||
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES
|
||||
('transaction_loan', 'REFUNDBYCASH_LOAN', 'LOAN', 'REFUNDBYCASH', 1);
|
||||
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES
|
||||
('transaction_loan', 'REFUNDBYCASH_LOAN_CHECKER', 'LOAN', 'REFUNDBYCASH', 0);
|
||||
|
||||
Reference in New Issue
Block a user