diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index 2e591724f..15a554d15 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -203,6 +203,26 @@
Update a Client Identifier |
Delete a Client Identifier |
+
+ | Client Images |
+ clients/{clientId}/images |
+ Upload an Image for a Client (as DATA URI) |
+ Get Client Image (DATA URI) |
+ Update Client Image (DATA URI) |
+ Delete Client Image |
+
+
+ |
+ |
+
+ Upload an Image for a Client (Multi-part form data)
+ |
+ Get Client Image (Binary file) |
+
+ Update Client Image (Multi-part form data)
+ |
+ |
+
@@ -2297,6 +2317,225 @@ No Request Body:
+
+
+
+
+
+
Client Images
+
The current API provides support for the addition of a single
+ image for a Client
+ Allowed formats: JPEG (.jpg or .jpeg), GIF (.gif) and PNG (.png)
+
+
The API supports two different Approaches for manipulating Client Images
+
+ - Data URI's:
+ For easier manipulation by Javascript clients etc in supported Browsers.
+
+ -
+ Multi-part form data:
+ Images can be uploaded using Multi part forms and downloaded as regular binary files
+
+
+
+
+
+
+
+
+
Get Client Image (DATA URI)
+
Example Requests:
+
clients/1/images
+
+
+
+
+GET https://DomainName/api/v1/clients/{clientId}/images
+
+
+data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
+bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp
+DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB
++BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA
+gAEAOg13B6R/SAgAAAAASUVORK5CYII=
+
+
+
+
+
+
+
+
Get Client Image (Binary file)
+
+
+
+GET https://DomainName/api/v1/clients/{clientId}/images
+
+
+POST clients/1/images
+Accept: application/octet-stream
+
+
+Not shown: The corresponding binary (image) file
+
+
+
+
+
+
+
+
Upload an Image for a Client (Data URI)
+
+
+
+POST https://DomainName/api/v1/clients/{clientId}/images
+
+
+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
+
+
+{
+ "resourceId": 1,
+ "changes": {},
+ "resourceIdentifier": "1"
+}
+
+
+
+
+
+
+
+
Upload an Image for a Client (Multi-part Form data)
+
+
+ Mandatory Fields |
+
+
+ | file
+ |
+
+
+
+ The form should contain a required named body part with the
+ name "file".
+ If you are using a HTML form, a snippet like
+ <input type="file" name="file"></input>
+ can be used for uploading the image file
+
+
+
+
+POST https://DomainName/api/v1/clients/{clientId}/images
+
+
+POST clients/1/images
+Content-Type: multipart/form-data
+Request Body: Not shown
+
+
+{
+ "resourceId": 1,
+ "changes": {},
+ "resourceIdentifier": "1"
+}
+
+
+
+
+
+
+
+
Update a Client Image (Data URI)
+
+
+
+PUT https://DomainName/api/v1/clients/{clientId}/images
+
+
+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
+
+
+{
+ "resourceId": 1,
+ "changes": {},
+ "resourceIdentifier": "1"
+}
+
+
+
+
+
+
+
+
Update a Client Image (Multi-part Form data)
+
+
+ Mandatory Fields |
+
+
+ | file
+ |
+
+
+
+ The form should contain a required named body part with the
+ name "file".
+ If you are using a HTML form, a snippet like
+ <input type="file" name="file"></input>
+ can be used for uploading the image file
+
+
+
+
+PUT https://DomainName/api/v1/clients/{clientId}/images
+
+
+PUT clients/1/images
+Content-Type: multipart/form-data
+Request Body: Not shown
+
+
+
+
+
+
+
+
Delete a Client Image
+
+
+
+DELETE https://DomainName/api/v1/clients/{clientId}/images
+
+
+DELETE clients/1/images
+Content-Type: application/json
+No Request Body:
+
+
+{
+ "resourceId": 1,
+ "changes": {},
+ "resourceIdentifier": "1"
+}
+
+
+
+
@@ -11619,6 +11858,26 @@ Content-Type: application/json
Update a Client Identifier |
Delete a Client Identifier |
+
+ | Client Images |
+ clients/{clientId}/images |
+ Upload an Image for a Client (as DATA URI) |
+ Get Client Image (DATA URI) |
+ Update Client Image (DATA URI) |
+ Delete Client Image |
+
+
+ |
+ |
+
+ Upload an Image for a Client (Multi-part form data)
+ |
+ Get Client Image (Binary file) |
+
+ Update Client Image (Multi-part form data)
+ |
+ |
+
| Loans |
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/FileUtils.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/FileUtils.java
index 369a5e1cb..5f464a226 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/FileUtils.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/FileUtils.java
@@ -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();
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java
index 1b8b8ed5a..2e829a4fc 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeDeletedException.java
@@ -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();
}
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java
new file mode 100755
index 000000000..36f1e9671
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/exception/LoanChargeCannotBeUpdatedException.java
@@ -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);
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientImagesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientImagesApiResource.java
index cdda2bff7..8c7ea0736 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientImagesApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientImagesApiResource.java
@@ -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 toApiJsonSerializer;
+ private final PlatformSecurityContext context;
+ private final ClientReadPlatformService clientReadPlatformService;
+ private final ClientWritePlatformService clientWritePlatformService;
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
- @Autowired
- public ClientImagesApiResource(final PlatformSecurityContext context,
- final ClientReadPlatformService readPlatformService,
- final ClientWritePlatformService clientWritePlatformService,
- final DefaultToApiJsonSerializer 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 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);
- }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ImageNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ImageNotFoundException.java
index f6198003d..c96d4fd74 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ImageNotFoundException.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ImageNotFoundException.java
@@ -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",
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
index f100719c1..1dcb5842a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
@@ -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:
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
index 08011ceb4..abf9b99a9 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
@@ -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() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 724ec2e77..cece3ba8f 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -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 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);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java
index 93df32e6a..2215708c5 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java
@@ -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");