Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Zoltan Nebli
2020-08-12 21:35:50 +02:00
1026 changed files with 13619 additions and 69517 deletions
@@ -29,16 +29,13 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
/**
* Fineract main() application which launches Fineract in an embedded Tomcat
* HTTP (using Spring Boot).
* Fineract main() application which launches Fineract in an embedded Tomcat HTTP (using Spring Boot).
*
* The DataSource used is a to a "normal" external database (not use MariaDB4j).
* This DataSource can be configured with parameters, see
* {@link DataSourceProperties}.
* The DataSource used is a to a "normal" external database (not use MariaDB4j). This DataSource can be configured with
* parameters, see {@link DataSourceProperties}.
*
* You can easily launch this via Debug as Java Application in your IDE -
* without needing command line Gradle stuff, no need to build and deploy a WAR,
* remote attachment etc.
* You can easily launch this via Debug as Java Application in your IDE - without needing command line Gradle stuff, no
* need to build and deploy a WAR, remote attachment etc.
*
* It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Fineract! ;-)
*
@@ -18,15 +18,12 @@
*/
package org.apache.fineract.accounting.accrual.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -44,9 +41,7 @@ import org.springframework.stereotype.Component;
@Path("/runaccruals")
@Component
@Scope("singleton")
@Api(tags = { "Periodic Accrual Accounting" })
@SwaggerDefinition(tags = {
@Tag(name = "Periodic Accrual Accounting", description = "Periodic Accrual is to accrue the loan income till the specific date or till batch job scheduled time.\n") })
@Tag(name = "Periodic Accrual Accounting", description = "Periodic Accrual is to accrue the loan income till the specific date or till batch job scheduled time.\n")
public class AccrualAccountingApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@@ -63,13 +58,11 @@ public class AccrualAccountingApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Executes Periodic Accrual Accounting", httpMethod = "POST", notes = "Mandatory Fields\n" + "\n" + "tillDate\n")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", required = true, type = "body", dataTypeClass = AccrualAccountingApiResourceSwagger.PostRunaccrualsRequest.class, value = "Request Body\n"
+ "\n" + "Field Descriptions: \n" + "tillDate: \n"
+ "which specifies periodic accruals should happen till the given Date") })
@ApiResponses({ @ApiResponse(code = 200, message = "OK") })
public String executePeriodicAccrualAccounting(@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(summary = "Executes Periodic Accrual Accounting", method = "POST", description = "Mandatory Fields\n" + "\n" + "tillDate\n")
@Parameter(required = true, schema = @Schema(implementation = AccrualAccountingApiResourceSwagger.PostRunaccrualsRequest.class, description = "Request Body\n"
+ "\n" + "Field Descriptions: \n" + "tillDate: \n" + "which specifies periodic accruals should happen till the given Date"))
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String executePeriodicAccrualAccounting(@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().excuteAccrualAccounting().withJson(jsonRequestBody).build();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.accrual.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Created by sanyam on 24/7/17.
@@ -30,18 +29,18 @@ final class AccrualAccountingApiResourceSwagger {
// don't allow to instantiate; use only for live API documentation
}
@ApiModel(value = "runaccrualsRequest")
@Schema(description = "runaccrualsRequest")
public static final class PostRunaccrualsRequest {
private PostRunaccrualsRequest() {
// don't allow to instantiate; use only for live API documentation
}
@ApiModelProperty(example = "en")
@Schema(example = "en")
public String locale;
@ApiModelProperty(example = "dd MMMM yyyy")
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
@ApiModelProperty(example = "04 June 2014", notes = "which specifies periodic accruals should happen till the given Date", required = true)
@Schema(example = "04 June 2014", description = "which specifies periodic accruals should happen till the given Date", required = true)
public String tillDate;
}
@@ -45,8 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link GuarantorCommand}'s.
* Implementation of {@link FromApiJsonDeserializer} for {@link GuarantorCommand}'s.
*/
@Component
public final class AccrualAccountingDataValidator {
@@ -61,7 +61,7 @@ public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccount
.resource(PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME);
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE,
e.getMessage());
throw new PlatformApiDataValidationException(dataValidationErrors);
throw new PlatformApiDataValidationException(dataValidationErrors, e);
}
return CommandProcessingResult.empty();
}
@@ -18,15 +18,15 @@
*/
package org.apache.fineract.accounting.closure.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -61,12 +61,11 @@ import org.springframework.stereotype.Component;
@Path("/glclosures")
@Component
@Scope("singleton")
@Api(tags = { "Accounting Closure" })
@SwaggerDefinition(tags = {
@Tag(name = "Accounting Closure", description = "An accounting closure indicates that no more journal entries may be logged (or reversed) in the system, either manually or via the portfolio with an entry date prior to the defined closure date\n"
+ "\n" + "Field Descriptions\n" + "closingDate\n" + "The date for which the accounting closure is defined\n" + "officeId\n"
+ "The identifer of the branch for which accounting has been closed\n" + "comments\n"
+ "Description associated with an Accounting closure") })
@Tag(name = "Accounting Closure", description = "An accounting closure indicates that no more journal entries may be logged (or reversed) in the system, either manually or via the portfolio with an entry date prior to the defined closure date\n"
+ "\n" + "Field Descriptions\n" + "closingDate\n" + "The date for which the accounting closure is defined\n" + "officeId\n"
+ "The identifer of the branch for which accounting has been closed\n" + "comments\n"
+ "Description associated with an Accounting closure")
public class GLClosuresApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
@@ -98,11 +97,11 @@ public class GLClosuresApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "List Accounting closures", notes = "Example Requests:\n" + "\n" + "glclosures")
@Operation(summary = "List Accounting closures", description = "Example Requests:\n" + "\n" + "glclosures")
@ApiResponses({
@ApiResponse(code = 200, response = GLClosuresApiResourceSwagger.GetGlClosureResponse.class, message = "", responseContainer = "list") })
@ApiResponse(responseCode = "200", content = @Content(array = @ArraySchema(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class, description = "")))) })
public String retrieveAllClosures(@Context final UriInfo uriInfo,
@QueryParam("officeId") @ApiParam(value = "officeId") final Long officeId) {
@QueryParam("officeId") @Parameter(name = "officeId") final Long officeId) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
final List<GLClosureData> glClosureDatas = this.glClosureReadPlatformService.retrieveAllGLClosures(officeId);
@@ -115,10 +114,11 @@ public class GLClosuresApiResource {
@Path("{glClosureId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve an Accounting Closure", notes = "Example Requests:\n" + "\n" + "glclosures/1\n" + "\n" + "\n"
@Operation(summary = "Retrieve an Accounting Closure", description = "Example Requests:\n" + "\n" + "glclosures/1\n" + "\n" + "\n"
+ "/glclosures/1?fields=officeName,closingDate")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLClosuresApiResourceSwagger.GetGlClosureResponse.class) })
public String retreiveClosure(@PathParam("glClosureId") @ApiParam(value = "glClosureId") final Long glClosureId,
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class))) })
public String retreiveClosure(@PathParam("glClosureId") @Parameter(description = "glClosureId") final Long glClosureId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -137,11 +137,11 @@ public class GLClosuresApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create an Accounting Closure", notes = "Mandatory Fields\n" + "officeId,closingDate")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", required = true, type = "body", dataTypeClass = GLClosuresApiResourceSwagger.PostGlClosuresRequest.class, value = "Request Body") })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLClosuresApiResourceSwagger.PostGlClosuresResponse.class) })
public String createGLClosure(@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(summary = "Create an Accounting Closure", description = "Mandatory Fields\n" + "officeId,closingDate")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PostGlClosuresRequest.class, description = "Request Body")))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PostGlClosuresResponse.class))) })
public String createGLClosure(@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createGLClosure().withJson(jsonRequestBody).build();
@@ -154,12 +154,12 @@ public class GLClosuresApiResource {
@Path("{glClosureId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Update an Accounting closure", notes = "Once an accounting closure is created, only the comments associated with it may be edited")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "Request body", dataType = "body", dataTypeClass = GLClosuresApiResourceSwagger.PutGlClosuresRequest.class, required = true) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLClosuresApiResourceSwagger.PutGlClosuresResponse.class) })
public String updateGLClosure(@PathParam("glClosureId") @ApiParam(value = "glClosureId") final Long glClosureId,
@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(summary = "Update an Accounting closure", description = "Once an accounting closure is created, only the comments associated with it may be edited")
@RequestBody(content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PutGlClosuresRequest.class, required = true)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PutGlClosuresResponse.class))) })
public String updateGLClosure(@PathParam("glClosureId") @Parameter(description = "glClosureId") final Long glClosureId,
@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGLClosure(glClosureId).withJson(jsonRequestBody).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -171,9 +171,10 @@ public class GLClosuresApiResource {
@Path("{glClosureId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Delete an accounting closure", notes = "Note: Only the latest accounting closure associated with a branch may be deleted.")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLClosuresApiResourceSwagger.DeleteGlClosuresResponse.class) })
public String deleteGLClosure(@PathParam("glClosureId") @ApiParam(value = "glclosureId") final Long glClosureId) {
@Operation(summary = "Delete an accounting closure", description = "Note: Only the latest accounting closure associated with a branch may be deleted.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.DeleteGlClosuresResponse.class))) })
public String deleteGLClosure(@PathParam("glClosureId") @Parameter(description = "glclosureId") final Long glClosureId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLClosure(glClosureId).build();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.closure.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.joda.time.LocalDate;
final class GLClosuresApiResourceSwagger {
@@ -29,113 +28,112 @@ final class GLClosuresApiResourceSwagger {
}
/**
* TODO: describe where this belongs: {@link GLClosuresApiResource }
* {@Link GLClosuresApiResource}
* TODO: describe where this belongs: {@link GLClosuresApiResource } {@link GLClosuresApiResource}
*/
// Check !!
@ApiModel(value = "GetGLClosureResponse")
@Schema(description = "GetGLClosureResponse")
public static final class GetGlClosureResponse {
private GetGlClosureResponse() {
// dont allow to initiatiate
}
@ApiModelProperty(example = "7")
@Schema(example = "7")
public Long id;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "Head Office")
@Schema(example = "Head Office")
public String officeName;
@ApiModelProperty(example = "2013,1,2")
@Schema(example = "2013,1,2")
public LocalDate closingDate;
@ApiModelProperty(example = "false")
@Schema(example = "false")
public boolean deleted;
@ApiModelProperty(example = "2013,1,3")
@Schema(example = "2013,1,3")
public LocalDate createdDate;
@ApiModelProperty(example = "2013,1,3")
@Schema(example = "2013,1,3")
public LocalDate lastUpdatedDate;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long createdByUserId;
@ApiModelProperty(example = "mifos")
@Schema(example = "mifos")
public String createdByUsername;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long lastUpdatedByUserId;
@ApiModelProperty(example = "mifos")
@Schema(example = "mifos")
public String lastUpdatedByUsername;
@ApiModelProperty(example = "closed")
@Schema(example = "closed")
public String comments;
}
@ApiModel(value = "PostGLCLosuresRequest")
@Schema(description = "PostGLCLosuresRequest")
public static final class PostGlClosuresRequest {
private PostGlClosuresRequest() {
// don't allow to instantiate; use only for live API documentation
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "06 December 2012")
@Schema(example = "06 December 2012")
public LocalDate closingDate;
@ApiModelProperty(example = "The accountants are heading for a carribean vacation")
@Schema(example = "The accountants are heading for a carribean vacation")
public String comments;
@ApiModelProperty(example = "en")
@Schema(example = "en")
public String locale;
@ApiModelProperty(example = "dd MMMM yyyy")
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
}
@ApiModel(value = "PostGlClosuresResponse")
@Schema(description = "PostGlClosuresResponse")
public static final class PostGlClosuresResponse {
private PostGlClosuresResponse() {
// don't allow to instantiate; use only for live API documentation
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "9")
@Schema(example = "9")
public Long resourceId;
}
@ApiModel(value = "PutGlClosuresRequest")
@Schema(description = "PutGlClosuresRequest")
public static final class PutGlClosuresRequest {
private PutGlClosuresRequest() {
// don't allow to instantiate; use only for live API documentation
}
@ApiModelProperty(example = "All transactions verified by Johnny Cash")
@Schema(example = "All transactions verified by Johnny Cash")
public String comments;
}
@ApiModel(value = "PutGlClosuresResponse")
@Schema(description = "PutGlClosuresResponse")
public static final class PutGlClosuresResponse {
private PutGlClosuresResponse() {
// don't allow to instantiate; use only for live API documentation
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "9")
@Schema(example = "9")
public Long resourceId;
@ApiModelProperty(example = "All transactions verified by Johnny Cash")
@Schema(example = "All transactions verified by Johnny Cash")
public String comments;
}
@ApiModel(value = "DeleteGlClosuresResponse")
@Schema(description = "DeleteGlClosuresResponse")
public static final class DeleteGlClosuresResponse {
private DeleteGlClosuresResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "9")
@Schema(example = "9")
public Long resourceId;
}
}
@@ -26,8 +26,7 @@ import org.joda.time.LocalDate;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google-gson will produce json from fields
* of object.
* Note: no getter/setters required as google-gson will produce json from fields of object.
*/
public class GLClosureData {
@@ -22,8 +22,7 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainR
import org.joda.time.LocalDate;
/**
* A {@link RuntimeException} thrown when a GL Closure for a given date and
* Office combination is already present
* A {@link RuntimeException} thrown when a GL Closure for a given date and Office combination is already present
*/
public class GLClosureDuplicateException extends AbstractPlatformDomainRuleException {
@@ -19,14 +19,18 @@
package org.apache.fineract.accounting.closure.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when GL account closure resources are not
* found.
* A {@link RuntimeException} thrown when GL account closure resources are not found.
*/
public class GLClosureNotFoundException extends AbstractPlatformResourceNotFoundException {
public GLClosureNotFoundException(final Long id) {
super("error.msg.glclosure.id.invalid", "Accounting Closure with identifier " + id + " does not exist", id);
}
public GLClosureNotFoundException(final Long id, EmptyResultDataAccessException e) {
super("error.msg.glclosure.id.invalid", "Accounting Closure with identifier " + id + " does not exist", id, e);
}
}
@@ -36,8 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link GuarantorCommand}'s.
* Implementation of {@link FromApiJsonDeserializer} for {@link GuarantorCommand}'s.
*/
@Component
public final class GLClosureCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<GLClosureCommand> {
@@ -106,7 +106,7 @@ public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformSe
return glAccountData;
} catch (final EmptyResultDataAccessException e) {
throw new GLClosureNotFoundException(glClosureId);
throw new GLClosureNotFoundException(glClosureId, e);
}
}
@@ -41,6 +41,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -91,8 +93,9 @@ public class GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosure
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
.withEntityId(glClosure.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleGLClosureIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleGLClosureIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
@@ -124,8 +127,7 @@ public class GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosure
.orElseThrow(() -> new GLClosureNotFoundException(glClosureId));
/**
* check if any closures are present for this branch at a later date
* than this closure date
* check if any closures are present for this branch at a later date than this closure date
**/
final Date closureDate = glClosure.getClosingDate();
final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(glClosure.getOffice().getId());
@@ -142,9 +144,9 @@ public class GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosure
/**
* @param command
* @param dve
* @param dve
*/
private void handleGLClosureIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
private void handleGLClosureIntegrityIssues(final JsonCommand command, final Throwable realCause, NonTransientDataAccessException dve) {
if (realCause.getMessage().contains("office_id_closing_date")) {
throw new GLClosureDuplicateException(command.longValueOfParameterNamed(GLClosureJsonInputParams.OFFICE_ID.getValue()),
new LocalDate(command.dateValueOfParameterNamed(GLClosureJsonInputParams.CLOSING_DATE.getValue())));
@@ -102,8 +102,7 @@ public class AccountingConstants {
}
/***
* Enum of all accounting related input parameter names used while
* creating/updating a loan product
* Enum of all accounting related input parameter names used while creating/updating a loan product
***/
public static enum LoanProductAccountingParams {
@@ -201,8 +200,7 @@ public class AccountingConstants {
}
/***
* Enum of all accounting related input parameter names used while
* creating/updating a savings product
* Enum of all accounting related input parameter names used while creating/updating a savings product
***/
public static enum SavingProductAccountingParams {
@@ -374,8 +372,7 @@ public class AccountingConstants {
}
/***
* Enum of all accounting related input parameter names used while
* creating/updating a savings product
* Enum of all accounting related input parameter names used while creating/updating a savings product
***/
public static enum SharesProductAccountingParams {
@@ -18,15 +18,15 @@
*/
package org.apache.fineract.accounting.financialactivityaccount.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -56,11 +56,9 @@ import org.springframework.stereotype.Component;
@Path("/financialactivityaccounts")
@Component
@Scope("singleton")
@Api(tags = { "Mapping Financial Activities to Accounts" })
@SwaggerDefinition(tags = {
@Tag(name = "Mapping Financial Activities to Accounts", description = "Organization Level Financial Activities like Asset and Liability Transfer can be mapped to GL Account. Integrated accounting takes these accounts into consideration when an Account transfer is made between a savings to loan/savings account and vice-versa\n"
+ "\n" + "Field Descriptions\n" + "financialActivityId\n" + "The identifier of the Financial Activity\n" + "glAccountId\n"
+ "The identifier of a GL Account ( Ledger Account) which shall be used as the default account for the selected Financial Activity") })
@Tag(name = "Mapping Financial Activities to Accounts", description = "Organization Level Financial Activities like Asset and Liability Transfer can be mapped to GL Account. Integrated accounting takes these accounts into consideration when an Account transfer is made between a savings to loan/savings account and vice-versa\n"
+ "\n" + "Field Descriptions\n" + "financialActivityId\n" + "The identifier of the Financial Activity\n" + "glAccountId\n"
+ "The identifier of a GL Account ( Ledger Account) which shall be used as the default account for the selected Financial Activity")
public class FinancialActivityAccountsApiResource {
private final FinancialActivityAccountReadPlatformService financialActivityAccountReadPlatformService;
@@ -101,10 +99,10 @@ public class FinancialActivityAccountsApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "List Financial Activities to Accounts Mappings", notes = "Example Requests:\n" + "\n"
@Operation(summary = "List Financial Activities to Accounts Mappings", description = "Example Requests:\n" + "\n"
+ "financialactivityaccounts")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = FinancialActivityAccountsApiResourceSwagger.GetFinancialActivityAccountsResponse.class, responseContainer = "list") })
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.GetFinancialActivityAccountsResponse.class)))) })
public String retrieveAll(@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(FinancialActivityAccountsConstants.resourceNameForPermission);
@@ -119,11 +117,12 @@ public class FinancialActivityAccountsApiResource {
@Path("{mappingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve a Financial Activity to Account Mapping\n", notes = "Example Requests:\n" + "\n"
@Operation(summary = "Retrieve a Financial Activity to Account Mapping\n", description = "Example Requests:\n" + "\n"
+ "financialactivityaccounts/1")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = FinancialActivityAccountsApiResourceSwagger.GetFinancialActivityAccountsResponse.class) })
public String retreive(@PathParam("mappingId") @ApiParam(value = "mappingId") final Long mappingId, @Context final UriInfo uriInfo) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.GetFinancialActivityAccountsResponse.class))) })
public String retreive(@PathParam("mappingId") @Parameter(description = "mappingId") final Long mappingId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(FinancialActivityAccountsConstants.resourceNameForPermission);
@@ -141,13 +140,12 @@ public class FinancialActivityAccountsApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create a new Financial Activity to Accounts Mapping", notes = "Mandatory Fields\n"
@Operation(summary = "Create a new Financial Activity to Accounts Mapping", description = "Mandatory Fields\n"
+ "financialActivityId, glAccountId")
@ApiImplicitParams({
@ApiImplicitParam(value = "Request body", paramType = "body", dataType = "body", dataTypeClass = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class) })
@RequestBody(content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class)))
@ApiResponses({
@ApiResponse(code = 200, message = "", response = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsResponse.class) })
public String createGLAccount(@ApiParam(hidden = true) final String jsonRequestBody) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsResponse.class))) })
public String createGLAccount(@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createOfficeToGLAccountMapping().withJson(jsonRequestBody)
.build();
@@ -161,13 +159,12 @@ public class FinancialActivityAccountsApiResource {
@Path("{mappingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Update a Financial Activity to Account Mapping", notes = "the API updates the Ledger account linked to a Financial Activity \n")
@ApiImplicitParams({
@ApiImplicitParam(value = "Request body", dataType = "body", paramType = "body", dataTypeClass = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class) })
@Operation(summary = "Update a Financial Activity to Account Mapping", description = "the API updates the Ledger account linked to a Financial Activity \n")
@RequestBody(content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class)))
@ApiResponses({
@ApiResponse(code = 200, message = "", response = FinancialActivityAccountsApiResourceSwagger.PutFinancialActivityAccountsResponse.class) })
public String updateGLAccount(@PathParam("mappingId") @ApiParam(value = "mappingId") final Long mappingId,
@ApiParam(hidden = true) final String jsonRequestBody) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PutFinancialActivityAccountsResponse.class))) })
public String updateGLAccount(@PathParam("mappingId") @Parameter(description = "mappingId") final Long mappingId,
@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateOfficeToGLAccountMapping(mappingId)
.withJson(jsonRequestBody).build();
@@ -181,10 +178,10 @@ public class FinancialActivityAccountsApiResource {
@Path("{mappingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Delete a Financial Activity to Account Mapping")
@Operation(summary = "Delete a Financial Activity to Account Mapping")
@ApiResponses({
@ApiResponse(code = 200, message = "OK", response = FinancialActivityAccountsApiResourceSwagger.DeleteFinancialActivityAccountsResponse.class) })
public String deleteGLAccount(@PathParam("mappingId") @ApiParam(value = "mappingId") final Long mappingId) {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.DeleteFinancialActivityAccountsResponse.class))) })
public String deleteGLAccount(@PathParam("mappingId") @Parameter(description = "mappingId") final Long mappingId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteOfficeToGLAccountMapping(mappingId).build();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.financialactivityaccount.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.fineract.accounting.financialactivityaccount.data.FinancialActivityData;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
@@ -30,58 +29,58 @@ final class FinancialActivityAccountsApiResourceSwagger {
private FinancialActivityAccountsApiResourceSwagger() {}
@ApiModel(value = "GetFinancialActivityAccountsResponse")
@Schema(description = "GetFinancialActivityAccountsResponse")
public static final class GetFinancialActivityAccountsResponse {
private GetFinancialActivityAccountsResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long id;
public FinancialActivityData financialActivityData;
public GLAccountData glAccountData;
}
@ApiModel(value = "PostFinancialActivityAccountsRequest")
@Schema(description = "PostFinancialActivityAccountsRequest")
public static final class PostFinancialActivityAccountsRequest {
private PostFinancialActivityAccountsRequest() {
}
@ApiModelProperty(example = "200")
@Schema(example = "200")
public Long financialActivityId;
@ApiModelProperty(example = "2")
@Schema(example = "2")
public Long glAccountId;
}
@ApiModel(value = "PostFinancialActivityAccountsResponse")
@Schema(description = "PostFinancialActivityAccountsResponse")
public static final class PostFinancialActivityAccountsResponse {
private PostFinancialActivityAccountsResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
@ApiModel(value = "PutFinancialActivityAccountsRequest")
@Schema(description = "PutFinancialActivityAccountsRequest")
public static final class PutFinancialActivityAccountsRequest {
private PutFinancialActivityAccountsRequest() {
}
@ApiModelProperty(example = "200")
@Schema(example = "200")
public Long financialActivityId;
@ApiModelProperty(example = "3")
@Schema(example = "3")
public Long glAccountId;
}
@ApiModel(value = "PutFinancialActivityAccountsResponse")
@Schema(description = "PutFinancialActivityAccountsResponse")
public static final class PutFinancialActivityAccountsResponse {
private PutFinancialActivityAccountsResponse() {
@@ -92,23 +91,23 @@ final class FinancialActivityAccountsApiResourceSwagger {
private PutFinancialActivityAccountscommentsSwagger() {}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long glAccountId;
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
public PutFinancialActivityAccountscommentsSwagger comments;
}
@ApiModel(value = "DeleteFinancialActivityAccountsResponse")
@Schema(description = "DeleteFinancialActivityAccountsResponse")
public static final class DeleteFinancialActivityAccountsResponse {
private DeleteFinancialActivityAccountsResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
@@ -39,7 +39,7 @@ public class FinancialActivityData {
}
public FinancialActivityData(Integer id, String name, GLAccountType mappedGLAccountType) {
super();
this.id = id;
this.name = name;
this.mappedGLAccountType = mappedGLAccountType;
@@ -25,8 +25,7 @@ import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link FinancialActivityAccountRepository} that adds NULL
* checking and Error handling capabilities
* Wrapper for {@link FinancialActivityAccountRepository} that adds NULL checking and Error handling capabilities
* </p>
*/
@Service
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.financialactivityaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class DuplicateFinancialActivityAccountFoundException extends AbstractPlatformDomainRuleException {
@@ -23,8 +23,7 @@ import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class FinancialActivityAccountInvalidException extends AbstractPlatformDomainRuleException {
@@ -19,10 +19,10 @@
package org.apache.fineract.accounting.financialactivityaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class FinancialActivityAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
@@ -30,6 +30,10 @@ public class FinancialActivityAccountNotFoundException extends AbstractPlatformR
super("error.msg.financialActivityAccount.not.found", "Financial Activity account with Id " + id + " does not exist", id);
}
public FinancialActivityAccountNotFoundException(final Long id, EmptyResultDataAccessException e) {
super("error.msg.financialActivityAccount.not.found", "Financial Activity account with Id " + id + " does not exist", id, e);
}
public FinancialActivityAccountNotFoundException(final Integer financialActivityType) {
super("error.msg.financialActivityAccount.not.found",
"Financial Activity account with for the financial Activity with Id " + financialActivityType + " does not exist",
@@ -67,7 +67,7 @@ public class FinancialActivityAccountReadPlatformServiceImpl implements Financia
return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), this.financialActivityAccountMapper,
new Object[] { financialActivityAccountId });
} catch (final EmptyResultDataAccessException e) {
throw new FinancialActivityAccountNotFoundException(financialActivityAccountId);
throw new FinancialActivityAccountNotFoundException(financialActivityAccountId, e);
}
}
@@ -39,6 +39,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
@Service
@@ -77,20 +79,18 @@ public class FinancialActivityAccountWritePlatformServiceImpl implements Financi
.withCommandId(command.commandId()) //
.withEntityId(financialActivityAccount.getId()) //
.build();
} catch (DataIntegrityViolationException dataIntegrityViolationException) {
handleFinancialActivityAccountDataIntegrityIssues(command, dataIntegrityViolationException.getMostSpecificCause(),
dataIntegrityViolationException);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleFinancialActivityAccountDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
} catch (final PersistenceException ee) {
Throwable throwable = ExceptionUtils.getRootCause(ee.getCause());
final Throwable throwable = ExceptionUtils.getRootCause(ee.getCause());
handleFinancialActivityAccountDataIntegrityIssues(command, throwable, ee);
return CommandProcessingResult.empty();
}
}
/**
* Validate that the GL Account is appropriate for the particular Financial
* Activity Type
* Validate that the GL Account is appropriate for the particular Financial Activity Type
**/
private void validateFinancialActivityAndAccountMapping(FinancialActivityAccount financialActivityAccount) {
FinancialActivity financialActivity = FinancialActivity.fromInt(financialActivityAccount.getFinancialActivityType());
@@ -129,9 +129,8 @@ public class FinancialActivityAccountWritePlatformServiceImpl implements Financi
.withEntityId(financialActivityAccountId) //
.with(changes) //
.build();
} catch (DataIntegrityViolationException dataIntegrityViolationException) {
handleFinancialActivityAccountDataIntegrityIssues(command, dataIntegrityViolationException.getMostSpecificCause(),
dataIntegrityViolationException);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleFinancialActivityAccountDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
} catch (final PersistenceException ee) {
Throwable throwable = ExceptionUtils.getRootCause(ee.getCause());
@@ -152,7 +151,19 @@ public class FinancialActivityAccountWritePlatformServiceImpl implements Financi
}
private void handleFinancialActivityAccountDataIntegrityIssues(final JsonCommand command, final Throwable realCause,
final Exception dve) {
final NonTransientDataAccessException dve) {
if (realCause.getMessage().contains("financial_activity_type")) {
final Integer financialActivityId = command
.integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
throw new DuplicateFinancialActivityAccountFoundException(financialActivityId);
}
LOG.error("Error occured.", dve);
throw new PlatformDataIntegrityException("error.msg.glAccount.unknown.data.integrity.issue",
"Unknown data integrity issue with resource GL Account: " + realCause.getMessage());
}
private void handleFinancialActivityAccountDataIntegrityIssues(JsonCommand command, Throwable realCause, PersistenceException dve) {
if (realCause.getMessage().contains("financial_activity_type")) {
final Integer financialActivityId = command
.integerValueSansLocaleOfParameterNamed(FinancialActivityAccountsJsonInputParams.FINANCIAL_ACTIVITY_ID.getValue());
@@ -20,15 +20,15 @@ package org.apache.fineract.accounting.glaccount.api;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
@@ -75,10 +75,8 @@ import org.springframework.stereotype.Component;
@Path("/glaccounts")
@Component
@Scope("singleton")
@Api(tags = { "General Ledger Account" })
@SwaggerDefinition(tags = {
@Tag(name = "General Ledger Account", description = "Ledger accounts represent an Individual account within an Organizations Chart Of Accounts(COA) and are assigned a name and unique number by which they can be identified. \n"
+ "All transactions relating to a company's assets, liabilities, owners' equity, revenue and expenses are recorded against these accounts") })
@Tag(name = "General Ledger Account", description = "Ledger accounts represent an Individual account within an Organizations Chart Of Accounts(COA) and are assigned a name and unique number by which they can be identified. \n"
+ "All transactions relating to a company's assets, liabilities, owners' equity, revenue and expenses are recorded against these accounts")
public class GLAccountsApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "parentId", "glCode", "disabled",
@@ -121,13 +119,15 @@ public class GLAccountsApiResource {
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve GL Accounts Template", notes = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ "\n" + "Field Defaults\n" + "Allowed Value Lists\n" + "Example Request:\n" + "\n" + "glaccounts/template\n"
+ "glaccounts/template?type=1\n" + "\n" + "type is optional and integer value from 1 to 5.\n" + "\n" + "1.Assets \n"
+ "2.Liabilities \n" + "3.Equity \n" + "4.Income \n" + "5.Expenses")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.GetGLAccountsTemplateResponse.class) })
@Operation(tags = {
"General Ledger Account" }, summary = "Retrieve GL Accounts Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ "\n" + "Field Defaults\n" + "Allowed Value Lists\n" + "Example Request:\n" + "\n" + "glaccounts/template\n"
+ "glaccounts/template?type=1\n" + "\n" + "type is optional and integer value from 1 to 5.\n" + "\n" + "1.Assets \n"
+ "2.Liabilities \n" + "3.Equity \n" + "4.Income \n" + "5.Expenses")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.GetGLAccountsTemplateResponse.class))) })
public String retrieveNewAccountDetails(@Context final UriInfo uriInfo,
@QueryParam("type") @ApiParam(value = "type") final Integer type) {
@QueryParam("type") @Parameter(description = "type") final Integer type) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -141,18 +141,19 @@ public class GLAccountsApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "List General Ledger Accounts", notes = "ARGUMENTS\n"
@Operation(tags = { "General Ledger Account" }, summary = "List General Ledger Accounts", description = "ARGUMENTS\n"
+ "type Integer optional manualEntriesAllowed boolean optional usage Integer optional disabled boolean optional parentId Long optional tagId Long optional\n"
+ "Example Requests:\n" + "\n" + "glaccounts\n" + "\n" + "\n"
+ "glaccounts?type=1&manualEntriesAllowed=true&usage=1&disabled=false\n" + "\n" + "glaccounts?fetchRunningBalance=true")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.GetGLAccountsResponse.class, responseContainer = "list") })
public String retrieveAllAccounts(@Context final UriInfo uriInfo, @QueryParam("type") @ApiParam(value = "type") final Integer type,
@QueryParam("searchParam") @ApiParam(value = "searchParam") final String searchParam,
@QueryParam("usage") @ApiParam(value = "usage") final Integer usage,
@QueryParam("manualEntriesAllowed") @ApiParam(value = "manualEntriesAllowed") final Boolean manualEntriesAllowed,
@QueryParam("disabled") @ApiParam(value = "disabled") final Boolean disabled,
@QueryParam("fetchRunningBalance") @ApiParam(value = "fetchRunningBalance") final boolean runningBalance) {
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = GLAccountsApiResourceSwagger.GetGLAccountsResponse.class)))) })
public String retrieveAllAccounts(@Context final UriInfo uriInfo,
@QueryParam("type") @Parameter(description = "type") final Integer type,
@QueryParam("searchParam") @Parameter(description = "searchParam") final String searchParam,
@QueryParam("usage") @Parameter(description = "usage") final Integer usage,
@QueryParam("manualEntriesAllowed") @Parameter(description = "manualEntriesAllowed") final Boolean manualEntriesAllowed,
@QueryParam("disabled") @Parameter(description = "disabled") final Boolean disabled,
@QueryParam("fetchRunningBalance") @Parameter(description = "fetchRunningBalance") final boolean runningBalance) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(false, runningBalance);
@@ -167,13 +168,14 @@ public class GLAccountsApiResource {
@Path("{glAccountId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve a General Ledger Account", notes = "Example Requests:\n" + "\n" + "glaccounts/1\n" + "\n" + "\n"
+ "glaccounts/1?template=true\n" + "\n" + "\n" + "glaccounts/1?fields=name,glCode\n" + "\n" + "\n"
+ "glaccounts/1?fetchRunningBalance=true")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.GetGLAccountsResponse.class) })
public String retreiveAccount(@PathParam("glAccountId") @ApiParam(value = "glAccountId") final Long glAccountId,
@Operation(tags = { "General Ledger Account" }, summary = "Retrieve a General Ledger Account", description = "Example Requests:\n"
+ "\n" + "glaccounts/1\n" + "\n" + "\n" + "glaccounts/1?template=true\n" + "\n" + "\n" + "glaccounts/1?fields=name,glCode\n"
+ "\n" + "\n" + "glaccounts/1?fetchRunningBalance=true")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.GetGLAccountsResponse.class))) })
public String retreiveAccount(@PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId,
@Context final UriInfo uriInfo,
@QueryParam("fetchRunningBalance") @ApiParam(value = "fetchRunningBalance") final boolean runningBalance) {
@QueryParam("fetchRunningBalance") @Parameter(description = "fetchRunningBalance") final boolean runningBalance) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -190,12 +192,13 @@ public class GLAccountsApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create a General Ledger Account", notes = "Note: You may optionally create Hierarchical Chart of Accounts by using the \"parentId\" property of an Account\n"
+ "Mandatory Fields: \n" + "name, glCode, type, usage and manualEntriesAllowed")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = GLAccountsApiResourceSwagger.PostGLAccountsRequest.class) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.PostGLAccountsResponse.class) })
public String createGLAccount(@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(tags = {
"General Ledger Account" }, summary = "Create a General Ledger Account", description = "Note: You may optionally create Hierarchical Chart of Accounts by using the \"parentId\" property of an Account\n"
+ "Mandatory Fields: \n" + "name, glCode, type, usage and manualEntriesAllowed")
@RequestBody(content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PostGLAccountsRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PostGLAccountsResponse.class))) })
public String createGLAccount(@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createGLAccount().withJson(jsonRequestBody).build();
@@ -208,13 +211,14 @@ public class GLAccountsApiResource {
@Path("{glAccountId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Update an Accounting closure", notes = "Once an accounting closure is created, only the comments associated with it may be edited\n"
+ "\n")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = GLAccountsApiResourceSwagger.PutGLAccountsRequest.class) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.PutGLAccountsResponse.class) })
public String updateGLAccount(@PathParam("glAccountId") @ApiParam(value = "glAccountId") final Long glAccountId,
@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(tags = {
"General Ledger Account" }, summary = "Update an Accounting closure", description = "Once an accounting closure is created, only the comments associated with it may be edited\n"
+ "\n")
@RequestBody(content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PutGLAccountsRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PutGLAccountsResponse.class))) })
public String updateGLAccount(@PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId,
@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGLAccount(glAccountId).withJson(jsonRequestBody).build();
@@ -227,9 +231,11 @@ public class GLAccountsApiResource {
@Path("{glAccountId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Delete an accounting closure", notes = "Note: Only the latest accounting closure associated with a branch may be deleted.")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = GLAccountsApiResourceSwagger.DeleteGLAccountsRequest.class) })
public String deleteGLAccount(@PathParam("glAccountId") @ApiParam(value = "glAccountId") final Long glAccountId) {
@Operation(tags = {
"General Ledger Account" }, summary = "Delete an accounting closure", description = "Note: Only the latest accounting closure associated with a branch may be deleted.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.DeleteGLAccountsRequest.class))) })
public String deleteGLAccount(@PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLAccount(glAccountId).build();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.glaccount.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
@@ -35,57 +34,57 @@ final class GLAccountsApiResourceSwagger {
}
@ApiModel(value = "GetGLAccountsResponse")
@Schema(description = "GetGLAccountsResponse")
public static final class GetGLAccountsResponse {
private GetGLAccountsResponse() {
}
@ApiModelProperty(example = "16")
@Schema(example = "16")
public Long id;
@ApiModelProperty(example = "Cash")
@Schema(example = "Cash")
public String name;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long parentId;
@ApiModelProperty(example = "100001")
@Schema(example = "100001")
public String glCode;
@ApiModelProperty(example = "false")
@Schema(example = "false")
public Boolean disabled;
@ApiModelProperty(example = "true")
@Schema(example = "true")
public Boolean manualEntriesAllowed;
public EnumOptionData type;
public EnumOptionData usage;
@ApiModelProperty(example = "Desc")
@Schema(example = "Desc")
public String description;
@ApiModelProperty(example = "....Cash")
@Schema(example = "....Cash")
public String nameDecorated;
public CodeValueData tagId;
@ApiModelProperty(example = "118437")
@Schema(example = "118437")
public Long organizationRunningBalance;
}
@ApiModel(value = "GetGLAccountsTemplateResponse")
@Schema(description = "GetGLAccountsTemplateResponse")
public static final class GetGLAccountsTemplateResponse {
private GetGLAccountsTemplateResponse() {
}
@ApiModelProperty(example = "false")
@Schema(example = "false")
public Boolean disabled;
@ApiModelProperty(example = "true")
@Schema(example = "true")
public Boolean manualEntriesAllowed;
public EnumOptionData type;
@@ -104,83 +103,83 @@ final class GLAccountsApiResourceSwagger {
}
@ApiModel(value = "PostGLAccountsRequest")
@Schema(description = "PostGLAccountsRequest")
public static final class PostGLAccountsRequest {
private PostGLAccountsRequest() {
}
@ApiModelProperty(example = "Cash at Bangalore")
@Schema(example = "Cash at Bangalore")
public String name;
@ApiModelProperty(example = "100001")
@Schema(example = "100001")
public String glCode;
@ApiModelProperty(example = "true")
@Schema(example = "true")
public Boolean manualEntriesAllowed;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public String type;
@ApiModelProperty(example = "10")
@Schema(example = "10")
public String tagId;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long parentId;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public EnumOptionData usage;
@ApiModelProperty(example = "Desc")
@Schema(example = "Desc")
public String description;
}
@ApiModel(value = "PostGLAccountsResponse")
@Schema(description = "PostGLAccountsResponse")
public static final class PostGLAccountsResponse {
private PostGLAccountsResponse() {
}
@ApiModelProperty(example = "22")
@Schema(example = "22")
public int resourceId;
}
@ApiModel(value = "PutGLAccountsRequest")
@Schema(description = "PutGLAccountsRequest")
public static final class PutGLAccountsRequest {
private PutGLAccountsRequest() {
}
@ApiModelProperty(example = "Cash at Bangalore")
@Schema(example = "Cash at Bangalore")
public String name;
}
@ApiModel(value = "PutGLAccountsResponse")
@Schema(description = "PutGLAccountsResponse")
public static final class PutGLAccountsResponse {
private PutGLAccountsResponse() {
}
@ApiModel
@Schema
public static final class PutGLAccountsResponsechangesSwagger {
private PutGLAccountsResponsechangesSwagger() {}
@ApiModelProperty(example = "Cash at Bangalore")
@Schema(example = "Cash at Bangalore")
public String name;
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public int resourceId;
public PutGLAccountsResponsechangesSwagger changes;
}
@ApiModel(value = "DeleteGLAccountsRequest")
@Schema(description = "DeleteGLAccountsRequest")
public static final class DeleteGLAccountsRequest {
private DeleteGLAccountsRequest() {
@@ -193,7 +192,7 @@ final class GLAccountsApiResourceSwagger {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public int resourceId;
private DeleteGLAccountsRequestchangesSwagger changes;
@@ -30,8 +30,7 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google-gson will produce json from fields
* of object.
* Note: no getter/setters required as google-gson will produce json from fields of object.
*/
public class GLAccountData implements Serializable {
@@ -100,8 +100,9 @@ public class TrialBalance extends AbstractPersistableCustom {
}
TrialBalance other = (TrialBalance) obj;
return Objects.equals(other.officeId, officeId) && Objects.equals(other.glAccountId, glAccountId)
&& Objects.equals(other.amount, amount) && Objects.equals(other.entryDate, entryDate)
&& Objects.equals(other.transactionDate, transactionDate) && Objects.equals(other.closingBalance, closingBalance);
&& Objects.equals(other.amount, amount) && other.entryDate.compareTo(entryDate) == 0 ? Boolean.TRUE
: Boolean.FALSE && other.transactionDate.compareTo(transactionDate) == 0 ? Boolean.TRUE
: Boolean.FALSE && Objects.equals(other.closingBalance, closingBalance);
}
@Override
@@ -22,8 +22,7 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainR
/**
*
* when gl account is attached with any product(loan/saving) then we can not
* disable the gl account
* when gl account is attached with any product(loan/saving) then we can not disable the gl account
*/
public class GLAccountDisableException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Account with a given GL Code of
* the particular type is already present
* A {@link RuntimeException} thrown when a GL Account with a given GL Code of the particular type is already present
*/
public class GLAccountDuplicateException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when trying to fetch accounts belonging to
* an Invalid Usage Type
* A {@link RuntimeException} thrown when trying to fetch accounts belonging to an Invalid Usage Type
*/
public class GLAccountInvalidClassificationException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Account with a given GL Code of
* the particular type is already present
* A {@link RuntimeException} thrown when a GL Account with a given GL Code of the particular type is already present
*/
public class GLAccountInvalidDeleteException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when trying to map invalid parents to a GL
* account
* A {@link RuntimeException} thrown when trying to map invalid parents to a GL account
*/
public class GLAccountInvalidParentException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when an error is encountered during
* updating a GL Account
* A {@link RuntimeException} thrown when an error is encountered during updating a GL Account
*/
public class GLAccountInvalidUpdateException extends AbstractPlatformDomainRuleException {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when trying to fetch accounts belonging to
* an Invalid Type
* A {@link RuntimeException} thrown when trying to fetch accounts belonging to an Invalid Type
*/
public class GLAccountInvalidUsageException extends AbstractPlatformDomainRuleException {
@@ -19,6 +19,7 @@
package org.apache.fineract.accounting.glaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when GL account resources are not found.
@@ -28,4 +29,8 @@ public class GLAccountNotFoundException extends AbstractPlatformResourceNotFound
public GLAccountNotFoundException(final Long id) {
super("error.msg.glaccount.id.invalid", "General Ledger account with identifier " + id + " does not exist ", id);
}
public GLAccountNotFoundException(final Long id, EmptyResultDataAccessException e) {
super("error.msg.glaccount.id.invalid", "General Ledger account with identifier " + id + " does not exist ", id, e);
}
}
@@ -35,8 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link GuarantorCommand}'s.
* Implementation of {@link FromApiJsonDeserializer} for {@link GuarantorCommand}'s.
*/
@Component
public final class GLAccountCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<GLAccountCommand> {
@@ -224,7 +224,7 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
return glAccountData;
} catch (final EmptyResultDataAccessException e) {
throw new GLAccountNotFoundException(glAccountId);
throw new GLAccountNotFoundException(glAccountId, e);
}
}
@@ -50,7 +50,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -108,8 +110,9 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
this.glAccountRepository.save(glAccount);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(glAccount.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleGLAccountDataIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleGLAccountDataIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
@@ -151,8 +154,7 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
}
/**
* a detail account cannot be changed to a header account if
* transactions are already logged against it
* a detail account cannot be changed to a header account if transactions are already logged against it
**/
if (changesOnly.containsKey(GLAccountJsonInputParams.USAGE.getValue())) {
if (glAccount.isHeaderAccount()) {
@@ -170,8 +172,9 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(glAccount.getId()).with(changesOnly)
.build();
} catch (final DataIntegrityViolationException dve) {
handleGLAccountDataIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleGLAccountDataIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
@@ -230,8 +233,8 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
* @param command
* @param dve
*/
private void handleGLAccountDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
private void handleGLAccountDataIntegrityIssues(final JsonCommand command, final Throwable realCause,
final NonTransientDataAccessException dve) {
if (realCause.getMessage().contains("acc_gl_code")) {
final String glCode = command.stringValueOfParameterNamed(GLAccountJsonInputParams.GL_CODE.getValue());
throw new GLAccountDuplicateException(glCode);
@@ -20,15 +20,16 @@ package org.apache.fineract.accounting.journalentry.api;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
@@ -73,10 +74,8 @@ import org.springframework.stereotype.Component;
@Path("/journalentries")
@Component
@Scope("singleton")
@Api(tags = { "Journal Entries" })
@SwaggerDefinition(tags = {
@Tag(name = "Journal Entries", description = "A journal entry refers to the logging of transactions against general ledger accounts. A journal entry may consist of several line items, each of which is either a \"debit\" or a \"credit\". The total amount of the debits must equal the total amount of the credits or the journal entry is said to be \"unbalanced\" \n"
+ "\n" + "A journal entry directly changes the account balances on the general ledger") })
@Tag(name = "Journal Entries", description = "A journal entry refers to the logging of transactions against general ledger accounts. A journal entry may consist of several line items, each of which is either a \"debit\" or a \"credit\". The total amount of the debits must equal the total amount of the credits or the journal entry is said to be \"unbalanced\" \n"
+ "\n" + "A journal entry directly changes the account balances on the general ledger")
public class JournalEntriesApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
@@ -113,30 +112,32 @@ public class JournalEntriesApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "List Journal Entries", notes = "The list capability of journal entries can support pagination and sorting.\n\n"
@Operation(summary = "List Journal Entries", description = "The list capability of journal entries can support pagination and sorting.\n\n"
+ "Example Requests:\n" + "\n" + "journalentries\n" + "\n" + "journalentries?transactionId=PB37X8Y21EQUY4S\n" + "\n"
+ "journalentries?officeId=1&manualEntriesOnly=true&fromDate=1 July 2013&toDate=15 July 2013&dateFormat=dd MMMM yyyy&locale=en\n"
+ "\n" + "journalentries?fields=officeName,glAccountName,transactionDate\n" + "\n" + "journalentries?offset=10&limit=50\n"
+ "\n" + "journalentries?orderBy=transactionId&sortOrder=DESC\n" + "\n" + "journalentries?runningBalance=true\n" + "\n"
+ "journalentries?transactionDetails=true\n" + "\n" + "journalentries?loanId=12\n" + "\n" + "journalentries?savingsId=24")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = JournalEntryData.class, responseContainer = "list") })
public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("officeId") @ApiParam(value = "officeId") final Long officeId,
@QueryParam("glAccountId") @ApiParam(value = "glAccountId") final Long glAccountId,
@QueryParam("manualEntriesOnly") @ApiParam(value = "manualEntriesOnly") final Boolean onlyManualEntries,
@QueryParam("fromDate") @ApiParam(value = "fromDate") final DateParam fromDateParam,
@QueryParam("toDate") @ApiParam(value = "toDate") final DateParam toDateParam,
@QueryParam("transactionId") @ApiParam(value = "transactionId") final String transactionId,
@QueryParam("entityType") @ApiParam(value = "entityType") final Integer entityType,
@QueryParam("offset") @ApiParam(value = "offset") final Integer offset,
@QueryParam("limit") @ApiParam(value = "limit") final Integer limit,
@QueryParam("orderBy") @ApiParam(value = "orderBy") final String orderBy,
@QueryParam("sortOrder") @ApiParam(value = "sortOrder") final String sortOrder,
@QueryParam("locale") @ApiParam(value = "locale") final String locale,
@QueryParam("dateFormat") @ApiParam(value = "dateFormat") final String dateFormat,
@QueryParam("loanId") @ApiParam(value = "loanId") final Long loanId,
@QueryParam("savingsId") @ApiParam(value = "savingsId") final Long savingsId,
@QueryParam("runningBalance") @ApiParam(value = "runningBalance") final boolean runningBalance,
@QueryParam("transactionDetails") @ApiParam(value = "transactionDetails") final boolean transactionDetails) {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = JournalEntryData.class)))) })
public String retrieveAll(@Context final UriInfo uriInfo,
@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId,
@QueryParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId,
@QueryParam("manualEntriesOnly") @Parameter(description = "manualEntriesOnly") final Boolean onlyManualEntries,
@QueryParam("fromDate") @Parameter(description = "fromDate") final DateParam fromDateParam,
@QueryParam("toDate") @Parameter(description = "toDate") final DateParam toDateParam,
@QueryParam("transactionId") @Parameter(description = "transactionId") final String transactionId,
@QueryParam("entityType") @Parameter(description = "entityType") final Integer entityType,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
@QueryParam("locale") @Parameter(description = "locale") final String locale,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String dateFormat,
@QueryParam("loanId") @Parameter(description = "loanId") final Long loanId,
@QueryParam("savingsId") @Parameter(description = "savingsId") final Long savingsId,
@QueryParam("runningBalance") @Parameter(description = "runningBalance") final boolean runningBalance,
@QueryParam("transactionDetails") @Parameter(description = "transactionDetails") final boolean transactionDetails) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -164,13 +165,16 @@ public class JournalEntriesApiResource {
@Path("{journalEntryId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve a single Entry", notes = "Example Requests:\n" + "\n" + "journalentries/1\n" + "\n" + "\n" + "\n"
@Operation(summary = "Retrieve a single Entry", description = "Example Requests:\n" + "\n" + "journalentries/1\n" + "\n" + "\n" + "\n"
+ "journalentries/1?fields=officeName,glAccountId,entryType,amount\n" + "\n" + "journalentries/1?runningBalance=true\n" + "\n"
+ "journalentries/1?transactionDetails=true")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = JournalEntryData.class) })
public String retreiveJournalEntryById(@PathParam("journalEntryId") @ApiParam(value = "journalEntryId") final Long journalEntryId,
@Context final UriInfo uriInfo, @QueryParam("runningBalance") @ApiParam(value = "runningBalance") final boolean runningBalance,
@QueryParam("transactionDetails") @ApiParam(value = "transactionDetails") final boolean transactionDetails) {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = JournalEntryData.class))) })
public String retreiveJournalEntryById(
@PathParam("journalEntryId") @Parameter(description = "journalEntryId") final Long journalEntryId,
@Context final UriInfo uriInfo,
@QueryParam("runningBalance") @Parameter(description = "runningBalance") final boolean runningBalance,
@QueryParam("transactionDetails") @Parameter(description = "transactionDetails") final boolean transactionDetails) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails,
@@ -185,16 +189,16 @@ public class JournalEntriesApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create \"Balanced\" Journal Entries", notes = "Note: A Balanced (simple) Journal entry would have atleast one \"Debit\" and one \"Credit\" entry whose amounts are equal \n"
@Operation(summary = "Create \"Balanced\" Journal Entries", description = "Note: A Balanced (simple) Journal entry would have atleast one \"Debit\" and one \"Credit\" entry whose amounts are equal \n"
+ "Compound Journal entries may have \"n\" debits and \"m\" credits where both \"m\" and \"n\" are greater than 0 and the net sum or all debits and credits are equal \n\n"
+ "\n" + "Mandatory Fields\n" + "officeId, transactionDate\n\n" + "\ncredits- glAccountId, amount, comments\n\n "
+ "\ndebits- glAccountId, amount, comments\n\n " + "\n" + "Optional Fields\n"
+ "paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = JournalEntryCommand.class) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = JournalEntriesApiResourceSwagger.PostJournalEntriesResponse.class) })
public String createGLJournalEntry(@ApiParam(hidden = true) final String jsonRequestBody,
@QueryParam("command") @ApiParam(value = "command") final String commandParam) {
@Parameters({ @Parameter(content = @Content(schema = @Schema(implementation = JournalEntryCommand.class))) })
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = JournalEntriesApiResourceSwagger.PostJournalEntriesResponse.class))) })
public String createGLJournalEntry(@Parameter(hidden = true) final String jsonRequestBody,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
CommandProcessingResult result = null;
if (is(commandParam, "updateRunningBalance")) {
@@ -216,15 +220,14 @@ public class JournalEntriesApiResource {
@Path("{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Update Running balances for Journal Entries", notes = "This API calculates the running balances for office. If office ID not provided this API calculates running balances for all offices. \n"
@Operation(summary = "Update Running balances for Journal Entries", description = "This API calculates the running balances for office. If office ID not provided this API calculates running balances for all offices. \n"
+ "Mandatory Fields\n" + "officeId")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = JournalEntriesApiResourceSwagger.PostJournalEntriesTransactionIdRequest.class) })
@RequestBody(content = @Content(schema = @Schema(implementation = JournalEntriesApiResourceSwagger.PostJournalEntriesTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(code = 200, message = "", response = JournalEntriesApiResourceSwagger.PostJournalEntriesTransactionIdResponse.class) })
public String createReversalJournalEntry(@ApiParam(hidden = true) final String jsonRequestBody,
@PathParam("transactionId") @ApiParam(value = "transactionId") final String transactionId,
@QueryParam("command") @ApiParam(value = "command") final String commandParam) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = JournalEntriesApiResourceSwagger.PostJournalEntriesTransactionIdResponse.class))) })
public String createReversalJournalEntry(@Parameter(hidden = true) final String jsonRequestBody,
@PathParam("transactionId") @Parameter(description = "transactionId") final String transactionId,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
CommandProcessingResult result = null;
if (is(commandParam, "reverse")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().reverseJournalEntry(transactionId).withJson(jsonRequestBody)
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.journalentry.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Created by sanyam on 25/7/17.
@@ -28,39 +27,39 @@ final class JournalEntriesApiResourceSwagger {
private JournalEntriesApiResourceSwagger() {}
@ApiModel(value = "PostJournalEntriesResponse")
@Schema(description = "PostJournalEntriesResponse")
public static final class PostJournalEntriesResponse {
private PostJournalEntriesResponse() {
}
@ApiModelProperty(value = "1")
@Schema(description = "1")
public Long officeId;
@ApiModelProperty(value = "RS9MCISID4WK1ZM")
@Schema(description = "RS9MCISID4WK1ZM")
public String transactionId;
}
@ApiModel(value = "PostJournalEntriesTransactionIdRequest")
@Schema(description = "PostJournalEntriesTransactionIdRequest")
public static final class PostJournalEntriesTransactionIdRequest {
private PostJournalEntriesTransactionIdRequest() {
}
@ApiModelProperty(value = "1")
@Schema(description = "1")
public Long officeId;
}
@ApiModel(value = "PostJournalEntriesTransactionIdResponse")
@Schema(description = "PostJournalEntriesTransactionIdResponse")
public static final class PostJournalEntriesTransactionIdResponse {
private PostJournalEntriesTransactionIdResponse() {
}
@ApiModelProperty(value = "1")
@Schema(description = "1")
public Long officeId;
}
}
@@ -29,7 +29,7 @@ public class ClientChargePaymentDTO {
private final Long incomeAccountId;
public ClientChargePaymentDTO(Long chargeId, BigDecimal amount, Long clientChargeId, boolean isPenalty, Long incomeAccountId) {
super();
this.chargeId = chargeId;
this.amount = amount;
this.clientChargeId = clientChargeId;
@@ -28,8 +28,7 @@ import org.joda.time.LocalDate;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google will produce json from fields of
* object.
* Note: no getter/setters required as google will produce json from fields of object.
*/
public class JournalEntryData {
@@ -40,7 +40,7 @@ public class JournalEntryDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private final Set<String> RUNNING_BALANCE_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
private static final Set<String> RUNNING_BALANCE_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(JournalEntryJsonInputParams.OFFICE_ID.getValue()));
@Autowired
@@ -46,13 +46,11 @@ public interface JournalEntryRepository
* @Query("select journalEntry from JournalEntry journalEntry where glAccount.id= :accountId and transactionId= :transactionId and transactionDate= :transactionDate"
* +
* " and type= :journalEntryType and entityType=1 and entityId= :loanId and loanTransaction.id= :loanTransactionId"
* ) JournalEntry findLOANJournalEntryWith(@Param("accountId") Long
* accountId, @Param("transactionId") String transactionId,
* ) JournalEntry findLOANJournalEntryWith(@Param("accountId") Long accountId, @Param("transactionId") String
* transactionId,
*
* @Param("transactionDate") Date
* transactionDate, @Param("journalEntryType") Integer journalEntryType,
* @Param("transactionDate") Date transactionDate, @Param("journalEntryType") Integer journalEntryType,
*
* @Param("loanId") Long loanId, @Param("loanTransactionId") Long
* loanTransactionId);
* @Param("loanId") Long loanId, @Param("loanTransactionId") Long loanTransactionId);
*/
}
@@ -19,6 +19,7 @@
package org.apache.fineract.accounting.journalentry.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when journal entry resources are not found.
@@ -34,4 +35,8 @@ public class JournalEntriesNotFoundException extends AbstractPlatformResourceNot
public JournalEntriesNotFoundException(final Long entryId) {
super("error.msg.journalEntries.id.invalid", "Journal Entry with entry Id " + entryId + " does not exist ", entryId);
}
public JournalEntriesNotFoundException(final Long entryId, EmptyResultDataAccessException e) {
super("error.msg.journalEntries.id.invalid", "Journal Entry with entry Id " + entryId + " does not exist ", entryId, e);
}
}
@@ -41,8 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link JournalEntryCommand}'s.
* Implementation of {@link FromApiJsonDeserializer} for {@link JournalEntryCommand}'s.
*/
@Component
public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<JournalEntryCommand> {
@@ -34,9 +34,8 @@ public class AccountingProcessorForSavingsFactory {
}
/***
* Looks like overkill for now, but wanted to keep the savings side of
* accounting identical to that of Loans (would we need an Accrual based
* accounting in the future?)
* Looks like overkill for now, but wanted to keep the savings side of accounting identical to that of Loans (would
* we need an Accrual based accounting in the future?)
***/
public AccountingProcessorForSavings determineProcessor(final SavingsDTO savingsDTO) {
@@ -338,11 +338,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Accrual Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Accrual Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* @param accountTypeToBeDebited
@@ -372,11 +370,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Accrual Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Accrual Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* office
@@ -445,11 +441,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Cash Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* @param accountTypeToBeDebited
@@ -480,11 +474,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Cash Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* @param accountTypeToBeDebited
@@ -535,11 +527,12 @@ public class AccountingProcessorHelper {
*/
public void checkForBranchClosures(final GLClosure latestGLClosure, final Date transactionDate) {
/**
* check if an accounting closure has happened for this branch after the
* transaction Date
* check if an accounting closure has happened for this branch after the transaction Date
**/
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) {
if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().compareTo(transactionDate) == 0
? Boolean.TRUE
: Boolean.FALSE) {
throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(),
null, null);
}
@@ -573,11 +566,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Cash Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* @param currencyCode
@@ -695,8 +686,7 @@ public class AccountingProcessorHelper {
final Date transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
final List<ChargePaymentDTO> chargePaymentDTOs) {
/***
* Map to track each account and the net credit to be made for a
* particular account
* Map to track each account and the net credit to be made for a particular account
***/
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
@@ -736,11 +726,9 @@ public class AccountingProcessorHelper {
}
/**
* Convenience method that creates a pair of related Debits and Credits for
* Cash Based accounting.
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
*
* The target accounts for debits and credits are switched in case of a
* reversal
* The target accounts for debits and credits are switched in case of a reversal
*
* @param office
* office
@@ -775,10 +763,9 @@ public class AccountingProcessorHelper {
// TODO Vishwas: Remove this validation, as and when appropriate Junit
// tests are written for accounting
/**
* Accounting module currently supports a single charge per transaction,
* throw an error if this is not the case here so any developers
* changing the expected portfolio behavior would also take care of
* modifying the accounting code appropriately
* Accounting module currently supports a single charge per transaction, throw an error if this is not the case
* here so any developers changing the expected portfolio behavior would also take care of modifying the
* accounting code appropriately
**/
if (chargePaymentDTOs.size() != 1) {
throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code",
@@ -1124,9 +1111,8 @@ public class AccountingProcessorHelper {
PortfolioProductType.LOAN.getValue(), accountMappingTypeId);
/****
* Get more specific mapping for FUND source accounts (based on
* payment channels). Note that fund source placeholder ID would be
* same for both cash and accrual accounts
* Get more specific mapping for FUND source accounts (based on payment channels). Note that fund source
* placeholder ID would be same for both cash and accrual accounts
***/
if (accountMappingTypeId == CashAccountsForLoan.FUND_SOURCE.getValue()) {
final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository
@@ -1150,10 +1136,9 @@ public class AccountingProcessorHelper {
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId,
PortfolioProductType.LOAN.getValue(), accountMappingTypeId);
/*****
* Get more specific mappings for Charges and penalties (based on the
* actual charge /penalty coupled with the loan product). Note the
* income from fees and income from penalties placeholder ID would be
* the same for both cash and accrual based accounts
* Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the
* loan product). Note the income from fees and income from penalties placeholder ID would be the same for both
* cash and accrual based accounts
*****/
// Vishwas TODO: remove this condition as it should always be true
@@ -1174,10 +1159,9 @@ public class AccountingProcessorHelper {
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
PortfolioProductType.SAVING.getValue(), accountMappingTypeId);
/*****
* Get more specific mappings for Charges and penalties (based on the
* actual charge /penalty coupled with the loan product). Note the
* income from fees and income from penalties placeholder ID would be
* the same for both cash and accrual based accounts
* Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the
* loan product). Note the income from fees and income from penalties placeholder ID would be the same for both
* cash and accrual based accounts
*****/
// Vishwas TODO: remove this condition as it should always be true
@@ -1204,9 +1188,8 @@ public class AccountingProcessorHelper {
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId,
PortfolioProductType.SAVING.getValue(), accountMappingTypeId);
/****
* Get more specific mapping for FUND source accounts (based on
* payment channels). Note that fund source placeholder ID would be
* same for both cash and accrual accounts
* Get more specific mapping for FUND source accounts (based on payment channels). Note that fund source
* placeholder ID would be same for both cash and accrual accounts
***/
if (accountMappingTypeId == CashAccountsForSavings.SAVINGS_REFERENCE.getValue()) {
final ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository
@@ -1249,10 +1232,9 @@ public class AccountingProcessorHelper {
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(shareProductId,
PortfolioProductType.SHARES.getValue(), accountMappingTypeId);
/*****
* Get more specific mappings for Charges and penalties (based on the
* actual charge /penalty coupled with the loan product). Note the
* income from fees and income from penalties placeholder ID would be
* the same for both cash and accrual based accounts
* Get more specific mappings for Charges and penalties (based on the actual charge /penalty coupled with the
* loan product). Note the income from fees and income from penalties placeholder ID would be the same for both
* cash and accrual based accounts
*****/
final ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository
@@ -1276,8 +1258,7 @@ public class AccountingProcessorHelper {
final Long clientId, final Long transactionId, final Date transactionDate, final Boolean isReversal,
final List<ClientChargePaymentDTO> clientChargePaymentDTOs) {
/***
* Map to track each account affected and the net credit to be made for
* a particular account
* Map to track each account affected and the net credit to be made for a particular account
***/
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
for (final ClientChargePaymentDTO clientChargePaymentDTO : clientChargePaymentDTOs) {
@@ -65,8 +65,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
/***
* Handle repayments, repayments at disbursement and reversal of
* Repayments and Repayments at disbursement
* Handle repayments, repayments at disbursement and reversal of Repayments and Repayments at disbursement
***/
else if (loanTransactionDTO.getTransactionType().isRepayment()
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
@@ -144,35 +143,27 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
* <br/>
* <br/>
*
* <b>Principal Repayment</b>: Debits "Fund Source" and Credits "Loan
* Portfolio"<br/>
* <b>Principal Repayment</b>: Debits "Fund Source" and Credits "Loan Portfolio"<br/>
*
* <b>Interest Repayment</b>:Debits "Fund Source" and and Credits
* "Receivable Interest" <br/>
* <b>Interest Repayment</b>:Debits "Fund Source" and and Credits "Receivable Interest" <br/>
*
* <b>Fee Repayment</b>:Debits "Fund Source" (or "Interest on Loans" in case
* of repayment at disbursement) and and Credits "Receivable Fees" <br/>
* <b>Fee Repayment</b>:Debits "Fund Source" (or "Interest on Loans" in case of repayment at disbursement) and and
* Credits "Receivable Fees" <br/>
*
* <b>Penalty Repayment</b>: Debits "Fund Source" and and Credits
* "Receivable Penalties" <br/>
* <b>Penalty Repayment</b>: Debits "Fund Source" and and Credits "Receivable Penalties" <br/>
* <br/>
* Handles write offs using the following posting rules <br/>
* <br/>
* <b>Principal Write off</b>: Debits "Losses Written Off" and Credits "Loan
* Portfolio"<br/>
* <b>Principal Write off</b>: Debits "Losses Written Off" and Credits "Loan Portfolio"<br/>
*
* <b>Interest Write off</b>:Debits "Losses Written off" and and Credits
* "Receivable Interest" <br/>
* <b>Interest Write off</b>:Debits "Losses Written off" and and Credits "Receivable Interest" <br/>
*
* <b>Fee Write off</b>:Debits "Losses Written off" and and Credits
* "Receivable Fees" <br/>
* <b>Fee Write off</b>:Debits "Losses Written off" and and Credits "Receivable Fees" <br/>
*
* <b>Penalty Write off</b>: Debits "Losses Written off" and and Credits
* "Receivable Penalties" <br/>
* <b>Penalty Write off</b>: Debits "Losses Written off" and and Credits "Receivable Penalties" <br/>
* <br/>
* <br/>
* In case the loan transaction has been reversed, all debits are turned
* into credits and vice versa
* In case the loan transaction has been reversed, all debits are turned into credits and vice versa
*
* @param loanTransactionDTO
* @param loanDTO
@@ -289,8 +280,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
/**
* Single DEBIT transaction for write-offs or Repayments (and their
* reversals)
* Single DEBIT transaction for write-offs or Repayments (and their reversals)
***/
if (!(totalDebitAmount.compareTo(BigDecimal.ZERO) == 0)) {
if (writeOff) {
@@ -315,11 +305,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
/**
* Create a single Debit to fund source and a single credit to "Income from
* Recovery"
* Create a single Debit to fund source and a single credit to "Income from Recovery"
*
* In case the loan transaction is a reversal, all debits are turned into
* credits and vice versa
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -345,11 +333,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
* Recognize the receivable interest <br/>
* Debit "Interest Receivable" and Credit "Income from Interest"
*
* <b>Fees:</b> Debit <i>Fees Receivable</i> and credit <i>Income from
* Fees</i> <br/>
* <b>Fees:</b> Debit <i>Fees Receivable</i> and credit <i>Income from Fees</i> <br/>
*
* <b>Penalties:</b> Debit <i>Penalties Receivable</i> and credit <i>Income
* from Penalties</i>
* <b>Penalties:</b> Debit <i>Penalties Receivable</i> and credit <i>Income from Penalties</i>
*
* Also handles reversals for both fees and payment applications
*
@@ -52,11 +52,10 @@ public class CashBasedAccountingProcessorForClientTransactions implements Accoun
}
/**
* Create a single debit to fund source and multiple credits for the income
* account mapped with each charge this payment pays off
* Create a single debit to fund source and multiple credits for the income account mapped with each charge this
* payment pays off
*
* In case the loan transaction is a reversal, all debits are turned into
* credits and vice versa
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForChargePayments(final ClientTransactionDTO clientTransactionDTO, final Office office) {
// client properties
@@ -74,9 +73,8 @@ public class CashBasedAccountingProcessorForClientTransactions implements Accoun
transactionId, transactionDate, isReversal, clientTransactionDTO.getChargePayments());
/***
* create a single Debit entry (or reversal) for the entire amount
* that was credited (accounting is turned on at the level of for
* each charge that has been paid by this transaction)
* create a single Debit entry (or reversal) for the entire amount that was credited (accounting is turned
* on at the level of for each charge that has been paid by this transaction)
**/
this.helper.createDebitJournalEntryOrReversalForClientChargePayments(office, currencyCode, clientId, transactionId,
transactionDate, totalCreditedAmount, isReversal);
@@ -63,8 +63,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
}
/***
* Logic for repayments, repayments at disbursement and reversal of
* Repayments and Repayments at disbursement
* Logic for repayments, repayments at disbursement and reversal of Repayments and Repayments at
* disbursement
***/
else if (loanTransactionDTO.getTransactionType().isRepayment()
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
@@ -82,9 +82,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
}
/***
* Only principal write off affects cash based accounting (interest
* and fee write off need not be considered). Debit losses written
* off and credit Loan Portfolio
* Only principal write off affects cash based accounting (interest and fee write off need not be
* considered). Debit losses written off and credit Loan Portfolio
**/
else if (loanTransactionDTO.getTransactionType().isWriteOff()) {
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
@@ -109,8 +108,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
/**
* Debit loan Portfolio and credit Fund source for a Disbursement <br/>
*
* All debits are turned into credits and vice versa in case of disbursement
* reversals
* All debits are turned into credits and vice versa in case of disbursement reversals
*
*
* @param loanDTO
@@ -149,8 +147,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
/**
* Debit loan Portfolio and credit Fund source for a Disbursement <br/>
*
* All debits are turned into credits and vice versa in case of disbursement
* reversals
* All debits are turned into credits and vice versa in case of disbursement reversals
*
*
* @param loanDTO
@@ -182,13 +179,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
/**
* Create a single Debit to fund source and multiple credits if applicable
* (loan portfolio for principal repayments, Interest on loans for interest
* repayments, Income from fees for fees payment and Income from penalties
* for penalty payment)
* Create a single Debit to fund source and multiple credits if applicable (loan portfolio for principal repayments,
* Interest on loans for interest repayments, Income from fees for fees payment and Income from penalties for
* penalty payment)
*
* In case the loan transaction is a reversal, all debits are turned into
* credits and vice versa
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -256,11 +251,9 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
/**
* Create a single Debit to fund source and a single credit to "Income from
* Recovery"
* Create a single Debit to fund source and a single credit to "Income from Recovery"
*
* In case the loan transaction is a reversal, all debits are turned into
* credits and vice versa
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -283,12 +276,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
/**
* Credit loan Portfolio and Debit Suspense Account for a Transfer
* Initiation. A Transfer acceptance would be treated the opposite i.e Debit
* Loan Portfolio and Credit Suspense Account <br/>
* Credit loan Portfolio and Debit Suspense Account for a Transfer Initiation. A Transfer acceptance would be
* treated the opposite i.e Debit Loan Portfolio and Credit Suspense Account <br/>
*
* All debits are turned into credits and vice versa in case of Transfer
* Initiation disbursals
* All debits are turned into credits and vice versa in case of Transfer Initiation disbursals
*
*
* @param loanDTO
@@ -146,8 +146,7 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
}
/**
* Handle Interest Applications and reversals of Interest
* Applications
* Handle Interest Applications and reversals of Interest Applications
**/
else if (savingsTransactionDTO.getTransactionType().isInterestPosting() && savingsTransactionDTO.isOverdraftTransaction()) {
// Post journal entry if earned interest amount is greater than
@@ -391,7 +391,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
return glJournalEntryData;
} catch (final EmptyResultDataAccessException e) {
throw new JournalEntriesNotFoundException(glJournalEntryId);
throw new JournalEntriesNotFoundException(glJournalEntryId, e);
}
}
@@ -534,7 +534,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
Object[] data = { transactionId, entityId, entityType };
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sql, data, rm);
} catch (final EmptyResultDataAccessException e) {
throw new JournalEntriesNotFoundException(entityId);
throw new JournalEntriesNotFoundException(entityId, e);
}
}
}
@@ -81,6 +81,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -218,16 +220,17 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
.withTransactionId(transactionId).build();
} catch (final DataIntegrityViolationException dve) {
throw handleJournalEntryDataIntegrityIssues(dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
throw handleJournalEntryDataIntegrityIssues(throwable, dve);
}
}
private void validateDebitOrCreditArrayForExistingGLAccount(final GLAccount glaccount,
final SingleDebitOrCreditEntryCommand[] creditOrDebits) {
/**
* If a glaccount is assigned for a rule the credits or debits array
* should have only one entry and it must be same as existing account
* If a glaccount is assigned for a rule the credits or debits array should have only one entry and it must be
* same as existing account
*/
if (creditOrDebits.length != 1) {
throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.INVALID_DEBIT_OR_CREDIT_ACCOUNTS, null, null, null);
@@ -305,8 +308,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
private void validateGLAccountForTransaction(final GLAccount creditOrDebitAccountHead) {
/***
* validate that the account allows manual adjustments and is not
* disabled
* validate that the account allows manual adjustments and is not disabled
**/
if (creditOrDebitAccountHead.isDisabled()) {
throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.GL_ACCOUNT_DISABLED, null,
@@ -347,7 +349,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
final GLClosure latestGLClosureByBranch = this.glClosureRepository.getLatestGLClosureByBranch(officeId);
if (latestGLClosureByBranch != null) {
if (latestGLClosureByBranch.getClosingDate().after(journalEntriesTransactionDate)
|| latestGLClosureByBranch.getClosingDate().equals(journalEntriesTransactionDate)) {
|| latestGLClosureByBranch.getClosingDate().compareTo(journalEntriesTransactionDate) == 0 ? Boolean.TRUE
: Boolean.FALSE) {
final String accountName = null;
final String accountGLCode = null;
throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED,
@@ -598,7 +601,9 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
// shouldn't be before an accounting closure
final GLClosure latestGLClosure = this.glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) {
if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().compareTo(transactionDate) == 0
? Boolean.TRUE
: Boolean.FALSE) {
throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(),
null, null);
}
@@ -644,8 +649,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
}
/**
* TODO: Need a better implementation with guaranteed uniqueness (but not a
* long UUID)...maybe something tied to system clock..
* TODO: Need a better implementation with guaranteed uniqueness (but not a long UUID)...maybe something tied to
* system clock..
*/
private String generateTransactionId(final Long officeId) {
final AppUser user = this.context.authenticatedUser();
@@ -655,8 +660,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
return transactionId;
}
private PlatformDataIntegrityException handleJournalEntryDataIntegrityIssues(final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
private PlatformDataIntegrityException handleJournalEntryDataIntegrityIssues(final Throwable realCause,
final NonTransientDataAccessException dve) {
LOG.error("Error occured.", dve);
return new PlatformDataIntegrityException("error.msg.glJournalEntry.unknown.data.integrity.issue",
"Unknown data integrity issue with resource Journal Entry: " + realCause.getMessage());
@@ -710,8 +715,9 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
.withTransactionId(transactionId).build();
} catch (final DataIntegrityViolationException dve) {
throw handleJournalEntryDataIntegrityIssues(dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
throw handleJournalEntryDataIntegrityIssues(throwable, dve);
}
}
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.producttoaccountmapping.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class ProductToGLAccountMappingInvalidException extends AbstractPlatformDomainRuleException {
@@ -22,8 +22,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioPr
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
* A {@link RuntimeException} thrown when product to GL account mapping are not found.
*/
public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
@@ -47,25 +47,21 @@ import org.springframework.stereotype.Component;
/**
* TODO Vishwas find a better approach for validation
*
* Currently, validation of the passed in JSON is done before calling save or
* update method on the target resource (in our case the loan Product)
* Currently, validation of the passed in JSON is done before calling save or update method on the target resource (in
* our case the loan Product)
*
* However, in the case of a loan product it would be difficult to validate the
* passed in JSON for valid {@link LoanProduct} to {@link GLAccount} mappings
* during update because of the following scenario
* However, in the case of a loan product it would be difficult to validate the passed in JSON for valid
* {@link LoanProduct} to {@link GLAccount} mappings during update because of the following scenario
*
* The accounting rule type may be changed in the update command, so we would
* have to validate if all required account heads for a particular account type
* have been passed in (would be different for CASH and Accrual based). However,
* till we have access to the domain object it would not be possible to detect
* if an accounting rule has actually been changed
* The accounting rule type may be changed in the update command, so we would have to validate if all required account
* heads for a particular account type have been passed in (would be different for CASH and Accrual based). However,
* till we have access to the domain object it would not be possible to detect if an accounting rule has actually been
* changed
*
* Hence, method {@link #validateForLoanProductCreate(String)} from this class
* is called separately for validation only if an accounting rule change is
* detected by {@link ProductToGLAccountMappingWritePlatformService}
* Hence, method {@link #validateForLoanProductCreate(String)} from this class is called separately for validation only
* if an accounting rule change is detected by {@link ProductToGLAccountMappingWritePlatformService}
*
* Also, the class is probably named wrong (*FromApiJsonDeserializer) should
* probably be named as (*Validator) instead
* Also, the class is probably named wrong (*FromApiJsonDeserializer) should probably be named as (*Validator) instead
*
*/
@Component
@@ -212,8 +212,7 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
}
/**
* Examines and updates each account mapping for given loan product with
* changes passed in from the Json element
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param loanProductId
* @param changes
@@ -127,8 +127,8 @@ public class ProductToGLAccountMappingHelper {
}
/**
* Saves the payment type to Fund source mappings for a particular
* product/product type (also populates the changes array if passed in)
* Saves the payment type to Fund source mappings for a particular product/product type (also populates the changes
* array if passed in)
*
* @param command
* @param element
@@ -154,8 +154,8 @@ public class ProductToGLAccountMappingHelper {
}
/**
* Saves the Charge to Income / Liability account mappings for a particular
* product/product type (also populates the changes array if passed in)
* Saves the Charge to Income / Liability account mappings for a particular product/product type (also populates the
* changes array if passed in)
*
* @param command
* @param element
@@ -210,14 +210,12 @@ public class ProductToGLAccountMappingHelper {
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
/**
* Variable stores a map representation of charges (key) and their
* associated income Id's (value) extracted from the passed in
* Jsoncommand
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
* the passed in Jsoncommand
**/
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
/***
* Variable stores all charges which have already been mapped to Income
* Accounts in the system
* Variable stores all charges which have already been mapped to Income Accounts in the system
**/
final Set<Long> existingCharges = new HashSet<>();
if (chargeToIncomeAccountMappingArray != null) {
@@ -238,10 +236,8 @@ public class ProductToGLAccountMappingHelper {
} /**
* Else, <br/>
* update existing mappings OR <br/>
* delete old mappings (which are already present, but not passed
* in as a part of Jsoncommand)<br/>
* Create new mappings for charges that are passed in as a part of
* the Jsoncommand but not already present
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
*
**/
else {
@@ -296,13 +292,12 @@ public class ProductToGLAccountMappingHelper {
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
/**
* Variable stores a map representation of Payment channels (key) and
* their fund sources (value) extracted from the passed in Jsoncommand
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
* the passed in Jsoncommand
**/
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
/***
* Variable stores all payment types which have already been mapped to
* Fund Sources in the system
* Variable stores all payment types which have already been mapped to Fund Sources in the system
**/
final Set<Long> existingPaymentTypes = new HashSet<>();
if (paymentChannelMappingArray != null) {
@@ -324,10 +319,9 @@ public class ProductToGLAccountMappingHelper {
} /**
* Else, <br/>
* update existing mappings OR <br/>
* delete old mappings (which re already present, but not passed
* in as a part of Jsoncommand)<br/>
* Create new mappings for payment types that are passed in as a
* part of the Jsoncommand but not already present
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
* present
*
**/
else {
@@ -388,8 +382,7 @@ public class ProductToGLAccountMappingHelper {
GLAccount glAccount;
/**
* Both CASH and Accrual placeholders have the same value for income
* from Interest and penalties
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
**/
CashAccountsForLoan placeHolderAccountType;
if (isPenalty) {
@@ -415,8 +408,8 @@ public class ProductToGLAccountMappingHelper {
}
/**
* Fetches account with a particular Id and throws and Exception it is not
* of the expected Account Category ('ASSET','liability' etc)
* Fetches account with a particular Id and throws and Exception it is not of the expected Account Category
* ('ASSET','liability' etc)
*
* @param paramName
* @param expectedAccountType
@@ -288,16 +288,15 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
public Map<String, Object> updateLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId) {
/***
* Variable tracks all accounting mapping properties that have been
* updated
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping
* for the product and recreate a new set of mappings
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.deserializer.validateForLoanProductCreate(command.json());
@@ -319,16 +318,15 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
public Map<String, Object> updateSavingsProductToGLAccountMapping(final Long savingsProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId, final DepositAccountType accountType) {
/***
* Variable tracks all accounting mapping properties that have been
* updated
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping
* for the product and recreate a new set of mappings
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.deserializer.validateForSavingsProductCreate(command.json(), accountType);
@@ -351,16 +349,15 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
public Map<String, Object> updateShareProductToGLAccountMapping(final Long shareProductId, final JsonCommand command,
final boolean accountingRuleChanged, final int accountingRuleTypeId) {
/***
* Variable tracks all accounting mapping properties that have been
* updated
* Variable tracks all accounting mapping properties that have been updated
***/
Map<String, Object> changes = new HashMap<>();
final JsonElement element = this.fromApiJsonHelper.parse(command.json());
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId);
/***
* If the accounting rule has been changed, delete all existing mapping
* for the product and recreate a new set of mappings
* If the accounting rule has been changed, delete all existing mapping for the product and recreate a new set
* of mappings
***/
if (accountingRuleChanged) {
this.shareProductToGLAccountMappingHelper.deleteSharesProductToGLAccountMapping(shareProductId);
@@ -177,8 +177,7 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
}
/**
* Examines and updates each account mapping for given loan product with
* changes passed in from the Json element
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param savingsProductId
* @param changes
@@ -154,8 +154,7 @@ public class ShareProductToGLAccountMappingHelper extends ProductToGLAccountMapp
}
/**
* Examines and updates each account mapping for given loan product with
* changes passed in from the Json element
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param sharesProductId
* @param changes
@@ -18,15 +18,15 @@
*/
package org.apache.fineract.accounting.provisioning.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -62,11 +62,9 @@ import org.springframework.stereotype.Component;
@Path("/provisioningentries")
@Component
@Scope("singleton")
@Api(tags = { "Provisioning Entries" })
@SwaggerDefinition(tags = {
@Tag(name = "Provisioning Entries", description = "This defines the Provisioning Entries for all active loan products\n" + "\n"
+ "Field Descriptions\n" + "date\n" + "Date on which day provisioning entries should be created\n"
+ "createjournalentries\n" + "Boolean variable whether to add journal entries for generated provisioning entries\n") })
@Tag(name = "Provisioning Entries", description = "This defines the Provisioning Entries for all active loan products\n" + "\n"
+ "Field Descriptions\n" + "date\n" + "Date on which day provisioning entries should be created\n" + "createjournalentries\n"
+ "Boolean variable whether to add journal entries for generated provisioning entries\n")
public class ProvisioningEntriesApiResource {
private final PlatformSecurityContext platformSecurityContext;
@@ -97,13 +95,12 @@ public class ProvisioningEntriesApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create new Provisioning Entries", notes = "Creates a new Provisioning Entries\n" + "\n" + "Mandatory Fields\n"
+ "date\n" + "dateFormat\n" + "locale\n" + "Optional Fields\n" + "createjournalentries")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = ProvisioningEntriesApiResourceSwagger.PostProvisioningEntriesRequest.class) })
@Operation(summary = "Create new Provisioning Entries", description = "Creates a new Provisioning Entries\n" + "\n"
+ "Mandatory Fields\n" + "date\n" + "dateFormat\n" + "locale\n" + "Optional Fields\n" + "createjournalentries")
@RequestBody(content = @Content(schema = @Schema(implementation = ProvisioningEntriesApiResourceSwagger.PostProvisioningEntriesRequest.class)))
@ApiResponses({
@ApiResponse(code = 200, message = "", response = ProvisioningEntriesApiResourceSwagger.PostProvisioningEntriesResponse.class) })
public String createProvisioningEntries(@ApiParam(hidden = true) final String apiRequestBodyAsJson) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = ProvisioningEntriesApiResourceSwagger.PostProvisioningEntriesResponse.class))) })
public String createProvisioningEntries(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
CommandWrapper commandWrapper = null;
this.platformSecurityContext.authenticatedUser();
commandWrapper = new CommandWrapperBuilder().createProvisioningEntries().withJson(apiRequestBodyAsJson).build();
@@ -115,14 +112,13 @@ public class ProvisioningEntriesApiResource {
@Path("{entryId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Recreates Provisioning Entry", notes = "Recreates Provisioning Entry | createjournalentry.")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = ProvisioningEntriesApiResourceSwagger.PutProvisioningEntriesRequest.class) })
@Operation(summary = "Recreates Provisioning Entry", description = "Recreates Provisioning Entry | createjournalentry.")
@RequestBody(content = @Content(schema = @Schema(implementation = ProvisioningEntriesApiResourceSwagger.PutProvisioningEntriesRequest.class)))
@ApiResponses({
@ApiResponse(code = 200, message = "", response = ProvisioningEntriesApiResourceSwagger.PutProvisioningEntriesResponse.class) })
public String modifyProvisioningEntry(@PathParam("entryId") @ApiParam(value = "entryId") final Long entryId,
@QueryParam("command") @ApiParam(value = "command=createjournalentry\ncommand=recreateprovisioningentry") final String commandParam,
@ApiParam(hidden = true) final String apiRequestBodyAsJson) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = ProvisioningEntriesApiResourceSwagger.PutProvisioningEntriesResponse.class))) })
public String modifyProvisioningEntry(@PathParam("entryId") @Parameter(description = "entryId") final Long entryId,
@QueryParam("command") @Parameter(description = "command=createjournalentry\ncommand=recreateprovisioningentry") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
CommandWrapper commandWrapper = null;
this.platformSecurityContext.authenticatedUser();
if ("createjournalentry".equals(commandParam)) {
@@ -143,9 +139,10 @@ public class ProvisioningEntriesApiResource {
@Path("{entryId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieves a Provisioning Entry", notes = "Returns the details of a generated Provisioning Entry.")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = ProvisioningEntryData.class) })
public String retrieveProvisioningEntry(@PathParam("entryId") @ApiParam(value = "entryId") final Long entryId,
@Operation(summary = "Retrieves a Provisioning Entry", description = "Returns the details of a generated Provisioning Entry.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = ProvisioningEntryData.class))) })
public String retrieveProvisioningEntry(@PathParam("entryId") @Parameter(description = "entryId") final Long entryId,
@Context final UriInfo uriInfo) {
platformSecurityContext.authenticatedUser();
ProvisioningEntryData data = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntryData(entryId);
@@ -157,7 +154,8 @@ public class ProvisioningEntriesApiResource {
@Path("entries")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = LoanProductProvisioningEntryData.class) })
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = LoanProductProvisioningEntryData.class))) })
public String retrieveProviioningEntries(@QueryParam("entryId") final Long entryId, @QueryParam("offset") final Integer offset,
@QueryParam("limit") final Integer limit, @QueryParam("officeId") final Long officeId,
@QueryParam("productId") final Long productId, @QueryParam("categoryId") final Long categoryId,
@@ -172,10 +170,11 @@ public class ProvisioningEntriesApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "List all Provisioning Entries")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = ProvisioningEntryData.class, responseContainer = "list") })
public String retrieveAllProvisioningEntries(@QueryParam("offset") @ApiParam(value = "offset") final Integer offset,
@QueryParam("limit") @ApiParam(value = "limit") final Integer limit, @Context final UriInfo uriInfo) {
@Operation(summary = "List all Provisioning Entries")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ProvisioningEntryData.class)))) })
public String retrieveAllProvisioningEntries(@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@QueryParam("limit") @Parameter(description = "limit") final Integer limit, @Context final UriInfo uriInfo) {
platformSecurityContext.authenticatedUser();
Page<ProvisioningEntryData> data = this.provisioningEntriesReadPlatformService.retrieveAllProvisioningEntries(offset, limit);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.provisioning.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Created by sanyam on 25/7/17.
@@ -30,23 +29,23 @@ final class ProvisioningEntriesApiResourceSwagger {
// only for Swagger Documentation
}
@ApiModel(value = "PostProvisioningEntriesRequest")
@Schema(description = "PostProvisioningEntriesRequest")
public static final class PostProvisioningEntriesRequest {
private PostProvisioningEntriesRequest() {
}
@ApiModelProperty(example = "16 October 2015")
@Schema(example = "16 October 2015")
public String date;
@ApiModelProperty(example = "en")
@Schema(example = "en")
public String locale;
@ApiModelProperty(example = "dd MMMM yyyy")
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
@ApiModelProperty(example = "true")
@Schema(example = "true")
public String createjournalentries;
public String provisioningentry;
@@ -54,36 +53,36 @@ final class ProvisioningEntriesApiResourceSwagger {
public String entries;
}
@ApiModel(value = "PostProvisioningEntriesResponse")
@Schema(description = "PostProvisioningEntriesResponse")
public static final class PostProvisioningEntriesResponse {
private PostProvisioningEntriesResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
@ApiModel(value = "PutProvisioningEntriesRequest")
@Schema(description = "PutProvisioningEntriesRequest")
public static final class PutProvisioningEntriesRequest {
private PutProvisioningEntriesRequest() {
}
@ApiModelProperty(example = "recreateprovisioningentry")
@Schema(example = "recreateprovisioningentry")
public String command;
}
@ApiModel(value = "PutProvisioningEntriesResponse")
@Schema(description = "PutProvisioningEntriesResponse")
public static final class PutProvisioningEntriesResponse {
private PutProvisioningEntriesResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
}
@@ -67,7 +67,7 @@ public class ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningE
private final StringBuilder sqlQuery;
protected LoanProductProvisioningEntryMapper(String formattedDate) {
private LoanProductProvisioningEntryMapper(String formattedDate) {
sqlQuery = new StringBuilder().append(
"select if(loan.loan_type_enum=1, mclient.office_id, mgroup.office_id) as office_id, loan.loan_type_enum, pcd.criteria_id as criteriaid, loan.product_id,loan.currency_code,")
.append("GREATEST(datediff(").append(formattedDate)
@@ -63,6 +63,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
@Service
@@ -133,7 +134,7 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
private void validateForCreateJournalEntry(ProvisioningEntryData existingEntry, ProvisioningEntry requested) {
Date existingDate = existingEntry.getCreatedDate();
Date requestedDate = requested.getCreatedDate();
if (existingDate.after(requestedDate) || existingDate.equals(requestedDate)) {
if (existingDate.after(requestedDate) || existingDate.compareTo(requestedDate) == 0 ? Boolean.TRUE : Boolean.FALSE) {
throw new ProvisioningJournalEntriesCannotbeCreatedException(existingEntry.getCreatedDate(), requestedDate);
}
}
@@ -167,9 +168,9 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
}
createProvsioningEntry(currentDate, addJournalEntries);
} catch (ProvisioningEntryAlreadyCreatedException peace) {
LOG.error("{}", peace.getDefaultUserMessage());
} catch (DataIntegrityViolationException dive) {
LOG.error("Problem occurred in generateLoanLossProvisioningAmount function", dive);
LOG.error("Provisioning Entry already created", peace);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
LOG.error("Problem occurred in generateLoanLossProvisioningAmount function", dve);
}
}
@@ -186,7 +187,7 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
}
ProvisioningEntry requestedEntry = createProvsioningEntry(createdDate, addJournalEntries);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
} catch (DataIntegrityViolationException dve) {
} catch (final JpaSystemException | DataIntegrityViolationException e) {
return CommandProcessingResult.empty();
}
}
@@ -18,15 +18,15 @@
*/
package org.apache.fineract.accounting.rule.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -73,15 +73,13 @@ import org.springframework.stereotype.Component;
@Path("/accountingrules")
@Component
@Scope("singleton")
@Api(tags = { "Accounting Rules" })
@SwaggerDefinition(tags = {
@Tag(name = "Accounting Rules", description = "It is typical scenario in MFI's that non accountants pass journal entries on a regular basis. For Ex: A branch office might deposit their entire cash at hand to their Bank account at the end of a working day. The branch office users might not understand enough of accounting to figure out which account needs to get credited and which account needs to be debited to represent this transaction.\n"
+ "\n"
+ "Enter accounting rules, an abstraction on top of manual Journal entires for enabling simpler data entry. An accounting rule can define any of the following abstractions\n"
+ "\n" + "A Simple journal entry where both the credit and debit account have been preselected\n"
+ "A Simple journal entry where either credit or debit accounts have been limited to a pre-selected list of accounts (Ex: Debit account should be one of \"Bank of America\" of \"JP Morgan\" and credit account should be \"Cash\")\n"
+ "A Compound journal entry where multiple debits and / or multiple credits may be made amongst a set of preselected list of accounts (Ex: Credit account should be either \"Bank Of America\" or \"Cash\" and debit account can be \"Employee Salary\" and/or \"Miscellenous Expenses\")\n"
+ "An accounting rule can also be optionally associated with a branch, so that only a particular Branch's users have access to the rule") })
@Tag(name = "Accounting Rules", description = "It is typical scenario in MFI's that non accountants pass journal entries on a regular basis. For Ex: A branch office might deposit their entire cash at hand to their Bank account at the end of a working day. The branch office users might not understand enough of accounting to figure out which account needs to get credited and which account needs to be debited to represent this transaction.\n"
+ "\n"
+ "Enter accounting rules, an abstraction on top of manual Journal entires for enabling simpler data entry. An accounting rule can define any of the following abstractions\n"
+ "\n" + "A Simple journal entry where both the credit and debit account have been preselected\n"
+ "A Simple journal entry where either credit or debit accounts have been limited to a pre-selected list of accounts (Ex: Debit account should be one of \"Bank of America\" of \"JP Morgan\" and credit account should be \"Cash\")\n"
+ "A Compound journal entry where multiple debits and / or multiple credits may be made amongst a set of preselected list of accounts (Ex: Credit account should be either \"Bank Of America\" or \"Cash\" and debit account can be \"Employee Salary\" and/or \"Miscellenous Expenses\")\n"
+ "An accounting rule can also be optionally associated with a branch, so that only a particular Branch's users have access to the rule")
public class AccountingRuleApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
@@ -121,10 +119,10 @@ public class AccountingRuleApiResource {
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve Accounting Rule Details Template", notes = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
@Operation(summary = "Retrieve Accounting Rule Details Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ "\n" + "Field Defaults\n" + "Allowed Value Lists\n" + "Example Request:\n" + "\n" + "accountingrules/template")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = AccountingRuleApiResourceSwagger.GetAccountRulesTemplateResponse.class) })
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.GetAccountRulesTemplateResponse.class))) })
public String retrieveTemplate(@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -139,10 +137,10 @@ public class AccountingRuleApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve Accounting Rules", notes = "Returns the list of defined accounting rules.\n" + "\n"
@Operation(summary = "Retrieve Accounting Rules", description = "Returns the list of defined accounting rules.\n" + "\n"
+ "Example Requests:\n" + "\n" + "accountingrules")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = AccountingRuleApiResourceSwagger.GetAccountRulesResponse.class, responseContainer = "list") })
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.GetAccountRulesResponse.class)))) })
public String retrieveAllAccountingRules(@Context final UriInfo uriInfo) {
final AppUser currentUser = this.context.authenticatedUser();
@@ -171,10 +169,12 @@ public class AccountingRuleApiResource {
@Path("{accountingRuleId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Retrieve a Accounting rule", notes = "Returns the details of a defined Accounting rule.\n" + "\n"
@Operation(summary = "Retrieve a Accounting rule", description = "Returns the details of a defined Accounting rule.\n" + "\n"
+ "Example Requests:\n" + "\n" + "accountingrules/1")
@ApiResponses({ @ApiResponse(code = 200, message = "", response = AccountingRuleData.class) })
public String retreiveAccountingRule(@PathParam("accountingRuleId") @ApiParam(value = "accountingRuleId") final Long accountingRuleId,
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = AccountingRuleData.class))) })
public String retreiveAccountingRule(
@PathParam("accountingRuleId") @Parameter(description = "accountingRuleId") final Long accountingRuleId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
@@ -191,13 +191,13 @@ public class AccountingRuleApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create/Define a Accounting rule", notes = "Define a new Accounting rule.\n" + "\n" + "Mandatory Fields\n"
@Operation(summary = "Create/Define a Accounting rule", description = "Define a new Accounting rule.\n" + "\n" + "Mandatory Fields\n"
+ "name, officeId,\n" + "accountToDebit OR debitTags,\n" + "accountToCredit OR creditTags.\n" + "\n" + "Optional Fields\n"
+ "description")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = AccountingRuleApiResourceSwagger.PostAccountingRulesRequest.class) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = AccountingRuleApiResourceSwagger.PostAccountingRulesResponse.class) })
public String createAccountingRule(@ApiParam(hidden = true) final String jsonRequestBody) {
@RequestBody(content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.PostAccountingRulesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.PostAccountingRulesResponse.class))) })
public String createAccountingRule(@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountingRule().withJson(jsonRequestBody).build();
@@ -210,12 +210,13 @@ public class AccountingRuleApiResource {
@Path("{accountingRuleId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Update a Accounting Rule", notes = "Updates the details of a Accounting rule.")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", value = "body", dataType = "body", dataTypeClass = AccountingRuleApiResourceSwagger.PutAccountingRulesRequest.class) })
@ApiResponses({ @ApiResponse(code = 200, message = "", response = AccountingRuleApiResourceSwagger.PutAccountingRulesResponse.class) })
public String updateAccountingRule(@PathParam("accountingRuleId") @ApiParam(value = "accountingRuleId") final Long accountingRuleId,
@ApiParam(hidden = true) final String jsonRequestBody) {
@Operation(summary = "Update a Accounting Rule", description = "Updates the details of a Accounting rule.")
@RequestBody(content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.PutAccountingRulesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.PutAccountingRulesResponse.class))) })
public String updateAccountingRule(
@PathParam("accountingRuleId") @Parameter(description = "accountingRuleId") final Long accountingRuleId,
@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateAccountingRule(accountingRuleId).withJson(jsonRequestBody)
.build();
@@ -228,10 +229,11 @@ public class AccountingRuleApiResource {
@Path("{accountingRuleId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Delete a Accounting Rule", notes = "Deletes a Accounting rule.")
@Operation(summary = "Delete a Accounting Rule", description = "Deletes a Accounting rule.")
@ApiResponses({
@ApiResponse(code = 200, message = "", response = AccountingRuleApiResourceSwagger.DeleteAccountingRulesResponse.class) })
public String deleteAccountingRule(@PathParam("accountingRuleId") @ApiParam(value = "accountingRuleId") final Long accountingRuleId) {
@ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = AccountingRuleApiResourceSwagger.DeleteAccountingRulesResponse.class))) })
public String deleteAccountingRule(
@PathParam("accountingRuleId") @Parameter(description = "accountingRuleId") final Long accountingRuleId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteAccountingRule(accountingRuleId).build();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.accounting.rule.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
@@ -35,7 +34,7 @@ final class AccountingRuleApiResourceSwagger {
// For Swagger Documentation
}
@ApiModel(value = "GetAccountRulesResponse")
@Schema(description = "GetAccountRulesResponse")
public static final class GetAccountRulesResponse {
private GetAccountRulesResponse() {
@@ -54,63 +53,63 @@ final class AccountingRuleApiResourceSwagger {
public List<AccountingTagRuleData> debitTags;
}
@ApiModel(value = "GetAccountRulesTemplateResponse")
@Schema(description = "GetAccountRulesTemplateResponse")
public static final class GetAccountRulesTemplateResponse {
private GetAccountRulesTemplateResponse() {
}
@ApiModelProperty(example = "false")
@Schema(example = "false")
public boolean systemDefined;
public List<OfficeData> allowedOffices = new ArrayList<OfficeData>();
public List<GLAccountData> allowedAccounts = new ArrayList<GLAccountData>();
}
@ApiModel(value = "PostAccountingRulesRequest")
@Schema(description = "PostAccountingRulesRequest")
public static final class PostAccountingRulesRequest {
private PostAccountingRulesRequest() {
}
@ApiModelProperty(example = "test")
@Schema(example = "test")
public String name;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "21")
@Schema(example = "21")
public Long accountToDebit;
@ApiModelProperty(example = "9")
@Schema(example = "9")
public Long accountToCredit;
@ApiModelProperty(example = "Employee salary")
@Schema(example = "Employee salary")
public String description;
}
@ApiModel(value = "PostAccountingRulesResponse")
@Schema(description = "PostAccountingRulesResponse")
public static final class PostAccountingRulesResponse {
private PostAccountingRulesResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
@ApiModel(value = "PutAccountingRulesRequest")
@Schema(description = "PutAccountingRulesRequest")
public static final class PutAccountingRulesRequest {
private PutAccountingRulesRequest() {
}
@ApiModelProperty(example = "Employee Salary posting rule")
@Schema(example = "Employee Salary posting rule")
public String name;
}
@ApiModel(value = "PutAccountingRulesResponse")
@Schema(description = "PutAccountingRulesResponse")
public static final class PutAccountingRulesResponse {
private PutAccountingRulesResponse() {
@@ -121,23 +120,23 @@ final class AccountingRuleApiResourceSwagger {
public PutAccountingRulesResponsechangesSwagger() {}
@ApiModelProperty(example = "Employee Salary posting rule")
@Schema(example = "Employee Salary posting rule")
public String name;
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
public PutAccountingRulesResponsechangesSwagger changes;
}
@ApiModel(value = "DeleteAccountingRulesResponse")
@Schema(description = "DeleteAccountingRulesResponse")
public static final class DeleteAccountingRulesResponse {
private DeleteAccountingRulesResponse() {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long resourceId;
}
}
@@ -29,8 +29,7 @@ import org.apache.fineract.organisation.office.data.OfficeData;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google-gson will produce json from fields
* of object.
* Note: no getter/setters required as google-gson will produce json from fields of object.
*/
public class AccountingRuleData {
@@ -21,8 +21,7 @@ package org.apache.fineract.accounting.rule.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when an Accounting rule with a given name
* already exists
* A {@link RuntimeException} thrown when an Accounting rule with a given name already exists
*/
public class AccountingRuleDuplicateException extends AbstractPlatformDomainRuleException {
@@ -19,14 +19,18 @@
package org.apache.fineract.accounting.rule.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when Accounting rule resources are not
* found.
* A {@link RuntimeException} thrown when Accounting rule resources are not found.
*/
public class AccountingRuleNotFoundException extends AbstractPlatformResourceNotFoundException {
public AccountingRuleNotFoundException(final Long id) {
super("error.msg.accounting.rule.id.invalid", "Accounting Rule with identifier " + id + " does not exist", id);
}
public AccountingRuleNotFoundException(final Long id, EmptyResultDataAccessException e) {
super("error.msg.accounting.rule.id.invalid", "Accounting Rule with identifier " + id + " does not exist", id, e);
}
}
@@ -184,7 +184,7 @@ public class AccountingRuleReadPlatformServiceImpl implements AccountingRuleRead
}
return accountingRuleData;
} catch (final EmptyResultDataAccessException e) {
throw new AccountingRuleNotFoundException(accountingRuleId);
throw new AccountingRuleNotFoundException(accountingRuleId, e);
}
}
@@ -51,6 +51,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -83,8 +85,8 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
* @param command
* @param dve
*/
private void handleAccountingRuleIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
private void handleAccountingRuleIntegrityIssues(final JsonCommand command, final Throwable realCause,
final NonTransientDataAccessException dve) {
if (realCause.getMessage().contains("accounting_rule_name_unique")) {
throw new AccountingRuleDuplicateException(command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue()));
} else if (realCause.getMessage().contains("UNIQUE_ACCOUNT_RULE_TAGS")) {
@@ -114,8 +116,9 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
this.accountingRuleRepository.saveAndFlush(accountingRule);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
.withEntityId(accountingRule.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleAccountingRuleIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleAccountingRuleIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
@@ -280,8 +283,9 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(accountingRule.getId())
.with(changesOnly).build();
} catch (final DataIntegrityViolationException dve) {
handleAccountingRuleIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleAccountingRuleIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
@@ -18,10 +18,8 @@
*/
package org.apache.fineract.adhocquery.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -37,7 +35,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.adhocquery.data.AdHocData;
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
import org.apache.fineract.commands.domain.CommandWrapper;
@@ -55,16 +52,14 @@ import org.springframework.stereotype.Component;
@Path("/adhocquery")
@Component
@Scope("singleton")
@Api(tags = { "AdhocQuery Api" })
@SwaggerDefinition(tags = { @Tag(name = "AdhocQuery Api", description = "") })
@Tag(name = "AdhocQuery Api", description = "")
public class AdHocApiResource {
/**
* The set of parameters that are supported in response for
* {@link AdhocData}
* The set of parameters that are supported in response for {@link AdhocData}
*/
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "query", "tableName", "tableField",
"isActive", "createdBy", "createdOn", "createdById", "updatedById", "updatedOn", "email"));
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "query", "tableName",
"tableField", "isActive", "createdBy", "createdOn", "createdById", "updatedById", "updatedOn", "email"));
private final PlatformSecurityContext context;
private final AdHocReadPlatformService adHocReadPlatformService;
@@ -91,7 +86,7 @@ public class AdHocApiResource {
this.context.authenticatedUser();
final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllAdHocQuery();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, adhocs, this.RESPONSE_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, adhocs, RESPONSE_DATA_PARAMETERS);
}
@GET
@@ -102,7 +97,7 @@ public class AdHocApiResource {
this.context.authenticatedUser();
final AdHocData user = this.adHocReadPlatformService.retrieveNewAdHocDetails();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, user, RESPONSE_DATA_PARAMETERS);
}
@POST
@@ -124,7 +119,7 @@ public class AdHocApiResource {
@Path("{adHocId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAdHocQuery(@PathParam("adHocId") @ApiParam(value = "adHocId") final Long adHocId,
public String retrieveAdHocQuery(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser();
@@ -133,14 +128,14 @@ public class AdHocApiResource {
final AdHocData adhoc = this.adHocReadPlatformService.retrieveOne(adHocId);
return this.toApiJsonSerializer.serialize(settings, adhoc, this.RESPONSE_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, adhoc, RESPONSE_DATA_PARAMETERS);
}
@PUT
@Path("{adHocId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String update(@PathParam("adHocId") @ApiParam(value = "adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
public String update(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.updateAdHoc(adHocId) //
@@ -162,7 +157,7 @@ public class AdHocApiResource {
@Path("{adHocId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteAdHocQuery(@PathParam("adHocId") @ApiParam(value = "adHocId") final Long adHocId) {
public String deleteAdHocQuery(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.deleteAdHoc(adHocId) //
@@ -173,8 +168,4 @@ public class AdHocApiResource {
return this.toApiJsonSerializer.serialize(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.adhocquery.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/**
* A {@link RuntimeException} thrown when AdHoc resources are not found.
@@ -28,4 +29,8 @@ public class AdHocNotFoundException extends AbstractPlatformResourceNotFoundExce
public AdHocNotFoundException(final Long id) {
super("error.msg.adhocquery.adhoc.id.invalid", "Adhoc Record with identifier " + id + " does not exist", id);
}
public AdHocNotFoundException(final Long id, EmptyResultDataAccessException e) {
super("error.msg.adhocquery.adhoc.id.invalid", "Adhoc Record with identifier " + id + " does not exist", id, e);
}
}
@@ -139,12 +139,9 @@ public final class AdHocDataValidator {
baseDataValidator.reset().parameter("reportRunEvery").value(reportRunEvery).integerGreaterThanZero();
}
/*
* if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
* final Integer isActive =
* this.fromApiJsonHelper.extractIntegerNamed("isActive", element,
* Locale.getDefault());
* baseDataValidator.reset().parameter("isActive").value(isActive).
* notNull().inMinMaxRange(1, 2); }
* if (this.fromApiJsonHelper.parameterExists("isActive", element)) { final Integer isActive =
* this.fromApiJsonHelper.extractIntegerNamed("isActive", element, Locale.getDefault());
* baseDataValidator.reset().parameter("isActive").value(isActive). notNull().inMinMaxRange(1, 2); }
*/
throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -66,7 +66,7 @@ public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id });
} catch (final EmptyResultDataAccessException e) {
throw new AdHocNotFoundException(id);
throw new AdHocNotFoundException(id, e);
}
}
@@ -101,7 +101,8 @@ public class AdHocScheduledJobRunnerServiceImpl implements AdHocScheduledJobRunn
.append(adhoc.getQuery());
if (insertSqlBuilder.length() > 0) {
final int result = this.jdbcTemplate.update(insertSqlBuilder.toString());
LOG.info("{}: Results affected by inserted: {}", ThreadLocalContextUtil.getTenant().getName(), result);
LOG.info("{}: Records affected by generateClientSchedule: {}", ThreadLocalContextUtil.getTenant().getName(),
result);
this.jdbcTemplate.update("UPDATE m_adhoc SET last_run=? WHERE id=?", new Date(), adhoc.getId());
}
@@ -111,7 +112,7 @@ public class AdHocScheduledJobRunnerServiceImpl implements AdHocScheduledJobRunn
}
});
} else {
LOG.info("{} Nothing to update", ThreadLocalContextUtil.getTenant().getName());
LOG.info("{}: Nothing to update by generateClientSchedule", ThreadLocalContextUtil.getTenant().getName());
}
}
@@ -31,6 +31,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -64,8 +66,9 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocRepository.save(entity);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entity.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleDataIntegrityIssues(command, throwable, dve);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.build();
@@ -73,12 +76,11 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
}
/*
* Guaranteed to throw an exception no matter what the data integrity issue
* is.
* Guaranteed to throw an exception no matter what the data integrity issue is.
*/
private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause,
final NonTransientDataAccessException dve) {
final Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("unq_name")) {
final String name = command.stringValueOfParameterNamed("name");
@@ -86,15 +88,11 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
"AdHocQuery with name `" + name + "` already exists", "name", name);
}
logAsErrorUnexpectedDataIntegrityException(dve);
LOG.error("Error occured.", dve);
throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
LOG.error("Error occured.", dve);
}
@Transactional
@Override
public CommandProcessingResult updateAdHocQuery(final Long adHocId, final JsonCommand command) {
@@ -115,8 +113,9 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
.withEntityId(adHocId) //
.with(changes) //
.build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
final Throwable throwable = dve.getMostSpecificCause();
handleDataIntegrityIssues(command, throwable, dve);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.build();
@@ -138,9 +137,9 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocRepository.delete(adHoc);
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
} catch (final DataIntegrityViolationException e) {
} catch (final JpaSystemException | DataIntegrityViolationException e) {
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
"Unknown data integrity issue with resource: " + e.getMostSpecificCause(), e);
}
}
@@ -159,9 +158,9 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocRepository.save(adHoc);
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
} catch (final DataIntegrityViolationException e) {
} catch (final JpaSystemException | DataIntegrityViolationException e) {
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
"Unknown data integrity issue with resource: " + e.getMostSpecificCause(), e);
}
}
@@ -180,9 +179,9 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocRepository.save(adHoc);
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
} catch (final DataIntegrityViolationException e) {
} catch (final JpaSystemException | DataIntegrityViolationException e) {
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
"Unknown data integrity issue with resource: " + e.getMostSpecificCause(), e);
}
}
}
@@ -18,15 +18,14 @@
*/
package org.apache.fineract.batch.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Consumes;
@@ -50,12 +49,10 @@ import org.springframework.stereotype.Component;
/**
* Provides a REST resource for Batch Requests. This class acts as a proxy to
* {@link org.apache.fineract.batch.service.BatchApiService} and de-serializes
* the incoming JSON string to a list of
* {@link org.apache.fineract.batch.domain .BatchRequest} type. This list is
* forwarded to BatchApiService which finally returns a list of
* {@link org.apache.fineract.batch.domain.BatchResponse} type which is then
* serialized into JSON response by this Resource class.
* {@link org.apache.fineract.batch.service.BatchApiService} and de-serializes the incoming JSON string to a list of
* {@link org.apache.fineract.batch.domain .BatchRequest} type. This list is forwarded to BatchApiService which finally
* returns a list of {@link org.apache.fineract.batch.domain.BatchResponse} type which is then serialized into JSON
* response by this Resource class.
*
* @author Rishabh Shukla
*
@@ -66,15 +63,13 @@ import org.springframework.stereotype.Component;
@Path("/batches")
@Component
@Scope("singleton")
@Api(tags = { "Batch API" })
@SwaggerDefinition(tags = {
@Tag(name = "Batch API", description = "The Apache Fineract Batch API enables a consumer to access significant amounts of data in a single call or to make changes to several objects at once. Batching allows a consumer to pass instructions for several operations in a single HTTP request. A consumer can also specify dependencies between related operations. Once all operations have been completed, a consolidated response will be passed back and the HTTP connection will be closed.\n"
+ "\n"
+ "The Batch API takes in an array of logical HTTP requests represented as JSON arrays - each request has a requestId (the id of a request used to specify the sequence and as a dependency between requests), a method (corresponding to HTTP method GET/PUT/POST/DELETE etc.), a relativeUrl (the portion of the URL after https://example.org/api/v2/), optional headers array (corresponding to HTTP headers), optional reference parameter if a request is dependent on another request and an optional body (for POST and PUT requests). The Batch API returns an array of logical HTTP responses represented as JSON arrays - each response has a requestId, a status code, an optional headers array and an optional body (which is a JSON encoded string).\n"
+ "\n"
+ "Batch API uses Json Path to handle dependent parameters. For example, if request '2' is referencing request '1' and in the \"body\" or in \"relativeUrl\" of request '2', there is a dependent parameter (which will look like \"$.parameter_name\"), then Batch API will internally substitute this dependent parameter from the response body of request '1'.\n"
+ "\n"
+ "Batch API is able to handle deeply nested dependent requests as well nested parameters. As shown in the example, requests are dependent on each other as, 1<--2<--6, i.e a nested dependency, where request '6' is not directly dependent on request '1' but still it is one of the nested child of request '1'. In the same way Batch API could handle a deeply nested dependent value, such as {..[..{..,$.parameter_name,..}..]}.") })
@Tag(name = "Batch API", description = "The Apache Fineract Batch API enables a consumer to access significant amounts of data in a single call or to make changes to several objects at once. Batching allows a consumer to pass instructions for several operations in a single HTTP request. A consumer can also specify dependencies between related operations. Once all operations have been completed, a consolidated response will be passed back and the HTTP connection will be closed.\n"
+ "\n"
+ "The Batch API takes in an array of logical HTTP requests represented as JSON arrays - each request has a requestId (the id of a request used to specify the sequence and as a dependency between requests), a method (corresponding to HTTP method GET/PUT/POST/DELETE etc.), a relativeUrl (the portion of the URL after https://example.org/api/v2/), optional headers array (corresponding to HTTP headers), optional reference parameter if a request is dependent on another request and an optional body (for POST and PUT requests). The Batch API returns an array of logical HTTP responses represented as JSON arrays - each response has a requestId, a status code, an optional headers array and an optional body (which is a JSON encoded string).\n"
+ "\n"
+ "Batch API uses Json Path to handle dependent parameters. For example, if request '2' is referencing request '1' and in the \"body\" or in \"relativeUrl\" of request '2', there is a dependent parameter (which will look like \"$.parameter_name\"), then Batch API will internally substitute this dependent parameter from the response body of request '1'.\n"
+ "\n"
+ "Batch API is able to handle deeply nested dependent requests as well nested parameters. As shown in the example, requests are dependent on each other as, 1<--2<--6, i.e a nested dependency, where request '6' is not directly dependent on request '1' but still it is one of the nested child of request '1'. In the same way Batch API could handle a deeply nested dependent value, such as {..[..{..,$.parameter_name,..}..]}.")
public class BatchApiResource {
private final PlatformSecurityContext context;
@@ -83,8 +78,7 @@ public class BatchApiResource {
private final BatchRequestJsonHelper batchRequestJsonHelper;
/**
* Constructs a 'BatchApiService' with context, toApiJsonSerializer, service
* and batchRequestJsonHelper.
* Constructs a 'BatchApiService' with context, toApiJsonSerializer, service and batchRequestJsonHelper.
*
* @param context
* @param toApiJsonSerializer
@@ -102,8 +96,7 @@ public class BatchApiResource {
}
/**
* Rest assured POST method to get {@link BatchRequest} and returns back the
* consolidated {@link BatchResponse}
* Rest assured POST method to get {@link BatchRequest} and returns back the consolidated {@link BatchResponse}
*
* @param jsonRequestString
* @param enclosingTransaction
@@ -113,15 +106,15 @@ public class BatchApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Batch requests in a single transaction", httpMethod = "POST", notes = "The Apache Fineract Batch API is also capable of executing all the requests in a single transaction, by setting a Query Parameter, \"enclosingTransaction=true\". So, if one or more of the requests in a batch returns an erroneous response all of the Data base transactions made by other successful requests will be rolled back.\n"
@Operation(summary = "Batch requests in a single transaction", description = "The Apache Fineract Batch API is also capable of executing all the requests in a single transaction, by setting a Query Parameter, \"enclosingTransaction=true\". So, if one or more of the requests in a batch returns an erroneous response all of the Data base transactions made by other successful requests will be rolled back.\n"
+ "\n"
+ "If there has been a rollback in a transaction then a single response will be provided, with a '400' status code and a body consisting of the error details of the first failed request.")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataType = "BatchRequest", required = true, type = "body", dataTypeClass = BatchApiResourceSwagger.PostBatchesRequest.class, value = "request body") })
@ApiResponses({ @ApiResponse(code = 200, message = "Success", response = BatchResponse.class) })
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = BatchApiResourceSwagger.PostBatchesRequest.class, description = "request body")))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Success", content = @Content(schema = @Schema(implementation = BatchResponse.class))) })
public String handleBatchRequests(
@DefaultValue("false") @QueryParam("enclosingTransaction") @ApiParam(value = "enclosingTransaction", defaultValue = "false") final boolean enclosingTransaction,
@ApiParam(hidden = true) final String jsonRequestString, @Context UriInfo uriInfo) {
@DefaultValue("false") @QueryParam("enclosingTransaction") @Parameter(description = "enclosingTransaction", required = false) final boolean enclosingTransaction,
@Parameter(hidden = true) final String jsonRequestString, @Context UriInfo uriInfo) {
// Handles user authentication
this.context.authenticatedUser();
@@ -18,8 +18,7 @@
*/
package org.apache.fineract.batch.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Set;
import org.apache.fineract.batch.domain.Header;
@@ -32,7 +31,7 @@ final class BatchApiResourceSwagger {
}
@ApiModel(value = "PostBatchesRequest")
@Schema(description = "PostBatchesRequest")
public static final class PostBatchesRequest {
private PostBatchesRequest() {
@@ -45,35 +44,35 @@ final class BatchApiResourceSwagger {
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long officeId;
@ApiModelProperty(example = "\"Petra\"")
@Schema(example = "\"Petra\"")
public String firstname;
@ApiModelProperty(example = "\"Yton\"")
@Schema(example = "\"Yton\"")
public String lastname;
@ApiModelProperty(example = "\"ex_externalId1\"")
@Schema(example = "\"ex_externalId1\"")
public String externalId;
@ApiModelProperty(example = "\"dd MMMM yyyy\"")
@Schema(example = "\"dd MMMM yyyy\"")
public String dateFormat;
@ApiModelProperty(example = "\"en\"")
@Schema(example = "\"en\"")
public String locale;
@ApiModelProperty(example = "true")
@Schema(example = "true")
public boolean active;
@ApiModelProperty(example = "\"04 March 2009\"")
@Schema(example = "\"04 March 2009\"")
public String activationDate;
@ApiModelProperty(example = "\"04 March 2009\"")
@Schema(example = "\"04 March 2009\"")
public String submittedOnDate;
}
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long requestId;
@ApiModelProperty(example = "clients")
@Schema(example = "clients")
public String relativeUrl;
@ApiModelProperty(example = "POST")
@Schema(example = "POST")
public String method;
public Set<Header> headers;
@ApiModelProperty(example = "1")
@Schema(example = "1")
public Long reference;
public PostBodyRequestSwagger body;
}
@@ -19,10 +19,8 @@
package org.apache.fineract.batch.command;
/**
* Provides an object to
* {@link org.apache.fineract.batch.service.BatchApiService} to get the proper
* commandStrategy for each request in BatchRequest. It uses Builder pattern to
* create object of this type.
* Provides an object to {@link org.apache.fineract.batch.service.BatchApiService} to get the proper commandStrategy for
* each request in BatchRequest. It uses Builder pattern to create object of this type.
*
* @author Rishabh Shukla
*
@@ -69,9 +67,8 @@ public final class CommandContext {
}
/**
* Returns a boolean value if the relativeUrl 'matches' one of the regex
* keys in the available commandStrategies. It take CommandContext object as
* parameter which contains a 'resource' member as a regex key for available
* Returns a boolean value if the relativeUrl 'matches' one of the regex keys in the available commandStrategies. It
* take CommandContext object as parameter which contains a 'resource' member as a regex key for available
* commandStrategies.
*
* @param other
@@ -23,8 +23,8 @@ import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
/**
* An interface for various Command Strategies. It contains a single function
* which returns appropriate response from a particular command strategy.
* An interface for various Command Strategies. It contains a single function which returns appropriate response from a
* particular command strategy.
*
* @author Rishabh Shukla
*
@@ -33,10 +33,8 @@ import org.apache.fineract.batch.domain.BatchResponse;
public interface CommandStrategy {
/**
* Returns an object of type
* {@link org.apache.fineract.batch.domain.BatchResponse}. This takes
* {@link org.apache.fineract.batch.domain.BatchRequest} as it's single
* argument and provides appropriate response.
* Returns an object of type {@link org.apache.fineract.batch.domain.BatchResponse}. This takes
* {@link org.apache.fineract.batch.domain.BatchRequest} as it's single argument and provides appropriate response.
*
* @param batchRequest
* @param uriInfo
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.batch.command;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.fineract.batch.command.internal.UnknownCommandStrategy;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,8 +26,8 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* Provides an appropriate CommandStrategy using the 'method' and 'resourceUrl'.
* CommandStrategy bean is created using Spring Application Context.
* Provides an appropriate CommandStrategy using the 'method' and 'resourceUrl'. CommandStrategy bean is created using
* Spring Application Context.
*
* @author Rishabh Shukla
*
@@ -36,12 +37,11 @@ import org.springframework.stereotype.Component;
public class CommandStrategyProvider {
private final ApplicationContext applicationContext;
private final ConcurrentHashMap<CommandContext, String> commandStrategies = new ConcurrentHashMap<>();
private final Map<CommandContext, String> commandStrategies = new ConcurrentHashMap<>();
/**
* Constructs a CommandStrategyProvider with argument of ApplicationContext
* type. It also initialize commandStrategies using init() function by
* filling it with available CommandStrategies in
* Constructs a CommandStrategyProvider with argument of ApplicationContext type. It also initialize
* commandStrategies using init() function by filling it with available CommandStrategies in
* {@link org.apache.fineract.batch.command.internal}.
*
* @param applicationContext
@@ -56,9 +56,8 @@ public class CommandStrategyProvider {
}
/**
* Returns an appropriate commandStrategy after determining it using the
* CommandContext of the request. If no such Strategy is found then a
* default strategy is returned back.
* Returns an appropriate commandStrategy after determining it using the CommandContext of the request. If no such
* Strategy is found then a default strategy is returned back.
*
* @param commandContext
* @return CommandStrategy
@@ -70,7 +69,7 @@ public class CommandStrategyProvider {
return (CommandStrategy) this.applicationContext.getBean(this.commandStrategies.get(commandContext));
}
for (ConcurrentHashMap.Entry<CommandContext, String> entry : this.commandStrategies.entrySet()) {
for (Map.Entry<CommandContext, String> entry : this.commandStrategies.entrySet()) {
if (commandContext.matcher(entry.getKey())) {
return (CommandStrategy) this.applicationContext.getBean(this.commandStrategies.get(entry.getKey()));
}
@@ -80,10 +79,8 @@ public class CommandStrategyProvider {
}
/**
* Contains various available command strategies in
* {@link org.apache.fineract.batch.command.internal}. Any new command
* Strategy will have to be added within this function in order to initiate
* it within the constructor.
* Contains various available command strategies in {@link org.apache.fineract.batch.command.internal}. Any new
* command Strategy will have to be added within this function in order to initiate it within the constructor.
*/
private void init() {
this.commandStrategies.put(CommandContext.resource("clients").method("POST").build(), "createClientCommandStrategy");
@@ -31,13 +31,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to
* handle activation of a pending client. It passes the contents of the body
* from the BatchRequest to
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
* back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
* those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle activation of a pending client. It
* passes the contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets back the response. This class will also
* catch any errors raised by {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map those errors
* to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -29,13 +29,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
* applies a new loan on an existing client. It passes the contents of the body
* from the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and applies a new loan on an existing client. It
* passes the contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and gets back the response. This class will
* also catch any errors raised by {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and map those
* errors to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -29,13 +29,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
* applies a new savings on an existing client. It passes the contents of the
* body from the BatchRequest to
* {@link org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource}
* and gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource}
* and map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and applies a new savings on an existing client.
* It passes the contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource} and gets back the response. This class
* will also catch any errors raised by {@link org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource} and
* map those errors to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -31,13 +31,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to
* handle approval of a pending loan. It passes the contents of the body from
* the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle approval of a pending loan. It passes
* the contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and gets back the response. This class will
* also catch any errors raised by {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and map those
* errors to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -31,13 +31,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
* Collect Charges for a Loan. It passes the contents of the body from the
* BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource}
* and gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource}
* and map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and Collect Charges for a Loan. It passes the
* contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource} and gets back the response. This class
* will also catch any errors raised by {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource} and
* map those errors to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -31,13 +31,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
* Create Charge for a Loan. It passes the contents of the body from the
* BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource}
* and gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource}
* and map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and Create Charge for a Loan. It passes the
* contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource} and gets back the response. This class
* will also catch any errors raised by {@link org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource} and
* map those errors to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -29,13 +29,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to
* handle creation of a new client. It passes the contents of the body from the
* BatchRequest to
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
* back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
* those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle creation of a new client. It passes
* the contents of the body from the BatchRequest to {@link org.apache.fineract.portfolio.client.api.ClientsApiResource}
* and gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map those errors to appropriate status codes
* in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -31,13 +31,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to
* handle disburse of a loan. It passes the contents of the body from the
* BatchRequest to
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and
* map those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} to handle disburse of a loan. It passes the
* contents of the body from the BatchRequest to {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource}
* and gets back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.loanaccount.api.LoansApiResource} and map those errors to appropriate status
* codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -31,11 +31,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link CommandStrategy} and handles repayment for a Loan. It
* passes the contents of the body from the BatchRequest to
* {@link LoanTransactionsApiResource} and gets back the response. This class
* will also catch any errors raised by {@link LoanTransactionsApiResource} and
* map those errors to appropriate status codes in BatchResponse.
* Implements {@link CommandStrategy} and handles repayment for a Loan. It passes the contents of the body from the
* BatchRequest to {@link LoanTransactionsApiResource} and gets back the response. This class will also catch any errors
* raised by {@link LoanTransactionsApiResource} and map those errors to appropriate status codes in BatchResponse.
*
* @author Mohit Sinha
*
@@ -25,9 +25,8 @@ import org.apache.fineract.batch.domain.BatchResponse;
import org.springframework.stereotype.Component;
/**
* Provides a default CommandStrategy by implementing
* {@link org.apache.fineract.batch.command.CommandStrategy} in case there is no
* appropriate command strategy with requested 'method' and 'resoureUrl'.
* Provides a default CommandStrategy by implementing {@link org.apache.fineract.batch.command.CommandStrategy} in case
* there is no appropriate command strategy with requested 'method' and 'resoureUrl'.
*
* @author Rishabh Shukla
*/
@@ -29,13 +29,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and
* updates the information of an existing client. It passes the contents of the
* body from the BatchRequest to
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets
* back the response. This class will also catch any errors raised by
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map
* those errors to appropriate status codes in BatchResponse.
* Implements {@link org.apache.fineract.batch.command.CommandStrategy} and updates the information of an existing
* client. It passes the contents of the body from the BatchRequest to
* {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and gets back the response. This class will also
* catch any errors raised by {@link org.apache.fineract.portfolio.client.api.ClientsApiResource} and map those errors
* to appropriate status codes in BatchResponse.
*
* @author Rishabh Shukla
*
@@ -21,10 +21,9 @@ package org.apache.fineract.batch.domain;
import java.util.Set;
/**
* Provides an object for separate HTTP requests in the Batch Request for Batch
* API. A requestId is also included as data field which takes care of
* dependency issues among various requests. This class also provides getter and
* setter functions to access Batch Request data fields.
* Provides an object for separate HTTP requests in the Batch Request for Batch API. A requestId is also included as
* data field which takes care of dependency issues among various requests. This class also provides getter and setter
* functions to access Batch Request data fields.
*
* @author Rishabh Shukla
*
@@ -41,8 +40,8 @@ public class BatchRequest {
private String body;
/**
* Constructs a 'BatchRequest' with requestId, relativeUrl, method, headers,
* reference and body of the incoming request.
* Constructs a 'BatchRequest' with requestId, relativeUrl, method, headers, reference and body of the incoming
* request.
*
* @param requestId
* of HTTP request.
@@ -131,8 +130,7 @@ public class BatchRequest {
}
/**
* Returns the values of 'headers' of {@link Header} type of an object of
* this class.
* Returns the values of 'headers' of {@link Header} type of an object of this class.
*
* @return headers of the HTTP request.
* @see Header
@@ -142,8 +140,7 @@ public class BatchRequest {
}
/**
* Sets the values of 'headers' of {@link Header} type of an object of this
* class.
* Sets the values of 'headers' of {@link Header} type of an object of this class.
*
* @param headers
* @see Header
@@ -21,10 +21,9 @@ package org.apache.fineract.batch.domain;
import java.util.Set;
/**
* Provides an object for separate HTTP responses in the Batch Response for
* Batch API. It contains all the information about a particular HTTP response
* in the Batch Response. Getter and Setter functions are also included to
* access response data fields.
* Provides an object for separate HTTP responses in the Batch Response for Batch API. It contains all the information
* about a particular HTTP response in the Batch Response. Getter and Setter functions are also included to access
* response data fields.
*
* @author Rishabh Shukla
*
@@ -40,8 +39,7 @@ public class BatchResponse {
private String body;
/**
* Constructs a 'BatchResponse' with requestId, statusCode, headers and body
* of the HTTP requests.
* Constructs a 'BatchResponse' with requestId, statusCode, headers and body of the HTTP requests.
*
* @param requestId
* @param statusCode
@@ -110,8 +108,7 @@ public class BatchResponse {
}
/**
* Sets the value of 'headers' of {@link Header} type of an object of this
* class.
* Sets the value of 'headers' of {@link Header} type of an object of this class.
*
* @param headers
* of {@link Header} Type
@@ -19,9 +19,8 @@
package org.apache.fineract.batch.domain;
/**
* Provides an object to handle HTTP headers as name and value pairs for Batch
* API. It is used in {@link BatchRequest} and {@link BatchResponse} to store
* the information regarding the headers in incoming and outgoing JSON Strings.
* Provides an object to handle HTTP headers as name and value pairs for Batch API. It is used in {@link BatchRequest}
* and {@link BatchResponse} to store the information regarding the headers in incoming and outgoing JSON Strings.
*
* @author Rishabh Shukla
*
@@ -38,11 +38,9 @@ import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.transaction.TransactionException;
/**
* Provides an Error Handler method that returns an object of type
* {@link ErrorInfo} to the CommandStrategy which raised the exception. This
* class uses various subclasses of RuntimeException to check the kind of
* exception raised and provide appropriate status and error codes for each one
* of the raised exception.
* Provides an Error Handler method that returns an object of type {@link ErrorInfo} to the CommandStrategy which raised
* the exception. This class uses various subclasses of RuntimeException to check the kind of exception raised and
* provide appropriate status and error codes for each one of the raised exception.
*
* @author Rishabh Shukla
*
@@ -57,12 +55,11 @@ public class ErrorHandler extends RuntimeException {
* Sole Constructor
*/
ErrorHandler() {
super();
}
/**
* Returns an object of ErrorInfo type containing the information regarding
* the raised error.
* Returns an object of ErrorInfo type containing the information regarding the raised error.
*
* @param exception
* @return ErrorInfo
@@ -19,8 +19,7 @@
package org.apache.fineract.batch.exception;
/**
* Provides members to hold the basic information about the exceptions raised in
* commandStrategy classes.
* Provides members to hold the basic information about the exceptions raised in commandStrategy classes.
*
* @author Rishabh Shukla
*
@@ -40,7 +39,7 @@ public final class ErrorInfo {
* @param message
*/
public ErrorInfo(final Integer statusCode, final Integer errorCode, final String message) {
super();
this.statusCode = statusCode;
this.errorCode = errorCode;
this.message = message;
@@ -50,7 +49,7 @@ public final class ErrorInfo {
* Constructor so JSON serialization will work with out special Serialiazer
*/
ErrorInfo() {
super();
}
/**
@@ -90,8 +89,7 @@ public final class ErrorInfo {
}
/**
* Getter method to provide the message of the error for an object of this
* type.
* Getter method to provide the message of the error for an object of this type.
*
* @return String
*/

Some files were not shown because too many files have changed in this diff Show More