Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fineract-charge/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ dependencies {
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-command'))
implementation(project(path: ':fineract-tax'))

implementation(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-security',
'org.springframework.boot:spring-boot-starter-validation',
'jakarta.ws.rs:jakarta.ws.rs-api',
'org.glassfish.jersey.media:jersey-media-multipart',

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
Expand All @@ -34,26 +35,27 @@
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.util.List;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.command.core.CommandDispatcher;
import org.apache.fineract.infrastructure.core.annotation.AlternativeOperationId;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.command.ChargeCreateCommand;
import org.apache.fineract.portfolio.charge.command.ChargeDeleteCommand;
import org.apache.fineract.portfolio.charge.command.ChargeUpdateCommand;
import org.apache.fineract.portfolio.charge.data.ChargeCreateRequest;
import org.apache.fineract.portfolio.charge.data.ChargeCreateResponse;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.request.ChargeRequest;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.charge.data.ChargeDeleteRequest;
import org.apache.fineract.portfolio.charge.data.ChargeDeleteResponse;
import org.apache.fineract.portfolio.charge.data.ChargeUpdateRequest;
import org.apache.fineract.portfolio.charge.data.ChargeUpdateResponse;
import org.apache.fineract.portfolio.charge.service.ChargeReadService;
import org.springframework.stereotype.Component;

@Path("/v1/charges")
@Produces({ MediaType.APPLICATION_JSON })
@Component
@Tag(name = "Charges", description = """
Its typical for MFIs to add extra costs for their financial products. These are typically Fees or Penalties.
Expand All @@ -64,30 +66,22 @@
@RequiredArgsConstructor
public class ChargesApiResource {

private static final String RESOURCE_NAME_FOR_PERMISSIONS = "CHARGE";

private final PlatformSecurityContext context;
private final ChargeReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer<ChargeData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final CommandDispatcher dispatcher;
private final ChargeReadService chargeReadService;

@GET
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Charges", operationId = "retrieveAllCharges", description = """
Returns the list of defined charges.

Example Requests:

charges""")
public List<ChargeData> retrieveAllCharges() {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
return readPlatformService.retrieveAllCharges();
return chargeReadService.retrieveAllCharges();
}

@GET
@Path("{chargeId}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Charge", operationId = "retrieveOneCharge", description = """
Returns the details of a defined Charge.

Expand All @@ -97,14 +91,10 @@ public List<ChargeData> retrieveAllCharges() {
@AlternativeOperationId("retrieveCharge")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargesApiResourceSwagger.GetChargesResponse.class)))
public ChargeData retrieveCharge(@PathParam("chargeId") @Parameter(description = "chargeId") final Long chargeId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);

final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());

ChargeData charge = readPlatformService.retrieveCharge(chargeId);
if (settings.isTemplate()) {
final ChargeData templateData = readPlatformService.retrieveNewChargeDetails(charge.getChargeAppliesTo().getId(),
@QueryParam("template") @Parameter(description = "template") final boolean template) {
ChargeData charge = chargeReadService.retrieveCharge(chargeId);
if (template) {
final ChargeData templateData = chargeReadService.retrieveNewChargeDetails(charge.getChargeAppliesTo().getId(),
charge.getChargeTimeType().getId());
charge = ChargeData.withTemplate(charge, templateData);
}
Expand All @@ -113,7 +103,6 @@ public ChargeData retrieveCharge(@PathParam("chargeId") @Parameter(description =

@GET
@Path("template")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Charge Template", operationId = "retrieveTemplateCharge", 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:

Expand All @@ -126,43 +115,49 @@ public ChargeData retrieveCharge(@PathParam("chargeId") @Parameter(description =
@AlternativeOperationId("retrieveNewChargeDetails")
public ChargeData retrieveNewChargeDetails(@QueryParam("chargeAppliesTo") Long chargeAppliesTo,
@QueryParam("chargeTimeType") Long chargeTimeType) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
return readPlatformService.retrieveNewChargeDetails(chargeAppliesTo, chargeTimeType);
return chargeReadService.retrieveNewChargeDetails(chargeAppliesTo, chargeTimeType);
}

@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create/Define a Charge", operationId = "createCharge", description = "Define a new charge that can later be associated with loans and savings through their respective product definitions or directly on each account instance.")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = ChargeRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargesApiResourceSwagger.PostChargesResponse.class)))
public CommandProcessingResult createCharge(@Parameter(hidden = true) ChargeRequest chargeRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createCharge()
.withJson(toApiJsonSerializer.serialize(chargeRequest)).build();
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargeCreateResponse.class)))
public ChargeCreateResponse createCharge(@RequestBody(required = true) @Valid final ChargeCreateRequest chargeRequest) {
final ChargeCreateCommand command = new ChargeCreateCommand();
command.setPayload(chargeRequest);

final Supplier<ChargeCreateResponse> response = dispatcher.dispatch(command);

return response.get();
}

@PUT
@Path("{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a Charge", operationId = "updateCharge", description = "Updates the details of a Charge.")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = ChargeRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargesApiResourceSwagger.PutChargesChargeIdResponse.class)))
public CommandProcessingResult updateCharge(@PathParam("chargeId") @Parameter(description = "chargeId") final Long chargeId,
@Parameter(hidden = true) ChargeRequest chargeRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCharge(chargeId)
.withJson(toApiJsonSerializer.serialize(chargeRequest)).build();
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
public ChargeUpdateResponse updateCharge(@PathParam("chargeId") @Parameter(description = "chargeId") final Long chargeId,
@RequestBody(required = true) @Valid final ChargeUpdateRequest chargeRequest) {
chargeRequest.setId(chargeId);

final ChargeUpdateCommand command = new ChargeUpdateCommand();
command.setPayload(chargeRequest);

final Supplier<ChargeUpdateResponse> response = dispatcher.dispatch(command);

return response.get();
}

@DELETE
@Path("{chargeId}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a Charge", operationId = "deleteCharge", description = "Deletes a Charge.")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargesApiResourceSwagger.DeleteChargesChargeIdResponse.class)))
public CommandProcessingResult deleteCharge(@PathParam("chargeId") @Parameter(description = "chargeId") final Long chargeId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCharge(chargeId).build();
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ChargeDeleteResponse.class)))
public ChargeDeleteResponse deleteCharge(@PathParam("chargeId") @Parameter(description = "chargeId") final Long chargeId) {
final ChargeDeleteCommand command = new ChargeDeleteCommand();
command.setPayload(new ChargeDeleteRequest(chargeId));

final Supplier<ChargeDeleteResponse> response = dispatcher.dispatch(command);

return response.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@

import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.util.Set;

/**
* Created by Chirag Gupta on 12/01/17.
* Documentation-only schema classes that preserve the published OpenAPI contract of the /v1/charges endpoints. The
* runtime request/response types are the typed DTOs in org.apache.fineract.portfolio.charge.data.
*/
final class ChargesApiResourceSwagger {

Expand Down Expand Up @@ -126,58 +126,6 @@ private GetChargesTaxGroup() {}
public GetChargesTaxGroup taxGroup;
}

@Schema(description = "PostChargesRequest")
public static final class PostChargesRequest {

private PostChargesRequest() {}

@Schema(example = "Loan Service fee")
public String name;
@Schema(example = "1")
public Integer chargeAppliesTo;
@Schema(example = "USD")
public String currencyCode;
@Schema(example = "en")
public String locale;
@Schema(example = "230.56")
public Double amount;
@Schema(example = "1")
public Integer chargeTimeType;
@Schema(example = "1")
public Integer chargeCalculationType;
@Schema(example = "1")
public Integer chargePaymentMode;
@Schema(example = "true")
public boolean active;
@Schema(example = "dd MMMM")
public String monthDayFormat;
@Schema(example = "false")
public boolean penalty;
@Schema(example = "23.43")
public BigDecimal minCap;
@Schema(example = "45.56")
public BigDecimal maxCap;
@Schema(example = "1")
public Long taxGroupId;
@Schema(example = "1")
public Integer freeWithdrawalFrequency;
@Schema(example = "10")
public Integer restartCountFrequency;
@Schema(example = "1")
public Integer countFrequencyType;
@Schema(example = "true")
public boolean enableFreeWithdrawalCharge;
}

@Schema(description = "PostChargesResponse")
public static final class PostChargesResponse {

private PostChargesResponse() {}

@Schema(example = "1")
public Long resourceId;
}

@Schema(description = "PutChargesChargeIdRequest")
public static final class PutChargesChargeIdRequest {

Expand Down Expand Up @@ -240,70 +188,4 @@ private PutChargesChargeIdResponse() {}
public Long resourceId;
public PutChargesChargeIdRequest changes;
}

@Schema(description = "DeleteChargesChargeIdResponse")
public static final class DeleteChargesChargeIdResponse {

private DeleteChargesChargeIdResponse() {}

@Schema(example = "1")
public Long resourceId;
}

@Schema(description = "GetChargesTemplateResponse")
public static final class GetChargesTemplateResponse {

private GetChargesTemplateResponse() {}

static final class GetChargesTemplateLoanChargeCalculationTypeOptions {

private GetChargesTemplateLoanChargeCalculationTypeOptions() {}

@Schema(example = "1")
public Long id;
@Schema(example = "chargeCalculationType.flat")
public String code;
@Schema(example = "Flat")
public String description;
}

static final class GetChargesTemplateLoanChargeTimeTypeOptions {

private GetChargesTemplateLoanChargeTimeTypeOptions() {}

@Schema(example = "2")
public Long id;
@Schema(example = "chargeTimeType.specifiedDueDate")
public String code;
@Schema(example = "Specified due date")
public String description;
}

static final class GetChargesTemplateFeeFrequencyOptions {

private GetChargesTemplateFeeFrequencyOptions() {}

@Schema(example = "0")
public Long id;
@Schema(example = "loanTermFrequency.periodFrequencyType.days")
public String code;
@Schema(example = "Days")
public String description;
}

@Schema(example = "false")
public boolean active;
@Schema(example = "false")
public boolean penalty;
public Set<GetChargesResponse.GetChargesCurrencyResponse> currencyOptions;
public Set<GetChargesResponse.GetChargesCalculationTypeResponse> chargeCalculationTypeOptions;
public Set<GetChargesResponse.GetChargesAppliesToResponse> chargeAppliesToOptions;
public Set<GetChargesResponse.GetChargesTimeTypeResponse> chargeTimeTypeOptions;
public Set<GetChargesResponse.GetChargesPaymentModeResponse> chargePaymentModeOptions;
public Set<GetChargesTemplateLoanChargeCalculationTypeOptions> loanChargeCalculationTypeOptions;
public Set<GetChargesTemplateLoanChargeTimeTypeOptions> loanChargeTimeTypeOptions;
public Set<GetChargesTemplateLoanChargeCalculationTypeOptions> savingsChargeCalculationTypeOptions;
public Set<GetChargesTemplateLoanChargeTimeTypeOptions> savingsChargeTimeTypeOptions;
public Set<GetChargesTemplateFeeFrequencyOptions> feeFrequencyOptions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.command;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.portfolio.charge.data.ChargeCreateRequest;

@Data
@EqualsAndHashCode(callSuper = true)
public class ChargeCreateCommand extends Command<ChargeCreateRequest> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.charge.command;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.portfolio.charge.data.ChargeDeleteRequest;

@Data
@EqualsAndHashCode(callSuper = true)
public class ChargeDeleteCommand extends Command<ChargeDeleteRequest> {}
Loading
Loading