MIFOSX-172: WIP - update api docs on loan application workflow api.

This commit is contained in:
Keith Woodlock
2013-02-22 14:30:47 +00:00
parent 29360a8fd6
commit 634a66cbe9
16 changed files with 837 additions and 379 deletions
+630 -131
View File
@@ -349,23 +349,76 @@
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td><a href="#loans">LOAN</a></td> <td><a href="#loans">LOAN</a></td>
<td>loans</td> <td>loans?calculateLoanSchedule</td>
<td><a href="#loans_create">Create a New Loan / Submit <td><a href="#loans_calculate">Calculate Loan Repayment Schedule</a></td>
a Loan Application</a></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td></td>
<td>loans</td>
<td><a href="#loans_create">Submit a new Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td> <td></td>
<td>loans/{loanId}</td> <td>loans/{loanId}</td>
<td><a href="#loans_approve_etc">Approve, Undo <td></td>
Approval, Disburse, Undo Disbursal, Reject a Loan or Withdraw
a Loan Application</a></td>
<td><a href="#loans_retrieve">Retrieve a Loan</a></td> <td><a href="#loans_retrieve">Retrieve a Loan</a></td>
<td></td> <td></td>
<td><a href="#loans_delete">Delete a Loan</a></td> <td><a href="#loans_delete">Delete a Loan</a></td>
</tr> </tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=approve</td>
<td><a href="#loans_approve">Approve Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=undoApproval</td>
<td><a href="#loans_approve_undo">Undo Loan Application Approval</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=reject</td>
<td><a href="#loans_reject">Reject Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=withdraw</td>
<td><a href="#loans_withdraw">Withdraw Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=disburse</td>
<td><a href="#loans_disburse">Disburse Loan</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=undoDisbursal</td>
<td><a href="#loans_disburse_undo">Undo Loan Application Disbursal</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>loans/{loanId}/transactions</td> <td>loans/{loanId}/transactions</td>
@@ -2798,22 +2851,19 @@ GET https://DomainName/api/v1/loans/template?clientId=1
<b>'all':</b> Gets all association data. <b>'all':</b> Gets all association data.
</dd> </dd>
<dd> <dd>
<b>'summary':</b> Loan summary data. <b>'repaymentSchedule':</b> Loan schedule data.
</dd> </dd>
<dd> <dd>
<b>'repaymentSchedule':</b> Loan Schedule data. <b>'transactions':</b> Loan transactions data.
</dd> </dd>
<dd> <dd>
<b>'loanRepayments':</b> Loan Repayment data. <b>'charges':</b> Loan charges data.
</dd> </dd>
<dd> <dd>
<b>'permissions':</b> Data that describes what things can happen <b>'guarantors':</b> Loan guarantors data.
to the loan in its current state. Very specific to the needs of
the current Individual Lending client application.
</dd> </dd>
<dd> <dd>
<b>'convenienceData':</b> Some derived data that is specific to <b>'collateral':</b> Loan collateral data.
the needs of the current Individual Lending client application.
</dd> </dd>
</dl> </dl>
<p>Example Requests:</p> <p>Example Requests:</p>
@@ -2834,66 +2884,234 @@ GET https://DomainName/api/v1/loans/{loanId}
</code> </code>
<code class="method-response"> <code class="method-response">
{ {
"id": 1,
"accountNo": "000000001",
"status": {
"id": 100,
"code": "loanStatusType.submitted.and.pending.approval",
"value": "Submitted and pending approval",
"pendingApproval": true,
"waitingForDisbursal": false,
"active": false,
"closedObligationsMet": false,
"closedWrittenOff": false,
"closedRescheduled": false,
"closed": false,
"overpaid": false
},
"clientId": 1,
"clientName": "Petra Yton",
"clientOfficeId": 1,
"loanProductId": 1,
"loanProductName": "A new loan product for me",
"currency": {
"code": "USD",
"name": "US Dollar",
"decimalPlaces": 2,
"displaySymbol": "$",
"nameCode": "currency.USD",
"displayLabel": "US Dollar ($)"
},
"principal": 100000,
"termFrequency": 12,
"termPeriodFrequencyType": {
"id": 2,
"code": "termFrequency.periodFrequencyType.months",
"value": "Months"
},
"numberOfRepayments": 12,
"repaymentEvery": 1,
"repaymentFrequencyType": {
"id": 2,
"code": "repaymentFrequency.periodFrequencyType.months",
"value": "Months"
},
"transactionProcessingStrategyId": 1,
"amortizationType": {
"id": 1, "id": 1,
"accountNo": "000000001", "code": "amortizationType.equal.installments",
"clientId": 1, "value": "Equal installments"
"clientName": "Petra Yton", },
"loanProductId": 1, "interestRatePerPeriod": 2,
"loanProductName": "A new loan product for me", "interestRateFrequencyType": {
"status": { "id": 2,
"id": 100, "code": "interestRateFrequency.periodFrequencyType.months",
"code": "loanStatusType.submitted.and.pending.approval", "value": "Per month"
"value": "Submitted and pending approval" },
}, "annualInterestRate": 24,
"currency": { "interestType": {
"code": "USD", "id": 0,
"name": "US Dollar", "code": "interestType.declining.balance",
"decimalPlaces": 2, "value": "Declining Balance"
"displaySymbol": "$", },
"nameCode": "currency.USD", "interestCalculationPeriodType": {
"displayLabel": "US Dollar ($)" "id": 1,
}, "code": "interestCalculationPeriodType.same.as.repayment.period",
"principal": 100000, "value": "Same as repayment period"
"termFrequency": 12, },
"termPeriodFrequencyType": { "timeline": {
"id": 2, "submittedOnDate": [
"code": "termFrequency.periodFrequencyType.months", 2011,
"value": "Months" 9,
}, 20
],
"submittedByUsername": "mifos",
"submittedByFirstname": "App",
"submittedByLastname": "Administrator",
"expectedDisbursementDate": [
2011,
9,
20
],
"expectedMaturityDate": [
2012,
9,
20
]
}
}
</code>
</div>
</div>
<a id="loans_calculate" name="loans_calculate" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Calculate loan repayment schedule</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>productId, principal, loanTermFrequency,
loanTermFrequencyType, numberOfRepayments, repaymentEvery,
repaymentFrequencyType, interestRatePerPeriod,
interestRateFrequencyType, amortizationType, interestType,
interestCalculationPeriodType, expectedDisbursementDate,
transactionProcessingStrategyId</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans?command=calculateLoanSchedule
</code>
<code class="method-request">
POST loans?command=calculateLoanSchedule
Content-Type: application/json
Request Body:
{
"dateFormat": "dd MMMM yyyy",
"locale": "en_GB",
"productId": 1,
"principal": "100,000.00",
"loanTermFrequency": 12,
"loanTermFrequencyType": 2,
"numberOfRepayments": 12, "numberOfRepayments": 12,
"repaymentEvery": 1, "repaymentEvery": 1,
"repaymentFrequencyType": { "repaymentFrequencyType": 2,
"id": 2,
"code": "repaymentFrequency.periodFrequencyType.months",
"value": "Months"
},
"transactionProcessingStrategyId": 1,
"amortizationType": {
"id": 1,
"code": "amortizationType.equal.installments",
"value": "Equal installments"
},
"interestRatePerPeriod": 2, "interestRatePerPeriod": 2,
"interestRateFrequencyType": { "interestRateFrequencyType": 2,
"id": 2, "amortizationType": 1,
"code": "interestRateFrequency.periodFrequencyType.months", "interestType": 0,
"value": "Per month" "interestCalculationPeriodType": 1,
"expectedDisbursementDate": "20 September 2011",
"transactionProcessingStrategyId": 2
}
</code>
<code class="method-response">
{
"loanTermInDays": 366,
"currency": {
"code": "UGX",
"name": "Uganda Shilling",
"decimalPlaces": 2,
"displaySymbol": "USh",
"nameCode": "currency.UGX",
"displayLabel": "Uganda Shilling (USh)"
},
"cumulativePrincipalDisbursed": 100000,
"cumulativePrincipalDue": 100000,
"cumulativePrincipalOutstanding": 0,
"cumulativeInterestExpected": 13471.52,
"cumulativeFeeChargesToDate": 0,
"totalCostOfLoan": 13471.52,
"totalExpectedRepayment": 113471.52,
"totalPaidToDate": 0,
"totalWaivedToDate": 0,
"totalWrittenOffToDate": 0,
"totalOutstanding": 113471.52,
"totalInArrears": 0,
"periods": [
{
"period": 0,
"dueDate": [
2011,
9,
20
],
"principalDisbursed": 100000,
"principalLoanBalanceOutstanding": 100000,
"feeChargesDue": 0,
"feeChargesOutstanding": 0,
"totalOriginalDueForPeriod": 0,
"totalDueForPeriod": 0,
"totalOutstandingForPeriod": 0,
"totalOverdue": 0,
"totalActualCostOfLoanForPeriod": 0
}, },
"annualInterestRate": 24, {
"interestType": { "period": 1,
"id": 0, "fromDate": [
"code": "interestType.declining.balance", 2011,
"value": "Declining Balance" 9,
20
],
"dueDate": [
2011,
10,
20
],
"daysInPeriod": 30,
"principalOriginalDue": 7455.96,
"principalDue": 7455.96,
"principalOutstanding": 7455.96,
"principalLoanBalanceOutstanding": 92544.04,
"interestOriginalDue": 2000,
"interestDue": 2000,
"interestOutstanding": 2000,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 9455.96,
"totalDueForPeriod": 9455.96,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 9455.96,
"totalOverdue": 9455.96,
"totalActualCostOfLoanForPeriod": 2000
}, },
"interestCalculationPeriodType": { ...
"id": 1, {
"code": "interestCalculationPeriodType.same.as.repayment.period", "period": 12,
"value": "Same as repayment period" "fromDate": [2012,8,20],
}, "dueDate": [2012,9,20],
"submittedOnDate": [2011,9,20], "daysInPeriod": 31,
"expectedDisbursementDate": [2011,9,20], "principalOriginalDue": 9270.56,
"expectedMaturityDate": [2011,9,20], "principalDue": 9270.56,
"lifeCycleStatusDate": [2011,9,20] "principalOutstanding": 9270.56,
"principalLoanBalanceOutstanding": 0,
"interestOriginalDue": 185.4,
"interestDue": 185.4,
"interestOutstanding": 185.4,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 9455.96,
"totalDueForPeriod": 9455.96,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 9455.96,
"totalOverdue": 9455.96,
"totalActualCostOfLoanForPeriod": 185.4
}
]
} }
</code> </code>
</div> </div>
@@ -2902,24 +3120,7 @@ GET https://DomainName/api/v1/loans/{loanId}
<a id="loans_create" name="loans_create" class="old-syle-anchor">&nbsp;</a> <a id="loans_create" name="loans_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h4>Create a New Loan / Submit a Loan Application</h4> <h4>Submit a new Loan Application</h4>
<h5>Arguments</h5>
<dl class="argument-list">
<dt>command</dt>
<dd>
String<span> Optional, case-insensitive</span>
</dd>
<dd>
The default action is to Submit a New Loan Application
("Submitted and awaiting approval" status).<br>
</dd>
<dd>If "?command=calculateLoanSchedule" is added to the URL
then no update is made but the loan schedule (as would have been
if the loan had been submitted) is returned. This gives the
ability to try out various loan options and 'preview' the
schedule before committing to it.</dd>
</dl>
<br>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td> <td><div class="mifosXHeading2">Mandatory Fields</div></td>
@@ -2974,40 +3175,23 @@ Request Body:
</div> </div>
</div> </div>
<a id="loans_approve_etc" name="loans_approve_etc" <!-- ok -->
class="old-syle-anchor">&nbsp;</a> <a id="loans_approve" name="loans_approve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h4>Approve, Undo Approval, Disburse, Undo Disbursal, Reject <h4>Approve Loan Application</h4>
or Withdraw a Loan Application</h4>
<h5>Arguments</h5>
<dl class="argument-list">
<dt>command</dt>
<dd>
String<span> Mandatory, case-insensitive</span>
</dd>
<dd>'approve' : Approve Loan</dd>
<dd>'disburse' : Disburse Loan</dd>
<dd>'reject' : Reject Loan</dd>
<dd>'withdrewbyclient' : Client Withdrew Loan Application</dd>
<dd>'undoapproval' : Undo Loan Approval</dd>
<dd>'undodisbursal' : Undo Loan Disbursal</dd>
<dd>'assignloanofficer' : Assign Loan Officer</dd>
<dd>'unassignloanofficer' : Unassign Loan Officer as of today or in past but not beyond assigned date. Does not support unassigning in future date</dd>
</dl>
<br>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td> <td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr> </tr>
<tr class=alt> <tr class=alt>
<td>eventDate</td> <td>approvedOnDate</td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="method-example"> <div class="method-example">
<code class="method-declaration"> <code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId} POST https://DomainName/api/v1/loans/{loanId}?command=approve
</code> </code>
<code class="method-request"> <code class="method-request">
POST loans/1?command=approve POST loans/1?command=approve
@@ -3016,31 +3200,293 @@ Request Body:
{ {
"locale": "en", "locale": "en",
"dateFormat": "dd MMMM yyyy", "dateFormat": "dd MMMM yyyy",
"eventDate": "20 September 2011", "approvedOnDate": "20 September 2011",
"note": "and event note is added" "note": "Loan approval note"
} }
</code> </code>
<code class="method-response"> <code class="method-response">
{ {
"officeId": 1, "officeId": 1,
"clientId": 1, "clientId": 1,
"loanId": 2, "loanId": 1,
"resourceId": 2, "resourceId": 1,
"changes": { "changes": {
"status": { "status": {
"id": 200, "id": 200,
"code": "loanStatusType.approved", "code": "loanStatusType.approved",
"value": "Approved" "value": "Approved",
}, "pendingApproval": false,
"locale": "en", "waitingForDisbursal": true,
"dateFormat": "dd MMMM yyyy", "active": false,
"approvedOnDate": "20 September 2011", "closedObligationsMet": false,
"note": "and event note is added" "closedWrittenOff": false,
} "closedRescheduled": false,
"closed": false,
"overpaid": false
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"approvedOnDate": "20 September 2011",
"note": "Loan approval note"
}
} }
</code> </code>
</div> </div>
</div> </div>
<a id="loans_approve_undo" name="loans_approve_undo" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Undo Loan Application Approval</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}?command=undoApproval
</code>
<code class="method-request">
POST loans/1?command=approve
Content-Type: application/json
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 1,
"resourceId": 1,
"changes": {
"status": {
"id": 100,
"code": "loanStatusType.submitted.and.pending.approval",
"value": "Submitted and pending approval",
"pendingApproval": true,
"waitingForDisbursal": false,
"active": false,
"closedObligationsMet": false,
"closedWrittenOff": false,
"closedRescheduled": false,
"closed": false,
"overpaid": false
},
"approvedOnDate": ""
}
}
</code>
</div>
</div>
<a id="loans_reject" name="loans_reject" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Reject Loan Application</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>rejectedOnDate</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}?command=reject
</code>
<code class="method-request">
POST loans/1?command=reject
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"rejectedOnDate": "20 September 2011",
"note": "Loan rejection reason."
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 1,
"resourceId": 1,
"changes": {
"status": {
"id": 500,
"code": "loanStatusType.rejected",
"value": "Rejected",
"pendingApproval": false,
"waitingForDisbursal": false,
"active": false,
"closedObligationsMet": false,
"closedWrittenOff": false,
"closedRescheduled": false,
"closed": false,
"overpaid": false
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"rejectedOnDate": "20 September 2011",
"closedOnDate": "20 September 2011"
}
}
</code>
</div>
</div>
<a id="loans_withdraw" name="loans_withdraw" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Applicant Withdraws from Loan Application</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>withdrawnOnDate</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}?command=withdrawnByApplicant
</code>
<code class="method-request">
POST loans/1?command=withdrawnByApplicant
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "20 September 2011",
"note": "Reason loan applicant withdrew from application."
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 2,
"resourceId": 2,
"changes": {
"status": {
"id": 400,
"code": "loanStatusType.withdrawn.by.client",
"value": "Withdrawn by applicant",
"pendingApproval": false,
"waitingForDisbursal": false,
"active": false,
"closedObligationsMet": false,
"closedWrittenOff": false,
"closedRescheduled": false,
"closed": false,
"overpaid": false
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "20 September 2011",
"closedOnDate": "20 September 2011"
}
}
</code>
</div>
</div>
<a id="loans_disburse" name="loans_disburse" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Disburse Loan</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>actualDisbursementDate</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}?command=disburse
</code>
<code class="method-request">
POST loans/1?command=disburse
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"actualDisbursementDate": "20 September 2011",
"note": "Any additional info related to disbursement of funds to applicant."
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 4,
"resourceId": 4,
"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": "20 September 2011"
}
}
</code>
</div>
</div>
<a id="loans_disburse_undo" name="loans_disburse_undo" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Undo Loan Disbursal</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/{loanId}?command=undoDisbursal
</code>
<code class="method-request">
POST loans/1?command=undoDisbursal
Content-Type: application/json
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"loanId": 4,
"resourceId": 4,
"changes": {
"status": {
"id": 200,
"code": "loanStatusType.approved",
"value": "Approved",
"pendingApproval": false,
"waitingForDisbursal": true,
"active": false,
"closedObligationsMet": false,
"closedWrittenOff": false,
"closedRescheduled": false,
"closed": false,
"overpaid": false
},
"actualDisbursementDate": ""
}
}
</code>
</div>
</div>
<a id="loans_delete" name="loans_delete" class="old-syle-anchor">&nbsp;</a> <a id="loans_delete" name="loans_delete" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
@@ -10150,11 +10596,18 @@ Content-Type: application/json
<td><a href="#clients_updatenote">Update a Client Note</a></td> <td><a href="#clients_updatenote">Update a Client Note</a></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr class="alt">
<td><a href="#loans">LOAN</a></td> <td><a href="#loans">LOAN</a></td>
<td>loans?calculateLoanSchedule</td>
<td><a href="#loans_calculate">Calculate Loan Repayment Schedule</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans</td> <td>loans</td>
<td><a href="#loans_create">Create a New Loan / Submit a <td><a href="#loans_create">Submit a new Loan Application</a></td>
Loan Application</a></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
@@ -10162,13 +10615,59 @@ Content-Type: application/json
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>loans/{loanId}</td> <td>loans/{loanId}</td>
<td><a href="#loans_approve_etc">Approve, Undo Approval, <td></td>
Disburse, Undo Disbursal, Reject a Loan or Withdraw a Loan
Application</a></td>
<td><a href="#loans_retrieve">Retrieve a Loan</a></td> <td><a href="#loans_retrieve">Retrieve a Loan</a></td>
<td></td> <td></td>
<td><a href="#loans_delete">Delete a Loan</a></td> <td><a href="#loans_delete">Delete a Loan</a></td>
</tr> </tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=approve</td>
<td><a href="#loans_approve">Approve Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=undoApproval</td>
<td><a href="#loans_approve_undo">Undo Loan Application Approval</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=reject</td>
<td><a href="#loans_reject">Reject Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=withdraw</td>
<td><a href="#loans_withdraw">Withdraw Loan Application</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=disburse</td>
<td><a href="#loans_disburse">Disburse Loan</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loans/{loanId}?command=undoDisbursal</td>
<td><a href="#loans_disburse_undo">Undo Loan Application Disbursal</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>loans/{loanId}/transactions</td> <td>loans/{loanId}/transactions</td>
@@ -206,8 +206,8 @@ INSERT INTO `m_office` (`id`, `parent_id`, `hierarchy`, `external_id`, `name`, `
VALUES VALUES
(1,NULL,'.','1','Head Office','2009-01-01'); (1,NULL,'.','1','Head Office','2009-01-01');
INSERT INTO `m_group_level` (`id`, `parent_id`, `is_super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES (1, NULL, 1, 'Center', 1, 0); INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES (1, NULL, 1, 'Center', 1, 0);
INSERT INTO `m_group_level` (`id`, `parent_id`, `is_super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES (2, 1, 0, 'Group', 0, 1); INSERT INTO `m_group_level` (`id`, `parent_id`, `super_parent`, `level_name`, `recursable`, `can_have_clients`) VALUES (2, 1, 0, 'Group', 0, 1);
-- ========= roles and permissions ========= -- ========= roles and permissions =========
/* /*
@@ -496,13 +496,8 @@ VALUES
INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES (1,1); INSERT INTO `m_appuser_role` (`appuser_id`, `role_id`) VALUES (1,1);
-- Add in permissions for any special datatables or reports added in base reference data -- Add in permissions for any special datatables added in base reference data
-- This needs to always happen at end of the script -- This needs to always happen at end of the script
/* add a read permission for each defined report */
insert into m_permission(grouping, `code`, entity_name, action_name)
select 'report', concat('READ_', sr.report_name),
sr.report_name, 'READ'
from stretchy_report sr;
/* add a create, read, update and delete permission for each registered datatable */ /* add a create, read, update and delete permission for each registered datatable */
insert into m_permission(grouping, `code`, entity_name, action_name) insert into m_permission(grouping, `code`, entity_name, action_name)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -234,8 +234,7 @@ public class LoansApiResource {
if (!associationParameters.isEmpty()) { if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) { if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "permissions", "charges", "guarantors", associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "charges", "guarantors", "collateral"));
"collateral"));
} }
if (associationParameters.contains("guarantors")) { if (associationParameters.contains("guarantors")) {
@@ -254,11 +253,8 @@ public class LoansApiResource {
} }
} }
if (associationParameters.contains("repaymentSchedule") || associationParameters.contains("permissions")) { if (associationParameters.contains("repaymentSchedule")) {
mandatoryResponseParameters.add("repaymentSchedule");
if (associationParameters.contains("repaymentSchedule")) {
mandatoryResponseParameters.add("repaymentSchedule");
}
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData(); final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData();
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData); repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData);
@@ -393,7 +389,7 @@ public class LoansApiResource {
if (is(commandParam, "reject")) { if (is(commandParam, "reject")) {
final CommandWrapper commandRequest = builder.rejectLoanApplication(loanId).build(); final CommandWrapper commandRequest = builder.rejectLoanApplication(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "withdrewbyclient")) { } else if (is(commandParam, "withdrawnByApplicant")) {
final CommandWrapper commandRequest = builder.withdrawLoanApplication(loanId).build(); final CommandWrapper commandRequest = builder.withdrawLoanApplication(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (is(commandParam, "approve")) { } else if (is(commandParam, "approve")) {
@@ -1,82 +0,0 @@
/**
* 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.command;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
/**
* Immutable command for any of the loan state transitions eg. reject, withdrawn
* by client, approve, disburse
*/
public class LoanStateTransitionCommand {
private final LocalDate eventDate;
private final String note;
private final LocalDate approvedOnDate;
private final LocalDate rejectedOnDate;
private final LocalDate withdrawnOnDate;
private final LocalDate disbursedOnDate;
public LoanStateTransitionCommand(final LocalDate eventDate, final String note, final LocalDate approvedOnDate,
final LocalDate rejectedOnDate, final LocalDate withdrawnOnDate, final LocalDate disbursedOnDate) {
this.eventDate = eventDate;
this.note = note;
this.approvedOnDate = approvedOnDate;
this.rejectedOnDate = rejectedOnDate;
this.withdrawnOnDate = withdrawnOnDate;
this.disbursedOnDate = disbursedOnDate;
}
public LocalDate getApprovedOnDate() {
LocalDate dateValue = this.approvedOnDate;
if (this.approvedOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public LocalDate getRejectedOnDate() {
LocalDate dateValue = this.rejectedOnDate;
if (this.rejectedOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public LocalDate getWithdrawnOnDate() {
LocalDate dateValue = this.withdrawnOnDate;
if (this.withdrawnOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public LocalDate getDisbursedOnDate() {
LocalDate dateValue = this.disbursedOnDate;
if (this.disbursedOnDate == null) {
dateValue = eventDate;
}
return dateValue;
}
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transition");
baseDataValidator.reset().parameter("eventDate").value(this.eventDate).notNull();
baseDataValidator.reset().parameter("note").value(this.note).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -931,9 +931,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate");
if (rejectedOn == null) {
rejectedOn = command.localDateValueOfParameterNamed("eventDate");
}
final Locale locale = new Locale(command.locale()); final Locale locale = new Locale(command.locale());
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
@@ -146,6 +146,12 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
validateRepaymentsStartingFromDateAndInterestChargedFromDate(dataValidationErrors, expectedDisbursementDate, validateRepaymentsStartingFromDateAndInterestChargedFromDate(dataValidationErrors, expectedDisbursementDate,
repaymentsStartingFromDate, interestChargedFromDate); repaymentsStartingFromDate, interestChargedFromDate);
final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId";
final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed(transactionProcessingStrategyIdParameterName,
element);
baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull()
.integerGreaterThanZero();
// charges // charges
final String chargesParameterName = "charges"; final String chargesParameterName = "charges";
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) { if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
@@ -547,4 +547,25 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); } "Validation errors exist.", dataValidationErrors); }
} }
public void validateForUndo(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> undoSupportedParameters = new HashSet<String>(Arrays.asList("note"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication.undo");
final JsonElement element = fromApiJsonHelper.parse(json);
final String note = "note";
if (fromApiJsonHelper.parameterExists(note, element)) {
final String noteText = fromApiJsonHelper.extractStringNamed(note, element);
baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
} }
@@ -0,0 +1,110 @@
/**
* 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.serialization;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public final class LoanApplicationTransitionApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public LoanApplicationTransitionApiJsonValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateApproval(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("approvedOnDate", "note", "locale",
"dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate approvedOnDate = fromApiJsonHelper.extractLocalDateNamed("approvedOnDate", element);
baseDataValidator.reset().parameter("approvedOnDate").value(approvedOnDate).notNull();
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateRejection(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("rejectedOnDate", "note", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate rejectedOnDate = fromApiJsonHelper.extractLocalDateNamed("rejectedOnDate", element);
baseDataValidator.reset().parameter("rejectedOnDate").value(rejectedOnDate).notNull();
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateApplicantWithdrawal(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("withdrawnOnDate", "note", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanapplication");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate withdrawnOnDate = fromApiJsonHelper.extractLocalDateNamed("withdrawnOnDate", element);
baseDataValidator.reset().parameter("withdrawnOnDate").value(withdrawnOnDate).notNull();
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
@@ -1,66 +0,0 @@
/**
* 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.serialization;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link LoanStateTransitionCommand}'s.
*/
@Component
public final class LoanStateTransitionCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanStateTransitionCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("eventDate", "note", "locale", "dateFormat", "status",
"approvedOnDate", "rejectedOnDate", "withdrawnOnDate", "disbursedOnDate", "expectedMaturityDate"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public LoanStateTransitionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public LoanStateTransitionCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate eventDate = fromApiJsonHelper.extractLocalDateNamed("eventDate", element);
final String note = fromApiJsonHelper.extractStringNamed("note", element);
final LocalDate approvedOnDate = fromApiJsonHelper.extractLocalDateNamed("approvedOnDate", element);
final LocalDate rejectedOnDate = fromApiJsonHelper.extractLocalDateNamed("rejectedOnDate", element);
final LocalDate withdrawnOnDate = fromApiJsonHelper.extractLocalDateNamed("withdrawnOnDate", element);
final LocalDate disbursedOnDate = fromApiJsonHelper.extractLocalDateNamed("disbursedOnDate", element);
return new LoanStateTransitionCommand(eventDate, note, approvedOnDate, rejectedOnDate, withdrawnOnDate, disbursedOnDate);
}
}
@@ -15,9 +15,7 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer; import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -25,10 +23,6 @@ import org.springframework.stereotype.Component;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link LoanStateTransitionCommand}'s.
*/
@Component @Component
public final class LoanUpdateCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanUpdateCommand> { public final class LoanUpdateCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanUpdateCommand> {
@@ -11,7 +11,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.api.JsonQuery; import org.mifosplatform.infrastructure.core.api.JsonQuery;
@@ -19,7 +18,6 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
@@ -30,7 +28,6 @@ import org.mifosplatform.portfolio.client.domain.Note;
import org.mifosplatform.portfolio.client.domain.NoteRepository; import org.mifosplatform.portfolio.client.domain.NoteRepository;
import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
@@ -45,7 +42,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDat
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
@@ -67,8 +64,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final FromJsonHelper fromJsonHelper; private final FromJsonHelper fromJsonHelper;
private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
private final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final LoanScheduleCalculationPlatformService calculationPlatformService; private final LoanScheduleCalculationPlatformService calculationPlatformService;
@@ -82,17 +79,17 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
@Autowired @Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer, final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final AprCalculator aprCalculator,
final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler,
final LoanCollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository, final LoanCollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository,
final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService,
final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, final ClientRepository clientRepository, final LoanProductRepository loanProductRepository,
final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory) { final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory) {
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer;
this.aprCalculator = aprCalculator; this.aprCalculator = aprCalculator;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.loanChargeAssembler = loanChargeAssembler; this.loanChargeAssembler = loanChargeAssembler;
@@ -105,10 +102,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory;
} }
private boolean isBeforeToday(final LocalDate date) {
return date.isBefore(new LocalDate());
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
@@ -319,16 +312,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = context.authenticatedUser();
final LoanStateTransitionCommand approveLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json());
.commandFromApiJson(command.json());
approveLoanApplication.validate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LocalDate approvedOnLocalDate = approveLoanApplication.getApprovedOnDate();
if (this.isBeforeToday(approvedOnLocalDate) && currentUser.canNotApproveLoanInPast()) { throw new NoAuthorizationException(
"User has no authority to approve loan with a date in the past."); }
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, defaultLoanLifecycleStateMachine()); final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, defaultLoanLifecycleStateMachine());
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -358,6 +345,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
context.authenticatedUser(); context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUndo(command.json());
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final Map<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine()); final Map<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine());
@@ -388,16 +377,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = context.authenticatedUser();
final LoanStateTransitionCommand rejectLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer this.loanApplicationTransitionApiJsonValidator.validateRejection(command.json());
.commandFromApiJson(command.json());
rejectLoanApplication.validate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LocalDate eventDate = rejectLoanApplication.getRejectedOnDate();
if (this.isBeforeToday(eventDate) && currentUser.canNotRejectLoanInPast()) { throw new NoAuthorizationException(
"User has no authority to reject loan with a date in the past."); }
final Map<String, Object> changes = loan.loanApplicationRejection(currentUser, command, defaultLoanLifecycleStateMachine()); final Map<String, Object> changes = loan.loanApplicationRejection(currentUser, command, defaultLoanLifecycleStateMachine());
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -426,16 +409,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final AppUser currentUser = context.authenticatedUser(); final AppUser currentUser = context.authenticatedUser();
final LoanStateTransitionCommand applicantWithdrawsFromLoanApplication = this.loanStateTransitionCommandFromApiJsonDeserializer this.loanApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
.commandFromApiJson(command.json());
applicantWithdrawsFromLoanApplication.validate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LocalDate eventDate = applicantWithdrawsFromLoanApplication.getWithdrawnOnDate();
if (this.isBeforeToday(eventDate) && currentUser.canNotWithdrawByClientLoanInPast()) { throw new NoAuthorizationException(
"User has no authority to mark loan as withdrawn by applicant with a date in the past."); }
final Map<String, Object> changes = loan.loanApplicationWithdrawnByApplicant(currentUser, command, final Map<String, Object> changes = loan.loanApplicationWithdrawnByApplicant(currentUser, command,
defaultLoanLifecycleStateMachine()); defaultLoanLifecycleStateMachine());
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
@@ -10,12 +10,26 @@ import java.util.Map;
public enum AccountingRuleType { public enum AccountingRuleType {
NONE(1, "accountingRuleType.none"), CASH_BASED(2, "accountingRuleType.cash"), ACCRUAL_BASED(3, "accountingRuleType.accrual"); NONE(1, "accountingRuleType.none"), //
CASH_BASED(2, "accountingRuleType.cash"), //
ACCRUAL_BASED(3, "accountingRuleType.accrual");
private final Integer value; private final Integer value;
private final String code; private final String code;
private AccountingRuleType(final Integer value, String code) { private static final Map<Integer, AccountingRuleType> intToEnumMap = new HashMap<Integer, AccountingRuleType>();
static {
for (AccountingRuleType type : AccountingRuleType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static AccountingRuleType fromInt(final Integer ruleTypeValue) {
AccountingRuleType type = intToEnumMap.get(ruleTypeValue);
return type;
}
private AccountingRuleType(final Integer value, final String code) {
this.value = value; this.value = value;
this.code = code; this.code = code;
} }
@@ -32,17 +46,4 @@ public enum AccountingRuleType {
public String getCode() { public String getCode() {
return this.code; return this.code;
} }
}
private static final Map<Integer, AccountingRuleType> intToEnumMap = new HashMap<Integer, AccountingRuleType>();
static {
for (AccountingRuleType type : AccountingRuleType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static AccountingRuleType fromInt(int i) {
AccountingRuleType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
@@ -64,7 +64,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
private final LoanProductRelatedDetail loanProductRelatedDetail; private final LoanProductRelatedDetail loanProductRelatedDetail;
@Column(name = "accounting_type", nullable = false) @Column(name = "accounting_type", nullable = false)
private Integer accountingType; private Integer accountingRule;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final Set<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) { final Set<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -91,7 +91,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery"); final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance"); final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance");
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingType")); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, interestRatePerPeriod, return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, interestRatePerPeriod,
interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery,
@@ -128,7 +128,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance); repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
if (accountingRuleType != null) { if (accountingRuleType != null) {
this.accountingType = accountingRuleType.getValue(); this.accountingRule = accountingRuleType.getValue();
} }
} }
@@ -153,7 +153,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
} }
public Integer getAccountingType() { public Integer getAccountingType() {
return this.accountingType; return this.accountingRule;
} }
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) { public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
@@ -161,10 +161,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator); final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator);
final String accountingTypeParamName = "accountingRule"; final String accountingTypeParamName = "accountingRule";
if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingType)) { if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName); final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName);
actualChanges.put(accountingTypeParamName, newValue); actualChanges.put(accountingTypeParamName, newValue);
this.accountingType = newValue; this.accountingRule = newValue;
} }
final String nameParamName = "name"; final String nameParamName = "name";
@@ -223,14 +223,14 @@ public class LoanProduct extends AbstractPersistable<Long> {
} }
public boolean isAccountingEnabled() { public boolean isAccountingEnabled() {
return !AccountingRuleType.NONE.getValue().equals(this.accountingType); return !AccountingRuleType.NONE.getValue().equals(this.accountingRule);
} }
public boolean isCashBasedAccountingEnabled() { public boolean isCashBasedAccountingEnabled() {
return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingType); return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule);
} }
public boolean isAccrualBasedAccountingEnabled() { public boolean isAccrualBasedAccountingEnabled() {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingType); return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule);
} }
} }