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 629b9f6d7 FINERACT-1670: Rework command source to store dates in UTC
629b9f6d7 is described below

commit 629b9f6d7c88c9c036a532630ff2cda28f7f8aff
Author: Adam Saghy <[email protected]>
AuthorDate: Thu Aug 11 15:59:07 2022 +0200

    FINERACT-1670: Rework command source to store dates in UTC
---
 .../fineract/commands/domain/CommandSource.java    | 35 +++++++++------
 .../service/AuditReadPlatformServiceImpl.java      | 14 ++++--
 ...folioCommandSourceWritePlatformServiceImpl.java |  4 +-
 .../SynchronousCommandProcessingService.java       | 20 ++++-----
 .../infrastructure/core/domain/JdbcSupport.java    |  2 +-
 .../db/changelog/tenant/changelog-tenant.xml       |  1 +
 ...s_and_rework_command_source_datetime_fields.xml | 52 ++++++++++++++++++++++
 7 files changed, 95 insertions(+), 33 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
index 75a368eb8..bfd66e18a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java
@@ -18,8 +18,7 @@
  */
 package org.apache.fineract.commands.domain;
 
-import java.time.LocalDateTime;
-import java.time.ZonedDateTime;
+import java.time.OffsetDateTime;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.JoinColumn;
@@ -72,16 +71,24 @@ public class CommandSource extends 
AbstractPersistableCustom {
     @JoinColumn(name = "maker_id", nullable = false)
     private AppUser maker;
 
-    @Column(name = "made_on_date", nullable = false)
-    private LocalDateTime madeOnDate;
+    /*
+     * Deprecated: Columns and data left untouched to help migration.
+     *
+     * @Column(name = "made_on_date", nullable = false) private LocalDateTime 
madeOnDate;
+     *
+     * @Column(name = "checked_on_date", nullable = true) private 
LocalDateTime checkedOnDate;
+     */
+
+    @Column(name = "made_on_date_utc", nullable = false)
+    private OffsetDateTime madeOnDate;
+
+    @Column(name = "checked_on_date_utc")
+    private OffsetDateTime checkedOnDate;
 
     @ManyToOne
     @JoinColumn(name = "checker_id", nullable = true)
     private AppUser checker;
 
-    @Column(name = "checked_on_date", nullable = true)
-    private LocalDateTime checkedOnDate;
-
     @Column(name = "processing_result_enum", nullable = false)
     private Integer processingResult;
 
@@ -99,7 +106,7 @@ public class CommandSource extends AbstractPersistableCustom 
{
 
     public static CommandSource fullEntryFrom(final CommandWrapper wrapper, 
final JsonCommand command, final AppUser maker) {
         return new CommandSource(wrapper.actionName(), wrapper.entityName(), 
wrapper.getHref(), command.entityId(), command.subentityId(),
-                command.json(), maker, 
ZonedDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
+                command.json(), maker);
     }
 
     protected CommandSource() {
@@ -107,7 +114,7 @@ public class CommandSource extends 
AbstractPersistableCustom {
     }
 
     private CommandSource(final String actionName, final String entityName, 
final String href, final Long resourceId,
-            final Long subresourceId, final String commandSerializedAsJson, 
final AppUser maker, final ZonedDateTime madeOnDateTime) {
+            final Long subresourceId, final String commandSerializedAsJson, 
final AppUser maker) {
         this.actionName = actionName;
         this.entityName = entityName;
         this.resourceGetUrl = href;
@@ -115,7 +122,7 @@ public class CommandSource extends 
AbstractPersistableCustom {
         this.subresourceId = subresourceId;
         this.commandAsJson = commandSerializedAsJson;
         this.maker = maker;
-        this.madeOnDate = madeOnDateTime != null ? 
madeOnDateTime.toLocalDateTime() : null;
+        this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant();
         this.processingResult = 
CommandProcessingResultType.PROCESSED.getValue();
     }
 
@@ -135,15 +142,15 @@ public class CommandSource extends 
AbstractPersistableCustom {
         this.organisationCreditBureauId = organisationCreditBureauId;
     }
 
-    public void markAsChecked(final AppUser checker, final ZonedDateTime 
checkedOnDate) {
+    public void markAsChecked(final AppUser checker) {
         this.checker = checker;
-        this.checkedOnDate = checkedOnDate != null ? 
checkedOnDate.toLocalDateTime() : null;
+        this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
         this.processingResult = 
CommandProcessingResultType.PROCESSED.getValue();
     }
 
-    public void markAsRejected(final AppUser checker, final ZonedDateTime 
checkedOnDate) {
+    public void markAsRejected(final AppUser checker) {
         this.checker = checker;
-        this.checkedOnDate = checkedOnDate != null ? 
checkedOnDate.toLocalDateTime() : null;
+        this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
         this.processingResult = 
CommandProcessingResultType.REJECTED.getValue();
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
index 8aa3bf7a6..2f63ef1fe 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java
@@ -24,6 +24,7 @@ import com.google.gson.JsonObject;
 import java.lang.reflect.Type;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.time.OffsetDateTime;
 import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.Collection;
@@ -105,8 +106,8 @@ public class AuditReadPlatformServiceImpl implements 
AuditReadPlatformService {
 
             String partSql = " aud.id as id, aud.action_name as actionName, 
aud.entity_name as entityName,"
                     + " aud.resource_id as resourceId, aud.subresource_id as 
subresourceId,aud.client_id as clientId, aud.loan_id as loanId,"
-                    + " mk.username as maker, aud.made_on_date as madeOnDate, 
" + " aud.api_get_url as resourceGetUrl, "
-                    + "ck.username as checker, aud.checked_on_date as 
checkedOnDate, ev.enum_message_property as processingResult "
+                    + " mk.username as maker, aud.made_on_date as madeOnDate, 
aud.made_on_date_utc as madeOnDateUTC, aud.api_get_url as resourceGetUrl, "
+                    + "ck.username as checker, aud.checked_on_date as 
checkedOnDate, aud.checked_on_date_utc as checkedOnDateUTC,  
ev.enum_message_property as processingResult "
                     + commandAsJsonString + ", "
                     + " o.name as officeName, gl.level_name as groupLevelName, 
g.display_name as groupName, c.display_name as clientName, "
                     + " l.account_no as loanAccountNo, s.account_no as 
savingsAccountNo " + " from m_portfolio_command_source aud "
@@ -136,9 +137,11 @@ public class AuditReadPlatformServiceImpl implements 
AuditReadPlatformService {
             final Long loanId = JdbcSupport.getLong(rs, "loanId");
             final Long subresourceId = JdbcSupport.getLong(rs, 
"subresourceId");
             final String maker = rs.getString("maker");
-            final ZonedDateTime madeOnDate = JdbcSupport.getDateTime(rs, 
"madeOnDate");
+            final ZonedDateTime madeOnDateTenant = JdbcSupport.getDateTime(rs, 
"madeOnDate");
+            final OffsetDateTime madeOnDateUTC = 
JdbcSupport.getOffsetDateTime(rs, "madeOnDateUTC");
             final String checker = rs.getString("checker");
-            final ZonedDateTime checkedOnDate = JdbcSupport.getDateTime(rs, 
"checkedOnDate");
+            final ZonedDateTime checkedOnDateTenant = 
JdbcSupport.getDateTime(rs, "checkedOnDate");
+            final OffsetDateTime checkedOnDateUTC = 
JdbcSupport.getOffsetDateTime(rs, "checkedOnDateUTC");
             final String processingResult = rs.getString("processingResult");
             final String resourceGetUrl = rs.getString("resourceGetUrl");
             String commandAsJson;
@@ -156,6 +159,9 @@ public class AuditReadPlatformServiceImpl implements 
AuditReadPlatformService {
             final String loanAccountNo = rs.getString("loanAccountNo");
             final String savingsAccountNo = rs.getString("savingsAccountNo");
 
+            ZonedDateTime madeOnDate = madeOnDateUTC != null ? 
madeOnDateUTC.toZonedDateTime() : madeOnDateTenant;
+            ZonedDateTime checkedOnDate = checkedOnDateUTC != null ? 
checkedOnDateUTC.toZonedDateTime() : checkedOnDateTenant;
+
             return new AuditData(id, actionName, entityName, resourceId, 
subresourceId, maker, madeOnDate, checker, checkedOnDate,
                     processingResult, commandAsJson, officeName, 
groupLevelName, groupName, clientName, loanAccountNo, savingsAccountNo,
                     clientId, loanId, resourceGetUrl);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
index d6d3cd0a2..ad64791a1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
@@ -21,7 +21,6 @@ package org.apache.fineract.commands.service;
 import com.google.gson.JsonElement;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.security.SecureRandom;
-import java.time.ZonedDateTime;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.commands.domain.CommandSource;
@@ -33,7 +32,6 @@ import 
org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotA
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import 
org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -180,7 +178,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl 
implements Portfolio
         final CommandSource commandSourceInput = 
validateMakerCheckerTransaction(makerCheckerId);
         validateIsUpdateAllowed();
         final AppUser maker = this.context.authenticatedUser();
-        commandSourceInput.markAsRejected(maker, 
ZonedDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
+        commandSourceInput.markAsRejected(maker);
         this.commandSourceRepository.save(commandSourceInput);
         return makerCheckerId;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
index 016d7ac1f..90b911ac4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
@@ -22,7 +22,6 @@ import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import java.lang.reflect.Type;
 import java.time.Instant;
-import java.time.ZonedDateTime;
 import java.util.HashMap;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
@@ -32,6 +31,7 @@ import org.apache.fineract.batch.exception.ErrorInfo;
 import org.apache.fineract.commands.domain.CommandSource;
 import org.apache.fineract.commands.domain.CommandSourceRepository;
 import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.exception.CommandNotFoundException;
 import 
org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
 import org.apache.fineract.commands.exception.UnsupportedCommandException;
 import org.apache.fineract.commands.handler.NewCommandSourceHandler;
@@ -41,7 +41,6 @@ import 
org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import 
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.hooks.event.HookEvent;
 import org.apache.fineract.infrastructure.hooks.event.HookEventSource;
@@ -84,10 +83,11 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
 
         final AppUser maker = this.context.authenticatedUser(wrapper);
 
-        CommandSource commandSourceResult = null;
+        CommandSource commandSourceResult;
         if (command.commandId() != null) {
-            commandSourceResult = 
this.commandSourceRepository.findById(command.commandId()).orElse(null);
-            commandSourceResult.markAsChecked(maker, 
ZonedDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
+            commandSourceResult = 
this.commandSourceRepository.findById(command.commandId())
+                    .orElseThrow(() -> new 
CommandNotFoundException(command.commandId()));
+            commandSourceResult.markAsChecked(maker);
         } else {
             commandSourceResult = CommandSource.fullEntryFrom(wrapper, 
command, maker);
         }
@@ -95,7 +95,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
         commandSourceResult.updateForAudit(result.getOfficeId(), 
result.getGroupId(), result.getClientId(), result.getLoanId(),
                 result.getSavingsId(), result.getProductId(), 
result.getTransactionId());
 
-        String changesOnlyJson = null;
+        String changesOnlyJson;
         boolean rollBack = (rollbackTransaction || 
result.isRollbackTransaction()) && !isApprovedByChecker;
         if (result.hasChanges() && !rollBack) {
             changesOnlyJson = 
this.toApiJsonSerializer.serializeResult(result.getChanges());
@@ -142,7 +142,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
     }
 
     private NewCommandSourceHandler findCommandHandler(final CommandWrapper 
wrapper) {
-        NewCommandSourceHandler handler = null;
+        NewCommandSourceHandler handler;
 
         if (wrapper.isDatatableResource()) {
             if (wrapper.isCreateDatatable()) {
@@ -209,8 +209,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
     private void publishErrorEvent(CommandWrapper wrapper, JsonCommand 
command, Throwable t) {
 
         ErrorInfo ex;
-        if (t instanceof RuntimeException) {
-            final RuntimeException e = (RuntimeException) t;
+        if (t instanceof final RuntimeException e) {
             ex = ErrorHandler.handler(e);
         } else {
             ex = new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + 
"}");
@@ -249,8 +248,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
                     reqmap.put("clientId", resultCopy.getClientId());
                     resultCopy.setOfficeId(null);
                     reqmap.put("response", resultCopy);
-                } else if (result instanceof ErrorInfo) {
-                    ErrorInfo ex = (ErrorInfo) result;
+                } else if (result instanceof ErrorInfo ex) {
                     reqmap.put("status", "Exception");
 
                     Map<String, Object> errorMap = 
gson.fromJson(ex.getMessage(), type);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
index 97e1e9bf4..b37fc159f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java
@@ -42,7 +42,7 @@ public final class JdbcSupport {
         ZonedDateTime dateTime = null;
         final Timestamp dateValue = rs.getTimestamp(columnName);
         if (dateValue != null) {
-            dateTime = ZonedDateTime.ofInstant(dateValue.toInstant(), 
DateUtils.getDateTimeZoneOfTenant());
+            dateTime = ZonedDateTime.of(dateValue.toLocalDateTime(), 
DateUtils.getDateTimeZoneOfTenant());
         }
         return dateTime;
     }
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 4d60f259d..65cf79c76 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
@@ -55,4 +55,5 @@
     <include 
file="parts/0033_add_audit_entries_to_loan_repayment_schedule_installment.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0034_add_audit_entries_to_note.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0035_add_audit_entries_to_calendar.xml" 
relativeToChangelogFile="true"/>
+    <include 
file="parts/0036_add_audit_entries_and_rework_command_source_datetime_fields.xml"
 relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0036_add_audit_entries_and_rework_command_source_datetime_fields.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0036_add_audit_entries_and_rework_command_source_datetime_fields.xml
new file mode 100644
index 000000000..f8eab3857
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0036_add_audit_entries_and_rework_command_source_datetime_fields.xml
@@ -0,0 +1,52 @@
+<?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.3.xsd";>
+    <changeSet author="fineract" id="portfolio_command_source-1" 
context="mysql">
+        <addColumn tableName="m_portfolio_command_source">
+            <column name="made_on_date_utc" type="DATETIME"/>
+            <column name="checked_on_date_utc" type="DATETIME"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="portfolio_command_source-1" 
context="postgresql">
+        <addColumn tableName="m_portfolio_command_source">
+            <column name="made_on_date_utc" type="TIMESTAMP WITH TIME ZONE"/>
+            <column name="checked_on_date_utc" type="TIMESTAMP WITH TIME 
ZONE"/>
+        </addColumn>
+    </changeSet>
+    <changeSet id="portfolio_command_source-2" author="fineract">
+        <dropNotNullConstraint tableName="m_portfolio_command_source" 
columnName="made_on_date" columnDataType="datetime"/>
+    </changeSet>
+    <changeSet id="portfolio_command_source-3" author="fineract" 
context="mysql">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">select count(*) from 
m_portfolio_command_source</sqlCheck>
+        </preConditions>
+        <addNotNullConstraint tableName="m_portfolio_command_source" 
columnName="made_on_date_utc" columnDataType="DATETIME"/>
+    </changeSet>
+    <changeSet id="portfolio_command_source-3" author="fineract" 
context="postgresql">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">select count(*) from 
m_portfolio_command_source</sqlCheck>
+        </preConditions>
+        <addNotNullConstraint tableName="m_portfolio_command_source" 
columnName="made_on_date_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
+    </changeSet>
+</databaseChangeLog>

Reply via email to