MIFOSX-1146: added support for disbursing loan amount to savings account
This commit is contained in:
@@ -876,6 +876,14 @@
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>loans/{loanId}?command=disburseToSavings</td>
|
||||
<td><a href="#loans_disburse_to_savings">Disburse Loan To Savings Account</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<td></td>
|
||||
<td>loans/{loanId}?command=undoDisbursal</td>
|
||||
@@ -6927,6 +6935,74 @@ Request Body:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="loans_disburse_to_savings" name="loans_disburse_to_savings" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Disburse Loan To Savings Account</h4>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>actualDisbursementDate</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>transactionAmount,fixedEmiAmount</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/loans/{loanId}?command=disburseToSavings
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST loans/1?command=disburse
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"locale": "en",
|
||||
"transactionAmount":10000,
|
||||
"fixedEmiAmount""1100,
|
||||
"actualDisbursementDate": "14 May 2013",
|
||||
"note": ""
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId": 1,
|
||||
"clientId": 1,
|
||||
"loanId": 1,
|
||||
"resourceId": 1,
|
||||
"changes": {
|
||||
"status": {
|
||||
"id": 300,
|
||||
"code": "loanStatusType.active",
|
||||
"value": "Active",
|
||||
"pendingApproval": false,
|
||||
"waitingForDisbursal": false,
|
||||
"active": true,
|
||||
"closedObligationsMet": false,
|
||||
"closedWrittenOff": false,
|
||||
"closedRescheduled": false,
|
||||
"closed": false,
|
||||
"overpaid": false
|
||||
},
|
||||
"locale": "en",
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"actualDisbursementDate": "14 May 2013",
|
||||
"transactionAmount":10000
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="loans_disburse_undo" name="loans_disburse_undo" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
@@ -28905,6 +28981,14 @@ No Request Body:
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<td></td>
|
||||
<td>loans/{loanId}?command=disburseToSavings</td>
|
||||
<td><a href="#loans_disburse_to_savings">Disburse Loan To Savings Account</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>loans/{loanId}?command=undoDisbursal</td>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSETOSAVINGS_LOAN', 'LOAN', 'DISBURSETOSAVINGS', 0);
|
||||
+186
-12
@@ -56,8 +56,8 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
final Integer loanProductID = createLoanProduct();
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, null);
|
||||
final Integer loanProductID = createLoanProduct(false);
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, null, "12,000.00");
|
||||
final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec,
|
||||
loanID);
|
||||
verifyLoanRepaymentSchedule(loanSchedule);
|
||||
@@ -70,7 +70,7 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
final Integer loanProductID = createLoanProduct();
|
||||
final Integer loanProductID = createLoanProduct(false);
|
||||
|
||||
List<HashMap> charges = new ArrayList<HashMap>();
|
||||
Integer flatDisbursement = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getLoanDisbursementJSON());
|
||||
@@ -85,7 +85,7 @@ public class ClientLoanIntegrationTest {
|
||||
ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1"));
|
||||
addCharges(charges, interestPercentage, "1", null);
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges);
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
@@ -177,7 +177,7 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
final Integer loanProductID = createLoanProduct();
|
||||
final Integer loanProductID = createLoanProduct(false);
|
||||
|
||||
List<HashMap> charges = new ArrayList<HashMap>();
|
||||
Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
@@ -196,7 +196,7 @@ public class ClientLoanIntegrationTest {
|
||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1", false));
|
||||
addCharges(charges, interestPercentage, "1", "29 September 2011");
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges);
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
@@ -336,7 +336,7 @@ public class ClientLoanIntegrationTest {
|
||||
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 loanProductID = createLoanProduct();
|
||||
final Integer loanProductID = createLoanProduct(false);
|
||||
|
||||
List<HashMap> charges = new ArrayList<HashMap>();
|
||||
Integer flat = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
@@ -354,7 +354,7 @@ public class ClientLoanIntegrationTest {
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "1", false));
|
||||
addCharges(charges, interestPercentage, "1", "29 September 2011");
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges);
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "12,000.00");
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
@@ -594,6 +594,148 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanCharges_DISBURSEMENT_TO_SAVINGS() {
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
final Integer loanProductID = createLoanProduct(false);
|
||||
|
||||
final Integer savingsId = createSavings(clientID);
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, savingsId.toString(), "12,000.00");
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
HashMap summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
Float balance = new Float(MINIMUM_OPENING_BALANCE);
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
// DISBURSE
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings(SavingsAccountHelper.TRANSACTION_DATE, loanID);
|
||||
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("12000");
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
balance = new Float(MINIMUM_OPENING_BALANCE);
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanCharges_DISBURSEMENT_WITH_TRANCHES() {
|
||||
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 loanProductID = createLoanProduct(true);
|
||||
|
||||
List<HashMap> tranches = new ArrayList<HashMap>();
|
||||
tranches.add(createTrancheDetail("1 March 2014", "25000"));
|
||||
tranches.add(createTrancheDetail("23 April 2014", "20000"));
|
||||
|
||||
final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, null, null, "45,000.00", tranches);
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("1 March 2014", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
// DISBURSE first Tranche
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoan("1 March 2014", loanID);
|
||||
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
// DISBURSE Second Tranche
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoan("23 April 2014", loanID);
|
||||
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanCharges_DISBURSEMENT_TO_SAVINGS_WITH_TRANCHES() {
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
final Integer loanProductID = createLoanProduct(true);
|
||||
|
||||
final Integer savingsId = createSavings(clientID);
|
||||
|
||||
List<HashMap> tranches = new ArrayList<HashMap>();
|
||||
tranches.add(createTrancheDetail("1 March 2014", "25000"));
|
||||
tranches.add(createTrancheDetail("23 April 2014", "20000"));
|
||||
|
||||
final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, null, savingsId.toString(), "45,000.00",
|
||||
tranches);
|
||||
Assert.assertNotNull(loanID);
|
||||
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("1 March 2014", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
HashMap summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
Float balance = new Float(MINIMUM_OPENING_BALANCE);
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
// DISBURSE first Tranche
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings("1 March 2014", loanID);
|
||||
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("25000");
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
// DISBURSE Second Tranche
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanToSavings("23 April 2014", loanID);
|
||||
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
balance = new Float(MINIMUM_OPENING_BALANCE) + new Float("25000") + new Float("20000");
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.undoDisbursal(loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
summary = savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
balance = new Float(MINIMUM_OPENING_BALANCE);
|
||||
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
|
||||
|
||||
}
|
||||
|
||||
private void validateCharge(Integer amountPercentage, final List<HashMap> loanCharges, final String amount, final String outstanding,
|
||||
String amountPaid, String amountWaived) {
|
||||
HashMap chargeDetail = getloanCharge(amountPercentage, loanCharges);
|
||||
@@ -626,7 +768,7 @@ public class ClientLoanIntegrationTest {
|
||||
Assert.assertTrue(new Float(twoDForm.format(new Float(val))).compareTo(new Float(twoDForm.format(new Float(val2)))) == 0);
|
||||
}
|
||||
|
||||
private Integer createLoanProduct() {
|
||||
private Integer createLoanProduct(boolean multiDisburseLoan) {
|
||||
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||
final String loanProductJSON = new LoanProductTestBuilder() //
|
||||
.withPrincipal("12,000.00") //
|
||||
@@ -637,14 +779,16 @@ public class ClientLoanIntegrationTest {
|
||||
.withInterestRateFrequencyTypeAsMonths() //
|
||||
.withAmortizationTypeAsEqualInstallments() //
|
||||
.withInterestTypeAsDecliningBalance() //
|
||||
.withTranches(multiDisburseLoan) //
|
||||
.build(null);
|
||||
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges) {
|
||||
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
|
||||
final String savingsId, String principal) {
|
||||
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||
.withPrincipal("12,000.00") //
|
||||
.withPrincipal(principal) //
|
||||
.withLoanTermFrequency("4") //
|
||||
.withLoanTermFrequencyAsMonths() //
|
||||
.withNumberOfRepayments("4") //
|
||||
@@ -656,7 +800,29 @@ public class ClientLoanIntegrationTest {
|
||||
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
|
||||
.withExpectedDisbursementDate("20 September 2011") //
|
||||
.withSubmittedOnDate("20 September 2011") //
|
||||
.withCharges(charges).build(clientID.toString(), loanProductID.toString(), null);
|
||||
.withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
|
||||
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
|
||||
final String savingsId, String principal, List<HashMap> tranches) {
|
||||
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||
.withPrincipal(principal) //
|
||||
.withLoanTermFrequency("4") //
|
||||
.withLoanTermFrequencyAsMonths() //
|
||||
.withNumberOfRepayments("4") //
|
||||
.withRepaymentEveryAfter("1") //
|
||||
.withRepaymentFrequencyTypeAsMonths() //
|
||||
.withInterestRatePerPeriod("2") //
|
||||
.withAmortizationTypeAsEqualInstallments() //
|
||||
.withInterestTypeAsDecliningBalance() //
|
||||
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
|
||||
.withExpectedDisbursementDate("1 March 2014") //
|
||||
.withTranches(tranches) //
|
||||
.withSubmittedOnDate("1 March 2014") //
|
||||
|
||||
.withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId);
|
||||
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
}
|
||||
|
||||
@@ -784,4 +950,12 @@ public class ClientLoanIntegrationTest {
|
||||
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||
}
|
||||
|
||||
private HashMap createTrancheDetail(final String date, final String amount) {
|
||||
HashMap detail = new HashMap();
|
||||
detail.put("expectedDisbursementDate", date);
|
||||
detail.put("principal", amount);
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-1
@@ -119,9 +119,17 @@ public class ChargesHelper {
|
||||
}
|
||||
|
||||
public static String getLoanDisbursementJSON(final Integer chargeCalculationType, final String amount) {
|
||||
return getLoanDisbursementJSON(chargeCalculationType, amount, ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
|
||||
}
|
||||
|
||||
public static String getLoanDisbursementAccountTransferJSON(final Integer chargeCalculationType, final String amount) {
|
||||
return getLoanDisbursementJSON(chargeCalculationType, amount, ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
|
||||
}
|
||||
|
||||
public static String getLoanDisbursementJSON(final Integer chargeCalculationType, final String amount, final Integer paymentmode) {
|
||||
final HashMap<String, Object> map = populateDefaultsForLoan();
|
||||
map.put("chargeTimeType", CHARGE_DISBURSEMENT_FEE);
|
||||
map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
|
||||
map.put("chargePaymentMode", paymentmode);
|
||||
map.put("amount", amount);
|
||||
map.put("chargeCalculationType", chargeCalculationType);
|
||||
String chargesCreateJson = new Gson().toJson(map);
|
||||
|
||||
+15
-1
@@ -3,7 +3,6 @@ package org.mifosplatform.integrationtests.common.loans;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
@@ -35,6 +34,9 @@ public class LoanApplicationTestBuilder {
|
||||
private String expectedDisbursmentDate = "";
|
||||
private String submittedOnDate = "";
|
||||
private String loanType = "individual";
|
||||
private String fixedEmiAmount = "10000";
|
||||
private String maxOutstandingLoanBalance = "36000";
|
||||
private List<HashMap> disbursementData = null;
|
||||
private List<HashMap> charges = new ArrayList<HashMap>();
|
||||
|
||||
public String build(final String ID, final String loanProductId, final String savingsID) {
|
||||
@@ -68,6 +70,14 @@ public class LoanApplicationTestBuilder {
|
||||
if (savingsID != null) {
|
||||
map.put("linkAccountId", savingsID);
|
||||
}
|
||||
|
||||
if (disbursementData != null) {
|
||||
map.put("disbursementData", disbursementData);
|
||||
map.put("fixedEmiAmount", fixedEmiAmount);
|
||||
map.put("maxOutstandingLoanBalance", maxOutstandingLoanBalance);
|
||||
|
||||
}
|
||||
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
@@ -181,4 +191,8 @@ public class LoanApplicationTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanApplicationTestBuilder withTranches(final List<HashMap> disbursementData) {
|
||||
this.disbursementData = disbursementData;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -53,6 +53,10 @@ public class LoanProductTestBuilder {
|
||||
private String maxPrincipal = "100000.00";
|
||||
private Account[] accountList = null;
|
||||
|
||||
private Boolean multiDisburseLoan = false;
|
||||
private String outstandingLoanBalance = "35000";
|
||||
private String maxTrancheCount = "35000";
|
||||
|
||||
public String build(final String chargeId) {
|
||||
final HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
@@ -83,6 +87,11 @@ public class LoanProductTestBuilder {
|
||||
map.put("minPrincipal", this.minPrincipal);
|
||||
map.put("maxPrincipal", this.maxPrincipal);
|
||||
map.put("overdueDaysForNPA", this.overdueDaysForNPA);
|
||||
if (multiDisburseLoan) {
|
||||
map.put("multiDisburseLoan", this.multiDisburseLoan);
|
||||
map.put("maxTrancheCount", this.maxTrancheCount);
|
||||
map.put("outstandingLoanBalance", this.outstandingLoanBalance);
|
||||
}
|
||||
|
||||
if (this.accountingRule.equals(ACCRUAL_UPFRONT) || this.accountingRule.equals(ACCRUAL_PERIODIC)) {
|
||||
map.putAll(getAccountMappingForAccrualBased());
|
||||
@@ -215,6 +224,11 @@ public class LoanProductTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withTranches(boolean multiDisburseLoan) {
|
||||
this.multiDisburseLoan = multiDisburseLoan;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Map<String, String> getAccountMappingForCashBased() {
|
||||
final Map<String, String> map = new HashMap<String, String>();
|
||||
for (int i = 0; i < this.accountList.length; i++) {
|
||||
|
||||
+5
@@ -22,6 +22,7 @@ public class LoanTransactionHelper {
|
||||
private static final String APPROVE_LOAN_COMMAND = "approve";
|
||||
private static final String UNDO_APPROVAL_LOAN_COMMAND = "undoApproval";
|
||||
private static final String DISBURSE_LOAN_COMMAND = "disburse";
|
||||
private static final String DISBURSE_LOAN_TO_SAVINGS_COMMAND = "disburseToSavings";
|
||||
private static final String UNDO_DISBURSE_LOAN_COMMAND = "undoDisbursal";
|
||||
private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
|
||||
private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
|
||||
@@ -82,6 +83,10 @@ public class LoanTransactionHelper {
|
||||
public HashMap disburseLoan(final String date, final Integer loanID) {
|
||||
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date));
|
||||
}
|
||||
|
||||
public HashMap disburseLoanToSavings(final String date, final Integer loanID) {
|
||||
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_TO_SAVINGS_COMMAND, loanID), getDisburseLoanAsJSON(date));
|
||||
}
|
||||
|
||||
public HashMap undoDisbursal(final Integer loanID) {
|
||||
final String undoDisburseJson = "{'note' : 'UNDO DISBURSAL'}";
|
||||
|
||||
@@ -373,6 +373,10 @@ public class CommandWrapper {
|
||||
public boolean isDisbursementOfLoan() {
|
||||
return this.actionName.equalsIgnoreCase("DISBURSE") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
|
||||
public boolean isDisbursementOfLoanToSavings() {
|
||||
return this.actionName.equalsIgnoreCase("DISBURSETOSAVINGS") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
}
|
||||
|
||||
public boolean isUndoDisbursementOfLoan() {
|
||||
return this.actionName.equalsIgnoreCase("DISBURSALUNDO") && this.entityName.equalsIgnoreCase("LOAN");
|
||||
|
||||
+9
@@ -701,6 +701,15 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/loans/" + loanId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder disburseLoanToSavingsApplication(final Long loanId) {
|
||||
this.actionName = "DISBURSETOSAVINGS";
|
||||
this.entityName = "LOAN";
|
||||
this.entityId = loanId;
|
||||
this.loanId = loanId;
|
||||
this.href = "/loans/" + loanId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder undoLoanApplicationApproval(final Long loanId) {
|
||||
this.actionName = "APPROVALUNDO";
|
||||
|
||||
+2
@@ -335,6 +335,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("loanApplicationRejectedCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isDisbursementOfLoan()) {
|
||||
handler = this.applicationContext.getBean("disburseLoanCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isDisbursementOfLoanToSavings()) {
|
||||
handler = this.applicationContext.getBean("disburseLoanToSavingsCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isUndoDisbursementOfLoan()) {
|
||||
handler = this.applicationContext.getBean("undoDisbursalLoanCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isLoanRepayment()) {
|
||||
|
||||
+22
-3
@@ -7,6 +7,7 @@ import org.joda.time.LocalDate;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.mifosplatform.portfolio.account.PortfolioAccountType;
|
||||
import org.mifosplatform.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
|
||||
public class AccountTransferDTO {
|
||||
@@ -27,12 +28,16 @@ public class AccountTransferDTO {
|
||||
private final Integer loanInstallmentNumber;
|
||||
private final Integer transferType;
|
||||
private final AccountTransferDetails accountTransferDetails;
|
||||
private final String noteText;
|
||||
private final String txnExternalId;
|
||||
private final Loan loan;
|
||||
|
||||
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
|
||||
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
|
||||
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
|
||||
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId,
|
||||
Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails) {
|
||||
Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails,final String noteText,
|
||||
final String txnExternalId,final Loan loan) {
|
||||
this.transactionDate = transactionDate;
|
||||
this.transactionAmount = transactionAmount;
|
||||
this.fromAccountType = fromAccountType;
|
||||
@@ -49,6 +54,9 @@ public class AccountTransferDTO {
|
||||
this.loanInstallmentNumber = loanInstallmentNumber;
|
||||
this.transferType = transferType;
|
||||
this.accountTransferDetails = accountTransferDetails;
|
||||
this.noteText = noteText;
|
||||
this.txnExternalId = txnExternalId;
|
||||
this.loan = loan;
|
||||
}
|
||||
|
||||
public LocalDate getTransactionDate() {
|
||||
@@ -107,14 +115,25 @@ public class AccountTransferDTO {
|
||||
return this.loanInstallmentNumber;
|
||||
}
|
||||
|
||||
|
||||
public Integer getTransferType() {
|
||||
return this.transferType;
|
||||
}
|
||||
|
||||
|
||||
public AccountTransferDetails getAccountTransferDetails() {
|
||||
return this.accountTransferDetails;
|
||||
}
|
||||
|
||||
public String getNoteText() {
|
||||
return this.noteText;
|
||||
}
|
||||
|
||||
public String getTxnExternalId() {
|
||||
return this.txnExternalId;
|
||||
}
|
||||
|
||||
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-5
@@ -34,6 +34,7 @@ import org.mifosplatform.portfolio.account.domain.AccountTransferType;
|
||||
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.service.LoanAssembler;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
|
||||
@@ -292,7 +293,11 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
Loan fromLoanAccount = null;
|
||||
SavingsAccount toSavingsAccount = null;
|
||||
if (accountTransferDetails == null) {
|
||||
fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
|
||||
if (accountTransferDTO.getLoan() == null) {
|
||||
fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
|
||||
} else {
|
||||
fromLoanAccount = accountTransferDTO.getLoan();
|
||||
}
|
||||
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
|
||||
} else {
|
||||
fromLoanAccount = accountTransferDetails.fromLoanAccount();
|
||||
@@ -300,15 +305,23 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
||||
toSavingsAccount = accountTransferDetails.toSavingsAccount();
|
||||
this.savingsAccountAssembler.setHelpers(toSavingsAccount);
|
||||
}
|
||||
final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(),
|
||||
new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(),
|
||||
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), null, null);
|
||||
LoanTransaction loanTransaction = null;
|
||||
if (LoanTransactionType.DISBURSEMENT.getValue().equals(accountTransferDTO.getFromTransferType())) {
|
||||
loanTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(),
|
||||
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
||||
accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId());
|
||||
} else {
|
||||
loanTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(),
|
||||
new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(),
|
||||
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(),
|
||||
accountTransferDTO.getTxnExternalId());
|
||||
}
|
||||
|
||||
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
|
||||
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
||||
accountTransferDTO.getPaymentDetail());
|
||||
accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(accountTransferDTO, fromLoanAccount,
|
||||
toSavingsAccount, deposit, loanRefundTransaction);
|
||||
toSavingsAccount, deposit, loanTransaction);
|
||||
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||
transferTransactionId = accountTransferDetails.getId();
|
||||
}
|
||||
|
||||
+1
-1
@@ -217,7 +217,7 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
|
||||
data.toAccountType(), data.fromAccount().accountId(), data.toAccount().accountId(), data.name()
|
||||
+ " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data
|
||||
.transferType().getValue(), accountTransferDetails);
|
||||
.transferType().getValue(), accountTransferDetails, null, null, null);
|
||||
transferAmount(sb, accountTransferDTO, data.getId());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements Col
|
||||
final Map<String, Object> changes = new HashMap<String, Object>();
|
||||
final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand = this.bulkDisbursalCommandFromApiJsonDeserializer
|
||||
.commandFromApiJson(command.json());
|
||||
changes.putAll(this.loanWritePlatformService.bulkLoanDisbursal(command, bulkDisbursalCommand));
|
||||
changes.putAll(this.loanWritePlatformService.bulkLoanDisbursal(command, bulkDisbursalCommand, false));
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -96,8 +96,11 @@ public class LoanTransactionsApiResource {
|
||||
} else if (is(commandParam, "close")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
||||
} else if (is(commandParam, "disburse")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId);
|
||||
} else {
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
|
||||
} else if(is(commandParam, "disburseToSavings")){
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
|
||||
}
|
||||
else {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -551,6 +551,9 @@ public class LoansApiResource {
|
||||
} else if (is(commandParam, "disburse")) {
|
||||
final CommandWrapper commandRequest = builder.disburseLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "disburseToSavings")) {
|
||||
final CommandWrapper commandRequest = builder.disburseLoanToSavingsApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
if (is(commandParam, "undoapproval")) {
|
||||
|
||||
+103
-105
@@ -94,7 +94,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
|
||||
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@@ -1687,17 +1686,74 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public void disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, final JsonCommand command,
|
||||
final ApplicationCurrency currency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
final Map<String, Object> actualChanges, final PaymentDetail paymentDetail,
|
||||
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) {
|
||||
|
||||
validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
Date lastDusburseDate = this.actualDisbursementDate;
|
||||
final Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday,
|
||||
final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule) {
|
||||
|
||||
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
boolean isMultiTrancheDisburse = false;
|
||||
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
this.loanStatus = statusEnum.getValue();
|
||||
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
|
||||
|
||||
this.disbursedBy = currentUser;
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
actualChanges.put("locale", command.locale());
|
||||
actualChanges.put("dateFormat", command.dateFormat());
|
||||
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
|
||||
|
||||
// validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||
|
||||
handleDisbursementTransaction(actualDisbursementDate);
|
||||
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
||||
boolean isEmiAmountChanged = false;
|
||||
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
actualDisbursementDate.toDate(), emiAmount, this);
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
isEmiAmountChanged = true;
|
||||
}
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
|
||||
workingDays);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
|
||||
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
|
||||
|
||||
/**
|
||||
* Add an interest applied transaction of the interest is accrued
|
||||
* upfront (Up front accrual)
|
||||
**/
|
||||
|
||||
if (isUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
|
||||
actualDisbursementDate);
|
||||
this.loanTransactions.add(interestAppliedTransaction);
|
||||
}
|
||||
|
||||
// changedTransactionDetail =
|
||||
// reprocessTransactionForDisbursement(changedTransactionDetail);
|
||||
|
||||
}
|
||||
|
||||
public boolean canDisburse(final LocalDate actualDisbursementDate) {
|
||||
Date lastDusburseDate = this.actualDisbursementDate;
|
||||
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
|
||||
boolean isMultiTrancheDisburse = false;
|
||||
if (LoanStatus.fromInt(this.loanStatus).isActive() && isAllTranchesNotDisbursed()) {
|
||||
LoanDisbursementDetails details = fetchLastDisburseDetail();
|
||||
|
||||
@@ -1711,97 +1767,46 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
isMultiTrancheDisburse = true;
|
||||
}
|
||||
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus)) || isMultiTrancheDisburse) {
|
||||
return (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus)) || isMultiTrancheDisburse);
|
||||
}
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
this.loanStatus = statusEnum.getValue();
|
||||
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
|
||||
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
Money disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal().zero();
|
||||
BigDecimal principalDisbursed = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalDisbursedParameterName);
|
||||
if (this.actualDisbursementDate == null) {
|
||||
this.actualDisbursementDate = actualDisbursementDate.toDate();
|
||||
}
|
||||
BigDecimal diff = BigDecimal.ZERO;
|
||||
Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
|
||||
if (principalDisbursed == null) {
|
||||
disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
if (!details.isEmpty()) {
|
||||
disburseAmount = disburseAmount.zero();
|
||||
for (LoanDisbursementDetails disbursementDetails : details) {
|
||||
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
|
||||
disburseAmount = disburseAmount.plus(disbursementDetails.principal());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
disburseAmount = disburseAmount.plus(principalDisbursed);
|
||||
|
||||
if (details.isEmpty()) {
|
||||
diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount();
|
||||
} else {
|
||||
for (LoanDisbursementDetails disbursementDetails : details) {
|
||||
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
|
||||
diff = diff.add(disbursementDetails.principal().subtract(principalDisbursed));
|
||||
disbursementDetails.updatePrincipal(principalDisbursed);
|
||||
}
|
||||
}
|
||||
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
||||
if (diff.compareTo(BigDecimal.ZERO) == -1) {
|
||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
|
||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
|
||||
this.loanRepaymentScheduleDetail.getPrincipal().getAmount());
|
||||
}
|
||||
}
|
||||
this.disbursedBy = currentUser;
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
actualChanges.put("locale", command.locale());
|
||||
actualChanges.put("dateFormat", command.dateFormat());
|
||||
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
|
||||
|
||||
// validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId, disburseAmount);
|
||||
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
||||
boolean isEmiAmountChanged = false;
|
||||
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
actualDisbursementDate.toDate(), emiAmount, this);
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
isEmiAmountChanged = true;
|
||||
}
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || diff.compareTo(BigDecimal.ZERO) != 0
|
||||
|| isEmiAmountChanged) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||
holidays, workingDays);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
|
||||
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
|
||||
|
||||
/**
|
||||
* Add an interest applied transaction of the interest is accrued
|
||||
* upfront (Up front accrual)
|
||||
**/
|
||||
|
||||
if (isUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
|
||||
actualDisbursementDate);
|
||||
this.loanTransactions.add(interestAppliedTransaction);
|
||||
}
|
||||
|
||||
// changedTransactionDetail =
|
||||
// reprocessTransactionForDisbursement(changedTransactionDetail);
|
||||
public Money adjustDisburseAmount(final JsonCommand command, final LocalDate actualDisbursementDate) {
|
||||
Money disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal().zero();
|
||||
BigDecimal principalDisbursed = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalDisbursedParameterName);
|
||||
if (this.actualDisbursementDate == null) {
|
||||
this.actualDisbursementDate = actualDisbursementDate.toDate();
|
||||
}
|
||||
BigDecimal diff = BigDecimal.ZERO;
|
||||
Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
|
||||
if (principalDisbursed == null) {
|
||||
disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
if (!details.isEmpty()) {
|
||||
disburseAmount = disburseAmount.zero();
|
||||
for (LoanDisbursementDetails disbursementDetails : details) {
|
||||
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
|
||||
disburseAmount = disburseAmount.plus(disbursementDetails.principal());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
disburseAmount = disburseAmount.plus(principalDisbursed);
|
||||
|
||||
if (details.isEmpty()) {
|
||||
diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount();
|
||||
} else {
|
||||
for (LoanDisbursementDetails disbursementDetails : details) {
|
||||
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
|
||||
diff = diff.add(disbursementDetails.principal().subtract(principalDisbursed));
|
||||
disbursementDetails.updatePrincipal(principalDisbursed);
|
||||
}
|
||||
}
|
||||
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
||||
if (diff.compareTo(BigDecimal.ZERO) == -1) {
|
||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
|
||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
|
||||
this.loanRepaymentScheduleDetail.getPrincipal().getAmount());
|
||||
}
|
||||
}
|
||||
return disburseAmount;
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail reprocessTransactionForDisbursement() {
|
||||
@@ -1961,13 +1966,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
updateLoanSchedule(loanSchedule);
|
||||
}
|
||||
|
||||
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn,
|
||||
final String txnExternalId, Money disburseAmt) {
|
||||
// track disbursement transaction
|
||||
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(getOffice(), disburseAmt, paymentDetail, disbursedOn,
|
||||
txnExternalId);
|
||||
disbursementTransaction.updateLoan(this);
|
||||
this.loanTransactions.add(disbursementTransaction);
|
||||
private void handleDisbursementTransaction(final LocalDate disbursedOn) {
|
||||
|
||||
// add repayment transaction to track incoming money from client to mfi
|
||||
// for (charges due at time of disbursement)
|
||||
@@ -2036,7 +2035,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.a.future.date", errorMessage, disbursedOn);
|
||||
}
|
||||
|
||||
return disbursementTransaction;
|
||||
}
|
||||
|
||||
public LoanTransaction handlePayDisbursementTransaction(final Long chargeId, final LoanTransaction chargesPayment,
|
||||
@@ -3574,7 +3572,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return loanChargePerInstallments;
|
||||
}
|
||||
|
||||
private void validateAccountStatus(final LoanEvent event) {
|
||||
public void validateAccountStatus(final LoanEvent event) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
|
||||
+3
@@ -23,4 +23,7 @@ public interface LoanAccountDomainService {
|
||||
|
||||
LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber);
|
||||
|
||||
LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
}
|
||||
|
||||
+39
@@ -270,6 +270,45 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
return newRefundTransaction;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount,
|
||||
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId) {
|
||||
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
final Money amount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate,
|
||||
txnExternalId);
|
||||
disbursementTransaction.updateLoan(loan);
|
||||
loan.getLoanTransactions().add(disbursementTransaction);
|
||||
try {
|
||||
this.loanTransactionRepository.save(disbursementTransaction);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
final Throwable realCause = e.getCause();
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
|
||||
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
|
||||
baseDataValidator.reset().parameter("externalId").value(disbursementTransaction.getExternalId())
|
||||
.failWithCode("value.must.be.unique");
|
||||
}
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
return disbursementTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reverseTransfer(final LoanTransaction loanTransaction) {
|
||||
loanTransaction.reverse();
|
||||
|
||||
+1
-1
@@ -27,6 +27,6 @@ public class DisburseLoanCommandHandler implements NewCommandSourceHandler {
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.disburseLoan(command.entityId(), command);
|
||||
return this.writePlatformService.disburseLoan(command.entityId(), command, false);
|
||||
}
|
||||
}
|
||||
+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 DisburseLoanToSavingsCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DisburseLoanToSavingsCommandHandler(final LoanWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.disburseLoan(command.entityId(), command, true);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -205,7 +205,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
|
||||
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
|
||||
LoanProductConstants.graceOnArrearsAgeingParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing).zeroOrPositiveAmount();
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.graceOnArrearsAgeingParameterName).value(graceOnArrearsAgeing)
|
||||
.zeroOrPositiveAmount();
|
||||
|
||||
final String interestChargedFromDateParameterName = "interestChargedFromDate";
|
||||
if (this.fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) {
|
||||
@@ -814,7 +815,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
|
||||
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR);
|
||||
}
|
||||
if (!tatalDisbursement.equals(totalPrincipal)) {
|
||||
if (tatalDisbursement.compareTo(totalPrincipal) != 0) {
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName)
|
||||
.failWithCode(LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME);
|
||||
}
|
||||
|
||||
+11
-4
@@ -48,13 +48,20 @@ public final class LoanEventApiJsonValidator {
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
public void validateDisbursement(final String json) {
|
||||
public void validateDisbursement(final String json, boolean isAccountTransfer) {
|
||||
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note",
|
||||
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber",
|
||||
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
|
||||
Set<String> disbursementParameters = null;
|
||||
|
||||
if (isAccountTransfer) {
|
||||
disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale",
|
||||
"dateFormat", LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
|
||||
} else {
|
||||
disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale",
|
||||
"dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber",
|
||||
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName));
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ public interface LoanReadPlatformService {
|
||||
|
||||
LoanTransactionData retrieveNewClosureDetails();
|
||||
|
||||
LoanTransactionData retrieveDisbursalTemplate(Long loanId);
|
||||
LoanTransactionData retrieveDisbursalTemplate(Long loanId, boolean paymentDetailsRequired);
|
||||
|
||||
LoanAccountData retrieveTemplateWithCompleteGroupAndProductDetails(Long groupId, Long productId);
|
||||
|
||||
|
||||
+5
-3
@@ -388,12 +388,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanTransactionData retrieveDisbursalTemplate(final Long loanId) {
|
||||
public LoanTransactionData retrieveDisbursalTemplate(final Long loanId, boolean paymentDetailsRequired) {
|
||||
final Loan loan = this.loanRepository.findOne(loanId);
|
||||
if (loan == null) { throw new LoanNotFoundException(loanId); }
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.DISBURSEMENT);
|
||||
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
|
||||
Collection<CodeValueData> paymentOptions = null;
|
||||
if (paymentDetailsRequired) {
|
||||
paymentOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
|
||||
}
|
||||
return new LoanTransactionData(null, null, null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDateForTemplate(),
|
||||
loan.getDisburseAmountForTemplate(), null, null, null, null, null, paymentOptions, null, null, loan.retriveLastEmiAmount());
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,9 +22,9 @@ import java.util.Map;
|
||||
|
||||
public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult disburseLoan(Long loanId, JsonCommand command);
|
||||
CommandProcessingResult disburseLoan(Long loanId, JsonCommand command, Boolean isAccountTransfer);
|
||||
|
||||
Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand);
|
||||
Map<String, Object> bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand, Boolean isAccountTransfer);
|
||||
|
||||
CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command);
|
||||
|
||||
|
||||
+137
-17
@@ -92,6 +92,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanEvent;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
|
||||
@@ -217,11 +218,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command) {
|
||||
public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command, Boolean isAccountTransfer) {
|
||||
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
this.loanEventApiJsonValidator.validateDisbursement(command.json());
|
||||
this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer);
|
||||
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
@@ -259,11 +260,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
|
||||
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
Money amountBeforeAdjust = loan.getPrincpal();
|
||||
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
|
||||
if (canDisburse) {
|
||||
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
|
||||
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
if (isAccountTransfer) {
|
||||
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
|
||||
} else {
|
||||
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
|
||||
actualDisbursementDate, txnExternalId);
|
||||
disbursementTransaction.updateLoan(loan);
|
||||
loan.getLoanTransactions().add(disbursementTransaction);
|
||||
}
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
|
||||
workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule);
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
@@ -306,7 +322,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
|
||||
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loanId, "Loan Charge Payment",
|
||||
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null,
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null);
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
|
||||
@@ -371,7 +387,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
*****/
|
||||
@Transactional
|
||||
@Override
|
||||
public Map<String, Object> bulkLoanDisbursal(final JsonCommand command, final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand) {
|
||||
public Map<String, Object> bulkLoanDisbursal(final JsonCommand command, final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand,
|
||||
Boolean isAccountTransfer) {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
final SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions();
|
||||
@@ -405,12 +422,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
|
||||
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
|
||||
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays,
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
|
||||
if (canDisburse) {
|
||||
Money amountBeforeAdjust = loan.getPrincpal();
|
||||
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
|
||||
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
if (isAccountTransfer) {
|
||||
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
|
||||
} else {
|
||||
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
|
||||
actualDisbursementDate, txnExternalId);
|
||||
disbursementTransaction.updateLoan(loan);
|
||||
loan.getLoanTransactions().add(disbursementTransaction);
|
||||
}
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays,
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule);
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
try {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -463,7 +494,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
|
||||
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loan.getId(),
|
||||
"Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(),
|
||||
entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null);
|
||||
entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
}
|
||||
@@ -1222,7 +1253,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
|
||||
PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
|
||||
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null);
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
@@ -1235,6 +1266,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.withSavingsId(portfolioAccountData.accountId()).build();
|
||||
}
|
||||
|
||||
public void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
|
||||
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan.getId());
|
||||
if (portfolioAccountData == null) {
|
||||
final String errorMessage = "Disburse Loan with id:" + loan.getId() + " requires linked savings account for payment";
|
||||
throw new LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, loan.getId());
|
||||
}
|
||||
|
||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount.getAmount(),
|
||||
PortfolioAccountType.LOAN, PortfolioAccountType.SAVINGS, loan.getId(), portfolioAccountData.accountId(),
|
||||
"Loan Disbursement", locale, fmt, paymentDetail, LoanTransactionType.DISBURSEMENT.getValue(), null, null, null,
|
||||
AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, txnExternalId, loan);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.TRANSFER_FEE_CHARGE_FOR_LOANS)
|
||||
public void transferFeeCharges() throws JobExecutionException {
|
||||
@@ -1257,7 +1309,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
||||
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null,
|
||||
null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
|
||||
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null);
|
||||
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null,
|
||||
null, null, null);
|
||||
transferFeeCharge(sb, accountTransferDTO);
|
||||
}
|
||||
}
|
||||
@@ -1268,7 +1321,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
||||
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
|
||||
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null);
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null);
|
||||
transferFeeCharge(sb, accountTransferDTO);
|
||||
}
|
||||
}
|
||||
@@ -1919,6 +1972,73 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
}
|
||||
|
||||
public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command,
|
||||
final boolean isChargeIdIncludedInJson) {
|
||||
|
||||
LoanTransaction transaction = null;
|
||||
|
||||
this.loanEventApiJsonValidator.validateChargePaymentTransaction(command.json(), isChargeIdIncludedInJson);
|
||||
if (isChargeIdIncludedInJson) {
|
||||
loanChargeId = command.longValueOfParameterNamed("chargeId");
|
||||
}
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
checkClientOrGroupActive(loan);
|
||||
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
|
||||
|
||||
// Charges may be waived only when the loan associated with them are
|
||||
// active
|
||||
if (!loan.status().isActive()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.LOAN_INACTIVE,
|
||||
loanCharge.getId()); }
|
||||
|
||||
// validate loan charge is not already paid or waived
|
||||
if (loanCharge.isWaived()) {
|
||||
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
|
||||
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
|
||||
loanCharge.getId()); }
|
||||
|
||||
if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
|
||||
LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); }
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
|
||||
Integer loanInstallmentNumber = null;
|
||||
BigDecimal amount = loanCharge.amountOutstanding();
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanInstallmentCharge chargePerInstallment = null;
|
||||
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
|
||||
final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
|
||||
if (dueDate != null) {
|
||||
chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
|
||||
} else if (installmentNumber != null) {
|
||||
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
|
||||
}
|
||||
if (chargePerInstallment == null) {
|
||||
chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
|
||||
}
|
||||
if (chargePerInstallment.isWaived()) {
|
||||
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
|
||||
} else if (chargePerInstallment.isPaid()) { throw new LoanChargeCannotBePayedException(
|
||||
LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, loanCharge.getId()); }
|
||||
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
|
||||
amount = chargePerInstallment.getAmountOutstanding();
|
||||
}
|
||||
|
||||
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
|
||||
if (portfolioAccountData == null) {
|
||||
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
|
||||
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
|
||||
}
|
||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
|
||||
PortfolioAccountType.LOAN, portfolioAccountData.accountId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
|
||||
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
|
||||
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
|
||||
if (transferTransaction != null) {
|
||||
|
||||
Reference in New Issue
Block a user