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>