FINERACT-2169: Refactor standing instruction api controller;

This commit is contained in:
viktorpavlenko
2025-04-08 16:39:15 +02:00
committed by Adam Saghy
parent 056a20f58e
commit 0769b41b26
8 changed files with 238 additions and 186 deletions
@@ -26,6 +26,8 @@ public final class CommandParameterUtil {
public static final String SALE_COMMAND_VALUE = "sale"; public static final String SALE_COMMAND_VALUE = "sale";
public static final String BUY_BACK_COMMAND_VALUE = "buyback"; public static final String BUY_BACK_COMMAND_VALUE = "buyback";
public static final String CANCEL_COMMAND_VALUE = "cancel"; public static final String CANCEL_COMMAND_VALUE = "cancel";
public static final String UPDATE_COMMAND_VALUE = "update";
public static final String DELETE_COMMAND_VALUE = "delete";
private CommandParameterUtil() {} private CommandParameterUtil() {}
@@ -18,11 +18,6 @@
*/ */
package org.apache.fineract.portfolio.account.api; package org.apache.fineract.portfolio.account.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.portfolio.account.AccountDetailConstants;
public final class StandingInstructionApiConstants { public final class StandingInstructionApiConstants {
private StandingInstructionApiConstants() { private StandingInstructionApiConstants() {
@@ -44,8 +39,4 @@ public final class StandingInstructionApiConstants {
public static final String recurrenceOnMonthDayParamName = "recurrenceOnMonthDay"; public static final String recurrenceOnMonthDayParamName = "recurrenceOnMonthDay";
public static final String monthDayFormatParamName = "monthDayFormat"; public static final String monthDayFormatParamName = "monthDayFormat";
static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(AccountDetailConstants.idParamName, nameParamName, priorityParamName, instructionTypeParamName, statusParamName,
AccountDetailConstants.transferTypeParamName, validFromParamName, validTillParamName));
} }
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST; import jakarta.ws.rs.POST;
@@ -39,18 +40,19 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.batch.command.CommandHandlerRegistry;
import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -58,6 +60,9 @@ import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.account.data.StandingInstructionDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
import org.apache.fineract.portfolio.account.data.StandingInstructionData; import org.apache.fineract.portfolio.account.data.StandingInstructionData;
import org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
import org.apache.fineract.portfolio.account.data.request.StandingInstructionSearchParam;
import org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService; import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -72,11 +77,16 @@ public class StandingInstructionApiResource {
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService; private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final SqlValidator sqlValidator; private final SqlValidator sqlValidator;
private static final CommandHandlerRegistry<String, Long, String, CommandWrapper> COMMAND_HANDLER_REGISTRY = new CommandHandlerRegistry<>(
Map.of(CommandParameterUtil.UPDATE_COMMAND_VALUE,
(id, json) -> new CommandWrapperBuilder().updateStandingInstruction(id).withJson(json).build(),
CommandParameterUtil.DELETE_COMMAND_VALUE,
(id, json) -> new CommandWrapperBuilder().deleteStandingInstruction(id).withJson(json).build()));
@GET @GET
@Path("template") @Path("template")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -88,42 +98,27 @@ public class StandingInstructionApiResource {
+ "standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n" + "\n" + "standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n" + "\n"
+ "standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1") + "standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1")
@ApiResponses(@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class)))) @ApiResponses(@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class))))
public String template(@QueryParam("fromOfficeId") @Parameter(description = "fromOfficeId") final Long fromOfficeId, public StandingInstructionData template(@BeanParam StandingInstructionSearchParam instructionParam) {
@QueryParam("fromClientId") @Parameter(description = "fromClientId") final Long fromClientId, context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccountId,
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType,
@QueryParam("toOfficeId") @Parameter(description = "toOfficeId") final Long toOfficeId,
@QueryParam("toClientId") @Parameter(description = "toClientId") final Long toClientId,
@QueryParam("toAccountId") @Parameter(description = "toAccountId") final Long toAccountId,
@QueryParam("toAccountType") @Parameter(description = "toAccountType") final Integer toAccountType,
@QueryParam("transferType") @Parameter(description = "transferType") final Integer transferType,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME); return standingInstructionReadPlatformService.retrieveTemplate(instructionParam.getFromOfficeId(),
instructionParam.getFromClientId(), instructionParam.getFromAccountId(), instructionParam.getFromAccountType(),
final StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveTemplate(fromOfficeId, instructionParam.getToOfficeId(), instructionParam.getToClientId(), instructionParam.getToAccountId(),
fromClientId, fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType, transferType); instructionParam.getToAccountType(), instructionParam.getTransferType());
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, standingInstructionData,
StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
} }
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create new Standing Instruction", description = "Ability to create new instruction for transfer of monetary funds from one account to another") @Operation(summary = "Create new Standing Instruction", description = "Ability to create new instruction for transfer of monetary funds from one account to another")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PostStandingInstructionsRequest.class))) @RequestBody(required = true, content = @Content(schema = @Schema(implementation = StandingInstructionCreationRequest.class)))
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PostStandingInstructionsResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PostStandingInstructionsResponse.class))) })
public String create(@Parameter(hidden = true) final String apiRequestBodyAsJson) { public CommandProcessingResult create(@Parameter(hidden = true) StandingInstructionCreationRequest creationRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createStandingInstruction()
.withJson(toApiJsonSerializer.serialize(creationRequest)).build();
final CommandWrapper commandRequest = new CommandWrapperBuilder().createStandingInstruction().withJson(apiRequestBodyAsJson) return commandsSourceWritePlatformService.logCommandSource(commandRequest);
.build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
} }
@PUT @PUT
@@ -134,34 +129,19 @@ public class StandingInstructionApiResource {
+ "\n" + "PUT https://DomainName/api/v1/standinginstructions/1?command=update\n" + "\n\n" + "\n" + "PUT https://DomainName/api/v1/standinginstructions/1?command=update\n" + "\n\n"
+ "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n" + "\n" + "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n" + "\n"
+ "PUT https://DomainName/api/v1/standinginstructions/1?command=delete") + "PUT https://DomainName/api/v1/standinginstructions/1?command=delete")
@RequestBody(required = false, content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdRequest.class))) @RequestBody(content = @Content(schema = @Schema(implementation = StandingInstructionUpdatesRequest.class)))
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class))) })
public String update( public CommandProcessingResult update(
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId, @PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
@Parameter(hidden = true) final String apiRequestBodyAsJson, @Parameter(hidden = true) StandingInstructionUpdatesRequest updatesRequest,
@QueryParam("command") @Parameter(description = "command") final String commandParam) { @QueryParam("command") @Parameter(description = "command") final String commandParam) {
CommandWrapper commandRequest = null; final String serializedUpdatesRequest = toApiJsonSerializer.serialize(updatesRequest);
if (is(commandParam, "update")) { final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(StringUtils.toRootLowerCase(commandParam),
commandRequest = new CommandWrapperBuilder().updateStandingInstruction(standingInstructionId).withJson(apiRequestBodyAsJson) standingInstructionId, serializedUpdatesRequest, new UnrecognizedQueryParamException("command", commandParam));
.build();
} else if (is(commandParam, "delete")) {
commandRequest = new CommandWrapperBuilder().deleteStandingInstruction(standingInstructionId).withJson(apiRequestBodyAsJson)
.build();
}
if (commandRequest == null) { return commandsSourceWritePlatformService.logCommandSource(commandRequest);
throw new UnrecognizedQueryParamException("command", commandParam);
}
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
} }
@GET @GET
@@ -170,7 +150,7 @@ public class StandingInstructionApiResource {
@Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions") @Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions")
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class))) })
public String retrieveAll(@Context final UriInfo uriInfo, public Page<StandingInstructionData> retrieveAll(
@QueryParam("externalId") @Parameter(description = "externalId") final String externalId, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset, @QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@QueryParam("limit") @Parameter(description = "limit") final Integer limit, @QueryParam("limit") @Parameter(description = "limit") final Integer limit,
@@ -182,7 +162,7 @@ public class StandingInstructionApiResource {
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount, @QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount,
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) { @QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
sqlValidator.validate(orderBy); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder); sqlValidator.validate(sortOrder);
@@ -195,10 +175,7 @@ public class StandingInstructionApiResource {
StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId, StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
fromAccount, fromAccountType, startDateRange, endDateRange); fromAccount, fromAccountType, startDateRange, endDateRange);
final Page<StandingInstructionData> transfers = this.standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO); return standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfers, StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
} }
@GET @GET
@@ -208,7 +185,7 @@ public class StandingInstructionApiResource {
@Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1") @Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1")
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class))) })
public String retrieveOne( public StandingInstructionData retrieveOne(
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId, @PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
@Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId, @Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset, @QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@@ -216,36 +193,34 @@ public class StandingInstructionApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
sqlValidator.validate(orderBy); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder); sqlValidator.validate(sortOrder);
StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveOne(standingInstructionId); StandingInstructionData standingInstructionData = standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset) final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build(); .orderBy(orderBy).sortOrder(sortOrder).build();
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
Page<AccountTransferData> transfers = null;
if (!associationParameters.isEmpty()) { if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) { if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("transactions", "template")); associationParameters.addAll(Arrays.asList("transactions", "template"));
} }
if (associationParameters.contains("transactions")) { if (associationParameters.contains("transactions")) {
transfers = this.accountTransfersReadPlatformService.retrieveByStandingInstruction(standingInstructionId, searchParameters); Page<AccountTransferData> transfers = accountTransfersReadPlatformService
.retrieveByStandingInstruction(standingInstructionId, searchParameters);
standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers); standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
} }
if (associationParameters.contains("template")) { if (associationParameters.contains("template")) {
final StandingInstructionData templateData = this.standingInstructionReadPlatformService.retrieveTemplate( final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
standingInstructionData.fromClient().getOfficeId(), standingInstructionData.fromClient().getId(), standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
standingInstructionData.fromAccount().getId(), standingInstructionData.fromAccountType().getValue(), standingInstructionData.getFromAccount().getId(), standingInstructionData.getFromAccountType().getValue(),
standingInstructionData.toClient().getOfficeId(), standingInstructionData.toClient().getId(), standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
standingInstructionData.toAccount().getId(), standingInstructionData.toAccountType().getValue(), standingInstructionData.getToAccount().getId(), standingInstructionData.getToAccountType().getValue(),
standingInstructionData.transferType().getValue()); standingInstructionData.getTransferType().getValue());
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData); standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
} }
} }
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return standingInstructionData;
return this.toApiJsonSerializer.serialize(settings, standingInstructionData,
StandingInstructionApiConstants.RESPONSE_DATA_PARAMETERS);
} }
} }
@@ -23,6 +23,8 @@ import java.time.LocalDate;
import java.time.MonthDay; import java.time.MonthDay;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.Collection; import java.util.Collection;
import java.util.Optional;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.organisation.office.data.OfficeData; import org.apache.fineract.organisation.office.data.OfficeData;
@@ -40,26 +42,36 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class StandingInstructionData { public final class StandingInstructionData {
@Getter
private final Long id; private final Long id;
@Getter
private final Long accountDetailId; private final Long accountDetailId;
@Getter
private final String name; private final String name;
private final OfficeData fromOffice; private final OfficeData fromOffice;
@Getter
private final ClientData fromClient; private final ClientData fromClient;
private final EnumOptionData fromAccountType; private final EnumOptionData fromAccountType;
@Getter
private final PortfolioAccountData fromAccount; private final PortfolioAccountData fromAccount;
private final OfficeData toOffice; private final OfficeData toOffice;
@Getter
private final ClientData toClient; private final ClientData toClient;
private final EnumOptionData toAccountType; private final EnumOptionData toAccountType;
@Getter
private final PortfolioAccountData toAccount; private final PortfolioAccountData toAccount;
private final EnumOptionData transferType; private final EnumOptionData transferType;
private final EnumOptionData priority; private final EnumOptionData priority;
private final EnumOptionData instructionType; private final EnumOptionData instructionType;
private final EnumOptionData status; private final EnumOptionData status;
@Getter
private final BigDecimal amount; private final BigDecimal amount;
@Getter
private final LocalDate validFrom; private final LocalDate validFrom;
private final LocalDate validTill; private final LocalDate validTill;
private final EnumOptionData recurrenceType; private final EnumOptionData recurrenceType;
private final EnumOptionData recurrenceFrequency; private final EnumOptionData recurrenceFrequency;
@Getter
private final Integer recurrenceInterval; private final Integer recurrenceInterval;
private final MonthDay recurrenceOnMonthDay; private final MonthDay recurrenceOnMonthDay;
private final Page<AccountTransferData> transactions; private final Page<AccountTransferData> transactions;
@@ -269,97 +281,42 @@ public final class StandingInstructionData {
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions); instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
} }
public StandingInstructionType instructionType() { public StandingInstructionType getInstructionType() {
StandingInstructionType standingInstructionType = null; return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
if (this.instructionType != null) {
standingInstructionType = StandingInstructionType.fromInt(this.instructionType.getId().intValue());
}
return standingInstructionType;
} }
public AccountTransferRecurrenceType recurrenceType() { public AccountTransferRecurrenceType getRecurrenceType() {
AccountTransferRecurrenceType recurrenceType = null; return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
if (this.recurrenceType != null) {
recurrenceType = AccountTransferRecurrenceType.fromInt(this.recurrenceType.getId().intValue());
}
return recurrenceType;
} }
public PeriodFrequencyType recurrenceFrequency() { public PeriodFrequencyType getRecurrenceFrequency() {
PeriodFrequencyType frequencyType = null; return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
if (this.recurrenceFrequency != null) {
frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency.getId().intValue());
}
return frequencyType;
} }
public PortfolioAccountType fromAccountType() { public PortfolioAccountType getFromAccountType() {
PortfolioAccountType accountType = null; return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
if (this.fromAccountType != null) {
accountType = PortfolioAccountType.fromInt(this.fromAccountType.getId().intValue());
}
return accountType;
} }
public PortfolioAccountType toAccountType() { public PortfolioAccountType getToAccountType() {
PortfolioAccountType accountType = null; return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
if (this.toAccountType != null) {
accountType = PortfolioAccountType.fromInt(this.toAccountType.getId().intValue());
}
return accountType;
} }
public AccountTransferType transferType() { public AccountTransferType getTransferType() {
AccountTransferType accountTransferType = null; return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
if (this.transferType != null) {
accountTransferType = AccountTransferType.fromInt(this.transferType.getId().intValue());
}
return accountTransferType;
} }
public Integer recurrenceInterval() { public Integer getRecurrenceOnDay() {
return this.recurrenceInterval; return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.DAY_OF_MONTH)).orElse(0);
} }
public Integer recurrenceOnDay() { public Integer getRecurrenceOnMonth() {
Integer recurrenceOnDay = 0; return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.MONTH_OF_YEAR)).orElse(0);
if (this.recurrenceOnMonthDay != null) {
recurrenceOnDay = this.recurrenceOnMonthDay.get(ChronoField.DAY_OF_MONTH);
}
return recurrenceOnDay;
}
public Integer recurrenceOnMonth() {
Integer recurrenceOnMonth = 0;
if (this.recurrenceOnMonthDay != null) {
recurrenceOnMonth = this.recurrenceOnMonthDay.get(ChronoField.MONTH_OF_YEAR);
}
return recurrenceOnMonth;
}
public LocalDate validFrom() {
return this.validFrom;
}
public BigDecimal amount() {
return this.amount;
}
public PortfolioAccountData fromAccount() {
return this.fromAccount;
}
public PortfolioAccountData toAccount() {
return this.toAccount;
}
public String name() {
return this.name;
} }
public Integer toTransferType() { public Integer toTransferType() {
Integer transferType = null; Integer transferType = null;
AccountTransferType accountTransferType = transferType(); AccountTransferType accountTransferType = getTransferType();
if (accountTransferType.isChargePayment()) { if (accountTransferType.isChargePayment()) {
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue(); transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
} else if (accountTransferType.isLoanRepayment()) { } else if (accountTransferType.isLoanRepayment()) {
@@ -368,20 +325,4 @@ public final class StandingInstructionData {
return transferType; return transferType;
} }
public Long accountDetailId() {
return this.accountDetailId;
}
public ClientData fromClient() {
return this.fromClient;
}
public ClientData toClient() {
return this.toClient;
}
public Long getId() {
return this.id;
}
} }
@@ -0,0 +1,56 @@
/**
* 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.account.data.request;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class StandingInstructionCreationRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String toOfficeId;
private String amount;
private String validTill;
private String toAccountType;
private String dateFormat;
private String recurrenceOnMonthDay;
private String toAccountId;
private String fromClientId;
private String validFrom;
private String locale;
private String priority;
private String recurrenceType;
private String fromAccountType;
private String recurrenceInterval;
private String monthDayFormat;
private String toClientId;
private String instructionType;
private String fromAccountId;
private String recurrenceFrequency;
private String fromOfficeId;
private String name;
private String transferType;
private String status;
}
@@ -0,0 +1,40 @@
/**
* 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.account.data.request;
import jakarta.ws.rs.QueryParam;
import java.io.Serial;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
public class StandingInstructionSearchParam extends AccountTransSearchParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@QueryParam("transferType")
private Integer transferType;
}
@@ -0,0 +1,47 @@
/**
* 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.account.data.request;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class StandingInstructionUpdatesRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String amount;
private String validTill;
private String dateFormat;
private String validFrom;
private String locale;
private String priority;
private String recurrenceType;
private String recurrenceInterval;
private String instructionType;
private String recurrenceFrequency;
private String recurrenceOnMonthDay;
private String name;
private String monthDayFormat;
private String status;
}
@@ -66,34 +66,34 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
List<Throwable> errors = new ArrayList<>(); List<Throwable> errors = new ArrayList<>();
for (StandingInstructionData data : instructionData) { for (StandingInstructionData data : instructionData) {
boolean isDueForTransfer = false; boolean isDueForTransfer = false;
AccountTransferRecurrenceType recurrenceType = data.recurrenceType(); AccountTransferRecurrenceType recurrenceType = data.getRecurrenceType();
StandingInstructionType instructionType = data.instructionType(); StandingInstructionType instructionType = data.getInstructionType();
LocalDate transactionDate = DateUtils.getBusinessLocalDate(); LocalDate transactionDate = DateUtils.getBusinessLocalDate();
if (recurrenceType.isPeriodicRecurrence()) { if (recurrenceType.isPeriodicRecurrence()) {
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
PeriodFrequencyType frequencyType = data.recurrenceFrequency(); PeriodFrequencyType frequencyType = data.getRecurrenceFrequency();
LocalDate startDate = data.validFrom(); LocalDate startDate = data.getValidFrom();
if (frequencyType.isMonthly()) { if (frequencyType.isMonthly()) {
startDate = startDate.withDayOfMonth(data.recurrenceOnDay()); startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
if (DateUtils.isBefore(startDate, data.validFrom())) { if (DateUtils.isBefore(startDate, data.getValidFrom())) {
startDate = startDate.plusMonths(1); startDate = startDate.plusMonths(1);
} }
} else if (frequencyType.isYearly()) { } else if (frequencyType.isYearly()) {
startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonth(data.recurrenceOnMonth()); startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay()).withMonth(data.getRecurrenceOnMonth());
if (DateUtils.isBefore(startDate, data.validFrom())) { if (DateUtils.isBefore(startDate, data.getValidFrom())) {
startDate = startDate.plusYears(1); startDate = startDate.plusYears(1);
} }
} }
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.recurrenceInterval(), startDate, isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.getRecurrenceInterval(), startDate,
transactionDate); transactionDate);
} }
BigDecimal transactionAmount = data.amount(); BigDecimal transactionAmount = data.getAmount();
if (data.toAccountType().isLoanAccount() if (data.getToAccountType().isLoanAccount()
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) { && (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
.retriveLoanDuesData(data.toAccount().getId()); .retriveLoanDuesData(data.getToAccount().getId());
if (data.instructionType().isDuesAmoutTransfer()) { if (data.getInstructionType().isDuesAmoutTransfer()) {
transactionAmount = standingInstructionDuesData.totalDueAmount(); transactionAmount = standingInstructionDuesData.totalDueAmount();
} }
if (recurrenceType.isDuesRecurrence()) { if (recurrenceType.isDuesRecurrence()) {
@@ -105,10 +105,10 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
final SavingsAccount fromSavingsAccount = null; final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true; final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false; final boolean isExceptionForBalanceCheck = false;
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(), AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
data.toAccountType(), data.fromAccount().getId(), data.toAccount().getId(), data.getFromAccountType(), data.getToAccountType(), data.getFromAccount().getId(), data.getToAccount().getId(),
data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
data.transferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount, data.getTransferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
isRegularTransaction, isExceptionForBalanceCheck); isRegularTransaction, isExceptionForBalanceCheck);
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId()); final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());