FINERACT-1670: Add UTC auditable fields and submitted on date to Journal Entry

This commit is contained in:
Adam Saghy
2022-08-04 14:27:18 +02:00
committed by Arnold Gálovics
parent 77ca3a2ce0
commit 865b9efc6e
7 changed files with 139 additions and 43 deletions
@@ -28,7 +28,8 @@ import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.client.domain.ClientTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -37,7 +38,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
@Entity
@Table(name = "acc_gl_journal_entry")
public class JournalEntry extends AbstractAuditableCustom {
public class JournalEntry extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
@@ -103,6 +104,9 @@ public class JournalEntry extends AbstractAuditableCustom {
@Column(name = "ref_num")
private String referenceNumber;
@Column(name = "submitted_on_date", nullable = false)
private LocalDate submittedOnDate;
public static JournalEntry createNew(final Office office, final PaymentDetail paymentDetail, final GLAccount glAccount,
final String currencyCode, final String transactionId, final boolean manualEntry, final LocalDate transactionDate,
final JournalEntryType journalEntryType, final BigDecimal amount, final String description, final Integer entityType,
@@ -141,6 +145,7 @@ public class JournalEntry extends AbstractAuditableCustom {
this.clientTransaction = clientTransaction;
this.paymentDetail = paymentDetail;
this.shareTransactionId = shareTransactionId;
this.submittedOnDate = DateUtils.getBusinessLocalDate();
}
public boolean isDebitEntry() {
@@ -227,4 +232,8 @@ public class JournalEntry extends AbstractAuditableCustom {
return this.description;
}
public LocalDate getSubmittedOnDate() {
return this.submittedOnDate;
}
}
@@ -101,7 +101,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
.append(" journalEntry.type_enum as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId,")
.append(" journalEntry.entity_type_enum as entityType, journalEntry.entity_id as entityId, creatingUser.id as createdByUserId, ")
.append(" creatingUser.username as createdByUserName, journalEntry.description as comments, ")
.append(" journalEntry.created_date as createdDate, journalEntry.reversed as reversed, ")
.append(" journalEntry.submitted_on_date as createdDate, journalEntry.reversed as reversed, ")
.append(" journalEntry.currency_code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
.append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf ");
if (associationParametersData.isRunningBalanceRequired()) {
@@ -120,7 +120,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
sb.append(" from acc_gl_journal_entry as journalEntry ")
.append(" left join acc_gl_account as glAccount on glAccount.id = journalEntry.account_id")
.append(" left join m_office as office on office.id = journalEntry.office_id")
.append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.createdby_id ")
.append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.created_by ")
.append(" join m_currency curr on curr.code = journalEntry.currency_code ");
if (associationParametersData.isTransactionDetailsRequired()) {
sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
@@ -40,7 +40,9 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -63,6 +65,8 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
private final GLJournalEntryMapper entryMapper = new GLJournalEntryMapper();
private final PlatformSecurityContext platformSecurityContext;
@Override
public void updateRunningBalance() {
String dateFinder = "select MIN(je.entry_date) as entityDate from acc_gl_journal_entry je "
@@ -154,8 +158,10 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
if (entryDatas.size() > 0) {
// run a batch update of 1000 SQL statements at a time
final Integer batchUpdateSize = 1000;
ArrayList<String> updateSql = new ArrayList<>();
List<Object[]> params = new ArrayList<>();
int batchIndex = 0;
String sql = "UPDATE acc_gl_journal_entry SET is_running_balance_calculated=?, organization_running_balance=?,"
+ "office_running_balance=?, last_modified_by=?, last_modified_on_utc=? WHERE id=?";
for (int index = 0; index < entryDatas.size(); index++) {
JournalEntryData entryData = entryDatas.get(index);
Map<Long, BigDecimal> officeRunningBalanceMap = null;
@@ -167,18 +173,15 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
}
BigDecimal officeRunningBalance = calculateRunningBalance(entryData, officeRunningBalanceMap);
BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap);
String sql = "UPDATE acc_gl_journal_entry SET is_running_balance_calculated=true, organization_running_balance="
+ runningBalance + ",office_running_balance=" + officeRunningBalance + " WHERE id=" + entryData.getId();
updateSql.add(sql);
params.add(new Object[] { Boolean.TRUE, runningBalance, officeRunningBalance,
platformSecurityContext.authenticatedUser().getId(), DateUtils.getOffsetDateTimeOfTenant(), entryData.getId() });
batchIndex++;
if (batchIndex == batchUpdateSize || index == entryDatas.size() - 1) {
// run a batch update of the 1000 update SQL statements
String[] batch = new String[updateSql.size()];
updateSql.toArray(batch);
this.jdbcTemplate.batchUpdate(batch);
this.jdbcTemplate.batchUpdate(sql, params);
// reset counter and string array
batchIndex = 0;
updateSql.clear();
params.clear();
}
}
}
@@ -202,14 +205,15 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
}
}
List<JournalEntryData> entryDatas = jdbcTemplate.query(entryMapper.officeRunningBalanceSchema(), entryMapper, officeId, entityDate);
String[] updateSql = new String[entryDatas.size()];
int i = 0;
List<Object[]> params = new ArrayList<>();
String sql = "UPDATE acc_gl_journal_entry SET office_running_balance=?, last_modified_by=?, last_modified_on_utc=? WHERE id=?";
for (JournalEntryData entryData : entryDatas) {
BigDecimal runningBalance = calculateRunningBalance(entryData, runningBalanceMap);
String sql = "UPDATE acc_gl_journal_entry SET office_running_balance=" + runningBalance + " WHERE id=" + entryData.getId();
updateSql[i++] = sql;
params.add(new Object[] { runningBalance, platformSecurityContext.authenticatedUser().getId(),
DateUtils.getOffsetDateTimeOfTenant(), entryData.getId() });
}
this.jdbcTemplate.batchUpdate(updateSql);
this.jdbcTemplate.batchUpdate(sql, params);
}
private BigDecimal calculateRunningBalance(JournalEntryData entry, Map<Long, BigDecimal> runningBalanceMap) {
@@ -36,6 +36,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
@@ -66,6 +67,7 @@ public class PostInterestForSavingTasklet implements Tasklet {
private final Queue<List<SavingsAccountData>> queue = new ArrayDeque<>();
private final ApplicationContext applicationContext;
private final int queueSize = 1;
private final PlatformSecurityContext securityContext;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
@@ -157,6 +159,7 @@ public class PostInterestForSavingTasklet implements Tasklet {
savingsSchedularInterestPoster.setBackdatedTxnsAllowedTill(backdatedTxnsAllowedTill);
savingsSchedularInterestPoster.setTransactionTemplate(transactionTemplate);
savingsSchedularInterestPoster.setConfigurationDomainService(configurationDomainService);
savingsSchedularInterestPoster.setPlatformSecurityContext(securityContext);
posters.add(savingsSchedularInterestPoster);
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
@@ -82,6 +83,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
private CommandStrategyProvider strategyProvider;
private ResolutionHelper resolutionHelper;
private SavingsAccountReadPlatformService savingsAccountReadPlatformService;
private PlatformSecurityContext platformSecurityContext;
@Override
@SuppressFBWarnings(value = {
@@ -172,6 +174,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
private void batchUpdateJournalEntries(final List<SavingsAccountData> savingsAccountDataList,
final HashMap<String, SavingsAccountTransactionData> savingsAccountTransactionDataHashMap)
throws DataAccessException, NullPointerException {
Long userId = platformSecurityContext.authenticatedUser().getId();
String queryForJGLUpdate = batchQueryForJournalEntries();
List<Object[]> paramsForGLInsertion = new ArrayList<>();
for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
@@ -182,7 +185,6 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
if (savingsAccountTransactionData.getId() == null) {
final String key = savingsAccountTransactionData.getRefNo();
if (savingsAccountTransactionDataHashMap.containsKey(key)) {
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap.get(key);
savingsAccountTransactionData.setId(dataFromFetch.getId());
if (savingsAccountData.getGlAccountIdForSavingsControl() != 0
@@ -191,31 +193,23 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
savingsAccountData.getOfficeId(), null, currencyCode,
SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
savingsAccountTransactionData.getId(), null, false, null, false,
Date.from(savingsAccountTransactionData.getTransactionDate()
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
JournalEntryType.CREDIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null,
JournalEntryType.CREDIT.getValue().longValue(), savingsAccountData.getId(),
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), false,
BigDecimal.ZERO, BigDecimal.ZERO, null,
Date.from(savingsAccountTransactionData.getTransactionDate()
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
null, Integer.valueOf(1), Integer.valueOf(1) });
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.CREDIT.getValue().longValue(),
savingsAccountTransactionData.getAmount(), null, JournalEntryType.CREDIT.getValue().longValue(),
savingsAccountData.getId(), DateUtils.getOffsetDateTimeOfTenant(),
DateUtils.getOffsetDateTimeOfTenant(), false, BigDecimal.ZERO, BigDecimal.ZERO, null,
savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
DateUtils.getBusinessLocalDate() });
paramsForGLInsertion.add(new Object[] { savingsAccountData.getGlAccountIdForInterestOnSavings(),
savingsAccountData.getOfficeId(), null, currencyCode,
SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
savingsAccountTransactionData.getId(), null, false, null, false,
Date.from(savingsAccountTransactionData.getTransactionDate()
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
JournalEntryType.DEBIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null,
JournalEntryType.DEBIT.getValue().longValue(), savingsAccountData.getId(),
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), false,
BigDecimal.ZERO, BigDecimal.ZERO, null,
Date.from(savingsAccountTransactionData.getTransactionDate()
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
null, Integer.valueOf(1), Integer.valueOf(1) });
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.DEBIT.getValue().longValue(),
savingsAccountTransactionData.getAmount(), null, JournalEntryType.DEBIT.getValue().longValue(),
savingsAccountData.getId(), DateUtils.getOffsetDateTimeOfTenant(),
DateUtils.getOffsetDateTimeOfTenant(), false, BigDecimal.ZERO, BigDecimal.ZERO, null,
savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
DateUtils.getBusinessLocalDate() });
}
}
}
@@ -232,10 +226,10 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
query.append("INSERT INTO acc_gl_journal_entry(account_id,office_id,reversal_id,currency_code,transaction_id,");
query.append("savings_transaction_id,client_transaction_id,reversed,ref_num,manual_entry,entry_date,type_enum,");
query.append("amount,description,entity_type_enum,entity_id,created_date,");
query.append("lastmodified_date,is_running_balance_calculated,office_running_balance,organization_running_balance,");
query.append("payment_details_id,transaction_date,share_transaction_id, createdby_id, lastmodifiedby_id) ");
query.append("VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
query.append("amount,description,entity_type_enum,entity_id,created_on_utc,");
query.append("last_modified_on_utc,is_running_balance_calculated,office_running_balance,organization_running_balance,");
query.append("payment_details_id,transaction_date,share_transaction_id, created_by, last_modified_by, submitted_on_date) ");
query.append("VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
return query.toString();
}
@@ -44,4 +44,5 @@
<include file="parts/0022_add_batch_business_step_configuration_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0023_use_the_proper_date_or_datetime_type.xml" relativeToChangelogFile="true"/>
<include file="parts/0024_add_audit_entries.xml" relativeToChangelogFile="true"/>
<include file="parts/0025_add_audit_entries_to_journal_entry.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,85 @@
<?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">
<!--Journal entry-->
<changeSet author="fineract" id="journal-entry-1" context="mysql">
<addColumn tableName="acc_gl_journal_entry">
<column name="created_on_utc" type="DATETIME"/>
<column name="last_modified_on_utc" type="DATETIME"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="journal-entry-1" context="postgresql">
<addColumn tableName="acc_gl_journal_entry">
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
</addColumn>
</changeSet>
<changeSet id="journal-entry-2" author="fineract">
<dropNotNullConstraint tableName="acc_gl_journal_entry" columnName="created_date" columnDataType="datetime"/>
<dropNotNullConstraint tableName="acc_gl_journal_entry" columnName="lastmodified_date" columnDataType="datetime"/>
</changeSet>
<changeSet id="journal-entry-3" author="fineract">
<renameColumn tableName="acc_gl_journal_entry" oldColumnName="createdby_id" newColumnName="created_by" columnDataType="BIGINT"/>
<renameColumn tableName="acc_gl_journal_entry" oldColumnName="lastmodifiedby_id" newColumnName="last_modified_by" columnDataType="BIGINT"/>
</changeSet>
<changeSet author="fineract" id="journal-entry-4">
<addForeignKeyConstraint baseColumnNames="created_by" baseTableName="acc_gl_journal_entry"
constraintName="FK_journal_entry_created_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="last_modified_by" baseTableName="acc_gl_journal_entry"
constraintName="FK_journal_entry_last_modified_by" deferrable="false" initiallyDeferred="false"
onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="id"
referencedTableName="m_appuser" validate="true"/>
</changeSet>
<changeSet id="journal-entry-5" author="fineract" context="mysql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from acc_gl_journal_entry</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="acc_gl_journal_entry" columnName="created_on_utc" columnDataType="DATETIME"/>
<addNotNullConstraint tableName="acc_gl_journal_entry" columnName="last_modified_on_utc" columnDataType="DATETIME"/>
</changeSet>
<changeSet id="journal-entry-5" author="fineract" context="postgresql">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">select count(*) from acc_gl_journal_entry</sqlCheck>
</preConditions>
<addNotNullConstraint tableName="acc_gl_journal_entry" columnName="created_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
<addNotNullConstraint tableName="acc_gl_journal_entry" columnName="last_modified_on_utc" columnDataType="TIMESTAMP WITH TIME ZONE"/>
</changeSet>
<changeSet id="journal-entry-6" author="fineract">
<addColumn tableName="acc_gl_journal_entry">
<column name="submitted_on_date" type="DATE" valueComputed="created_date">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="journal-entry-7">
<setColumnRemarks
columnName="transaction_date"
remarks="Unfinished. Not maintained."
columnDataType="date"
tableName="acc_gl_journal_entry"/>
</changeSet>
</databaseChangeLog>