You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/06/14 17:05:59 UTC

[fineract] branch develop updated: Business date part 1 (#2362)

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 4465e632f Business date part 1 (#2362)
4465e632f is described below

commit 4465e632f14f9b2fcefd6e6ad168a7447892876b
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Tue Jun 14 19:05:52 2022 +0200

    Business date part 1 (#2362)
    
    Business date implementation - part 1
---
 .../groovy/org.apache.fineract.dependencies.gradle |   3 +
 fineract-provider/dependencies.gradle              |   3 +-
 .../commands/service/CommandWrapperBuilder.java    |   7 +
 .../businessdate/api/BusinessDateApiResource.java  | 107 +++++++++
 .../api/BusinessDateApiResourceSwagger.java        |  63 +++++
 .../businessdate/data/BusinessDateData.java        |  45 ++++
 .../businessdate/domain/BusinessDate.java          |  64 +++++
 .../domain/BusinessDateRepository.java             |  28 +++
 .../businessdate/domain/BusinessDateType.java      |  37 +++
 .../exception/BusinessDateActionException.java     |  31 +++
 .../exception/BusinessDateNotFoundException.java   |  41 ++++
 .../handler/BusinessDateUpdateHandler.java         |  43 ++++
 .../businessdate/mapper/BusinessDateMapper.java    |  35 +++
 .../service/BusinessDateReadPlatformService.java   |  29 +++
 .../BusinessDateReadPlatformServiceImpl.java       |  63 +++++
 .../service/BusinessDateWritePlatformService.java  |  32 +++
 .../BusinessDateWritePlatformServiceImpl.java      | 149 ++++++++++++
 .../BusinessDateDataParserAndValidator.java        | 107 +++++++++
 .../domain/ConfigurationDomainService.java         |   4 +
 .../domain/ConfigurationDomainServiceJpa.java      |  11 +
 .../infrastructure/core/api/JsonCommand.java       |   5 +
 .../core/data/ApiParameterError.java               |   2 +-
 .../core/data/CommandProcessingResult.java         |   2 +
 .../core/data/DataValidatorBuilder.java            | 186 ++++++++-------
 .../PlatformApiDataValidationException.java        |   5 +
 .../serialization/DefaultToApiJsonSerializer.java  |   2 +-
 .../core/serialization/JsonParserHelper.java       |  35 +--
 .../infrastructure/jobs/service/JobName.java       |   6 +-
 .../search/data/AdHocQueryDataValidator.java       |   4 +-
 .../service/ScheduledJobRunnerServiceImpl.java     |   8 +-
 .../useradministration/domain/AppUser.java         |  15 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../tenant/parts/0015_add_business_date.xml        | 121 ++++++++++
 .../businessdate/api/BusinessDateApiTest.java      | 163 +++++++++++++
 .../businessdate/data/BusinessDataSerialized.java  |  63 +++++
 .../businessdate/data/BusinessDataTypeTest.java    |  37 +++
 .../mapper/BusinessDateMapperTest.java             |  56 +++++
 .../BusinessDateReadPlatformServiceTest.java       |  96 ++++++++
 .../BusinessDateWritePlatformServiceTest.java      | 264 +++++++++++++++++++++
 .../validator/BusinessDateValidatorTest.java       | 120 ++++++++++
 .../integrationtests/SchedulerJobsTest.java        |   5 +
 .../common/GlobalConfigurationHelper.java          |  25 +-
 42 files changed, 1998 insertions(+), 125 deletions(-)

diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
index a5f1b92d9..02cc5347f 100644
--- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
+++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle
@@ -180,5 +180,8 @@ dependencyManagement {
             entry 'json-path'
             entry 'xml-path'
         }
+
+        dependency 'org.mapstruct:mapstruct:1.5.1.Final'
+        dependency 'org.mapstruct:mapstruct-processor:1.5.1.Final'
     }
 }
diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle
index 0d0d99e74..ffaa5baa3 100644
--- a/fineract-provider/dependencies.gradle
+++ b/fineract-provider/dependencies.gradle
@@ -73,6 +73,7 @@ dependencies {
             'org.springdoc:springdoc-openapi-ui',
             'org.springdoc:springdoc-openapi-common',
             'org.springdoc:springdoc-openapi-security',
+            'org.mapstruct:mapstruct',
             )
 
     implementation ('org.apache.commons:commons-email') {
@@ -128,7 +129,6 @@ dependencies {
         exclude group: 'xml-apis'
     }
 
-
     runtimeOnly('org.glassfish.jaxb:jaxb-runtime') {
         exclude group: 'com.sun.activation'
     }
@@ -144,6 +144,7 @@ dependencies {
 
     compileOnly 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'
+    annotationProcessor 'org.mapstruct:mapstruct-processor'
 
     // testCompile dependencies are ONLY used in src/test, not src/main.
     // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly!
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 5a2b9239a..8d750cdbb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3478,4 +3478,11 @@ public class CommandWrapperBuilder {
         return this;
     }
 
+    public CommandWrapperBuilder updateBusinessDate() {
+        this.actionName = "UPDATE";
+        this.entityName = "BUSINESS_DATE";
+        this.href = "/businessdate";
+        return this;
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java
new file mode 100644
index 000000000..63f756fc4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java
@@ -0,0 +1,107 @@
+/**
+ * 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.infrastructure.businessdate.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+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.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
+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.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Path("businessdate")
+@Component
+@Tag(name = "Business Date Management", description = "Business date management enables you to set up, fetch and adjust organisation business dates")
+public class BusinessDateApiResource {
+
+    private final ApiRequestParameterHelper parameterHelper;
+    private final PlatformSecurityContext securityContext;
+    private final DefaultToApiJsonSerializer<BusinessDateData> jsonSerializer;
+    private final BusinessDateReadPlatformService readPlatformService;
+    private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
+
+    @GET
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "List all business dates", description = "")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateResponse.class)))) })
+    public String getBusinessDates(@Context final UriInfo uriInfo) {
+        securityContext.authenticatedUser().validateHasReadPermission("BUSINESS_DATE");
+        final List<BusinessDateData> foundBusinessDates = this.readPlatformService.findAll();
+        ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
+        return this.jsonSerializer.serialize(settings, foundBusinessDates);
+    }
+
+    @GET
+    @Path("{type}")
+    @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Retrieve a specific Business date", description = "")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateResponse.class))) })
+    public String getBusinessDate(@PathParam("type") @Parameter(description = "type") final String type, @Context final UriInfo uriInfo) {
+        securityContext.authenticatedUser().validateHasReadPermission("BUSINESS_DATE");
+        final BusinessDateData businessDate = this.readPlatformService.findByType(type);
+        ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
+        return this.jsonSerializer.serialize(settings, businessDate);
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Update Business Date", description = "")
+    @RequestBody(required = true, content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateRequest.class)))
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateRequest.class))) })
+    public String updateBusinessDate(final String jsonRequestBody, @Context UriInfo uriInfo) {
+        securityContext.authenticatedUser().validateHasUpdatePermission("BUSINESS_DATE");
+        final CommandWrapper commandRequest = new CommandWrapperBuilder().updateBusinessDate().withJson(jsonRequestBody).build();
+
+        CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
+        return jsonSerializer.serialize(result);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResourceSwagger.java
new file mode 100644
index 000000000..273461c40
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResourceSwagger.java
@@ -0,0 +1,63 @@
+/**
+ * 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.infrastructure.businessdate.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDate;
+
+final class BusinessDateApiResourceSwagger {
+
+    private BusinessDateApiResourceSwagger() {
+
+    }
+
+    @Schema(description = "BusinessDateResponse")
+    public static final class BusinessDateResponse {
+
+        @Schema(example = "yyyy-MM-dd")
+        public String dateFormat;
+        @Schema(example = "COB date")
+        private String description;
+        @Schema(example = "COB_DATE")
+        private String type;
+        @Schema(example = "2015-02-15")
+        private LocalDate date;
+
+        private BusinessDateResponse() {
+
+        }
+    }
+
+    @Schema(description = "BusinessDateRequest")
+    public static final class BusinessDateRequest {
+
+        @Schema(example = "yyyy-MM-dd")
+        public String dateFormat;
+        @Schema(example = "COB_DATE")
+        private String type;
+        @Schema(example = "2015-02-15")
+        private LocalDate date;
+        @Schema(example = "en")
+        private String locale;
+
+        private BusinessDateRequest() {}
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDateData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDateData.java
new file mode 100644
index 000000000..3104e188d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDateData.java
@@ -0,0 +1,45 @@
+/**
+ * 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.infrastructure.businessdate.data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+
+@ToString
+@Getter
+public class BusinessDateData implements Serializable {
+
+    private String description;
+    private String type;
+    private LocalDate date;
+
+    public BusinessDateData(BusinessDateType businessDateType, LocalDate date) {
+        this.type = businessDateType.getName();
+        this.description = businessDateType.getDescription();
+        this.date = date;
+    }
+
+    public static BusinessDateData instance(BusinessDateType businessDateType, LocalDate value) {
+        return new BusinessDateData(businessDateType, value);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDate.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDate.java
new file mode 100644
index 000000000..2aef3a287
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDate.java
@@ -0,0 +1,64 @@
+/**
+ * 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.infrastructure.businessdate.domain;
+
+import java.time.LocalDate;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Entity
+@Table(name = "m_business_date", uniqueConstraints = { @UniqueConstraint(name = "uq_business_date_type", columnNames = { "type" }) })
+public class BusinessDate extends AbstractAuditableCustom {
+
+    @Enumerated(EnumType.STRING)
+    @Column(name = "type")
+    private BusinessDateType type;
+
+    @Column(name = "date", columnDefinition = "DATE")
+    private LocalDate date;
+
+    @Version
+    private Long version;
+
+    protected BusinessDate() {
+        // TODO Auto-generated constructor stub
+    }
+
+    protected BusinessDate(@NotNull BusinessDateType type, @NotNull LocalDate date) {
+        this.type = type;
+        this.date = date;
+    }
+
+    public static BusinessDate instance(@NotNull BusinessDateType businessDateType, @NotNull LocalDate date) {
+        return new BusinessDate(businessDateType, date);
+    }
+
+    public void updateDate(LocalDate date) {
+        this.date = date;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateRepository.java
new file mode 100644
index 000000000..7bf1d1b92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateRepository.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.infrastructure.businessdate.domain;
+
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface BusinessDateRepository extends JpaRepository<BusinessDate, Long>, JpaSpecificationExecutor<BusinessDate> {
+
+    Optional<BusinessDate> findByType(BusinessDateType type);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateType.java
new file mode 100644
index 000000000..eba229783
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/domain/BusinessDateType.java
@@ -0,0 +1,37 @@
+/**
+ * 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.infrastructure.businessdate.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum BusinessDateType {
+
+    BUSINESS_DATE(1, "Business Date"), COB_DATE(2, "Close of Business Date");
+
+    private final Integer id;
+    private final String description;
+
+    public String getName() {
+        return name();
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateActionException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateActionException.java
new file mode 100644
index 000000000..35d41f212
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateActionException.java
@@ -0,0 +1,31 @@
+/**
+ * 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.infrastructure.businessdate.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * A {@link RuntimeException} thrown when business date is violating any domain rule.
+ */
+public class BusinessDateActionException extends AbstractPlatformDomainRuleException {
+
+    public BusinessDateActionException(final String violationCode, String message) {
+        super(violationCode, message, violationCode);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateNotFoundException.java
new file mode 100644
index 000000000..fcf986648
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/exception/BusinessDateNotFoundException.java
@@ -0,0 +1,41 @@
+/**
+ * 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.infrastructure.businessdate.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when business date is not found.
+ */
+public class BusinessDateNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public BusinessDateNotFoundException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+    public static BusinessDateNotFoundException notExist(final String type, Throwable... e) {
+        return new BusinessDateNotFoundException("error.msg.businessdate.type.not.exist",
+                "Business date with type `" + type + "` does not exist.", type, e);
+    }
+
+    public static BusinessDateNotFoundException notFound(final String type, Throwable... e) {
+        return new BusinessDateNotFoundException("error.msg.businessdate.not.found",
+                "Business date with type `" + type + "` does not found.", type, e);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/handler/BusinessDateUpdateHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/handler/BusinessDateUpdateHandler.java
new file mode 100644
index 000000000..15d6a5689
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/handler/BusinessDateUpdateHandler.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.infrastructure.businessdate.handler;
+
+import javax.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.businessdate.service.BusinessDateWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@RequiredArgsConstructor
+@Service
+@CommandType(entity = "BUSINESS_DATE", action = "UPDATE")
+public class BusinessDateUpdateHandler implements NewCommandSourceHandler {
+
+    private final BusinessDateWritePlatformService businessDateWritePlatformService;
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(@NotNull final JsonCommand command) {
+        return businessDateWritePlatformService.updateBusinessDate(command);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapper.java
new file mode 100644
index 000000000..ba8abc95e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapper.java
@@ -0,0 +1,35 @@
+/**
+ * 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.infrastructure.businessdate.mapper;
+
+import java.util.List;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+
+@Mapper(componentModel = "spring")
+public interface BusinessDateMapper {
+
+    @Mappings({ @Mapping(target = "businessDateType", source = "source.type"), @Mapping(target = "date", source = "source.date") })
+    BusinessDateData map(BusinessDate source);
+
+    List<BusinessDateData> map(List<BusinessDate> sources);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformService.java
new file mode 100644
index 000000000..c2d836b83
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformService.java
@@ -0,0 +1,29 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import java.util.List;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+
+public interface BusinessDateReadPlatformService {
+
+    List<BusinessDateData> findAll();
+
+    BusinessDateData findByType(String type);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java
new file mode 100644
index 000000000..455a529ca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceImpl.java
@@ -0,0 +1,63 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.businessdate.exception.BusinessDateNotFoundException;
+import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateMapper;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class BusinessDateReadPlatformServiceImpl implements BusinessDateReadPlatformService {
+
+    private final BusinessDateRepository repository;
+    private final BusinessDateMapper mapper;
+
+    @Override
+    public List<BusinessDateData> findAll() {
+        List<BusinessDate> businessDateList = repository.findAll();
+        return mapper.map(businessDateList);
+    }
+
+    @Override
+    public BusinessDateData findByType(String type) {
+        BusinessDateType businessDateType;
+        try {
+            businessDateType = BusinessDateType.valueOf(type);
+        } catch (IllegalArgumentException e) {
+            log.error("Provided business date type cannot be found: {}", type);
+            throw BusinessDateNotFoundException.notExist(type, e);
+        }
+        Optional<BusinessDate> businessDate = repository.findByType(businessDateType);
+        if (businessDate.isEmpty()) {
+            log.error("Business date with the provided type cannot be found {}", type);
+            throw BusinessDateNotFoundException.notFound(type);
+        }
+        return mapper.map(businessDate.get());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformService.java
new file mode 100644
index 000000000..17c31a03f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+public interface BusinessDateWritePlatformService {
+
+    CommandProcessingResult updateBusinessDate(JsonCommand command);
+
+    void increaseCOBDateByOneDay() throws JobExecutionException;
+
+    void increaseBusinessDateByOneDay() throws JobExecutionException;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java
new file mode 100644
index 000000000..0a02df061
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceImpl.java
@@ -0,0 +1,149 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.businessdate.exception.BusinessDateActionException;
+import org.apache.fineract.infrastructure.businessdate.validator.BusinessDateDataParserAndValidator;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePlatformService {
+
+    private final BusinessDateDataParserAndValidator dataValidator;
+    private final BusinessDateRepository repository;
+    private final ConfigurationDomainService configurationDomainService;
+
+    @Override
+    public CommandProcessingResult updateBusinessDate(@NotNull final JsonCommand command) {
+        BusinessDateData data = dataValidator.validateAndParseUpdate(command);
+        Map<String, Object> changes = new HashMap<>();
+        adjustDate(data, changes);
+        return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build();
+
+    }
+
+    private void adjustDate(BusinessDateData data, Map<String, Object> changes) {
+        boolean isCOBDateAdjustmentEnabled = configurationDomainService.isCOBDateAdjustmentEnabled();
+        boolean isBusinessDateEnabled = configurationDomainService.isBusinessDateEnabled();
+
+        if (!isBusinessDateEnabled) {
+            log.error("Business date functionality is not enabled!");
+            throw new BusinessDateActionException("business.date.is.not.enabled", "Business date functionality is not enabled");
+        }
+        updateOrCreateBusinessDate(data.getType(), data.getDate(), changes);
+        if (isCOBDateAdjustmentEnabled && BusinessDateType.BUSINESS_DATE.name().equals(data.getType())) {
+            updateOrCreateBusinessDate(BusinessDateType.COB_DATE.getName(), data.getDate().minus(1, ChronoUnit.DAYS), changes);
+        }
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.INCREASE_COB_DATE_BY_1_DAY)
+    public void increaseCOBDateByOneDay() throws JobExecutionException {
+        increaseDateByTypeByOneDay(BusinessDateType.COB_DATE);
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.INCREASE_BUSINESS_DATE_BY_1_DAY)
+    public void increaseBusinessDateByOneDay() throws JobExecutionException {
+        increaseDateByTypeByOneDay(BusinessDateType.BUSINESS_DATE);
+    }
+
+    private void increaseDateByTypeByOneDay(BusinessDateType businessDateType) throws JobExecutionException {
+        List<Throwable> exceptions = new ArrayList<>();
+        Map<String, Object> changes = new HashMap<>();
+        Optional<BusinessDate> businessDateEntity = repository.findByType(businessDateType);
+        LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
+        businessDate = businessDate.plusDays(1);
+        try {
+            BusinessDateData businessDateData = BusinessDateData.instance(businessDateType, businessDate);
+            adjustDate(businessDateData, changes);
+        } catch (final PlatformApiDataValidationException e) {
+            final List<ApiParameterError> errors = e.getErrors();
+            for (final ApiParameterError error : errors) {
+                log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), error.getDeveloperMessage());
+            }
+            exceptions.add(e);
+        } catch (final AbstractPlatformDomainRuleException e) {
+            log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getDefaultUserMessage());
+            exceptions.add(e);
+        } catch (Exception e) {
+            log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getMessage());
+            exceptions.add(e);
+        }
+
+        if (!exceptions.isEmpty()) {
+            throw new JobExecutionException(exceptions);
+        }
+    }
+
+    private void updateOrCreateBusinessDate(String type, LocalDate newDate, Map<String, Object> changes) {
+        BusinessDateType businessDateType = BusinessDateType.valueOf(type);
+        Optional<BusinessDate> businessDate = repository.findByType(businessDateType);
+
+        if (businessDate.isEmpty()) {
+            BusinessDate newBusinessDate = BusinessDate.instance(businessDateType, newDate);
+            repository.save(newBusinessDate);
+            changes.put(type, newBusinessDate.getDate());
+        } else {
+            updateBusinessDate(businessDate.get(), newDate, changes);
+        }
+    }
+
+    private void updateBusinessDate(BusinessDate businessDate, LocalDate newDate, Map<String, Object> changes) {
+        LocalDate oldDate = businessDate.getDate();
+
+        if (!hasChange(oldDate, newDate)) {
+            return;
+        }
+        businessDate.updateDate(newDate);
+        repository.save(businessDate);
+        changes.put(businessDate.getType().name(), newDate);
+    }
+
+    private boolean hasChange(@NotNull LocalDate oldDate, @NotNull LocalDate date) {
+        return (date.isBefore(oldDate) || date.isAfter(oldDate));
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateDataParserAndValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateDataParserAndValidator.java
new file mode 100644
index 000000000..a42372947
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateDataParserAndValidator.java
@@ -0,0 +1,107 @@
+/**
+ * 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.infrastructure.businessdate.validator;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import javax.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class BusinessDateDataParserAndValidator {
+
+    private final FromJsonHelper jsonHelper;
+
+    public BusinessDateData validateAndParseUpdate(@NotNull final JsonCommand command) {
+        final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("businessdate.update");
+        JsonObject element = extractJsonObject(command);
+
+        BusinessDateData result = validateAndParseUpdate(dataValidator, element, jsonHelper);
+        throwExceptionIfValidationWarningsExist(dataValidator);
+
+        return result;
+    }
+
+    private JsonObject extractJsonObject(JsonCommand command) {
+        String json = command.json();
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final JsonElement element = jsonHelper.parse(json);
+        return element.getAsJsonObject();
+    }
+
+    private void throwExceptionIfValidationWarningsExist(DataValidatorBuilder dataValidator) {
+        if (dataValidator.hasError()) {
+            log.error("Business date - Validation errors: {}", dataValidator.getDataValidationErrors());
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
+                    dataValidator.getDataValidationErrors());
+        }
+    }
+
+    private BusinessDateData validateAndParseUpdate(final DataValidatorBuilder dataValidator, JsonObject element,
+            FromJsonHelper jsonHelper) {
+        if (element == null) {
+            return null;
+        }
+
+        jsonHelper.checkForUnsupportedParameters(element, List.of("type", "date", "dateFormat", "locale"));
+
+        String businessDateTypeName = jsonHelper.extractStringNamed("type", element);
+        final String localeValue = jsonHelper.extractStringNamed("locale", element);
+        final String dateFormat = jsonHelper.extractDateFormatParameter(element);
+        final String dateValue = jsonHelper.extractStringNamed("date", element);
+        dataValidator.reset().parameter("type").value(businessDateTypeName).notBlank();
+        dataValidator.reset().parameter("locale").value(localeValue).notBlank();
+        dataValidator.reset().parameter("dateFormat").value(dateFormat).notBlank();
+        dataValidator.reset().parameter("date").value(dateValue).notBlank();
+
+        if (dataValidator.hasError()) {
+            return null;
+        }
+        Locale locale = jsonHelper.extractLocaleParameter(element);
+        BusinessDateType type;
+        try {
+            type = BusinessDateType.valueOf(businessDateTypeName);
+        } catch (IllegalArgumentException e) {
+            dataValidator.reset().parameter("type").failWithCode("Invalid Business Type value: `" + businessDateTypeName + "`");
+            return null;
+        }
+        LocalDate date = jsonHelper.extractLocalDateNamed("date", element, dateFormat, locale);
+        dataValidator.reset().parameter("date").value(date).notNull();
+        return dataValidator.hasError() ? null : BusinessDateData.instance(type, date);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 037f4d6a2..e7839ed34 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -112,4 +112,8 @@ public interface ConfigurationDomainService {
     boolean isRelaxingDaysConfigForPivotDateEnabled();
 
     Long retrieveRelaxingDaysConfigForPivotDate();
+
+    boolean isBusinessDateEnabled();
+
+    boolean isCOBDateAdjustmentEnabled();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 30426ef74..4dbd63d45 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -38,6 +38,8 @@ import org.springframework.transaction.annotation.Transactional;
 @Service
 public class ConfigurationDomainServiceJpa implements ConfigurationDomainService {
 
+    public static final String ENABLE_BUSINESS_DATE = "enable_business_date";
+    public static final String ENABLE_AUTOMATIC_COB_DATE_ADJUSTMENT = "enable_automatic_cob_date_adjustment";
     private final PermissionRepository permissionRepository;
     private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
     private final PlatformCacheRepository cacheTypeRepository;
@@ -432,4 +434,13 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
         return property.isEnabled();
     }
 
+    @Override
+    public boolean isBusinessDateEnabled() {
+        return getGlobalConfigurationPropertyData(ENABLE_BUSINESS_DATE).isEnabled();
+    }
+
+    @Override
+    public boolean isCOBDateAdjustmentEnabled() {
+        return getGlobalConfigurationPropertyData(ENABLE_AUTOMATIC_COB_DATE_ADJUSTMENT).isEnabled();
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
index 9d9f265dd..595ae95d3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -175,6 +175,11 @@ public final class JsonCommand {
         this.organisationCreditBureauId = null;
     }
 
+    public static JsonCommand from(final String jsonCommand) {
+        return new JsonCommand(null, jsonCommand, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
+
+    }
+
     public Long getOrganisationCreditBureauId() {
         return this.organisationCreditBureauId;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java
index 887ebcf62..b88e84da8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiParameterError.java
@@ -133,6 +133,6 @@ public final class ApiParameterError {
 
     @Override
     public String toString() {
-        return "ApiParameterError{developerMessage=" + developerMessage + "; ... }";
+        return developerMessage;
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
index 8cd248166..d061e6322 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
@@ -21,10 +21,12 @@ package org.apache.fineract.infrastructure.core.data;
 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
+import lombok.ToString;
 
 /**
  * Represents the successful result of an REST API call that results in processing a command.
  */
+@ToString
 public class CommandProcessingResult implements Serializable {
 
     private Long commandId;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
index 2b3e0260c..5cbe6f4a8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
@@ -115,8 +115,8 @@ public class DataValidatorBuilder {
         if (StringUtils.isBlank(linkedValue)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(linkedParameterName).append(".cannot.be.empty.when.").append(this.parameter).append(".is.populated");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
-                    .append(" cannot be empty when ").append(this.parameter).append(" is populated.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(linkedParameterName)
+                    .append("` cannot be empty when ").append(this.parameter).append(" is populated.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
             this.dataValidationErrors.add(error);
@@ -149,8 +149,8 @@ public class DataValidatorBuilder {
         if (this.value != null && !this.value.equals(linkedValue)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(linkedParameterName).append(".not.equal.to.").append(this.parameter);
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
-                    .append(" is not equal to ").append(this.parameter).append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(linkedParameterName)
+                    .append("` is not equal to ").append(this.parameter).append(".");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
             this.dataValidationErrors.add(error);
@@ -166,8 +166,8 @@ public class DataValidatorBuilder {
         if (this.value != null && this.value.equals(linkedValue)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(linkedParameterName).append(".same.as.").append(this.parameter);
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(linkedParameterName)
-                    .append(" is same as ").append(this.parameter).append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(linkedParameterName)
+                    .append("` is same as ").append(this.parameter).append(".");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), linkedParameterName, linkedValue, this.value);
             this.dataValidationErrors.add(error);
@@ -184,8 +184,8 @@ public class DataValidatorBuilder {
         if (!trueOfFalseFieldProvided && !this.ignoreNullValue) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".must.be.true.or.false");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" must be set as true or false.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` must be set as true or false.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter);
             this.dataValidationErrors.add(error);
@@ -199,8 +199,8 @@ public class DataValidatorBuilder {
             if (!trueOfFalseField.toString().equalsIgnoreCase("true") && !trueOfFalseField.toString().equalsIgnoreCase("false")) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".must.be.true.or.false");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be set as true or false.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be set as true or false.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter);
                 this.dataValidationErrors.add(error);
@@ -223,8 +223,8 @@ public class DataValidatorBuilder {
             }
 
             validationErrorCode.append(".cannot.be.blank");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(realParameterName)
-                    .append(" is mandatory.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(realParameterName)
+                    .append("` is mandatory.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), realParameterName, this.arrayIndex);
             this.dataValidationErrors.add(error);
@@ -248,8 +248,8 @@ public class DataValidatorBuilder {
             }
 
             validationErrorCode.append(".cannot.be.blank");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(realParameterName)
-                    .append(" is mandatory.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(realParameterName)
+                    .append("` is mandatory.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), realParameterName, this.arrayIndex);
             this.dataValidationErrors.add(error);
@@ -265,8 +265,8 @@ public class DataValidatorBuilder {
         if (this.value != null && this.value.toString().trim().length() > maxLength) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".exceeds.max.length");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" exceeds max length of ").append(maxLength).append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` exceeds max length of ").append(maxLength).append(".");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, maxLength, this.value.toString());
             this.dataValidationErrors.add(error);
@@ -284,8 +284,8 @@ public class DataValidatorBuilder {
             if (number < min || number > max) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.not.within.expected.range");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be between ").append(min).append(" and ").append(max).append(".");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be between ").append(min).append(" and ").append(max).append(".");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, min, max);
                 this.dataValidationErrors.add(error);
@@ -305,8 +305,8 @@ public class DataValidatorBuilder {
         if (this.value == null || !valuesList.contains(this.value)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".is.not.one.of.expected.enumerations");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` must be one of [ ").append(valuesListStr).append(" ] ").append(".");
 
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value, values);
@@ -333,8 +333,8 @@ public class DataValidatorBuilder {
         if (this.value == null || !valuesList.contains(this.value.toString().toLowerCase())) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".is.not.one.of.expected.enumerations");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` must be one of [ ").append(valuesListStr).append(" ] ").append(".");
 
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value, values);
@@ -357,8 +357,8 @@ public class DataValidatorBuilder {
         if (this.value == null || !valuesListLowercase.contains(this.value.toString().toLowerCase())) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".is.not.one.of.expected.enumerations");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" must be one of [ ").append(valuesListStr).append(" ] ").append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` must be one of [ ").append(valuesListStr).append(" ] ").append(".");
 
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value, valuesList);
@@ -381,8 +381,8 @@ public class DataValidatorBuilder {
             if (valuesList.contains(this.value)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.one.of.unwanted.enumerations");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must not be any of [ ").append(valuesListStr).append(" ] ").append(".");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must not be any of [ ").append(valuesListStr).append(" ] ").append(".");
 
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, this.value, values);
@@ -403,8 +403,8 @@ public class DataValidatorBuilder {
             if (number.compareTo(BigDecimal.ZERO) <= 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.zero");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than 0.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than 0.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -426,8 +426,8 @@ public class DataValidatorBuilder {
             if (number.compareTo(BigDecimal.ZERO) < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.zero.or.greater");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than or equal to 0.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than or equal to 0.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -449,8 +449,8 @@ public class DataValidatorBuilder {
             if (number < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.zero.or.greater");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be zero or greater.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be zero or greater.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -472,8 +472,8 @@ public class DataValidatorBuilder {
             if (number < 1) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.zero");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than 0.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than 0.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -492,8 +492,8 @@ public class DataValidatorBuilder {
             if (intValue < number + 1) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.specified.number");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than ").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than ").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, intValue, number);
                 this.dataValidationErrors.add(error);
@@ -512,8 +512,8 @@ public class DataValidatorBuilder {
             if (intValue < number) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.equal.to.or.greater.than.specified.number");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be equal to or greater than").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be equal to or greater than").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, intValue, number);
                 this.dataValidationErrors.add(error);
@@ -532,8 +532,8 @@ public class DataValidatorBuilder {
             if (!intValue.equals(number)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.equal.to.specified.number");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be same as").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be same as").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, intValue, number);
                 this.dataValidationErrors.add(error);
@@ -552,8 +552,8 @@ public class DataValidatorBuilder {
             if (intValue < number || intValue % number != 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.in.multiples.of.specified.number");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be multiples of ").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be multiples of ").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, intValue, number);
                 this.dataValidationErrors.add(error);
@@ -575,8 +575,8 @@ public class DataValidatorBuilder {
             if (number < 1) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.zero");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than 0.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than 0.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -598,8 +598,8 @@ public class DataValidatorBuilder {
             if (number < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.equal.or.greater.than.zero");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be equal or greater than 0.");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be equal or greater than 0.");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, 0);
                 this.dataValidationErrors.add(error);
@@ -618,8 +618,8 @@ public class DataValidatorBuilder {
             if (longValue < number + 1) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.specified.number");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than ").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than ").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, longValue, number);
                 this.dataValidationErrors.add(error);
@@ -638,8 +638,8 @@ public class DataValidatorBuilder {
             if (longValue < number + 1) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".not.greater.than.specified.").append(paramName).append(".at Index.").append(index);
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                        .append(" must be greater than ").append(number);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than ").append(number);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, longValue, number);
                 this.dataValidationErrors.add(error);
@@ -657,8 +657,8 @@ public class DataValidatorBuilder {
         if (ObjectUtils.isEmpty(array)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".cannot.be.empty");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" cannot be empty. You must select at least one.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` cannot be empty. You must select at least one.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter);
             this.dataValidationErrors.add(error);
@@ -675,8 +675,8 @@ public class DataValidatorBuilder {
         if (this.value != null && !array.iterator().hasNext()) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".cannot.be.empty");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" cannot be empty. You must select at least one.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` cannot be empty. You must select at least one.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter);
             this.dataValidationErrors.add(error);
@@ -687,7 +687,8 @@ public class DataValidatorBuilder {
     public void expectedArrayButIsNot() {
         final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                 .append(this.parameter).append(".is.not.an.array");
-        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append(" is not an array.");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                .append("` is not an array.");
         final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
                 this.parameter);
         this.dataValidationErrors.add(error);
@@ -716,8 +717,8 @@ public class DataValidatorBuilder {
     public DataValidatorBuilder inValidValue(final String parameterValueCode, final Object invalidValue) {
         final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                 .append(this.parameter).append(".invalid.").append(parameterValueCode);
-        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                .append(" has an invalid value.");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                .append("` has an invalid value.");
         final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
                 this.parameter, invalidValue);
         this.dataValidationErrors.add(error);
@@ -740,8 +741,8 @@ public class DataValidatorBuilder {
 
         final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                 .append(this.parameter).append(".cannot.also.be.provided.when.").append(parameterName).append(".is.populated");
-        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                .append(" cannot also be provided when ").append(parameterName).append(" is populated.");
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                .append("` cannot also be provided when `").append(parameterName).append("` is populated.");
         final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
                 this.parameter, this.value, parameterName, parameterValue);
         this.dataValidationErrors.add(error);
@@ -765,8 +766,8 @@ public class DataValidatorBuilder {
         final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                 .append(this.parameter).append(".cannot.also.be.provided.when.").append(parameterName).append(".is.")
                 .append(parameterValue);
-        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                .append(" cannot also be provided when ").append(parameterName).append(" is ").append(parameterValue);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                .append("` cannot also be provided when `").append(parameterName).append("` is ").append(parameterValue);
         final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
                 this.parameter, this.value, parameterName, parameterValue);
         this.dataValidationErrors.add(error);
@@ -780,21 +781,22 @@ public class DataValidatorBuilder {
 
         final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                 .append(this.parameter).append(".must.be.provided.when.").append(parameterName).append(".is.").append(parameterValue);
-        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                .append(" must be provided when ").append(parameterName).append(" is ").append(parameterValue);
+        final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                .append("` must be provided when `").append(parameterName).append("` is ").append(parameterValue);
         final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
                 this.parameter, this.value, parameterName, parameterValue);
         this.dataValidationErrors.add(error);
         return this;
     }
 
-    public DataValidatorBuilder comapareMinimumAndMaximumAmounts(final BigDecimal minimumBalance, final BigDecimal maximumBalance) {
+    public DataValidatorBuilder compareMinimumAndMaximumAmounts(final BigDecimal minimumBalance, final BigDecimal maximumBalance) {
         if (minimumBalance != null && maximumBalance != null) {
             if (maximumBalance.compareTo(minimumBalance) < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.not.within.expected.range");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(" minimum amount ")
-                        .append(minimumBalance).append(" should less than maximum amount ").append(maximumBalance).append(".");
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` minimum amount ").append(minimumBalance).append(" should less than the maximum amount ")
+                        .append(maximumBalance).append(".");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, minimumBalance, maximumBalance);
                 this.dataValidationErrors.add(error);
@@ -810,7 +812,7 @@ public class DataValidatorBuilder {
             if (amount.compareTo(minimumAmount) < 0 || amount.compareTo(maximumAmount) > 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".amount.is.not.within.min.max.range");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" amount ")
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter).append("` amount ")
                         .append(amount).append(" must be between ").append(minimumAmount).append(" and ").append(maximumAmount)
                         .append(" .");
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
@@ -828,8 +830,8 @@ public class DataValidatorBuilder {
             if (amount.compareTo(min) < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.less.than.min");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" value ")
-                        .append(amount).append(" must not be less than minimum value ").append(min);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter).append("` value ")
+                        .append(amount).append(" must not be less than the minimum value ").append(min);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, amount, min);
                 this.dataValidationErrors.add(error);
@@ -845,7 +847,7 @@ public class DataValidatorBuilder {
             if (amount.compareTo(max) > 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.greater.than.max");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter).append(" value ")
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter).append("` value ")
                         .append(amount).append(" must not be more than maximum value ").append(max);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, amount, max);
@@ -856,7 +858,7 @@ public class DataValidatorBuilder {
         return this;
     }
 
-    public DataValidatorBuilder comapareMinAndMaxOfTwoBigDecmimalNos(final BigDecimal min, final BigDecimal max) {
+    public DataValidatorBuilder compareMinAndMaxOfTwoBigDecmimalNos(final BigDecimal min, final BigDecimal max) {
         if (min != null && max != null) {
             if (max.compareTo(min) < 0) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
@@ -884,7 +886,7 @@ public class DataValidatorBuilder {
                 return this;
             } catch (final ParseException e) {
                 final ApiParameterError error = ApiParameterError.parameterError("validation.msg.recurring.rule.parsing.error",
-                        "Error in pasring the Recurring Rule value: " + recurringRule + ".", this.parameter, recurringRule);
+                        "Error in parsing the Recurring Rule value: " + recurringRule + ".", this.parameter, recurringRule);
                 this.dataValidationErrors.add(error);
                 return this;
             }
@@ -902,8 +904,8 @@ public class DataValidatorBuilder {
             if (number < min) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.less.than.min");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be greater than minimum value ").append(min);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than the minimum value ").append(min);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, min);
                 this.dataValidationErrors.add(error);
@@ -922,8 +924,8 @@ public class DataValidatorBuilder {
             if (number > max) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.greater.than.max");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be less than maximum value ").append(max);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be less than the maximum value ").append(max);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, number, max);
                 this.dataValidationErrors.add(error);
@@ -940,8 +942,8 @@ public class DataValidatorBuilder {
         if (this.value != null && !this.value.toString().matches(expression)) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".does.not.match.regexp");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" must match the provided regular expression [ ").append(expression).append(" ] ").append(".");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` must match the provided regular expression [ ").append(expression).append(" ] ").append(".");
 
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value, expression);
@@ -986,8 +988,8 @@ public class DataValidatorBuilder {
         if (validationErr) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".value.should.true.or.false");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" value should true or false ");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` value should true or false ");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value);
             this.dataValidationErrors.add(error);
@@ -1017,8 +1019,8 @@ public class DataValidatorBuilder {
         if (validationErr) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".format.is.invalid");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.resource).append(this.parameter)
-                    .append(" is in invalid format, should contain '-','+','()' and numbers only.");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` is in invalid format, should contain '-','+','()' and numbers only.");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value);
             this.dataValidationErrors.add(error);
@@ -1030,8 +1032,8 @@ public class DataValidatorBuilder {
         if (this.value != null && !CronExpression.isValidExpression(this.value.toString().trim())) {
             final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                     .append(this.parameter).append(".invalid");
-            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter)
-                    .append(" value is not a valid cron expression");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                    .append("` value is not a valid cron expression");
             final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                     defaultEnglishMessage.toString(), this.parameter, this.value);
             this.dataValidationErrors.add(error);
@@ -1049,8 +1051,8 @@ public class DataValidatorBuilder {
             if (date.isAfter(dateVal)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.less.than.date");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be greter than provided date").append(date);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be greater than the provided date").append(date);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, dateVal, date);
                 this.dataValidationErrors.add(error);
@@ -1069,8 +1071,8 @@ public class DataValidatorBuilder {
             if (date.isBefore(dateVal)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.greater.than.date");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be less than provided date").append(date);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be less than the provided date").append(date);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, dateVal, date);
                 this.dataValidationErrors.add(error);
@@ -1089,8 +1091,8 @@ public class DataValidatorBuilder {
             if (dateVal.isAfter(date)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.greater.than.date");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be less than or equal to provided date").append(date);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be less than or equal to the provided date").append(date);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, dateVal, date);
                 this.dataValidationErrors.add(error);
@@ -1109,8 +1111,8 @@ public class DataValidatorBuilder {
             if (!dateVal.isEqual(date)) {
                 final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
                         .append(this.parameter).append(".is.not.equal.to.date");
-                final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter)
-                        .append(" must be equal to provided date").append(date);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter)
+                        .append("` must be equal to the provided date").append(date);
                 final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
                         defaultEnglishMessage.toString(), this.parameter, dateVal, date);
                 this.dataValidationErrors.add(error);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
index 70d8aeeca..f20b4cf34 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
@@ -59,4 +59,9 @@ public class PlatformApiDataValidationException extends AbstractPlatformExceptio
     public List<ApiParameterError> getErrors() {
         return this.errors;
     }
+
+    @Override
+    public String toString() {
+        return "PlatformApiDataValidationException{" + "errors=" + errors + '}';
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java
index 12251ce43..1cea0f608 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/DefaultToApiJsonSerializer.java
@@ -30,7 +30,7 @@ import org.springframework.stereotype.Component;
  * into JSON.
  */
 @Component
-public final class DefaultToApiJsonSerializer<T> implements ToApiJsonSerializer<T> {
+public class DefaultToApiJsonSerializer<T> implements ToApiJsonSerializer<T> {
 
     private final ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOff;
     private final ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson excludeNothingWithPrettyPrintingOn;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
index ac43cfbca..b7d30c2f9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -36,6 +36,7 @@ import java.time.LocalDateTime;
 import java.time.MonthDay;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoField;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -349,8 +350,8 @@ public class JsonParserHelper {
                     } catch (final IllegalArgumentException e) {
                         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
                         final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.month.day",
-                                "The parameter " + parameterName + " is invalid based on the monthDayFormat: '" + dateFormat
-                                        + "' and locale: '" + clientApplicationLocale + "' provided:",
+                                "The parameter `" + parameterName + "` is invalid based on the monthDayFormat: `" + dateFormat
+                                        + "` and locale: `" + clientApplicationLocale + "` provided:",
                                 parameterName, valueAsString, dateFormat);
                         dataValidationErrors.add(error);
 
@@ -422,7 +423,7 @@ public class JsonParserHelper {
                     }
                 } catch (IllegalArgumentException e) {
                     final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-                    final String defaultMessage = new StringBuilder("The parameter '" + timeValueAsString + "' is not in correct format.")
+                    final String defaultMessage = new StringBuilder("The parameter `" + timeValueAsString + "` is not in correct format.")
                             .toString();
                     final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.TimeFormat", defaultMessage,
                             parameterName);
@@ -475,12 +476,12 @@ public class JsonParserHelper {
                         .parseDefaulting(ChronoField.HOUR_OF_DAY, 0).parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                         .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).toFormatter(clientApplicationLocale);
                 eventLocalDateTime = LocalDateTime.parse(dateTimeAsString, formatter);
-            } catch (final IllegalArgumentException e) {
+            } catch (final IllegalArgumentException | DateTimeParseException e) {
                 final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
                 final ApiParameterError error = ApiParameterError
                         .parameterError("validation.msg.invalid.dateFormat.format",
-                                "The parameter " + parameterName + " is invalid based on the dateFormat: '" + dateTimeFormat
-                                        + "' and locale: '" + clientApplicationLocale + "' provided:",
+                                "The parameter `" + parameterName + "` is invalid based on the dateFormat: `" + dateTimeFormat
+                                        + "` and locale: `" + clientApplicationLocale + "` provided:",
                                 parameterName, eventLocalDateTime, dateTimeFormat);
                 dataValidationErrors.add(error);
 
@@ -499,14 +500,14 @@ public class JsonParserHelper {
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             if (StringUtils.isBlank(dateFormat)) {
                 final String defaultMessage = new StringBuilder(
-                        "The parameter '" + parameterName + "' requires a 'dateFormat' parameter to be passed with it.").toString();
+                        "The parameter `" + parameterName + "` requires a `dateFormat` parameter to be passed with it.").toString();
                 final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.dateFormat.parameter",
                         defaultMessage, parameterName);
                 dataValidationErrors.add(error);
             }
             if (clientApplicationLocale == null) {
                 final String defaultMessage = new StringBuilder(
-                        "The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
+                        "The parameter `" + parameterName + "` requires a `locale` parameter to be passed with it.").toString();
                 final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
                         parameterName);
                 dataValidationErrors.add(error);
@@ -524,7 +525,7 @@ public class JsonParserHelper {
 
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final String defaultMessage = new StringBuilder(
-                    "The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
+                    "The parameter `" + parameterName + "` requires a `locale` parameter to be passed with it.").toString();
             final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
                     parameterName);
             dataValidationErrors.add(error);
@@ -572,7 +573,7 @@ public class JsonParserHelper {
 
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final ApiParameterError error = ApiParameterError.parameterErrorWithValue("validation.msg.invalid.integer.format",
-                    "The parameter " + parameterName + " has value: " + numericalValueFormatted
+                    "The parameter `" + parameterName + "` has value: " + numericalValueFormatted
                             + " which is invalid integer value for provided locale of [" + clientApplicationLocale.toString() + "].",
                     parameterName, numericalValueFormatted, numericalValueFormatted, clientApplicationLocale);
             dataValidationErrors.add(error);
@@ -596,7 +597,7 @@ public class JsonParserHelper {
 
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final ApiParameterError error = ApiParameterError.parameterErrorWithValue("validation.msg.invalid.integer",
-                    "The parameter " + parameterName + " has value: " + numericalValueFormatted + " which is invalid integer.",
+                    "The parameter `" + parameterName + "` has value: " + numericalValueFormatted + " which is invalid integer.",
                     parameterName, numericalValueFormatted, numericalValueFormatted);
             dataValidationErrors.add(error);
 
@@ -611,7 +612,7 @@ public class JsonParserHelper {
 
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final String defaultMessage = new StringBuilder(
-                    "The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
+                    "The parameter `" + parameterName + "` requires a `locale` parameter to be passed with it.").toString();
             final ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage,
                     parameterName);
             dataValidationErrors.add(error);
@@ -650,8 +651,8 @@ public class JsonParserHelper {
 
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final ApiParameterError error = ApiParameterError.parameterErrorWithValue("validation.msg.invalid.decimal.format",
-                    "The parameter " + parameterName + " has value: " + numericalValueFormatted
-                            + " which is invalid decimal value for provided locale of [" + clientApplicationLocale.toString() + "].",
+                    "The parameter `" + parameterName + "` has value: " + numericalValueFormatted
+                            + " which is invalid decimal value for provided locale of [" + clientApplicationLocale + "].",
                     parameterName, numericalValueFormatted, numericalValueFormatted, clientApplicationLocale);
             dataValidationErrors.add(error);
 
@@ -668,7 +669,7 @@ public class JsonParserHelper {
         if (StringUtils.isBlank(localeAsString)) {
             final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
             final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
-                    "The parameter locale is invalid. It cannot be blank.", "locale");
+                    "The parameter `locale` is invalid. It cannot be blank.", "locale");
             dataValidationErrors.add(error);
 
             throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -706,7 +707,7 @@ public class JsonParserHelper {
         final List<String> allowedLanguages = Arrays.asList(Locale.getISOLanguages());
         if (!allowedLanguages.contains(languageCode.toLowerCase())) {
             final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
-                    "The parameter locale has an invalid language value " + languageCode + " .", "locale", languageCode);
+                    "The parameter `locale` has an invalid language value " + languageCode + " .", "locale", languageCode);
             dataValidationErrors.add(error);
         }
 
@@ -714,7 +715,7 @@ public class JsonParserHelper {
             final List<String> allowedCountries = Arrays.asList(Locale.getISOCountries());
             if (!allowedCountries.contains(courntryCode)) {
                 final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format",
-                        "The parameter locale has an invalid country value " + courntryCode + " .", "locale", courntryCode);
+                        "The parameter `locale` has an invalid country value " + courntryCode + " .", "locale", courntryCode);
                 dataValidationErrors.add(error);
             }
         }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index 23baa55cd..6da2d4bbf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -45,9 +45,11 @@ public enum JobName {
                                                                                                                                                                                     "Get Delivery Reports from SMS Gateway"), GENERATE_ADHOCCLIENT_SCEHDULE(
                                                                                                                                                                                             "Generate AdhocClient Schedule"), UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE(
                                                                                                                                                                                                     "Update Email Outbound with campaign message"), EXECUTE_EMAIL(
-                                                                                                                                                                                                            "Execute Email"), UPDATE_TRAIL_BALANCE_DETAILS(
+                                                                                                                                                                                                            "Execute Email"), UPDATE_TRIAL_BALANCE_DETAILS(
                                                                                                                                                                                                                     "Update Trial Balance Details"), EXECUTE_DIRTY_JOBS(
-                                                                                                                                                                                                                            "Execute All Dirty Jobs");
+                                                                                                                                                                                                                            "Execute All Dirty Jobs"), INCREASE_BUSINESS_DATE_BY_1_DAY(
+                                                                                                                                                                                                                                    "Increase Business Date by 1 day"), INCREASE_COB_DATE_BY_1_DAY(
+                                                                                                                                                                                                                                            "Increase COB Date by 1 day");
 
     private final String name;
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java
index c7b418105..643e8f575 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java
@@ -151,7 +151,7 @@ public class AdHocQueryDataValidator {
                         .extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element);
                 baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName)
                         .value(maxOutStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
-                baseDataValidator.reset().comapareMinAndMaxOfTwoBigDecmimalNos(minOutStandingAmountPercentage,
+                baseDataValidator.reset().compareMinAndMaxOfTwoBigDecmimalNos(minOutStandingAmountPercentage,
                         maxOutStandingAmountPercentage);
             } else {
                 if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element)) {
@@ -184,7 +184,7 @@ public class AdHocQueryDataValidator {
                         .extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutstandingAmountParamName, element);
                 baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutstandingAmountParamName).value(maxOutstandingAmount)
                         .notNull().notLessThanMin(BigDecimal.ZERO);
-                baseDataValidator.reset().comapareMinAndMaxOfTwoBigDecmimalNos(minOutstandingAmount, maxOutstandingAmount);
+                baseDataValidator.reset().compareMinAndMaxOfTwoBigDecmimalNos(minOutstandingAmount, maxOutstandingAmount);
             } else {
                 final BigDecimal outstandingAmount = this.fromApiJsonHelper
                         .extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.outstandingAmountParamName, element);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java
index 862dc0d71..3cc6775e7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java
@@ -20,7 +20,6 @@ package org.apache.fineract.scheduledjobs.service;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -329,7 +328,7 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
     }
 
     @Override
-    @CronTarget(jobName = JobName.UPDATE_TRAIL_BALANCE_DETAILS)
+    @CronTarget(jobName = JobName.UPDATE_TRIAL_BALANCE_DETAILS)
     public void updateTrialBalanceDetails() throws JobExecutionException {
         final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
         final StringBuilder tbGapSqlBuilder = new StringBuilder(500);
@@ -344,15 +343,14 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
             if (days < 1) {
                 continue;
             }
-            final String formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(tbGap);
             final StringBuilder sqlBuilder = new StringBuilder(600);
             sqlBuilder.append("Insert Into m_trial_balance(office_id, account_id, Amount, entry_date, created_date,closing_balance) ")
                     .append("Select je.office_id, je.account_id, SUM(CASE WHEN je.type_enum=1 THEN (-1) * je.amount ELSE je.amount END) ")
-                    .append("as Amount, Date(je.entry_date) as 'Entry_Date', je.transaction_date as 'Created_Date',sum(je.amount) as closing_balance ")
+                    .append("as Amount, Date(je.entry_date) as Entry_Date, je.transaction_date as Created_Date,sum(je.amount) as closing_balance ")
                     .append("from acc_gl_journal_entry je WHERE je.transaction_date = ? ")
                     .append("group by je.account_id, je.office_id, je.transaction_date, Date(je.entry_date)");
 
-            final int result = jdbcTemplate.update(sqlBuilder.toString(), formattedDate);
+            final int result = jdbcTemplate.update(sqlBuilder.toString(), tbGap);
             LOG.info("{}: Records affected by updateTrialBalanceDetails: {}", ThreadLocalContextUtil.getTenant().getName(), result);
         }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
index 6fce5360b..7f31e9ca2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -552,9 +552,20 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
     }
 
     public void validateHasReadPermission(final String resourceType) {
+        validateHasPermission("READ", resourceType);
+    }
+
+    public void validateHasCreatePermission(final String resourceType) {
+        validateHasPermission("CREATE", resourceType);
+    }
+
+    public void validateHasUpdatePermission(final String resourceType) {
+        validateHasPermission("UPDATE", resourceType);
+    }
 
-        final String authorizationMessage = "User has no authority to view " + resourceType.toLowerCase() + "s";
-        final String matchPermission = "READ_" + resourceType.toUpperCase();
+    private void validateHasPermission(final String prefix, final String resourceType) {
+        final String authorizationMessage = "User has no authority to " + prefix + " " + resourceType.toLowerCase() + "s";
+        final String matchPermission = prefix + "_" + resourceType.toUpperCase();
 
         if (!hasNotPermissionForAnyOf("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", matchPermission)) {
             return;
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 9d28ead57..00372f771 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -34,4 +34,5 @@
     <include file="parts/0012_add_merchantissuedrefund_payoutrefund_goodwillcredit_permissions.xml" relativeToChangelogFile="true"/>
     <include file="parts/0013_remove_topics.xml" relativeToChangelogFile="true"/>
     <include file="parts/0014_remove_unused_jobs.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0015_add_business_date.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0015_add_business_date.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0015_add_business_date.xml
new file mode 100644
index 000000000..f6c0d24a7
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0015_add_business_date.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+    <changeSet author="fineract" id="1">
+        <createTable tableName="m_business_date">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="type" type="VARCHAR(100)">
+                <constraints unique="true" nullable="false"/>
+            </column>
+            <column name="date" type="DATE">
+                <constraints unique="false" nullable="false"/>
+            </column>
+            <column name="createdby_id" type="BIGINT"/>
+            <column name="created_date" type="DATETIME "/>
+            <column name="version" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column defaultValueComputed="NULL" name="lastmodifiedby_id" type="BIGINT"/>
+            <column defaultValueComputed="NULL" name="lastmodified_date" type="DATETIME"/>
+        </createTable>
+    </changeSet>
+    <changeSet id="2" author="fineract">
+        <insert tableName="c_configuration">
+            <column name="name" value="enable_business_date"/>
+            <column name="value"/>
+            <column name="date_value"/>
+            <column name="string_value"/>
+            <column name="enabled" valueBoolean="false"/>
+            <column name="is_trap_door" valueBoolean="false"/>
+            <column name="description" value="Whether the logical business date functionality is enabled in the system"/>
+        </insert>
+        <insert tableName="c_configuration">
+            <column name="name" value="enable_automatic_cob_date_adjustment"/>
+            <column name="value"/>
+            <column name="date_value"/>
+            <column name="string_value"/>
+            <column name="enabled" valueBoolean="true"/>
+            <column name="is_trap_door" valueBoolean="false"/>
+            <column name="description" value="Whether the cob date will be automatically recalculated based on the business date"/>
+        </insert>
+    </changeSet>
+    <changeSet id="3" author="fineract">
+        <insert tableName="m_permission">
+            <column name="grouping" value="organisation"/>
+            <column name="code" value="READ_BUSINESS_DATE"/>
+            <column name="entity_name" value="BUSINESS_DATE"/>
+            <column name="action_name" value="READ"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+        <insert tableName="m_permission">
+            <column name="grouping" value="organisation"/>
+            <column name="code" value="UPDATE_BUSINESS_DATE"/>
+            <column name="entity_name" value="BUSINESS_DATE"/>
+            <column name="action_name" value="UPDATE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+    <changeSet id="4" author="fineract">
+        <insert tableName="job">
+            <column name="name" value="Increase Business Date by 1 day"/>
+            <column name="display_name" value="Increase Business Date by 1 day"/>
+            <column name="cron_expression" value="0 0 0 1/1 * ? *"/>
+            <column name="create_time" valueDate="${current_datetime}"/>
+            <column name="task_priority" valueNumeric="99"/>
+            <column name="group_name"/>
+            <column name="previous_run_start_time"/>
+            <column name="job_key" value="Increase Business Date by 1 dayJobDetail1 _ DEFAULT"/>
+            <column name="initializing_errorlog"/>
+            <column name="is_active" valueBoolean="false"/>
+            <column name="currently_running" valueBoolean="false"/>
+            <column name="updates_allowed" valueBoolean="true"/>
+            <column name="scheduler_group" valueNumeric="0"/>
+            <column name="is_misfired" valueBoolean="false"/>
+            <column name="node_id" valueNumeric="1"/>
+            <column name="is_mismatched_job" valueBoolean="true"/>
+        </insert>
+    </changeSet>
+    <changeSet id="5" author="fineract">
+        <insert tableName="job">
+            <column name="name" value="Increase COB Date by 1 day"/>
+            <column name="display_name" value="Increase COB Date by 1 day"/>
+            <column name="cron_expression" value="0 0 0 1/1 * ? *"/>
+            <column name="create_time" valueDate="${current_datetime}"/>
+            <column name="task_priority" valueNumeric="98"/>
+            <column name="group_name"/>
+            <column name="previous_run_start_time"/>
+            <column name="job_key" value="Increase COB Date by 1 dayJobDetail1 _ DEFAULT"/>
+            <column name="initializing_errorlog"/>
+            <column name="is_active" valueBoolean="false"/>
+            <column name="currently_running" valueBoolean="false"/>
+            <column name="updates_allowed" valueBoolean="true"/>
+            <column name="scheduler_group" valueNumeric="0"/>
+            <column name="is_misfired" valueBoolean="false"/>
+            <column name="node_id" valueNumeric="1"/>
+            <column name="is_mismatched_job" valueBoolean="true"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiTest.java
new file mode 100644
index 000000000..def9947cc
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiTest.java
@@ -0,0 +1,163 @@
+/**
+ * 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.infrastructure.businessdate.api;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.ServletException;
+import javax.ws.rs.core.UriInfo;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
+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.exception.NoAuthorizationException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class BusinessDateApiTest {
+
+    @Mock
+    private ApiRequestParameterHelper parameterHelper;
+
+    @Mock
+    private BusinessDateReadPlatformService readPlatformService;
+
+    @Mock
+    private UriInfo uriInfo;
+
+    @Mock
+    private PlatformSecurityContext securityContext;
+
+    @Mock
+    private DefaultToApiJsonSerializer<BusinessDateData> jsonSerializer;
+
+    @Mock
+    private PortfolioCommandSourceWritePlatformService commandWritePlatformService;
+
+    @InjectMocks
+    private BusinessDateApiResource underTest;
+
+    private ApiRequestJsonSerializationSettings apiRequestJsonSerializationSettings;
+
+    @BeforeEach
+    void setUp() throws IOException {
+        apiRequestJsonSerializationSettings = new ApiRequestJsonSerializationSettings(false, null, false, false, false);
+        given(parameterHelper.process(Mockito.any())).willReturn(apiRequestJsonSerializationSettings);
+    }
+
+    @Test
+    void getBusinessDatesAPIHasPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        List<BusinessDateData> response = Mockito.mock(List.class);
+        given(readPlatformService.findAll()).willReturn(response);
+        // given
+        Mockito.doNothing().when(appUser).validateHasReadPermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        // when
+        underTest.getBusinessDates(uriInfo);
+        // then
+        verify(readPlatformService, Mockito.times(1)).findAll();
+        verify(jsonSerializer, Mockito.times(1)).serialize(apiRequestJsonSerializationSettings, response);
+    }
+
+    @Test
+    void getBusinessDatesAPIHasNoPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        // given
+        Mockito.doThrow(NoAuthorizationException.class).when(appUser).validateHasReadPermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        // when
+        assertThatThrownBy(() -> underTest.getBusinessDates(uriInfo)).isInstanceOf(NoAuthorizationException.class);
+        // then
+        verifyNoInteractions(readPlatformService);
+    }
+
+    @Test
+    void getBusinessDateByTypeAPIHasPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        BusinessDateData response = Mockito.mock(BusinessDateData.class);
+        given(readPlatformService.findByType("type")).willReturn(response);
+        // given
+        Mockito.doNothing().when(appUser).validateHasReadPermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        // when
+        underTest.getBusinessDate("type", uriInfo);
+        // then
+        verify(readPlatformService, Mockito.times(1)).findByType("type");
+        verify(jsonSerializer, Mockito.times(1)).serialize(apiRequestJsonSerializationSettings, response);
+    }
+
+    @Test
+    void getBusinessDateByTypeAPIHasNoPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        // given
+        Mockito.doThrow(NoAuthorizationException.class).when(appUser).validateHasReadPermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        // when
+        assertThatThrownBy(() -> underTest.getBusinessDate("type", uriInfo)).isInstanceOf(NoAuthorizationException.class);
+        // then
+        verifyNoInteractions(readPlatformService);
+    }
+
+    @Test
+    void postBusinessDateAPIHasPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        CommandProcessingResult response = Mockito.mock(CommandProcessingResult.class);
+        // given
+        Mockito.doNothing().when(appUser).validateHasUpdatePermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        given(commandWritePlatformService.logCommandSource(Mockito.any())).willReturn(response);
+        // when
+        underTest.updateBusinessDate("{}", uriInfo);
+        // then
+        verify(commandWritePlatformService, Mockito.times(1)).logCommandSource(Mockito.any());
+        verify(jsonSerializer, Mockito.times(1)).serialize(response);
+    }
+
+    @Test
+    void postBusinessDateAPIHasNoPermission() throws ServletException, IOException {
+        AppUser appUser = Mockito.mock(AppUser.class);
+        // given
+        Mockito.doThrow(NoAuthorizationException.class).when(appUser).validateHasUpdatePermission("BUSINESS_DATE");
+        given(securityContext.authenticatedUser()).willReturn(appUser);
+        // when
+        assertThatThrownBy(() -> underTest.updateBusinessDate("{}", uriInfo)).isInstanceOf(NoAuthorizationException.class);
+        // then
+        verifyNoInteractions(readPlatformService);
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataSerialized.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataSerialized.java
new file mode 100644
index 000000000..ff5ede342
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataSerialized.java
@@ -0,0 +1,63 @@
+/**
+ * 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.infrastructure.businessdate.data;
+
+import static org.junit.Assert.assertEquals;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.serialization.CommandProcessingResultJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson;
+import org.apache.fineract.infrastructure.core.serialization.ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson;
+import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import org.junit.Test;
+
+public class BusinessDataSerialized {
+
+    @Test
+    public void serializeBusinessDateData() {
+        DefaultToApiJsonSerializer<BusinessDateData> jsonSerializer = new DefaultToApiJsonSerializer<>(
+                new ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson(),
+                new ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson(), new CommandProcessingResultJsonSerializer(),
+                new GoogleGsonSerializerHelper());
+
+        LocalDate now = LocalDate.now(ZoneId.systemDefault());
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, now);
+        String result = jsonSerializer.serialize(businessDateData);
+        assertEquals("{\"description\":\"Business Date\",\"type\":\"BUSINESS_DATE\",\"date\":[" + now.getYear() + "," + now.getMonthValue()
+                + "," + now.getDayOfMonth() + "]}", result);
+    }
+
+    @Test
+    public void serializeBusinessDateData_COB() {
+        DefaultToApiJsonSerializer<BusinessDateData> jsonSerializer = new DefaultToApiJsonSerializer<>(
+                new ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson(),
+                new ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson(), new CommandProcessingResultJsonSerializer(),
+                new GoogleGsonSerializerHelper());
+
+        LocalDate now = LocalDate.now(ZoneId.systemDefault());
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.COB_DATE, now);
+        String result = jsonSerializer.serialize(businessDateData);
+        assertEquals("{\"description\":\"Close of Business Date\",\"type\":\"COB_DATE\",\"date\":[" + now.getYear() + ","
+                + now.getMonthValue() + "," + now.getDayOfMonth() + "]}", result);
+    }
+
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataTypeTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataTypeTest.java
new file mode 100644
index 000000000..a3257a317
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/data/BusinessDataTypeTest.java
@@ -0,0 +1,37 @@
+/**
+ * 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.infrastructure.businessdate.data;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.junit.Test;
+
+public class BusinessDataTypeTest {
+
+    @Test
+    public void typoCheck() {
+        for (BusinessDateType businessDateType : BusinessDateType.values()) {
+            switch (businessDateType) {
+                case BUSINESS_DATE -> assertEquals("Business Date", businessDateType.getDescription());
+                case COB_DATE -> assertEquals("Close of Business Date", businessDateType.getDescription());
+            }
+        }
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapperTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapperTest.java
new file mode 100644
index 000000000..9bbd6a5f0
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/mapper/BusinessDateMapperTest.java
@@ -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.infrastructure.businessdate.mapper;
+
+import static org.junit.Assert.assertEquals;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.List;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.junit.Test;
+import org.mapstruct.factory.Mappers;
+
+public class BusinessDateMapperTest {
+
+    private BusinessDateMapper businessDateMapper = Mappers.getMapper(BusinessDateMapper.class);
+
+    @Test
+    public void testMapping() {
+        LocalDate now = LocalDate.now(ZoneId.systemDefault());
+        BusinessDate businessDate = BusinessDate.instance(BusinessDateType.BUSINESS_DATE, now);
+        BusinessDateData businessDateData = businessDateMapper.map(businessDate);
+        assertEquals(businessDate.getDate(), businessDateData.getDate());
+        assertEquals(businessDate.getType().getDescription(), businessDateData.getDescription());
+        assertEquals(businessDate.getType().getName(), businessDateData.getType());
+    }
+
+    @Test
+    public void testMappingList() {
+        LocalDate now = LocalDate.now(ZoneId.systemDefault());
+        BusinessDate businessDate = BusinessDate.instance(BusinessDateType.BUSINESS_DATE, now);
+        List<BusinessDateData> businessDateData = businessDateMapper.map(Collections.singletonList(businessDate));
+        assertEquals(businessDate.getDate(), businessDateData.get(0).getDate());
+        assertEquals(businessDate.getType().getDescription(), businessDateData.get(0).getDescription());
+        assertEquals(businessDate.getType().getName(), businessDateData.get(0).getType());
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceTest.java
new file mode 100644
index 000000000..b20bbb85e
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateReadPlatformServiceTest.java
@@ -0,0 +1,96 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.businessdate.exception.BusinessDateNotFoundException;
+import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class BusinessDateReadPlatformServiceTest {
+
+    @InjectMocks
+    private BusinessDateReadPlatformServiceImpl businessDateReadPlatformService;
+
+    @Mock
+    private BusinessDateRepository repository;
+
+    @Mock
+    private BusinessDateMapper mapper;
+
+    @Test
+    public void notFoundByTypeNonexistentType() {
+        BusinessDateNotFoundException businessDateNotFoundException = assertThrows(BusinessDateNotFoundException.class,
+                () -> businessDateReadPlatformService.findByType("invalid"));
+        assertEquals("Business date with type `invalid` does not exist.", businessDateNotFoundException.getDefaultUserMessage());
+    }
+
+    @Test
+    public void notFoundByTypeNotStoredInDB() {
+        BusinessDateNotFoundException businessDateNotFoundException = assertThrows(BusinessDateNotFoundException.class,
+                () -> businessDateReadPlatformService.findByType("BUSINESS_DATE"));
+        assertEquals("Business date with type `BUSINESS_DATE` does not found.", businessDateNotFoundException.getDefaultUserMessage());
+    }
+
+    @Test
+    public void findAll() {
+        List<BusinessDate> resultList = Mockito.mock(List.class);
+        given(repository.findAll()).willReturn(resultList);
+        businessDateReadPlatformService.findAll();
+        verify(repository, times(1)).findAll();
+        verify(mapper, times(1)).map(resultList);
+    }
+
+    @Test
+    public void findByCOBType() {
+        Optional<BusinessDate> result = Optional.of(Mockito.mock(BusinessDate.class));
+        given(repository.findByType(BusinessDateType.COB_DATE)).willReturn(result);
+        businessDateReadPlatformService.findByType("COB_DATE");
+        verify(repository, times(1)).findByType(BusinessDateType.COB_DATE);
+        verify(mapper, times(1)).map(result.get());
+    }
+
+    @Test
+    public void findByBusinessType() {
+        Optional<BusinessDate> result = Optional.of(Mockito.mock(BusinessDate.class));
+        given(repository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(result);
+        businessDateReadPlatformService.findByType("BUSINESS_DATE");
+        verify(repository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(mapper, times(1)).map(result.get());
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java
new file mode 100644
index 000000000..b896f8334
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/service/BusinessDateWritePlatformServiceTest.java
@@ -0,0 +1,264 @@
+/**
+ * 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.infrastructure.businessdate.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.businessdate.exception.BusinessDateActionException;
+import org.apache.fineract.infrastructure.businessdate.validator.BusinessDateDataParserAndValidator;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class BusinessDateWritePlatformServiceTest {
+
+    @InjectMocks
+    private BusinessDateWritePlatformServiceImpl underTest;
+
+    @Mock
+    private BusinessDateDataParserAndValidator businessDateDataParserAndValidator;
+
+    @Mock
+    private BusinessDateRepository businessDateRepository;
+
+    @Mock
+    private ConfigurationDomainService configurationDomainService;
+
+    @Captor
+    private ArgumentCaptor<BusinessDate> businessDateArgumentCaptor;
+
+    @Test
+    public void businessDateIsNotEnabled() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE,
+                LocalDate.now(ZoneId.systemDefault()));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        BusinessDateActionException exception = assertThrows(BusinessDateActionException.class,
+                () -> underTest.updateBusinessDate(command));
+        assertEquals("Business date functionality is not enabled", exception.getDefaultUserMessage());
+    }
+
+    @Test
+    public void businessDateSetNew() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.empty();
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        LocalDate resultData = (LocalDate) result.getChanges().get("BUSINESS_DATE");
+        assertEquals(LocalDate.of(2022, 6, 13), resultData);
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(1)).save(businessDateArgumentCaptor.capture());
+        assertEquals(LocalDate.of(2022, 6, 13), businessDateArgumentCaptor.getValue().getDate());
+        assertEquals(BusinessDateType.BUSINESS_DATE, businessDateArgumentCaptor.getValue().getType());
+    }
+
+    @Test
+    public void cobDateSetNew() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.COB_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.empty();
+        given(businessDateRepository.findByType(BusinessDateType.COB_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        LocalDate resultData = (LocalDate) result.getChanges().get("COB_DATE");
+        assertEquals(LocalDate.of(2022, 6, 13), resultData);
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.COB_DATE);
+        verify(businessDateRepository, times(1)).save(businessDateArgumentCaptor.capture());
+        assertEquals(LocalDate.of(2022, 6, 13), businessDateArgumentCaptor.getValue().getDate());
+        assertEquals(BusinessDateType.COB_DATE, businessDateArgumentCaptor.getValue().getType());
+    }
+
+    @Test
+    public void businessDateSetModifyExistingWhenItWasAfter() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 11));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.of(BusinessDate.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 12)));
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        LocalDate resultData = (LocalDate) result.getChanges().get("BUSINESS_DATE");
+        assertEquals(LocalDate.of(2022, 6, 11), resultData);
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(1)).save(businessDateArgumentCaptor.capture());
+        assertEquals(LocalDate.of(2022, 6, 11), businessDateArgumentCaptor.getValue().getDate());
+        assertEquals(BusinessDateType.BUSINESS_DATE, businessDateArgumentCaptor.getValue().getType());
+    }
+
+    @Test
+    public void businessDateSetModifyExistingWhenItWasBefore() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.of(BusinessDate.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 12)));
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        LocalDate resultData = (LocalDate) result.getChanges().get("BUSINESS_DATE");
+        assertEquals(LocalDate.of(2022, 6, 13), resultData);
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(1)).save(businessDateArgumentCaptor.capture());
+        assertEquals(LocalDate.of(2022, 6, 13), businessDateArgumentCaptor.getValue().getDate());
+        assertEquals(BusinessDateType.BUSINESS_DATE, businessDateArgumentCaptor.getValue().getType());
+    }
+
+    @Test
+    public void businessDateSetModifyExistingButNoChanges() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.FALSE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.of(BusinessDate.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13)));
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        assertNull(result.getChanges());
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(0)).save(businessDateArgumentCaptor.capture());
+    }
+
+    @Test
+    public void cobDateSetNewAutomatically() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.TRUE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newEntity = Optional.empty();
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        LocalDate businessDate = (LocalDate) result.getChanges().get("BUSINESS_DATE");
+        assertEquals(LocalDate.of(2022, 6, 13), businessDate);
+        LocalDate cobDate = (LocalDate) result.getChanges().get("COB_DATE");
+        assertEquals(LocalDate.of(2022, 6, 12), cobDate);
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.COB_DATE);
+        verify(businessDateRepository, times(2)).save(businessDateArgumentCaptor.capture());
+        assertEquals(LocalDate.of(2022, 6, 13), businessDateArgumentCaptor.getAllValues().get(0).getDate());
+        assertEquals(BusinessDateType.BUSINESS_DATE, businessDateArgumentCaptor.getAllValues().get(0).getType());
+        assertEquals(LocalDate.of(2022, 6, 12), businessDateArgumentCaptor.getAllValues().get(1).getDate());
+        assertEquals(BusinessDateType.COB_DATE, businessDateArgumentCaptor.getAllValues().get(1).getType());
+    }
+
+    @Test
+    public void businessDateAndCobDateSetModifyExistingButNoChanges() {
+        JsonCommand command = JsonCommand.from("");
+        BusinessDateData businessDateData = BusinessDateData.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13));
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.TRUE);
+        given(businessDateDataParserAndValidator.validateAndParseUpdate(command)).willReturn(businessDateData);
+        Optional<BusinessDate> newBusinessEntity = Optional
+                .of(BusinessDate.instance(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 13)));
+        Optional<BusinessDate> newCOBEntity = Optional.of(BusinessDate.instance(BusinessDateType.COB_DATE, LocalDate.of(2022, 6, 12)));
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newBusinessEntity);
+        given(businessDateRepository.findByType(BusinessDateType.COB_DATE)).willReturn(newCOBEntity);
+        CommandProcessingResult result = underTest.updateBusinessDate(command);
+        assertNull(result.getChanges());
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.BUSINESS_DATE);
+        verify(businessDateRepository, times(1)).findByType(BusinessDateType.COB_DATE);
+        verify(businessDateRepository, times(0)).save(Mockito.any());
+    }
+
+    @Test
+    public void businessDateIsNotEnabledTriggeredByJob() {
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.FALSE);
+        assertThrows(JobExecutionException.class, () -> underTest.increaseBusinessDateByOneDay());
+    }
+
+    @Test
+    public void businessDateSetNewTriggeredByJob() throws JobExecutionException {
+        LocalDate localDate = DateUtils.getLocalDateOfTenant();
+        LocalDate localDatePlus1 = localDate.plusDays(1);
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.TRUE);
+        Optional<BusinessDate> newEntity = Optional.empty();
+        given(businessDateRepository.findByType(BusinessDateType.BUSINESS_DATE)).willReturn(newEntity);
+        underTest.increaseBusinessDateByOneDay();
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(2)).save(businessDateArgumentCaptor.capture());
+        assertEquals(localDatePlus1, businessDateArgumentCaptor.getAllValues().get(0).getDate());
+        assertEquals(BusinessDateType.BUSINESS_DATE, businessDateArgumentCaptor.getAllValues().get(0).getType());
+        assertEquals(localDate, businessDateArgumentCaptor.getAllValues().get(1).getDate());
+        assertEquals(BusinessDateType.COB_DATE, businessDateArgumentCaptor.getAllValues().get(1).getType());
+    }
+
+    @Test
+    public void cobDateModifyExistingTriggeredByJob() throws JobExecutionException {
+        Optional<BusinessDate> newCOBEntity = Optional.of(BusinessDate.instance(BusinessDateType.COB_DATE, LocalDate.of(2022, 6, 12)));
+        given(businessDateRepository.findByType(BusinessDateType.COB_DATE)).willReturn(newCOBEntity);
+        LocalDate localDate = LocalDate.of(2022, 6, 12).plusDays(1);
+        given(configurationDomainService.isBusinessDateEnabled()).willReturn(Boolean.TRUE);
+        given(configurationDomainService.isCOBDateAdjustmentEnabled()).willReturn(Boolean.TRUE);
+        underTest.increaseCOBDateByOneDay();
+        verify(configurationDomainService, times(1)).isBusinessDateEnabled();
+        verify(configurationDomainService, times(1)).isCOBDateAdjustmentEnabled();
+        verify(businessDateRepository, times(1)).save(businessDateArgumentCaptor.capture());
+        assertEquals(localDate, businessDateArgumentCaptor.getValue().getDate());
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateValidatorTest.java
new file mode 100644
index 000000000..206c85bdf
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validator/BusinessDateValidatorTest.java
@@ -0,0 +1,120 @@
+/**
+ * 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.infrastructure.businessdate.validator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.time.LocalDate;
+import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.junit.Test;
+
+public class BusinessDateValidatorTest {
+
+    private BusinessDateDataParserAndValidator businessDateDataParserAndValidator = new BusinessDateDataParserAndValidator(
+            new FromJsonHelper());
+
+    @Test
+    public void validateAndParseUpdateWithEmptyRequest() {
+        String json = "{}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals(
+                "PlatformApiDataValidationException{errors=[The parameter `type` is mandatory., The parameter `locale` is mandatory., The parameter `dateFormat` is mandatory., The parameter `date` is mandatory.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithBlankFieldsInRequest() {
+        String json = "{\"type\":\"\", \"locale\":\"\",\"dateFormat\":\"\",\"date\":\"\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals(
+                "PlatformApiDataValidationException{errors=[The parameter `type` is mandatory., The parameter `locale` is mandatory., The parameter `dateFormat` is mandatory., The parameter `date` is mandatory.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithInvalidLocale() {
+        String json = "{\"type\":\"BUSINESS_DATE\", \"locale\":\"invalid\",\"dateFormat\":\"yyyy-MM-dd\",\"date\":\"2022-06-11\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals("PlatformApiDataValidationException{errors=[The parameter `locale` has an invalid language value: `invalid`.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithInvalidBusinessType() {
+        String json = "{\"type\":\"invalid\", \"locale\":\"hu\",\"dateFormat\":\"yyyy-MM-dd\",\"date\":\"2022-06-11\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals("PlatformApiDataValidationException{errors=[Failed data validation due to: Invalid Business Type value: `invalid`.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithInvalidDateFormat() {
+        String json = "{\"type\":\"BUSINESS_DATE\", \"locale\":\"hu\",\"dateFormat\":\"y2yyy-MM-dd\",\"date\":\"2022-06-11\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals(
+                "PlatformApiDataValidationException{errors=[The parameter `date` (value=2022-06-11) is invalid based on the dateFormat: `y2yyy-MM-dd` and locale: `hu` provided.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithInvalidDate() {
+        String json = "{\"type\":\"BUSINESS_DATE\", \"locale\":\"hu\",\"dateFormat\":\"yyyy-MM-dd\",\"date\":\"2y22-06-11\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals(
+                "PlatformApiDataValidationException{errors=[The parameter `date` (value=2y22-06-11) is invalid based on the dateFormat: `yyyy-MM-dd` and locale: `hu` provided.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithWrongDate() {
+        String json = "{\"type\":\"BUSINESS_DATE\", \"locale\":\"hu\",\"dateFormat\":\"yyyy-MM-dd\",\"date\":\"11-06-2022\"}";
+        JsonCommand command = JsonCommand.from(json);
+        PlatformApiDataValidationException exception = assertThrows(PlatformApiDataValidationException.class,
+                () -> businessDateDataParserAndValidator.validateAndParseUpdate(command));
+        assertEquals(
+                "PlatformApiDataValidationException{errors=[The parameter `date` (value=11-06-2022) is invalid based on the dateFormat: `yyyy-MM-dd` and locale: `hu` provided.]}",
+                exception.toString());
+    }
+
+    @Test
+    public void validateAndParseUpdateWithRighDate() {
+        String json = "{\"type\":\"COB_DATE\", \"locale\":\"hu\",\"dateFormat\":\"yyyy-MM-dd\",\"date\":\"2022-06-11\"}";
+        JsonCommand command = JsonCommand.from(json);
+        BusinessDateData result = businessDateDataParserAndValidator.validateAndParseUpdate(command);
+        assertEquals("COB_DATE", result.getType());
+        assertEquals("Close of Business Date", result.getDescription());
+        assertEquals(LocalDate.of(2022, 6, 11), result.getDate());
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java
index c36a2db90..4af42f74a 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java
@@ -22,14 +22,17 @@ import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
 import io.restassured.http.ContentType;
 import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
 import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.junit.jupiter.api.AfterEach;
@@ -134,6 +137,8 @@ public class SchedulerJobsTest {
 
     @Test
     public void testTriggeringManualExecutionOfAllSchedulerJobs() {
+        ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
         for (String jobName : schedulerJobHelper.getAllSchedulerJobNames()) {
             schedulerJobHelper.executeAndAwaitJob(jobName);
         }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
index 1499b61aa..6300c8c25 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
@@ -99,8 +99,8 @@ public class GlobalConfigurationHelper {
         ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
 
         // There are currently 37 global configurations.
-        Assertions.assertEquals(38, expectedGlobalConfigurations.size());
-        Assertions.assertEquals(38, actualGlobalConfigurations.size());
+        Assertions.assertEquals(40, expectedGlobalConfigurations.size());
+        Assertions.assertEquals(40, actualGlobalConfigurations.size());
 
         for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
 
@@ -440,6 +440,22 @@ public class GlobalConfigurationHelper {
         isPrincipalCompoundingDisabled.put("trapDoor", false);
         defaults.add(isPrincipalCompoundingDisabled);
 
+        HashMap<String, Object> isBusinessDateEnabled = new HashMap<>();
+        isBusinessDateEnabled.put("id", 44);
+        isBusinessDateEnabled.put("name", "enable_business_date");
+        isBusinessDateEnabled.put("value", 0);
+        isBusinessDateEnabled.put("enabled", true);
+        isBusinessDateEnabled.put("trapDoor", false);
+        defaults.add(isBusinessDateEnabled);
+
+        HashMap<String, Object> isAutomaticCOBDateAdjustmentEnabled = new HashMap<>();
+        isAutomaticCOBDateAdjustmentEnabled.put("id", 45);
+        isAutomaticCOBDateAdjustmentEnabled.put("name", "enable_automatic_cob_date_adjustment");
+        isAutomaticCOBDateAdjustmentEnabled.put("value", 0);
+        isAutomaticCOBDateAdjustmentEnabled.put("enabled", true);
+        isAutomaticCOBDateAdjustmentEnabled.put("trapDoor", false);
+        defaults.add(isAutomaticCOBDateAdjustmentEnabled);
+
         return defaults;
     }
 
@@ -530,4 +546,9 @@ public class GlobalConfigurationHelper {
         return new Gson().toJson(map);
     }
 
+    public static Integer updateIsBusinessDateEnabled(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final boolean enabled) {
+        long configId = 44;
+        return updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled);
+    }
 }