From 62fed9b4d12c577bca737157019ab5e36659bff7 Mon Sep 17 00:00:00 2001 From: yuwatidora Date: Wed, 19 Nov 2025 10:21:43 -0500 Subject: [PATCH] FINERACT 2289:New command processing - Working Days working days write service pipeline refactored update security config read service refactoring format spotless check validation fixes validation fixes2 fixing updateWorkingDaysTest update tests for auth and tenant based on security config, ran spotless and fixed style issues fixed updaterequest dto and removed generated fixed workingdaysupdaterequest to be backward compatible FINERACT-2289: Fixing working days integration test errors change test to original updated id to resourceId and tests are successful working days write service pipeline refactored update security config read service refactoring format spotless check validation fixes validation fixes2 fixing updateWorkingDaysTest update tests for auth and tenant based on security config, ran spotless and fixed style issues fixed updaterequest dto and removed generated fixed workingdaysupdaterequest to be backward compatible FINERACT-2289: Fixing working days integration test errors --- .gitignore | 2 - config/docker/env/mariadb.env | 20 ++++ docker-compose-mariadb.yml | 34 ++++++ docker-compose-mysql.yml | 34 ++++++ .../workingdays/domain/WorkingDays.java | 111 ++++++++++-------- .../domain/WorkingDaysEnumerations.java | 4 +- .../core/config/SecurityConfig.java | 7 ++ .../api/WorkingDaysApiResource.java | 46 +++----- .../command/WorkingDaysUpdateCommand.java | 28 +++++ .../workingdays/data/WorkingDayValidator.java | 1 + .../workingdays/data/WorkingDaysData.java | 36 +----- .../data/WorkingDaysUpdateRequest.java | 47 ++++++++ .../WorkingDaysUpdateRequestValidator.java | 61 ++++++++++ .../data/WorkingDaysUpdateResponse.java | 43 +++++++ .../UpdateWorkingDaysCommandHandler.java | 41 ++++--- .../WorkingDaysReadPlatformServiceImpl.java | 12 +- .../WorkingDaysWritePlatformService.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 53 +++++++-- .../OrganisationWorkingDaysConfiguration.java | 6 +- 19 files changed, 447 insertions(+), 147 deletions(-) create mode 100644 config/docker/env/mariadb.env create mode 100644 docker-compose-mariadb.yml create mode 100644 docker-compose-mysql.yml create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java diff --git a/.gitignore b/.gitignore index c63dfd09fa9..2ac78be74f5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,8 +26,6 @@ licenses **/.asciidoctor/diagram/* **/images/diag-*.svg -fineract-provider/src/main/generated/ - **/out/ gradleExp/ diff --git a/config/docker/env/mariadb.env b/config/docker/env/mariadb.env new file mode 100644 index 00000000000..26aae0972be --- /dev/null +++ b/config/docker/env/mariadb.env @@ -0,0 +1,20 @@ +# +# 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. +# + +MARIADB_ROOT_PASSWORD=mysql \ No newline at end of file diff --git a/docker-compose-mariadb.yml b/docker-compose-mariadb.yml new file mode 100644 index 00000000000..59f18160b9f --- /dev/null +++ b/docker-compose-mariadb.yml @@ -0,0 +1,34 @@ +# 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. +# + +services: + mariadb: + container_name: mariadb + image: mariadb:11.4 + volumes: + - ./config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ./config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro + restart: always + env_file: + - ./config/docker/env/mariadb.env + healthcheck: + test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ] + timeout: 10s + retries: 10 + ports: + - "3306:3306" diff --git a/docker-compose-mysql.yml b/docker-compose-mysql.yml new file mode 100644 index 00000000000..5f0521724ad --- /dev/null +++ b/docker-compose-mysql.yml @@ -0,0 +1,34 @@ +# 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. +# + +services: + mysql: + container_name: mysql + image: mysql:8 + volumes: + - ${PWD}/config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ${PWD}/config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro + restart: always + env_file: + - ${PWD}/config/docker/env/mysql.env + healthcheck: + test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ] + timeout: 10s + retries: 10 + ports: + - "3306:3306" diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java index 8fbe7125b58..0c5c7af8e48 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java @@ -21,16 +21,21 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.util.LinkedHashMap; -import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -import org.apache.fineract.infrastructure.core.api.JsonCommand; +import lombok.experimental.FieldNameConstants; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; +@Builder @Getter +@Setter @Entity +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants @Table(name = "m_working_days") public class WorkingDays extends AbstractPersistableCustom { @@ -47,51 +52,57 @@ public class WorkingDays extends AbstractPersistableCustom { @Column(name = "extend_term_holiday_repayment", nullable = false) private Boolean extendTermForRepaymentsOnHolidays; - protected WorkingDays() { - - } - - public WorkingDays(final String recurrence, final Integer repaymentReschedulingType, final Boolean extendTermForDailyRepayments, - final Boolean extendTermForRepaymentsOnHolidays) { - this.recurrence = recurrence; - this.repaymentReschedulingType = repaymentReschedulingType; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public Map update(final JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(7); - - final String recurrenceParamName = "recurrence"; - if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) { - final String newValue = command.stringValueOfParameterNamed(recurrenceParamName); - actualChanges.put(recurrenceParamName, newValue); - this.recurrence = newValue; - } - - final String repaymentRescheduleTypeParamName = "repaymentRescheduleType"; - if (command.isChangeInIntegerParameterNamed(repaymentRescheduleTypeParamName, this.repaymentReschedulingType)) { - final Integer newValue = command.integerValueOfParameterNamed(repaymentRescheduleTypeParamName); - actualChanges.put(repaymentRescheduleTypeParamName, WorkingDaysEnumerations.workingDaysStatusType(newValue)); - this.repaymentReschedulingType = RepaymentRescheduleType.fromInt(newValue).getValue(); - } - - if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments, - this.extendTermForDailyRepayments)) { - final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments); - actualChanges.put(WorkingDaysApiConstants.extendTermForDailyRepayments, newValue); - this.extendTermForDailyRepayments = newValue; - } - - if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, - this.extendTermForRepaymentsOnHolidays)) { - final Boolean newValue = command - .booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays); - actualChanges.put(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, newValue); - this.extendTermForRepaymentsOnHolidays = newValue; - } - - return actualChanges; - } + // protected WorkingDays() { + // + // } + // + // public WorkingDays(final String recurrence, final Integer repaymentReschedulingType, final Boolean + // extendTermForDailyRepayments, + // final Boolean extendTermForRepaymentsOnHolidays) { + // this.recurrence = recurrence; + // this.repaymentReschedulingType = repaymentReschedulingType; + // this.extendTermForDailyRepayments = extendTermForDailyRepayments; + // this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; + // } + // + // public Map update(final JsonCommand command) { + // final Map actualChanges = new LinkedHashMap<>(7); + // + // final String recurrenceParamName = "recurrence"; + // if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) { + // final String newValue = command.stringValueOfParameterNamed(recurrenceParamName); + // actualChanges.put(recurrenceParamName, newValue); + // this.recurrence = newValue; + // } + // + // final String repaymentRescheduleTypeParamName = "repaymentRescheduleType"; + // if (command.isChangeInIntegerParameterNamed(repaymentRescheduleTypeParamName, this.repaymentReschedulingType)) { + // final Integer newValue = command.integerValueOfParameterNamed(repaymentRescheduleTypeParamName); + // actualChanges.put(repaymentRescheduleTypeParamName, WorkingDaysEnumerations.workingDaysStatusType(newValue)); + // this.repaymentReschedulingType = RepaymentRescheduleType.fromInt(newValue).getValue(); + // } + // + // if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments, + // this.extendTermForDailyRepayments)) { + // final Boolean newValue = + // command.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments); + // actualChanges.put(WorkingDaysApiConstants.extendTermForDailyRepayments, newValue); + // this.extendTermForDailyRepayments = newValue; + // } + // + // if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, + // this.extendTermForRepaymentsOnHolidays)) { + // final Boolean newValue = command + // .booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays); + // actualChanges.put(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, newValue); + // this.extendTermForRepaymentsOnHolidays = newValue; + // } + // + // return actualChanges; + // } + // + // public Map update(WorkingDaysUpdateRequest request) { + // + // } } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java index e3f9606285a..c2caf644841 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java @@ -41,8 +41,8 @@ public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleTy case SAME_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.SAME_DAY.getValue().longValue(), RepaymentRescheduleType.SAME_DAY.getCode(), "same day"); - break; + case MOVE_TO_NEXT_WORKING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getCode(), "move to next working day"); @@ -52,10 +52,12 @@ public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleTy optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getCode(), "move to next repayment meeting day"); break; + case MOVE_TO_PREVIOUS_WORKING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getCode(), "move to previous working day"); break; + case MOVE_TO_NEXT_MEETING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getCode(), "move to next meeting day"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java index 6d6c7e50846..723381948cc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -154,6 +154,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CURRENCY") .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/currencies")) .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CURRENCY") + // working days + .requestMatchers(antMatcher(HttpMethod.GET, "/api/*/workingdays")) + .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_WORKING_DAYS") + .requestMatchers(antMatcher(HttpMethod.GET, "api/*/workingdays/template")) + .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_WORKING_DAYS") + .requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/workingdays")) + .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_WORKING_DAYS") // ... .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() // .requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() // diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java index 3cb7555c92f..c05d3c54eb1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java @@ -19,27 +19,24 @@ package org.apache.fineract.organisation.workingdays.api; 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 jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import java.util.UUID; +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.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.command.core.CommandPipeline; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.workingdays.command.WorkingDaysUpdateCommand; import org.apache.fineract.organisation.workingdays.data.WorkingDaysData; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService; import org.springframework.stereotype.Component; @@ -52,17 +49,15 @@ @RequiredArgsConstructor public class WorkingDaysApiResource { - private final DefaultToApiJsonSerializer toApiJsonSerializer; private final WorkingDaysReadPlatformService workingDaysReadPlatformService; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final PlatformSecurityContext context; + private final WorkingDaysUpdateRequestValidator workingDaysUpdateRequestValidator; + private final CommandPipeline commandPipeline; @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "List Working days", description = "Example Requests:\n" + "\n" + "workingdays") public WorkingDaysData retrieveAll() { - this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); return this.workingDaysReadPlatformService.retrieve(); } @@ -71,16 +66,17 @@ public WorkingDaysData retrieveAll() { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Update a Working Day", description = "Mandatory Fields\n" + "recurrence,repaymentRescheduleType,extendTermForDailyRepayments,locale") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.PutWorkingDaysRequest.class))) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.PutWorkingDaysResponse.class))) }) - public String update(@Parameter(hidden = true) final String jsonRequestBody) { + public WorkingDaysUpdateResponse update(@Valid WorkingDaysUpdateRequest request) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateWorkingDays().withJson(jsonRequestBody).build(); + final var command = new WorkingDaysUpdateCommand(); - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + command.setId(UUID.randomUUID()); + command.setCreatedAt(DateUtils.getAuditOffsetDateTime()); + command.setPayload(request); - return this.toApiJsonSerializer.serialize(result); + final Supplier response = commandPipeline.send(command); + + return response.get(); } @GET @@ -89,11 +85,7 @@ public String update(@Parameter(hidden = true) final String jsonRequestBody) { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Working Days Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for working days.\n" + "\n" + "Example Request:\n" + "\n" + "workingdays/template") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.GetWorkingDaysTemplateResponse.class))) }) public WorkingDaysData template() { - this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); - return this.workingDaysReadPlatformService.repaymentRescheduleType(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java new file mode 100644 index 00000000000..d9fb6693165 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java @@ -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.organisation.workingdays.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkingDaysUpdateCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java index 8c3e365c18a..97416f3806a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +//Not used @Component public class WorkingDayValidator { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java index ef2e62ab7b9..24d778779e8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java @@ -21,12 +21,18 @@ import java.io.Serial; import java.io.Serializable; import java.util.Collection; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants public class WorkingDaysData implements Serializable { @Serial @@ -42,37 +48,7 @@ public class WorkingDaysData implements Serializable { private Boolean extendTermForRepaymentsOnHolidays; - // template date @SuppressWarnings("unused") private Collection repaymentRescheduleOptions; - public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType, Boolean extendTermForDailyRepayments, - Boolean extendTermForRepaymentsOnHolidays) { - this.id = id; - this.recurrence = recurrence; - this.repaymentRescheduleType = repaymentRescheduleType; - this.repaymentRescheduleOptions = null; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType, - Collection repaymentRescheduleOptions, Boolean extendTermForDailyRepayments, - Boolean extendTermForRepaymentsOnHolidays) { - this.id = id; - this.recurrence = recurrence; - this.repaymentRescheduleType = repaymentRescheduleType; - this.repaymentRescheduleOptions = repaymentRescheduleOptions; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public WorkingDaysData(WorkingDaysData data, Collection repaymentRescheduleOptions) { - this.id = data.id; - this.recurrence = data.recurrence; - this.repaymentRescheduleType = data.repaymentRescheduleType; - this.repaymentRescheduleOptions = repaymentRescheduleOptions; - this.extendTermForDailyRepayments = data.extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = data.extendTermForRepaymentsOnHolidays; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java new file mode 100644 index 00000000000..8eebb989b71 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java @@ -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.organisation.workingdays.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class WorkingDaysUpdateRequest implements Serializable { + + @Serial + public static final long serialVersionUID = 1L; + + private String recurrence; + + private Integer repaymentRescheduleType; + + private Boolean extendTermForDailyRepayments; + + private Boolean extendTermForRepaymentsOnHolidays; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java new file mode 100644 index 00000000000..233d9e1c6fd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java @@ -0,0 +1,61 @@ +/** + * 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.organisation.workingdays.data; + +import java.util.ArrayList; +import java.util.List; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; +import org.springframework.stereotype.Component; + +@Component +public class WorkingDaysUpdateRequestValidator { + + public void validateForUpdate(final WorkingDaysUpdateRequest request) { + + List validationErrors = new ArrayList<>(); + DataValidatorBuilder validator = new DataValidatorBuilder(validationErrors) + .resource(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); + + // recurrence (mandatory) + String recurrence = request.getRecurrence(); + validator.reset().parameter(WorkingDaysApiConstants.recurrence).value(recurrence).notNull(); + + // repaymentRescheduleType (optional, but must be 1–4 if present) + validator.reset().parameter(WorkingDaysApiConstants.repayment_rescheduling_enum).value(request.getRepaymentRescheduleType()) + .ignoreIfNull().inMinMaxRange(1, 4); + + // extendTermForDailyRepayments (optional but must be boolean if provided) + validator.reset().parameter(WorkingDaysApiConstants.extendTermForDailyRepayments).value(request.getExtendTermForDailyRepayments()) + .ignoreIfNull().validateForBooleanValue(); + + // extendTermForRepaymentsOnHolidays (optional but must be boolean if provided) + validator.reset().parameter(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays) + .value(request.getExtendTermForRepaymentsOnHolidays()).ignoreIfNull().validateForBooleanValue(); + + } + + private void throwExceptionIfValidationWarningsExist(final List validationErrors) { + if (!validationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(validationErrors); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java new file mode 100644 index 00000000000..725c3e9e77a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java @@ -0,0 +1,43 @@ +/** + * 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.organisation.workingdays.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WorkingDaysUpdateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + private Long resourceId; + private String recurrence; + private Integer repaymentRescheduleType; + private Boolean extendTermForDailyRepayments; + private Boolean extendTermForRepaymentsOnHolidays; + private Map changes; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java index 0f1fc6bad15..2528d818f8e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java @@ -18,29 +18,38 @@ */ package org.apache.fineract.organisation.workingdays.handler; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import java.util.Collections; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse; import org.apache.fineract.organisation.workingdays.service.WorkingDaysWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "WORKINGDAYS", action = "UPDATE") -public class UpdateWorkingDaysCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class UpdateWorkingDaysCommandHandler implements CommandHandler { private final WorkingDaysWritePlatformService workingDaysWritePlatformService; - @Autowired - public UpdateWorkingDaysCommandHandler(final WorkingDaysWritePlatformService workingDaysWritePlatformService) { - this.workingDaysWritePlatformService = workingDaysWritePlatformService; - } - @Transactional @Override - public CommandProcessingResult processCommand(JsonCommand command) { - return this.workingDaysWritePlatformService.updateWorkingDays(command); + public WorkingDaysUpdateResponse handle(Command command) { + WorkingDaysUpdateRequest request = command.getPayload(); + Map changes = this.workingDaysWritePlatformService.updateWorkingDays(request); + if (changes == null) { + changes = Collections.emptyMap(); + } + + return WorkingDaysUpdateResponse.builder().resourceId((Long) changes.get("resourceId")).changes(changes) + .recurrence(request.getRecurrence()).repaymentRescheduleType(request.getRepaymentRescheduleType()) + .extendTermForDailyRepayments(request.getExtendTermForDailyRepayments()) + .extendTermForRepaymentsOnHolidays(request.getExtendTermForRepaymentsOnHolidays()).build(); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java index 59d6deeaa7d..1b4ecbafc29 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java @@ -65,7 +65,9 @@ public WorkingDaysData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowN final Boolean extendTermForDailyRepayments = rs.getBoolean("extendTermForDailyRepayments"); final Boolean extendTermForRepaymentsOnHolidays = rs.getBoolean("extendTermForRepaymentsOnHolidays"); - return new WorkingDaysData(id, recurrence, status, extendTermForDailyRepayments, extendTermForRepaymentsOnHolidays); + return WorkingDaysData.builder().id(id).recurrence(recurrence).repaymentRescheduleType(status) + .extendTermForDailyRepayments(extendTermForDailyRepayments) + .extendTermForRepaymentsOnHolidays(extendTermForRepaymentsOnHolidays).build(); } } @@ -77,7 +79,11 @@ public WorkingDaysData retrieve() { final String sql = " select " + rm.schema(); WorkingDaysData data = this.jdbcTemplate.queryForObject(sql, rm); // NOSONAR Collection repaymentRescheduleOptions = repaymentRescheduleTypeOptions(); - return new WorkingDaysData(data, repaymentRescheduleOptions); + return WorkingDaysData.builder().id(data.getId()).recurrence(data.getRecurrence()) + .repaymentRescheduleType(data.getRepaymentRescheduleType()) + .extendTermForDailyRepayments(data.getExtendTermForDailyRepayments()) + .extendTermForRepaymentsOnHolidays(data.getExtendTermForRepaymentsOnHolidays()) + .repaymentRescheduleOptions(repaymentRescheduleOptions).build(); } catch (final EmptyResultDataAccessException e) { throw new WorkingDaysNotFoundException(e); } @@ -86,7 +92,7 @@ public WorkingDaysData retrieve() { @Override public WorkingDaysData repaymentRescheduleType() { Collection repaymentRescheduleOptions = repaymentRescheduleTypeOptions(); - return new WorkingDaysData(null, null, null, repaymentRescheduleOptions, null, null); + return WorkingDaysData.builder().repaymentRescheduleOptions(repaymentRescheduleOptions).build(); } private Collection repaymentRescheduleTypeOptions() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java index dcdb3d1e40c..411991cd65f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java @@ -18,10 +18,12 @@ */ package org.apache.fineract.organisation.workingdays.service; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import java.util.Map; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.springframework.transaction.annotation.Transactional; public interface WorkingDaysWritePlatformService { - CommandProcessingResult updateWorkingDays(JsonCommand command); + @Transactional + Map updateWorkingDays(WorkingDaysUpdateRequest request); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java index b0611c28bfb..c7e866bd6f7 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java @@ -19,16 +19,15 @@ package org.apache.fineract.organisation.workingdays.service; import java.text.ParseException; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import lombok.RequiredArgsConstructor; import net.fortuna.ical4j.model.property.RRule; import net.fortuna.ical4j.validate.ValidationException; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; -import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; -import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.springframework.transaction.annotation.Transactional; @@ -37,25 +36,27 @@ public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService { private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; - private final WorkingDayValidator fromApiJsonDeserializer; + private final WorkingDaysUpdateRequestValidator validator; @Transactional @Override - public CommandProcessingResult updateWorkingDays(JsonCommand command) { + public Map updateWorkingDays(WorkingDaysUpdateRequest request) { String recurrence = ""; RRule rrule = null; try { - this.fromApiJsonDeserializer.validateForUpdate(command.json()); + this.validator.validateForUpdate(request); final WorkingDays workingDays = this.daysRepositoryWrapper.findOne(); - recurrence = command.stringValueOfParameterNamed(WorkingDaysApiConstants.recurrence); + recurrence = request.getRecurrence(); rrule = new RRule(recurrence); rrule.validate(); - Map changes = workingDays.update(command); + Map changes = update(workingDays, request); + // include the current WorkingDays resource id in the changes for response consumption + changes.put("resourceId", workingDays.getId()); this.daysRepositoryWrapper.saveAndFlush(workingDays); - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(workingDays.getId()).with(changes) - .build(); + return changes; + } catch (final ValidationException e) { throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurrence + " is not valid.", "recurrence", recurrence, e); @@ -65,4 +66,32 @@ public CommandProcessingResult updateWorkingDays(JsonCommand command) { } } + public HashMap update(WorkingDays workingDays, WorkingDaysUpdateRequest request) { + HashMap changes = new HashMap<>(); + + if (!Objects.equals(request.getRecurrence(), workingDays.getRecurrence())) { + workingDays.setRecurrence(request.getRecurrence()); + changes.put("recurrence", request.getRecurrence()); + } + + Integer repaymentRescheduleType = request.getRepaymentRescheduleType(); + if (repaymentRescheduleType != null && !Objects.equals(repaymentRescheduleType, workingDays.getRepaymentReschedulingType())) { + workingDays.setRepaymentReschedulingType(repaymentRescheduleType); + changes.put("repaymentRescheduleType", repaymentRescheduleType); + } + + Boolean extendDaily = request.getExtendTermForDailyRepayments(); + if (extendDaily != null && !Objects.equals(extendDaily, workingDays.getExtendTermForDailyRepayments())) { + workingDays.setExtendTermForDailyRepayments(extendDaily); + changes.put("extendTermForDailyRepayments", extendDaily); + } + + Boolean extendHolidays = request.getExtendTermForRepaymentsOnHolidays(); + if (extendHolidays != null && !Objects.equals(extendHolidays, workingDays.getExtendTermForRepaymentsOnHolidays())) { + workingDays.setExtendTermForRepaymentsOnHolidays(extendHolidays); + changes.put("extendTermForRepaymentsOnHolidays", extendHolidays); + } + return changes; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java index 256909f5137..14f0e5c096d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.organisation.workingdays.starter; -import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformServiceImpl; @@ -41,7 +41,7 @@ public WorkingDaysReadPlatformService workingDaysReadPlatformService(JdbcTemplat @Bean @ConditionalOnMissingBean(WorkingDaysWritePlatformService.class) public WorkingDaysWritePlatformService workingDaysWritePlatformService(WorkingDaysRepositoryWrapper daysRepositoryWrapper, - WorkingDayValidator fromApiJsonDeserializer) { - return new WorkingDaysWritePlatformServiceJpaRepositoryImpl(daysRepositoryWrapper, fromApiJsonDeserializer); + WorkingDaysUpdateRequestValidator validator) { + return new WorkingDaysWritePlatformServiceJpaRepositoryImpl(daysRepositoryWrapper, validator); } }