diff --git a/build.gradle b/build.gradle index 03a22692bf2..409e723c23b 100644 --- a/build.gradle +++ b/build.gradle @@ -117,7 +117,7 @@ allprojects { spotless { format 'misc', { target '**/*.md', '**/*.properties', '**/.gitignore', '**/.openapi-generator-ignore', '**/*.yml', '**/*.xml', '**/**.json', '**/*.sql' - targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat', '**/licenses/**', '**/banner.txt' + targetExclude '**/build/**', '**/bin/**', '**/.settings/**', '**/.idea/**', '**/.gradle/**', '**/gradlew.bat', '**/licenses/**', '**/banner.txt', '.vscode/**' indentWithSpaces(4) endWithNewline() trimTrailingWhitespace() diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index 93e48d63f4c..b39a3acc23f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -37,6 +37,9 @@ public final class DateUtils { + public static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final DateTimeFormatter DEFAULT_DATETIME_FORMATER = DateTimeFormatter.ofPattern(DEFAULT_DATETIME_FORMAT); + private DateUtils() { } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java index 41ceaec4d7a..91543500708 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java @@ -53,7 +53,6 @@ import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -71,7 +70,6 @@ public class DatatablesApiResource { private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final ToApiJsonSerializer toApiJsonSerializer; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DatatablesApiResource.class); @Autowired public DatatablesApiResource(final PlatformSecurityContext context, final GenericDataService genericDataService, @@ -259,8 +257,6 @@ public String getDatatable(@PathParam("datatable") @Parameter(description = "dat public String getDatatableManyEntry(@PathParam("datatable") final String datatable, @PathParam("apptableId") final Long apptableId, @PathParam("datatableId") final Long datatableId, @QueryParam("order") final String order, @Context final UriInfo uriInfo) { - LOG.debug("::1 we came in the getDatatbleManyEntry apiRessource method"); - this.context.authenticatedUser().validateHasDatatableReadPermission(datatable); final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java index c305c8cfc99..bbd8e12438b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java @@ -25,6 +25,7 @@ import java.util.List; import javax.sql.DataSource; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService; import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; @@ -32,8 +33,6 @@ import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnValueData; import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData; import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -47,7 +46,6 @@ public class GenericDataServiceImpl implements GenericDataService { private final JdbcTemplate jdbcTemplate; private final DataSource dataSource; private final DatabaseIndependentQueryService databaseIndependentQueryService; - private static final Logger LOG = LoggerFactory.getLogger(GenericDataServiceImpl.class); @Autowired public GenericDataServiceImpl(final RoutingDataSource dataSource, final JdbcTemplate jdbcTemplate, @@ -169,9 +167,10 @@ public String generateJsonFromGenericResultsetData(final GenericResultsetData gr writer.append( "[" + localDate.getYear() + ", " + localDate.getMonthValue() + ", " + localDate.getDayOfMonth() + "]"); } else if (currColType.equals("DATETIME")) { - final LocalDateTime localDateTime = LocalDateTime.parse(currVal); + final LocalDateTime localDateTime = LocalDateTime.parse(formatDateTimeValue(currVal), + DateUtils.DEFAULT_DATETIME_FORMATER); writer.append("[" + localDateTime.getYear() + ", " + localDateTime.getMonthValue() + ", " - + localDateTime.getDayOfMonth() + " " + localDateTime.getHour() + ", " + localDateTime.getMinute() + + localDateTime.getDayOfMonth() + ", " + localDateTime.getHour() + ", " + localDateTime.getMinute() + ", " + localDateTime.getSecond() + ", " + localDateTime.get(ChronoField.MILLI_OF_SECOND) + "]"); } else { writer.append(doubleQuote + replace(currVal, doubleQuote, slashDoubleQuote) + doubleQuote); @@ -262,4 +261,14 @@ private SqlRowSet getDatatableMetaData(final String datatable) { throw new DatatableNotFoundException(datatable); } } + + private String formatDateTimeValue(String dateTimeValue) { + if (dateTimeValue.length() > 19) { + dateTimeValue = dateTimeValue.substring(0, 19); + } + if (dateTimeValue.contains("T")) { + dateTimeValue = dateTimeValue.replace("T", " "); + } + return dateTimeValue; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 7db6c1c2009..de7353ef266 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -1710,7 +1710,8 @@ public Map getAffectedColumns(final List T performServerGet(final RequestSpecification requestSpec, fin return (T) JsonPath.from(json).get(jsonAttributeToGetBack); } + public static List performServerGetList(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String getURL, final String jsonAttributeToGetBack) { + final JsonPath jsonPath = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).jsonPath(); + List items = jsonPath.getList(jsonAttributeToGetBack); + return items; + } + + public static JsonElement performServerGetArray(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String getURL, final int position, final String jsonAttributeToGetBack) { + final JsonPath jsonPath = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).jsonPath(); + List> items = jsonPath.getList("$"); + return gson.fromJson(((ArrayList) items.get(position).get(jsonAttributeToGetBack)).toString(), JsonArray.class); + } + public static String performGetTextResponse(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String getURL) { return given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString(); @@ -251,6 +273,12 @@ public static String convertDateToURLFormat(final Calendar dateToBeConvert) { return dateFormat.format(dateToBeConvert.getTime()); } + public static String convertDateToURLFormat(final Calendar dateToBeConvert, final String dateGormat) { + DateFormat dateFormat = new SimpleDateFormat(dateGormat); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + return dateFormat.format(dateToBeConvert.getTime()); + } + public static LocalDate getLocalDateOfTenant() { LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()); final ZoneId zone = ZoneId.of(TENANT_TIME_ZONE); @@ -264,6 +292,24 @@ public static TimeZone getTimeZoneOfTenant() { return TimeZone.getTimeZone(TENANT_TIME_ZONE); } + public static Date convertJsonElementAsDate(JsonElement jsonElement) { + if (jsonElement.isJsonArray()) { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, jsonArray.get(0).getAsInt()); + calendar.set(Calendar.MONTH, jsonArray.get(1).getAsInt() - 1); + calendar.set(Calendar.DATE, jsonArray.get(2).getAsInt()); + // If the Array includes Time + if (jsonArray.size() > 3) { + calendar.set(Calendar.HOUR, jsonArray.get(3).getAsInt()); + calendar.set(Calendar.MINUTE, jsonArray.get(4).getAsInt()); + calendar.set(Calendar.SECOND, jsonArray.get(5).getAsInt()); + } + return calendar.getTime(); + } + return null; + } + public static String performServerTemplatePost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String postURL, final String legalFormType, final File file, final String locale, final String dateFormat) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java index ce9cac217f3..190416d348c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java @@ -81,6 +81,26 @@ public void setup() { this.datatableHelper = new DatatableHelper(this.requestSpec, this.responseSpec); } + @Test + public void validateCreateDeleteDatatableCheck() { + // creating datatable + String datatableName = this.datatableHelper.createDatatable(CLIENT_APP_TABLE_NAME, false); + DatatableHelper.verifyDatatableCreatedOnServer(this.requestSpec, this.responseSpec, datatableName); + + // creating new entity datatable check + Integer entityDatatableCheckId = this.entityDatatableChecksHelper.createEntityDatatableCheck(CLIENT_APP_TABLE_NAME, datatableName, + 100, null); + assertNotNull(entityDatatableCheckId, "ERROR IN CREATING THE ENTITY DATATABLE CHECK"); + + // deleting entity datatable check + entityDatatableCheckId = this.entityDatatableChecksHelper.deleteEntityDatatableCheck(entityDatatableCheckId); + assertNotNull(entityDatatableCheckId, "ERROR IN DELETING THE ENTITY DATATABLE CHECK"); + + // deleting the datatable + String deletedDataTableName = this.datatableHelper.deleteDatatable(datatableName); + assertEquals(datatableName, deletedDataTableName, "ERROR IN DELETING THE DATATABLE"); + } + @Test public void validateCreateDeleteEntityDatatableCheck() { // creating datatable diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java index eb966d4048f..8ab185f31fb 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java @@ -21,9 +21,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.google.gson.Gson; +import com.google.gson.JsonElement; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.HashMap; import java.util.List; import org.apache.fineract.integrationtests.common.Utils; @@ -48,6 +51,33 @@ public String createDatatable(final String apptableName, final boolean multiRow) getTestDatatableAsJSON(apptableName, multiRow), "resourceIdentifier"); } + public Integer createDatatableEntry(final String apptableName, final String datatableName, final Integer apptableId, + final boolean genericResultSet, final String dateFormat) { + return Utils.performServerPost( + this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + "/" + apptableId + "?genericResultSet=" + + Boolean.toString(genericResultSet) + "&" + Utils.TENANT_IDENTIFIER, + getTestDatatableEntryAsJSON(dateFormat), "resourceId"); + } + + public String readDatatableEntry(final String datatableName, final Integer resourceId, final boolean genericResultset) { + return Utils.performServerGet(this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + "/" + resourceId + + "?genericResultSet=" + String.valueOf(genericResultset) + "&" + Utils.TENANT_IDENTIFIER); + } + + public List readDatatableEntry(final String datatableName, final Integer resourceId, final boolean genericResultset, + final String jsonAttributeToGetBack) { + return Utils.performServerGetList(this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + "/" + resourceId + + "?genericResultSet=" + String.valueOf(genericResultset) + "&" + Utils.TENANT_IDENTIFIER, jsonAttributeToGetBack); + } + + public Date readDatatableEntry(final String datatableName, final Integer resourceId, final boolean genericResultset, final int position, + final String jsonAttributeToGetBack) { + final JsonElement jsonElement = Utils.performServerGetArray(this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + + "/" + resourceId + "?genericResultSet=" + String.valueOf(genericResultset) + "&" + Utils.TENANT_IDENTIFIER, position, + jsonAttributeToGetBack); + return Utils.convertJsonElementAsDate(jsonElement); + } + public String deleteDatatable(final String datatableName) { return Utils.performServerDelete(this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + "?" + Utils.TENANT_IDENTIFIER, "resourceIdentifier"); @@ -84,6 +114,19 @@ public static String getTestDatatableAsJSON(final String apptableName, final boo return requestJsonString; } + public static String getTestDatatableEntryAsJSON(final String dateFormat) { + final HashMap map = new HashMap<>(); + map.put("Spouse Name", Utils.randomNameGenerator("Spouse_Name_", 5)); + map.put("Number of Dependents", Utils.randomNumberGenerator(1)); + map.put("Date of Approval", Utils.convertDateToURLFormat(Calendar.getInstance(), dateFormat)); + map.put("locale", "en"); + map.put("dateFormat", dateFormat); + + String requestJsonString = new Gson().toJson(map); + LOG.info("map : {}", requestJsonString); + return requestJsonString; + } + public static List> addDatatableColumns(List> datatableColumnsList, String columnName, String columnType, boolean isMandatory, Integer length) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableIntegrationTest.java new file mode 100644 index 00000000000..8dd165acdd6 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableIntegrationTest.java @@ -0,0 +1,87 @@ +/** + * 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.common.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.util.Date; +import java.util.List; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DatatableIntegrationTest { + + private RequestSpecification requestSpec; + private ResponseSpecification responseSpec; + private DatatableHelper datatableHelper; + + private static final String CLIENT_APP_TABLE_NAME = "m_client"; + + @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.datatableHelper = new DatatableHelper(this.requestSpec, this.responseSpec); + } + + @Test + public void validateCreateReadDeleteDatatable() { + // creating datatable for client entity + String datatableName = this.datatableHelper.createDatatable(CLIENT_APP_TABLE_NAME, false); + DatatableHelper.verifyDatatableCreatedOnServer(this.requestSpec, this.responseSpec, datatableName); + + // creating client with datatables + final Integer clientID = ClientHelper.createClientAsPerson(requestSpec, responseSpec); + + // creating new client datatable entry + final boolean genericResultSet = true; + Integer datatableResourceID = this.datatableHelper.createDatatableEntry(CLIENT_APP_TABLE_NAME, datatableName, clientID, + genericResultSet, "yyyy-MM-dd"); + assertNotNull(datatableResourceID, "ERROR IN CREATING THE ENTITY DATATABLE RECORD"); + + // Read the Datatable entry generated with genericResultSet in true (default) + final List items = this.datatableHelper.readDatatableEntry(datatableName, clientID, genericResultSet, "data"); + assertEquals(1, items.size()); + + // Read the Datatable entry generated with genericResultSet in false + final Date valueDate = this.datatableHelper.readDatatableEntry(datatableName, clientID, !genericResultSet, 0, "Date of Approval"); + assertNotNull(valueDate, "ERROR IN GETTING THE DATE VALUE FROM DATATABLE RECORD"); + assertInstanceOf(Date.class, valueDate); + + // deleting datatable entries + Integer appTableId = this.datatableHelper.deleteDatatableEntries(datatableName, clientID, "clientId"); + assertEquals(clientID, appTableId, "ERROR IN DELETING THE DATATABLE ENTRIES"); + + // deleting the datatable + String deletedDataTableName = this.datatableHelper.deleteDatatable(datatableName); + assertEquals(datatableName, deletedDataTableName, "ERROR IN DELETING THE DATATABLE"); + } + +}