FINERACT-1670: Rework command source to store dates in UTC

This commit is contained in:
Adam Saghy
2022-08-12 09:55:30 +02:00
committed by Arnold Gálovics
parent d5ca6baab3
commit 629b9f6d7c
7 changed files with 95 additions and 33 deletions
@@ -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();
}
@@ -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);
@@ -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;
}
@@ -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);
@@ -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;
}
@@ -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>
@@ -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>