minor changes to client images and updates to documentation
This commit is contained in:
@@ -203,6 +203,26 @@
|
||||
<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
|
||||
<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<td><a href="#client_images">Client Images</a></td>
|
||||
<td>clients/{clientId}/images</td>
|
||||
<td><a href="#client_images_create">Upload an Image for a Client (as DATA URI)</a></td>
|
||||
<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
|
||||
<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
|
||||
<td><a href="#client_images_delete">Delete Client Image</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><a href="#client_images_create_form">
|
||||
Upload an Image for a Client (Multi-part form data)</a>
|
||||
</td>
|
||||
<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
|
||||
<td><a href="#client_images_update_form">
|
||||
Update Client Image (Multi-part form data)</a>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2297,6 +2317,225 @@ No Request Body:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- start of Client Images API docs-->
|
||||
|
||||
<a id="client_images" name="client_images" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h3>Client Images</h3>
|
||||
<p>The current API provides support for the addition of a single
|
||||
image for a Client<br/>
|
||||
Allowed formats: JPEG (.jpg or .jpeg), GIF (.gif) and PNG (.png)
|
||||
</p>
|
||||
<p> The API supports two different Approaches for manipulating Client Images
|
||||
<ul>
|
||||
<li><a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank"> Data URI's: </a>
|
||||
For easier manipulation by Javascript clients etc in supported Browsers.
|
||||
</li>
|
||||
<li><a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2">
|
||||
Multi-part form data:</a>
|
||||
Images can be uploaded using Multi part forms and downloaded as regular binary files
|
||||
</li>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_retrieve" name="client_images_retrieve"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Get Client Image (DATA URI)</h4>
|
||||
<p>Example Requests:</p>
|
||||
<div class=apiClick>clients/1/images</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-response">
|
||||
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
|
||||
bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp
|
||||
DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB
|
||||
+BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA
|
||||
gAEAOg13B6R/SAgAAAAASUVORK5CYII=
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_retrieve_binary" name="client_images_retrieve_binary"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Get Client Image (Binary file)</h4>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST clients/1/images
|
||||
Accept: application/octet-stream
|
||||
</code>
|
||||
<code class="method-response">
|
||||
<i>Not shown</i>: The corresponding binary (image) file
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_create" name="client_images_create" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Upload an Image for a Client (Data URI)</h4>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST clients/1/images
|
||||
Content-Type: text/plain
|
||||
Request Body:
|
||||
data:image/png;base64,iVBORw0KGgoAA
|
||||
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
|
||||
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
|
||||
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 1,
|
||||
"changes": {},
|
||||
"resourceIdentifier": "1"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_create_form" name="client_images_create_form" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Upload an Image for a Client (Multi-part Form data)</h4>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>file
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
The form should contain a required named body part with the
|
||||
name "file". <br/> <br/>
|
||||
If you are using a HTML form, a snippet like
|
||||
<small> <input type="file" name="file"></input> </small>
|
||||
can be used for uploading the image file
|
||||
</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST clients/1/images
|
||||
Content-Type: multipart/form-data
|
||||
Request Body: <i>Not shown</i>
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 1,
|
||||
"changes": {},
|
||||
"resourceIdentifier": "1"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_update" name="client_images_update" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Update a Client Image (Data URI)</h4>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
PUT https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
PUT clients/1/images
|
||||
Content-Type: text/plain
|
||||
Request Body:
|
||||
data:image/png;base64,iVBORw0KGgoAA
|
||||
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
|
||||
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
|
||||
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 1,
|
||||
"changes": {},
|
||||
"resourceIdentifier": "1"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_update_form" name="client_images_update_form" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Update a Client Image (Multi-part Form data)</h4>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>file
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
The form should contain a required named body part with the
|
||||
name "file". <br/> <br/>
|
||||
If you are using a HTML form, a snippet like
|
||||
<small> <input type="file" name="file"></input> </small>
|
||||
can be used for uploading the image file
|
||||
</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
PUT https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
PUT clients/1/images
|
||||
Content-Type: multipart/form-data
|
||||
Request Body: <i>Not shown</i>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="client_images_delete" name="client_images_delete" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Delete a Client Image</h4>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
DELETE https://DomainName/api/v1/clients/{clientId}/images
|
||||
</code>
|
||||
<code class="method-request">
|
||||
DELETE clients/1/images
|
||||
Content-Type: application/json
|
||||
No Request Body:
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 1,
|
||||
"changes": {},
|
||||
"resourceIdentifier": "1"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- start of Groups api docs -->
|
||||
<a id="groups" name="groups" class="old-syle-anchor"> </a>
|
||||
@@ -11619,6 +11858,26 @@ Content-Type: application/json
|
||||
<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
|
||||
<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#client_images">Client Images</a></td>
|
||||
<td>clients/{clientId}/images</td>
|
||||
<td><a href="#client_images_create">Upload an Image for a Client (as DATA URI)</a></td>
|
||||
<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
|
||||
<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
|
||||
<td><a href="#client_images_delete">Delete Client Image</a></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><a href="#client_images_create_form">
|
||||
Upload an Image for a Client (Multi-part form data)</a>
|
||||
</td>
|
||||
<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
|
||||
<td><a href="#client_images_update_form">
|
||||
Update Client Image (Multi-part form data)</a>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<!-- loans api full matrix-->
|
||||
<tr>
|
||||
<td><a href="#loans">Loans</a></td>
|
||||
|
||||
+55
-14
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.core.exception.ImageDataURLNotValidException;
|
||||
@@ -31,6 +32,49 @@ public class FileUtils {
|
||||
|
||||
public static final String MIFOSX_BASE_DIR = System.getProperty("user.home") + File.separator + ".mifosx";
|
||||
|
||||
public static enum IMAGE_MIME_TYPE {
|
||||
GIF("image/gif"), JPEG("image/jpeg"), PNG("image/png");
|
||||
|
||||
private final String value;
|
||||
|
||||
private IMAGE_MIME_TYPE(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
|
||||
public static enum IMAGE_FILE_EXTENSION {
|
||||
GIF(".gif"), JPEG(".jpeg"), JPG(".jpg"), PNG(".png");
|
||||
|
||||
private final String value;
|
||||
|
||||
private IMAGE_FILE_EXTENSION(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
|
||||
public static enum IMAGE_DATA_URI_SUFFIX {
|
||||
GIF("data:" + IMAGE_MIME_TYPE.GIF.getValue() + ";base64,"), JPEG("data:" + IMAGE_MIME_TYPE.JPEG.getValue() + ";base64,"), PNG(
|
||||
"data:" + IMAGE_MIME_TYPE.PNG.getValue() + ";base64,");
|
||||
|
||||
private final String value;
|
||||
|
||||
private IMAGE_DATA_URI_SUFFIX(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
|
||||
public static Random random = new Random();
|
||||
|
||||
/**
|
||||
@@ -138,7 +182,8 @@ public class FileUtils {
|
||||
* @param mimeType
|
||||
*/
|
||||
public static void validateImageMimeType(String mimeType) {
|
||||
if (!(mimeType.equalsIgnoreCase("image/gif") || mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) { throw new ImageUploadException(); }
|
||||
if (!(mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.GIF.getValue()) || mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.JPEG.getValue()) || mimeType
|
||||
.equalsIgnoreCase(IMAGE_MIME_TYPE.PNG.getValue()))) { throw new ImageUploadException(); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,19 +194,15 @@ public class FileUtils {
|
||||
public static Base64EncodedImage extractImageFromDataURL(String dataURL) {
|
||||
String fileExtension = "";
|
||||
String base64EncodedString = null;
|
||||
String gifDataURLSnippet = "data:image/gif;base64,";
|
||||
String pngDataURLSnippet = "data:image/png;base64,";
|
||||
String jpegDataURLSnippet = "data:image/jpeg;base64,";
|
||||
|
||||
if (dataURL.contains(gifDataURLSnippet)) {
|
||||
base64EncodedString = dataURL.replaceAll(gifDataURLSnippet, "");
|
||||
fileExtension = ".gif";
|
||||
} else if (dataURL.contains(pngDataURLSnippet)) {
|
||||
base64EncodedString = dataURL.replaceAll(pngDataURLSnippet, "");
|
||||
fileExtension = ".png";
|
||||
} else if (dataURL.contains(jpegDataURLSnippet)) {
|
||||
base64EncodedString = dataURL.replaceAll(jpegDataURLSnippet, "");
|
||||
fileExtension = ".jpeg";
|
||||
if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.GIF.getValue())) {
|
||||
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.GIF.getValue(), "");
|
||||
fileExtension = IMAGE_FILE_EXTENSION.GIF.getValue();
|
||||
} else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.PNG.getValue())) {
|
||||
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.PNG.getValue(), "");
|
||||
fileExtension = IMAGE_FILE_EXTENSION.PNG.getValue();
|
||||
} else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.JPEG.getValue())) {
|
||||
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.JPEG.getValue(), "");
|
||||
fileExtension = IMAGE_FILE_EXTENSION.JPEG.getValue();
|
||||
} else {
|
||||
throw new ImageDataURLNotValidException();
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,14 +11,14 @@ public class LoanChargeCannotBeDeletedException extends AbstractPlatformDomainRu
|
||||
|
||||
/*** enum of reasons of why Loan Charge cannot be waived **/
|
||||
public static enum LOAN_CHARGE_CANNOT_BE_DELETED_REASON {
|
||||
ALREADY_PAID, ALREADY_WAIVED, LOAN_APPROVED;
|
||||
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
|
||||
|
||||
public String errorMessage() {
|
||||
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
|
||||
return "This loan charge has been partially/completely paid";
|
||||
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
|
||||
return "This loan charge has already been waived";
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_APPROVED")) { return "This charge cannot be deleted as the loan it is associated with has already been approved"; }
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be deleted as the loan it is associated with is not in submitted and pending approval stage"; }
|
||||
return name().toString();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class LoanChargeCannotBeDeletedException extends AbstractPlatformDomainRu
|
||||
return "error.msg.loan.charge.already.paid";
|
||||
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
|
||||
return "error.msg.loan.charge.already.waived";
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_APPROVED")) { return "error.msg.loan.charge.associated.loan.already.approved"; }
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
|
||||
return name().toString();
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.charge.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
/*** enum of reasons of why Loan Charge cannot be waived **/
|
||||
public static enum LOAN_CHARGE_CANNOT_BE_UPDATED_REASON {
|
||||
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
|
||||
|
||||
public String errorMessage() {
|
||||
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
|
||||
return "This loan charge has been partially/completely paid";
|
||||
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
|
||||
return "This loan charge has already been waived";
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
|
||||
return name().toString();
|
||||
}
|
||||
|
||||
public String errorCode() {
|
||||
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
|
||||
return "error.msg.loan.charge.already.paid";
|
||||
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
|
||||
return "error.msg.loan.charge.already.waived";
|
||||
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
|
||||
return name().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public LoanChargeCannotBeUpdatedException(final LOAN_CHARGE_CANNOT_BE_UPDATED_REASON reason, final Long loanChargeId) {
|
||||
super(reason.errorCode(), reason.errorMessage(), loanChargeId);
|
||||
}
|
||||
}
|
||||
+130
-105
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.client.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
@@ -17,12 +18,18 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.infrastructure.core.api.ApiConstants;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_DATA_URI_SUFFIX;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_FILE_EXTENSION;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_MIME_TYPE;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.portfolio.client.data.ClientData;
|
||||
import org.mifosplatform.portfolio.client.exception.ImageNotFoundException;
|
||||
@@ -42,128 +49,146 @@ import com.sun.jersey.multipart.FormDataParam;
|
||||
@Scope("singleton")
|
||||
public class ClientImagesApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
private final ClientWritePlatformService clientWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
|
||||
private final PlatformSecurityContext context;
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
private final ClientWritePlatformService clientWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
|
||||
|
||||
@Autowired
|
||||
public ClientImagesApiResource(final PlatformSecurityContext context,
|
||||
final ClientReadPlatformService readPlatformService,
|
||||
final ClientWritePlatformService clientWritePlatformService,
|
||||
final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
|
||||
this.context = context;
|
||||
this.clientReadPlatformService = readPlatformService;
|
||||
this.clientWritePlatformService = clientWritePlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
}
|
||||
@Autowired
|
||||
public ClientImagesApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService,
|
||||
final ClientWritePlatformService clientWritePlatformService, final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
|
||||
this.context = context;
|
||||
this.clientReadPlatformService = readPlatformService;
|
||||
this.clientWritePlatformService = clientWritePlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload images through multi-part form upload
|
||||
*/
|
||||
@POST
|
||||
@Consumes({ MediaType.MULTIPART_FORM_DATA })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String addNewClientImage(
|
||||
@PathParam("clientId") final Long clientId,
|
||||
@HeaderParam("Content-Length") final Long fileSize,
|
||||
@FormDataParam("file") final InputStream inputStream,
|
||||
@FormDataParam("file") final FormDataContentDisposition fileDetails,
|
||||
@FormDataParam("file") final FormDataBodyPart bodyPart) {
|
||||
/**
|
||||
* Upload images through multi-part form upload
|
||||
*/
|
||||
@POST
|
||||
@Consumes({ MediaType.MULTIPART_FORM_DATA })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String addNewClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
|
||||
@FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
|
||||
@FormDataParam("file") final FormDataBodyPart bodyPart) {
|
||||
|
||||
// TODO: vishwas might need more advances validation (like reading magic
|
||||
// number) for handling malicious clients
|
||||
// and clients not setting mime type
|
||||
FileUtils.validateClientImageNotEmpty(fileDetails.getFileName());
|
||||
FileUtils.validateImageMimeType(bodyPart.getMediaType().toString());
|
||||
FileUtils.validateFileSizeWithinPermissibleRange(fileSize,
|
||||
fileDetails.getFileName(),
|
||||
ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
|
||||
// TODO: vishwas might need more advances validation (like reading magic
|
||||
// number) for handling malicious clients
|
||||
// and clients not setting mime type
|
||||
FileUtils.validateClientImageNotEmpty(fileDetails.getFileName());
|
||||
FileUtils.validateImageMimeType(bodyPart.getMediaType().toString());
|
||||
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, fileDetails.getFileName(), ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
|
||||
|
||||
final CommandProcessingResult result = this.clientWritePlatformService
|
||||
.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
|
||||
inputStream);
|
||||
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
|
||||
inputStream);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image as a Data URL (essentially a base64 encoded stream)
|
||||
*/
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String addNewClientImage(@PathParam("clientId") final Long clientId,
|
||||
final String jsonRequestBody) {
|
||||
/**
|
||||
* Upload image as a Data URL (essentially a base64 encoded stream)
|
||||
*/
|
||||
@POST
|
||||
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String addNewClientImage(@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
|
||||
|
||||
final Base64EncodedImage base64EncodedImage = FileUtils
|
||||
.extractImageFromDataURL(jsonRequestBody);
|
||||
final Base64EncodedImage base64EncodedImage = FileUtils.extractImageFromDataURL(jsonRequestBody);
|
||||
|
||||
final CommandProcessingResult result = this.clientWritePlatformService
|
||||
.saveOrUpdateClientImage(clientId, base64EncodedImage);
|
||||
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, base64EncodedImage);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a base 64 encoded client image
|
||||
*/
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveClientImage(@PathParam("clientId") final Long clientId) {
|
||||
/**
|
||||
* Returns a base 64 encoded client image Data URI
|
||||
*/
|
||||
@GET
|
||||
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.TEXT_PLAIN })
|
||||
public String retrieveClientImage(@PathParam("clientId") final Long clientId) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
|
||||
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
|
||||
|
||||
final ClientData clientData = this.clientReadPlatformService
|
||||
.retrieveIndividualClient(clientId);
|
||||
final ClientData clientData = this.clientReadPlatformService.retrieveIndividualClient(clientId);
|
||||
|
||||
if (clientData.imageKeyDoesNotExist()) {
|
||||
throw new ImageNotFoundException("clients", clientId);
|
||||
}
|
||||
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
|
||||
|
||||
return Base64.encodeFromFile(clientData.imageKey());
|
||||
}
|
||||
// TODO: Need a better way of determining image type
|
||||
String imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.JPEG.getValue();
|
||||
if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.PNG.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is added only for consistency with other URL patterns and for
|
||||
* maintaining consistency of usage of the HTTP "verb" at the client side
|
||||
*/
|
||||
@PUT
|
||||
@Consumes({ MediaType.MULTIPART_FORM_DATA })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateClientImage(
|
||||
@PathParam("clientId") final Long clientId,
|
||||
@HeaderParam("Content-Length") final Long fileSize,
|
||||
@FormDataParam("file") final InputStream inputStream,
|
||||
@FormDataParam("file") final FormDataContentDisposition fileDetails,
|
||||
@FormDataParam("file") final FormDataBodyPart bodyPart) {
|
||||
String clientImageAsBase64Text = imageDataURISuffix + Base64.encodeFromFile(clientData.imageKey());
|
||||
|
||||
return addNewClientImage(clientId, fileSize, inputStream, fileDetails,
|
||||
bodyPart);
|
||||
}
|
||||
return clientImageAsBase64Text;
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteClientImage(@PathParam("clientId") final Long clientId) {
|
||||
this.clientWritePlatformService.deleteClientImage(clientId);
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_OCTET_STREAM })
|
||||
public Response downloadClientImage(@PathParam("clientId") final Long clientId) {
|
||||
|
||||
return this.toApiJsonSerializer.serialize(new CommandProcessingResult(
|
||||
clientId));
|
||||
}
|
||||
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
|
||||
final ClientData clientData = this.clientReadPlatformService.retrieveIndividualClient(clientId);
|
||||
|
||||
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
|
||||
|
||||
File image = new File(clientData.imageKey());
|
||||
String imageName = image.getName();
|
||||
ResponseBuilder response = Response.ok(image);
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + imageName + "\"");
|
||||
|
||||
// TODO: Need a better way of determining image type
|
||||
|
||||
// determine image type
|
||||
String contentType = IMAGE_MIME_TYPE.JPEG.getValue();
|
||||
if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
contentType = IMAGE_MIME_TYPE.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
contentType = IMAGE_MIME_TYPE.PNG.getValue();
|
||||
}
|
||||
|
||||
response.header("Content-Type", contentType);
|
||||
return response.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is added only for consistency with other URL patterns and for
|
||||
* maintaining consistency of usage of the HTTP "verb" at the client side
|
||||
*/
|
||||
@PUT
|
||||
@Consumes({ MediaType.MULTIPART_FORM_DATA })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
|
||||
@FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
|
||||
@FormDataParam("file") final FormDataBodyPart bodyPart) {
|
||||
return addNewClientImage(clientId, fileSize, inputStream, fileDetails, bodyPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is added only for consistency with other URL patterns and for
|
||||
* maintaining consistency of usage of the HTTP "verb" at the client side
|
||||
*
|
||||
* Upload image as a Data URL (essentially a base64 encoded stream)
|
||||
*/
|
||||
@PUT
|
||||
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateClientImage(@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
|
||||
return addNewClientImage(clientId, jsonRequestBody);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteClientImage(@PathParam("clientId") final Long clientId) {
|
||||
this.clientWritePlatformService.deleteClientImage(clientId);
|
||||
return this.toApiJsonSerializer.serialize(new CommandProcessingResult(clientId));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is added only for consistency with other URL patterns and for
|
||||
* maintaining consistency of usage of the HTTP "verb" at the client side
|
||||
*
|
||||
* Upload image as a Data URL (essentially a base64 encoded stream)
|
||||
*/
|
||||
@PUT
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateClientImage(@PathParam("clientId") final Long clientId,
|
||||
final String jsonRequestBody) {
|
||||
return addNewClientImage(clientId, jsonRequestBody);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.client.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class ImageNotFoundException extends AbstractPlatformDomainRuleException {
|
||||
public class ImageNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public ImageNotFoundException(final String resource, final Long resourceId) {
|
||||
super("error.msg.entity.image.invalid", "Image for resource " + resource + " with Identifier " + resourceId + " does not exist",
|
||||
|
||||
+5
-5
@@ -23,21 +23,21 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
||||
switch (loanEvent) {
|
||||
case LOAN_CREATED:
|
||||
if (from == null) {
|
||||
newState = stateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL, allowedLoanStatuses);
|
||||
newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
case LOAN_REJECTED:
|
||||
if (from.hasStateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL)) {
|
||||
if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
|
||||
newState = stateOf(LoanStatus.REJECTED, allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
case LOAN_APPROVED:
|
||||
if (from.hasStateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL)) {
|
||||
if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
|
||||
newState = stateOf(LoanStatus.APPROVED, allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
case LOAN_WITHDRAWN:
|
||||
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITED_AND_PENDING_APPROVAL, LoanStatus.APPROVED)) {
|
||||
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, LoanStatus.APPROVED)) {
|
||||
newState = stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
@@ -48,7 +48,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
||||
break;
|
||||
case LOAN_APPROVAL_UNDO:
|
||||
if (from.hasStateOf(LoanStatus.APPROVED)) {
|
||||
newState = stateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL, allowedLoanStatuses);
|
||||
newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, allowedLoanStatuses);
|
||||
}
|
||||
break;
|
||||
case LOAN_DISBURSAL_UNDO:
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
public enum LoanStatus {
|
||||
|
||||
INVALID(0, "loanStatusType.invalid"), //
|
||||
SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
|
||||
SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
|
||||
APPROVED(200, "loanStatusType.approved"), //
|
||||
ACTIVE(300, "loanStatusType.active"), //
|
||||
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
|
||||
@@ -29,7 +29,7 @@ public enum LoanStatus {
|
||||
LoanStatus enumeration = LoanStatus.INVALID;
|
||||
switch (statusValue) {
|
||||
case 100:
|
||||
enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL;
|
||||
enumeration = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
|
||||
break;
|
||||
case 200:
|
||||
enumeration = LoanStatus.APPROVED;
|
||||
@@ -77,7 +77,7 @@ public enum LoanStatus {
|
||||
}
|
||||
|
||||
public boolean isSubmittedAndPendingApproval() {
|
||||
return this.value.equals(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue());
|
||||
return this.value.equals(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue());
|
||||
}
|
||||
|
||||
public boolean isApproved() {
|
||||
|
||||
+11
-5
@@ -26,6 +26,8 @@ import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
|
||||
@@ -529,6 +531,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Loan loan = retrieveLoanBy(loanId);
|
||||
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
|
||||
|
||||
// Charges may be edited only when the loan associated with them are
|
||||
// yet to be approved (are in submitted and pending status)
|
||||
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
|
||||
LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
|
||||
|
||||
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
|
||||
|
||||
this.loanRepository.save(loan);
|
||||
@@ -597,11 +604,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Loan loan = retrieveLoanBy(loanId);
|
||||
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
|
||||
|
||||
// validate loan charge is not partly paid or waived
|
||||
if (loanCharge.isWaived()) {
|
||||
throw new LoanChargeCannotBeDeletedException(LOAN_CHARGE_CANNOT_BE_DELETED_REASON.ALREADY_WAIVED, loanCharge.getId());
|
||||
} else if (loanCharge.isPaidOrPartiallyPaid(loan.getCurrency())) { throw new LoanChargeCannotBeDeletedException(
|
||||
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.ALREADY_PAID, loanCharge.getId()); }
|
||||
// Charges may be deleted only when the loan associated with them are
|
||||
// yet to be approved (are in submitted and pending status)
|
||||
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException(
|
||||
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
|
||||
|
||||
loan.removeLoanCharge(loanCharge);
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
+3
-3
@@ -263,9 +263,9 @@ public class LoanEnumerations {
|
||||
case INVALID:
|
||||
optionData = new LoanStatusEnumData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
|
||||
break;
|
||||
case SUBMITED_AND_PENDING_APPROVAL:
|
||||
optionData = new LoanStatusEnumData(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue().longValue(),
|
||||
LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
|
||||
case SUBMITTED_AND_PENDING_APPROVAL:
|
||||
optionData = new LoanStatusEnumData(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
|
||||
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
|
||||
break;
|
||||
case APPROVED:
|
||||
optionData = new LoanStatusEnumData(LoanStatus.APPROVED.getValue().longValue(), LoanStatus.APPROVED.getCode(), "Approved");
|
||||
|
||||
Reference in New Issue
Block a user