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

taskain 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 b27653c05 [FINERACT-1965] Change date resolvement from SMART to STRICT
b27653c05 is described below

commit b27653c055d185997a85133799de6208aa4d0404
Author: taskain7 <[email protected]>
AuthorDate: Tue Aug 1 05:56:24 2023 +0200

    [FINERACT-1965] Change date resolvement from SMART to STRICT
---
 .../infrastructure/core}/api/DateParam.java        |   7 +-
 .../infrastructure/core/api/JsonCommand.java       |   4 -
 .../infrastructure/core/data/DateFormat.java       |  62 ++++++
 .../core/serialization/JsonParserHelper.java       |  51 +++--
 .../api/JournalEntriesApiResource.java             |   6 +-
 .../campaigns/email/api/EmailApiResource.java      |   9 +-
 .../infrastructure/sms/api/SmsApiResource.java     |   8 +-
 .../fineract/interoperation/util/InteropUtil.java  |   2 +-
 .../holiday/api/HolidaysApiResource.java           |   7 +-
 .../api/StandingInstructionHistoryApiResource.java |   8 +-
 .../portfolio/group/api/CentersApiResource.java    |   5 +-
 .../api/LoanTransactionsApiResource.java           |  14 +-
 .../savings/data/DepositAccountDataValidator.java  |   4 -
 .../AccountingScenarioIntegrationTest.java         |   2 +-
 .../integrationtests/DateValidationTest.java       | 229 +++++++++++++++++++++
 .../integrationtests/FixedDepositTest.java         |   9 +-
 .../integrationtests/SchedulerJobsTestResults.java |   2 +-
 .../fixeddeposit/FixedDepositAccountHelper.java    |  16 +-
 .../interoperation/InteropHelper.java              |   9 +
 19 files changed, 403 insertions(+), 51 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/DateParam.java
similarity index 85%
rename from 
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/DateParam.java
index c4107ca6c..b801f041c 100755
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/DateParam.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/DateParam.java
@@ -16,17 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.accounting.journalentry.api;
+package org.apache.fineract.infrastructure.core.api;
 
 import jakarta.ws.rs.WebApplicationException;
 import java.time.LocalDate;
 import java.util.Locale;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
 
 /**
  * Class for parsing dates sent as query parameters
- *
- * TODO: Vishwas Should move this class to a more generic package
  */
 public class DateParam {
 
@@ -36,7 +35,7 @@ public class DateParam {
         this.dateAsString = dateStr;
     }
 
-    public LocalDate getDate(final String parameterName, final String 
dateFormat, final String localeAsString) {
+    public LocalDate getDate(final String parameterName, final DateFormat 
dateFormat, final String localeAsString) {
         final Locale locale = 
JsonParserHelper.localeFromString(localeAsString);
         return JsonParserHelper.convertFrom(this.dateAsString, parameterName, 
dateFormat, locale);
     }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
index 32175c5f7..07c6f0023 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -418,10 +418,6 @@ public final class JsonCommand {
         return this.fromApiJsonHelper.extractLocalTimeNamed(parameterName, 
this.parsedCommand);
     }
 
-    public LocalDateTime localDateTimeValueOfParameterNamed(final String 
parameterName) {
-        return this.fromApiJsonHelper.extractLocalDateTimeNamed(parameterName, 
this.parsedCommand);
-    }
-
     public MonthDay extractMonthDayNamed(final String parameterName) {
         return this.fromApiJsonHelper.extractMonthDayNamed(parameterName, 
this.parsedCommand);
     }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DateFormat.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DateFormat.java
new file mode 100644
index 000000000..af452c010
--- /dev/null
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DateFormat.java
@@ -0,0 +1,62 @@
+/**
+ * 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.core.data;
+
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+import java.time.temporal.ChronoField;
+import java.util.List;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+
+public class DateFormat {
+
+    @Getter
+    private final String dateFormat;
+
+    public DateFormat(String rawDateFormat) {
+        if (StringUtils.isBlank(rawDateFormat)) {
+            final ApiParameterError error = 
ApiParameterError.parameterError("validation.msg.invalid.dateFormat.format",
+                    "Dateformat is null", rawDateFormat);
+            throw new 
PlatformApiDataValidationException("validation.msg.invalid.dateFormat.format", 
"Validation errors exist.",
+                    List.of(error));
+        } else {
+            String compatibleDateFormat = rawDateFormat.replace("yyyy", 
"uuuu");
+            validate(compatibleDateFormat);
+            dateFormat = compatibleDateFormat;
+        }
+    }
+
+    private void validate(String dateTimeFormat) {
+        try {
+            DateTimeFormatter formatter = new 
DateTimeFormatterBuilder().parseCaseInsensitive().parseLenient().appendPattern(dateTimeFormat)
+                    .optionalStart().appendPattern(" 
HH:mm:ss").optionalEnd().parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
+                    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 
0).parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).toFormatter()
+                    .withResolverStyle(ResolverStyle.STRICT);
+        } catch (final IllegalArgumentException | DateTimeParseException e) {
+            final ApiParameterError error = 
ApiParameterError.parameterError("validation.msg.invalid.dateFormat.format",
+                    "Invalid dateFormat: `" + dateTimeFormat, dateTimeFormat);
+            throw new 
PlatformApiDataValidationException("validation.msg.invalid.dateFormat.format", 
"Validation errors exist.",
+                    List.of(error), e);
+        }
+    }
+}
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
index d93b55519..da47385bc 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -27,6 +27,7 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
 import java.text.ParseException;
+import java.time.DateTimeException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -34,14 +35,17 @@ import java.time.MonthDay;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
 import java.time.temporal.ChronoField;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.springframework.format.number.NumberStyleFormatter;
 
@@ -329,7 +333,7 @@ public class JsonParserHelper {
         return value;
     }
 
-    public MonthDay extractMonthDayNamed(final String parameterName, final 
JsonObject element, final String dateFormat,
+    public MonthDay extractMonthDayNamed(final String parameterName, final 
JsonObject element, String dateFormat,
             final Locale clientApplicationLocale) {
         MonthDay value = null;
         if (element.isJsonObject()) {
@@ -342,9 +346,9 @@ public class JsonParserHelper {
                 if (StringUtils.isNotBlank(valueAsString)) {
                     try {
                         final DateTimeFormatter formatter = new 
DateTimeFormatterBuilder().parseCaseInsensitive().parseLenient()
-                                
.appendPattern(dateFormat).toFormatter(clientApplicationLocale);
+                                
.appendPattern(dateFormat).toFormatter(clientApplicationLocale).withResolverStyle(ResolverStyle.STRICT);
                         value = MonthDay.parse(valueAsString, formatter);
-                    } catch (final IllegalArgumentException e) {
+                    } catch (final IllegalArgumentException | 
DateTimeException 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
@@ -396,7 +400,7 @@ public class JsonParserHelper {
 
         if (element.isJsonObject()) {
             final JsonObject object = element.getAsJsonObject();
-            value = extractLocalDateTimeNamed(parameterName, element, 
extractTimeFormatParameter(object), parametersPassedInCommand);
+            value = extractLocalDateTimeNamed(parameterName, element, 
extractDateFormatParameter(object), parametersPassedInCommand);
         }
         return value;
     }
@@ -437,11 +441,11 @@ public class JsonParserHelper {
                 parametersPassedInCommand.add(parameterName);
 
                 try {
-                    DateTimeFormatter timeFormtter = 
DateTimeFormatter.ofPattern(timeFormat);
+                    DateTimeFormatter timeFormatter = 
DateTimeFormatter.ofPattern(timeFormat);
                     final JsonPrimitive primitive = 
object.get(parameterName).getAsJsonPrimitive();
                     timeValueAsString = primitive.getAsString();
                     if (StringUtils.isNotBlank(timeValueAsString)) {
-                        value = LocalTime.parse(timeValueAsString, 
timeFormtter);
+                        value = LocalTime.parse(timeValueAsString, 
timeFormatter);
                     }
                 } catch (IllegalArgumentException e) {
                     final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
@@ -459,28 +463,31 @@ public class JsonParserHelper {
         return value;
     }
 
-    public LocalDateTime extractLocalDateTimeNamed(final String parameterName, 
final JsonElement element, final String timeFormat,
+    public LocalDateTime extractLocalDateTimeNamed(final String parameterName, 
final JsonElement element, String timeFormat,
             final Locale clientApplicationLocale, final Set<String> 
parametersPassedInCommand) {
         LocalDateTime value = null;
-        String timeValueAsString = null;
+        String timeValueAsString;
         if (element.isJsonObject()) {
             final JsonObject object = element.getAsJsonObject();
             if (object.has(parameterName) && 
object.get(parameterName).isJsonPrimitive()) {
                 parametersPassedInCommand.add(parameterName);
 
                 try {
-                    DateTimeFormatter timeFormtter = 
DateTimeFormatter.ofPattern(timeFormat);
+                    timeFormat = timeFormat.replace("y", "u");
                     final JsonPrimitive primitive = 
object.get(parameterName).getAsJsonPrimitive();
                     timeValueAsString = primitive.getAsString();
+                    DateTimeFormatter timeFormatter = new 
DateTimeFormatterBuilder().parseCaseInsensitive().parseLenient()
+                            
.appendPattern(timeFormat).toFormatter(clientApplicationLocale).withResolverStyle(ResolverStyle.STRICT);
                     if (StringUtils.isNotBlank(timeValueAsString)) {
-                        value = LocalDateTime.parse(timeValueAsString, 
timeFormtter);
+                        value = LocalDateTime.parse(timeValueAsString, 
timeFormatter);
                     }
-                } catch (IllegalArgumentException e) {
+                } catch (IllegalArgumentException | DateTimeParseException e) {
                     final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
-                    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);
+                    final ApiParameterError error = ApiParameterError
+                            
.parameterError("validation.msg.invalid.dateFormat.format",
+                                    "The parameter `" + parameterName + "` is 
invalid based on the dateFormat: `" + timeFormat
+                                            + "` and locale: `" + 
clientApplicationLocale + "` provided:",
+                                    parameterName, value, timeFormat);
                     dataValidationErrors.add(error);
                     throw new 
PlatformApiDataValidationException("validation.msg.validation.errors.exist", 
"Validation errors exist.",
                             dataValidationErrors, e);
@@ -518,17 +525,27 @@ public class JsonParserHelper {
         return convertDateTimeFrom(dateAsString, parameterName, dateFormat, 
clientApplicationLocale).toLocalDate();
     }
 
-    public static LocalDateTime convertDateTimeFrom(final String 
dateTimeAsString, final String parameterName, final String dateTimeFormat,
+    public static LocalDate convertFrom(final String dateAsString, final 
String parameterName, final DateFormat dateFormat,
+            final Locale clientApplicationLocale) {
+
+        String rawDateFormat = Objects.isNull(dateFormat) ? null : 
dateFormat.getDateFormat();
+
+        return convertDateTimeFrom(dateAsString, parameterName, rawDateFormat, 
clientApplicationLocale).toLocalDate();
+    }
+
+    public static LocalDateTime convertDateTimeFrom(final String 
dateTimeAsString, final String parameterName, String dateTimeFormat,
             final Locale clientApplicationLocale) {
 
         validateDateFormatAndLocale(parameterName, dateTimeFormat, 
clientApplicationLocale);
         LocalDateTime eventLocalDateTime = null;
         if (StringUtils.isNotBlank(dateTimeAsString)) {
             try {
+                dateTimeFormat = dateTimeFormat.replace("y", "u");
                 DateTimeFormatter formatter = new 
DateTimeFormatterBuilder().parseCaseInsensitive().parseLenient()
                         
.appendPattern(dateTimeFormat).optionalStart().appendPattern(" 
HH:mm:ss").optionalEnd()
                         .parseDefaulting(ChronoField.HOUR_OF_DAY, 
0).parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
-                        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 
0).toFormatter(clientApplicationLocale);
+                        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 
0).toFormatter(clientApplicationLocale)
+                        .withResolverStyle(ResolverStyle.STRICT);
                 eventLocalDateTime = LocalDateTime.parse(dateTimeAsString, 
formatter);
             } catch (final IllegalArgumentException | DateTimeParseException 
e) {
                 final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
index c5fc810ab..c107c8a60 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java
@@ -57,7 +57,9 @@ import 
org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
 import 
org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService;
 import 
org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import org.apache.fineract.infrastructure.core.data.UploadRequest;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
@@ -117,7 +119,7 @@ public class JournalEntriesApiResource {
             @QueryParam("orderBy") @Parameter(description = "orderBy") final 
String orderBy,
             @QueryParam("sortOrder") @Parameter(description = "sortOrder") 
final String sortOrder,
             @QueryParam("locale") @Parameter(description = "locale") final 
String locale,
-            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String dateFormat,
+            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String rawDateFormat,
             @QueryParam("loanId") @Parameter(description = "loanId") final 
Long loanId,
             @QueryParam("savingsId") @Parameter(description = "savingsId") 
final Long savingsId,
             @QueryParam("runningBalance") @Parameter(description = 
"runningBalance") final boolean runningBalance,
@@ -125,6 +127,8 @@ public class JournalEntriesApiResource {
 
         
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         LocalDate fromDate = null;
         if (fromDateParam != null) {
             fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
index 85b33c6d6..50e90b12d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
@@ -33,14 +33,16 @@ import jakarta.ws.rs.core.UriInfo;
 import java.time.LocalDate;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.commons.lang3.StringUtils;
 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.campaigns.email.data.EmailData;
 import 
org.apache.fineract.infrastructure.campaigns.email.service.EmailReadPlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.Page;
@@ -104,7 +106,10 @@ public class EmailApiResource {
             @QueryParam("limit") final Integer limit, @QueryParam("status") 
final Integer status,
             @QueryParam("orderBy") final String orderBy, 
@QueryParam("sortOrder") final String sortOrder,
             @QueryParam("fromDate") final DateParam fromDateParam, 
@QueryParam("toDate") final DateParam toDateParam,
-            @QueryParam("locale") final String locale, 
@QueryParam("dateFormat") final String dateFormat, @Context final UriInfo 
uriInfo) {
+            @QueryParam("locale") final String locale, 
@QueryParam("dateFormat") final String rawDateFormat,
+            @Context final UriInfo uriInfo) {
+
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
 
         
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
         LocalDate fromDate = null;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
index 4e191ec1d..e6a7bc455 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
@@ -34,12 +34,14 @@ import jakarta.ws.rs.core.UriInfo;
 import java.time.LocalDate;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.Page;
@@ -93,12 +95,14 @@ public class SmsApiResource {
     public String retrieveAllSmsByStatus(@PathParam("campaignId") final Long 
campaignId, @Context final UriInfo uriInfo,
             @QueryParam("status") final Long status, @QueryParam("fromDate") 
final DateParam fromDateParam,
             @QueryParam("toDate") final DateParam toDateParam, 
@QueryParam("locale") final String locale,
-            @QueryParam("dateFormat") final String dateFormat, 
@QueryParam("sqlSearch") final String sqlSearch,
+            @QueryParam("dateFormat") final String rawDateFormat, 
@QueryParam("sqlSearch") final String sqlSearch,
             @QueryParam("offset") final Integer offset, @QueryParam("limit") 
final Integer limit,
             @QueryParam("orderBy") final String orderBy, 
@QueryParam("sortOrder") final String sortOrder) {
         
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
         final SearchParameters searchParameters = 
SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         LocalDate fromDate = null;
         if (fromDateParam != null) {
             fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
index d93c24ec2..e120ac7b5 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
@@ -26,7 +26,7 @@ public final class InteropUtil {
 
     }
 
-    public static final String ISO8601_DATE_TIME_FORMAT = 
"yyyy-MM-ddTHH:mm:ss.SSS[-HH:MM]";
+    public static final String ISO8601_DATE_TIME_FORMAT = 
"yyyy-MM-dd'T'HH:mm:ss.SSS[-HH:MM]";
     public static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd";
 
     public static final Locale DEFAULT_LOCALE = Locale.US;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
index ddac2519d..916bf63aa 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
@@ -46,12 +46,13 @@ import java.time.LocalDate;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
@@ -192,10 +193,12 @@ public class HolidaysApiResource {
             @QueryParam("fromDate") @Parameter(description = "fromDate") final 
DateParam fromDateParam,
             @QueryParam("toDate") @Parameter(description = "toDate") final 
DateParam toDateParam,
             @QueryParam("locale") @Parameter(description = "locale") final 
String locale,
-            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String dateFormat) {
+            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String rawDateFormat) {
 
         
this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         LocalDate fromDate = null;
         if (fromDateParam != null) {
             fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
index 4648830f1..ced9d6390 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java
@@ -35,8 +35,10 @@ import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.UriInfo;
 import java.time.LocalDate;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.Page;
@@ -79,12 +81,14 @@ public class StandingInstructionHistoryApiResource {
             @QueryParam("fromAccountId") @Parameter(description = 
"fromAccountId") final Long fromAccount,
             @QueryParam("fromAccountType") @Parameter(description = 
"fromAccountType") final Integer fromAccountType,
             @QueryParam("locale") @Parameter(description = "locale") final 
String locale,
-            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String dateFormat,
+            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String rawDateFormat,
             @QueryParam("fromDate") @Parameter(description = "fromDate") final 
DateParam fromDateParam,
             @QueryParam("toDate") @Parameter(description = "toDate") final 
DateParam toDateParam) {
 
         
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         final SearchParameters searchParameters = 
SearchParameters.forAccountTransfer(sqlSearch, externalId, offset, limit, 
orderBy,
                 sortOrder);
         LocalDate startDateRange = null;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
index 0b491d5df..35c273474 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java
@@ -50,7 +50,6 @@ import java.util.List;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -59,8 +58,10 @@ import 
org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookP
 import 
org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService;
 import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.api.JsonQuery;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import org.apache.fineract.infrastructure.core.data.PaginationParameters;
 import org.apache.fineract.infrastructure.core.data.UploadRequest;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
@@ -170,7 +171,7 @@ public class CentersApiResource {
         
this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         if (meetingDateParam != null && officeId != null) {
-            LocalDate meetingDate = meetingDateParam.getDate("meetingDate", 
dateFormat, locale);
+            LocalDate meetingDate = meetingDateParam.getDate("meetingDate", 
new DateFormat(dateFormat), locale);
             Collection<StaffCenterData> staffCenterDataArray = 
this.centerReadPlatformService.retriveAllCentersByMeetingDate(officeId,
                     meetingDate, staffId);
             return this.toApiJsonSerializer.serialize(settings, 
staffCenterDataArray,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 1fd74e59b..c1425e938 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -45,12 +45,14 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import lombok.AllArgsConstructor;
-import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.DateParam;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.DateFormat;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
@@ -111,10 +113,12 @@ public class LoanTransactionsApiResource {
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class)))
 })
     public String retrieveTransactionTemplate(@PathParam("loanId") 
@Parameter(description = "loanId", required = true) final Long loanId,
             @QueryParam("command") @Parameter(description = "command") final 
String commandParam, @Context final UriInfo uriInfo,
-            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String dateFormat,
+            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String rawDateFormat,
             @QueryParam("transactionDate") @Parameter(description = 
"transactionDate") final DateParam transactionDateParam,
             @QueryParam("locale") @Parameter(description = "locale") final 
String locale) {
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         return retrieveTransactionTemplate(loanId, null, commandParam, 
uriInfo, dateFormat, transactionDateParam, locale);
     }
 
@@ -139,10 +143,12 @@ public class LoanTransactionsApiResource {
     public String retrieveTransactionTemplate(
             @PathParam("loanExternalId") @Parameter(description = 
"loanExternalId", required = true) final String loanExternalId,
             @QueryParam("command") @Parameter(description = "command") final 
String commandParam, @Context final UriInfo uriInfo,
-            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String dateFormat,
+            @QueryParam("dateFormat") @Parameter(description = "dateFormat") 
final String rawDateFormat,
             @QueryParam("transactionDate") @Parameter(description = 
"transactionDate") final DateParam transactionDateParam,
             @QueryParam("locale") @Parameter(description = "locale") final 
String locale) {
 
+        final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? 
null : new DateFormat(rawDateFormat);
+
         return retrieveTransactionTemplate(null, loanExternalId, commandParam, 
uriInfo, dateFormat, transactionDateParam, locale);
     }
 
@@ -476,7 +482,7 @@ public class LoanTransactionsApiResource {
     }
 
     private String retrieveTransactionTemplate(Long loanId, String 
loanExternalIdStr, String commandParam, UriInfo uriInfo,
-            String dateFormat, DateParam transactionDateParam, String locale) {
+            DateFormat dateFormat, DateParam transactionDateParam, String 
locale) {
         
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
 
         ExternalId loanExternalId = 
ExternalIdFactory.produce(loanExternalIdStr);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
index 5603a1526..a28eab439 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
@@ -720,10 +720,6 @@ public class DepositAccountDataValidator {
 
                     final JsonObject savingsChargeElement = 
array.get(i).getAsJsonObject();
 
-                    // final Long id =
-                    // this.fromApiJsonHelper.extractLongNamed(idParamName,
-                    // savingsChargeElement);
-
                     final Long chargeId = 
this.fromApiJsonHelper.extractLongNamed(chargeIdParamName, 
savingsChargeElement);
                     
baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).longGreaterThanZero();
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
index f9f72b8cf..db4dfe687 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
@@ -544,7 +544,7 @@ public class AccountingScenarioIntegrationTest {
         LOG.info("--------------------------------APPLYING FOR FIXED DEPOSIT 
ACCOUNT --------------------------------");
         final String fixedDepositApplicationJSON = new 
FixedDepositAccountHelper(requestSpec, responseSpec) //
                 .withSubmittedOnDate(submittedOnDate).build(clientID, 
productID, penalInterestType);
-        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 requestSpec, responseSpec);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplicationGetId(fixedDepositApplicationJSON,
 requestSpec, responseSpec);
     }
 
     private Integer createRecurringDepositProduct(final String validFrom, 
final String validTo, Account... accounts) {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DateValidationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DateValidationTest.java
new file mode 100644
index 000000000..57a5270fd
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DateValidationTest.java
@@ -0,0 +1,229 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+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.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.PostClientsRequest;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import 
org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositAccountHelper;
+import 
org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProductHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.interoperation.InteropHelper;
+import org.apache.fineract.interoperation.domain.InteropInitiatorType;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.apache.fineract.interoperation.domain.InteropTransactionScenario;
+import org.apache.fineract.interoperation.util.InteropUtil;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class DateValidationTest {
+
+    public static final String WHOLE_TERM = "1";
+    public static final String MINIMUM_OPENING_BALANCE = "1000.0";
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification errorResponseSpec;
+    private RequestSpecification requestSpec;
+    private ClientHelper clientHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private InteropHelper interopHelper;
+    private AccountHelper accountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.errorResponseSpec = new 
ResponseSpecBuilder().expectStatusCode(400).build();
+        this.clientHelper = new ClientHelper(this.requestSpec, 
this.responseSpec);
+        this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+        this.interopHelper = new InteropHelper(requestSpec, errorResponseSpec);
+        this.accountHelper = new AccountHelper(requestSpec, responseSpec);
+    }
+
+    @Test
+    public void testShouldFailIfDateIsInvalid() {
+
+        String invalidDate = "31 June 2022";
+
+        PostClientsRequest postClientsRequest = 
ClientHelper.defaultClientCreationRequest();
+
+        PostClientsResponse client = 
clientHelper.createClient(postClientsRequest);
+        Long clientId = client.getClientId();
+
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
+                
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
+                
.withAccountingRuleAsNone().withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30")
+                .withDaysInYear("365").withMoratorium("0", 
"0").withInArrearsTolerance("1001").withMultiDisburse()
+                .withDisallowExpectedDisbursements(true).build(null);
+        final Integer loanProductID = 
loanTransactionHelper.getLoanProductId(loanProductJSON);
+
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
errorResponseSpec);
+
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
+                
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
+                
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+                
.withExpectedDisbursementDate(invalidDate).withSubmittedOnDate("01 March 
2022").withLoanType("individual")
+                .build(clientId.toString(), loanProductID.toString(), null);
+        HashMap<String, Object> response = (HashMap) 
loanTransactionHelper.createLoanAccount(loanApplicationJSON, "");
+        List<HashMap<String, Object>> errors = (List) response.get("errors");
+        assertNotNull(errors);
+        HashMap<String, Object> error = errors.get(0);
+        assertNotNull(error);
+        assertEquals(
+                "The parameter `expectedDisbursementDate` is invalid based on 
the dateFormat: `dd MMMM uuuu` and locale: `en_GB` provided:",
+                error.get("developerMessage"));
+    }
+
+    @Test
+    public void testShouldFailWithInvalidDateTime() {
+        String requestCode = UUID.randomUUID().toString();
+        InteropTransactionRole role = InteropTransactionRole.PAYER;
+        String requestBody = buildRequestBody(requestCode, role);
+        String response = interopHelper.postTransactionRequest(requestCode, 
role, requestBody);
+        HashMap<String, Object> map = new Gson().fromJson(response, new 
TypeToken<HashMap<String, Object>>() {}.getType());
+        List<Map<String, Object>> errors = (List) map.get("errors");
+        assertNotNull(errors);
+        Map<String, Object> error = errors.get(0);
+        assertNotNull(error);
+        assertEquals("The parameter `expiration` is invalid based on the 
dateFormat: `dd MMMM uuuu HH:mm:ss` and locale: `en` provided:",
+                error.get("developerMessage"));
+    }
+
+    @Test
+    public void testShouldFailWithInvalidMonthDay() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+        final Account liabilityAccount = 
this.accountHelper.createLiabilityAccount();
+
+        DateTimeFormatter formatter = new 
DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(Locale.US);
+
+        LocalDate todaysDate = 
LocalDate.now(ZoneId.systemDefault()).minusMonths(3);
+        final String VALID_FROM = todaysDate.format(formatter);
+        todaysDate = todaysDate.plusYears(10);
+        final String VALID_TO = todaysDate.format(formatter);
+
+        todaysDate = LocalDate.now(ZoneId.systemDefault()).minusMonths(1);
+        final String SUBMITTED_ON_DATE = todaysDate.format(formatter);
+
+        PostClientsRequest postClientsRequest = 
ClientHelper.defaultClientCreationRequest();
+
+        Long clientId = 
clientHelper.createClient(postClientsRequest).getClientId();
+        Assertions.assertNotNull(clientId);
+
+        Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, 
VALID_TO, assetAccount, liabilityAccount, incomeAccount,
+                expenseAccount);
+        Assertions.assertNotNull(fixedDepositProductId);
+
+        final Integer maturityInstructionId = 400;
+        String response = applyForFixedDepositApplication(clientId.toString(), 
fixedDepositProductId.toString(), SUBMITTED_ON_DATE,
+                maturityInstructionId, getCharges());
+        HashMap<String, Object> map = new Gson().fromJson(response, new 
TypeToken<HashMap<String, Object>>() {}.getType());
+        List<Map<String, Object>> errors = (List) map.get("errors");
+        assertNotNull(errors);
+        Map<String, Object> error = errors.get(0);
+        assertNotNull(error);
+        assertEquals("The parameter `feeOnMonthDay` is invalid based on the 
monthDayFormat: `dd MMM` and locale: `en_GB` provided:",
+                error.get("developerMessage"));
+    }
+
+    private String buildRequestBody(final String requestCode, final 
InteropTransactionRole role) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.put(InteropUtil.PARAM_TRANSACTION_CODE, 
UUID.randomUUID().toString());
+        map.put(InteropUtil.PARAM_REQUEST_CODE, requestCode);
+        map.put(InteropUtil.PARAM_ACCOUNT_ID, UUID.randomUUID().toString());
+        map.put(InteropUtil.PARAM_TRANSACTION_ROLE, role);
+        map.put(InteropUtil.PARAM_EXPIRATION, "31 November 2022 11:11:11");
+        map.put(InteropUtil.PARAM_LOCALE, "en");
+        map.put(InteropUtil.PARAM_DATE_FORMAT, "dd MMMM yyyy HH:mm:ss");
+
+        HashMap<String, Object> amountMap = new HashMap<>();
+        amountMap.put(InteropUtil.PARAM_AMOUNT, "10");
+        amountMap.put(InteropUtil.PARAM_CURRENCY, "EUR");
+        map.put(InteropUtil.PARAM_AMOUNT, amountMap);
+
+        HashMap<String, Object> typeMap = new HashMap<>();
+        typeMap.put(InteropUtil.PARAM_SCENARIO, 
InteropTransactionScenario.PAYMENT);
+        typeMap.put(InteropUtil.PARAM_INITIATOR, InteropTransactionRole.PAYEE);
+        typeMap.put(InteropUtil.PARAM_INITIATOR_TYPE, 
InteropInitiatorType.CONSUMER);
+        map.put(InteropUtil.PARAM_TRANSACTION_TYPE, typeMap);
+
+        return new Gson().toJson(map);
+    }
+
+    private Integer createFixedDepositProduct(final String validFrom, final 
String validTo, Account... accounts) {
+        log.info("------------------------------CREATING NEW FIXED DEPOSIT 
PRODUCT ---------------------------------------");
+        FixedDepositProductHelper fixedDepositProductHelper = new 
FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+        fixedDepositProductHelper = 
fixedDepositProductHelper.withAccountingRuleAsCashBased(accounts);
+        final String fixedDepositProductJSON = 
fixedDepositProductHelper.withPeriodRangeChart() //
+                .build(validFrom, validTo, true);
+        return 
FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, 
requestSpec, responseSpec);
+    }
+
+    private String applyForFixedDepositApplication(final String clientID, 
final String productID, final String submittedOnDate,
+            final Integer maturityInstructionId, final List<HashMap<String, 
String>> charges) {
+        log.info("--------------------------------APPLYING FOR FIXED DEPOSIT 
ACCOUNT --------------------------------");
+        final String fixedDepositApplicationJSON = new 
FixedDepositAccountHelper(this.requestSpec, this.errorResponseSpec) //
+                
.withSubmittedOnDate(submittedOnDate).withMaturityInstructionId(maturityInstructionId).withCharges(charges)
+                .build(clientID, productID, WHOLE_TERM);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 this.requestSpec,
+                this.errorResponseSpec);
+    }
+
+    private List<HashMap<String, String>> getCharges() {
+        List<HashMap<String, String>> list = new ArrayList<>();
+        HashMap<String, String> map = new HashMap<>();
+        map.put("feeOnMonthDay", "31 June");
+        list.add(map);
+        return list;
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
index 5da091e8c..69741dcb5 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
@@ -2584,7 +2584,8 @@ public class FixedDepositTest {
         log.info("--------------------------------APPLYING FOR FIXED DEPOSIT 
ACCOUNT --------------------------------");
         final String fixedDepositApplicationJSON = new 
FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
                 .withSubmittedOnDate(submittedOnDate).build(clientID, 
productID, penalInterestType);
-        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 this.requestSpec, this.responseSpec);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplicationGetId(fixedDepositApplicationJSON,
 this.requestSpec,
+                this.responseSpec);
     }
 
     private Integer applyForFixedDepositApplication(final String clientID, 
final String productID, final String submittedOnDate,
@@ -2593,7 +2594,8 @@ public class FixedDepositTest {
         final String fixedDepositApplicationJSON = new 
FixedDepositAccountHelper(this.requestSpec, this.responseSpec) //
                 
.withSubmittedOnDate(submittedOnDate).withMaturityInstructionId(maturityInstructionId)
                 .build(clientID, productID, penalInterestType);
-        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 this.requestSpec, this.responseSpec);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplicationGetId(fixedDepositApplicationJSON,
 this.requestSpec,
+                this.responseSpec);
     }
 
     private Integer applyForFixedDepositApplication(final String clientID, 
final String productID, final String submittedOnDate,
@@ -2603,7 +2605,8 @@ public class FixedDepositTest {
                 //
                 
.withSubmittedOnDate(submittedOnDate).withDepositPeriod(depositPeriod).withDepositAmount(depositAmount)
                 .build(clientID, productID, penalInterestType);
-        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 this.requestSpec, this.responseSpec);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplicationGetId(fixedDepositApplicationJSON,
 this.requestSpec,
+                this.responseSpec);
     }
 
     private Integer createSavingsProduct(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
index c6c7e3500..445437797 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
@@ -1288,7 +1288,7 @@ public class SchedulerJobsTestResults {
         final String fixedDepositApplicationJSON = new 
FixedDepositAccountHelper(requestSpec, responseSpec)
                 
.withSubmittedOnDate(submittedOnDate).withSavings(savingsId).transferInterest(true)
                 .withLockinPeriodFrequency("1", 
FixedDepositAccountHelper.DAYS).build(clientID, productID, penalInterestType);
-        return 
FixedDepositAccountHelper.applyFixedDepositApplication(fixedDepositApplicationJSON,
 requestSpec, responseSpec);
+        return 
FixedDepositAccountHelper.applyFixedDepositApplicationGetId(fixedDepositApplicationJSON,
 requestSpec, responseSpec);
     }
 
     private void validateNumberForEqualExcludePrecision(String val, String 
val2) {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
index 9021b5d73..67860327b 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
@@ -24,6 +24,7 @@ import io.restassured.specification.ResponseSpecification;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.HashMap;
+import java.util.List;
 import org.apache.fineract.integrationtests.common.CommonConstants;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.slf4j.Logger;
@@ -97,6 +98,7 @@ public class FixedDepositAccountHelper {
     private String savingsId = null;
     private boolean transferInterest = false;
     private Integer maturityInstructionId;
+    private List<HashMap<String, String>> charges;
 
     public String build(final String clientId, final String productId, final 
String penalInterestType) {
         final HashMap<String, Object> map = new HashMap<>();
@@ -129,19 +131,26 @@ public class FixedDepositAccountHelper {
         map.put("linkAccountId", savingsId);
         map.put("transferInterestToSavings", transferInterest);
         map.put("maturityInstructionId", maturityInstructionId);
+        map.put("charges", charges);
 
         String fixedDepositAccountJson = new Gson().toJson(map);
         LOG.info("{}", fixedDepositAccountJson);
         return fixedDepositAccountJson;
     }
 
-    public static Integer applyFixedDepositApplication(final String 
fixedDepositAccountAsJson, final RequestSpecification requestSpec,
+    public static Integer applyFixedDepositApplicationGetId(final String 
fixedDepositAccountAsJson, final RequestSpecification requestSpec,
             final ResponseSpecification responseSpec) {
         LOG.info("--------------------- APPLYING FOR FIXED DEPOSIT ACCOUNT 
------------------------");
         return Utils.performServerPost(requestSpec, responseSpec, 
APPLY_FIXED_DEPOSIT_ACCOUNT_URL, fixedDepositAccountAsJson,
                 CommonConstants.RESPONSE_RESOURCE_ID);
     }
 
+    public static String applyFixedDepositApplication(final String 
fixedDepositAccountAsJson, final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        LOG.info("--------------------- APPLYING FOR FIXED DEPOSIT ACCOUNT 
------------------------");
+        return Utils.performServerPost(requestSpec, responseSpec, 
APPLY_FIXED_DEPOSIT_ACCOUNT_URL, fixedDepositAccountAsJson);
+    }
+
     public static HashMap getFixedDepositAccountById(final 
RequestSpecification requestSpec, final ResponseSpecification responseSpec,
             final Integer accountID) {
         final String GET_FIXED_DEPOSIT_BY_ID_URL = FIXED_DEPOSIT_ACCOUNT_URL + 
"/" + accountID + "?" + Utils.TENANT_IDENTIFIER;
@@ -475,4 +484,9 @@ public class FixedDepositAccountHelper {
         this.maturityInstructionId = maturityInstructionId;
         return this;
     }
+
+    public FixedDepositAccountHelper withCharges(List<HashMap<String, String>> 
charges) {
+        this.charges = charges;
+        return this;
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/interoperation/InteropHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/interoperation/InteropHelper.java
index 0f4103e01..22a36c394 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/interoperation/InteropHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/interoperation/InteropHelper.java
@@ -214,6 +214,15 @@ public class InteropHelper {
         return response;
     }
 
+    public String postTransactionRequest(String requestCode, 
InteropTransactionRole role, String request) {
+        String url = buildUrl(REQUESTS_URL);
+        LOG.debug("Calling Interoperable POST Request: {}, body: {}", url, 
request);
+
+        String response = Utils.performServerPost(requestSpec, responseSpec, 
url, request, null);
+        LOG.debug("Response Interoperable POST Request: {}", response);
+        return response;
+    }
+
     private String buildTransactionRequestJson(String requestCode, 
InteropTransactionRole role) {
         HashMap<String, Object> map = new HashMap<>();
         map.put(InteropUtil.PARAM_TRANSACTION_CODE, transactionCode);

Reply via email to