support setting timezone type for the time range of statistical data

This commit is contained in:
MaysWind
2024-04-12 09:51:01 +08:00
parent 14f6de8af1
commit ea32bfa5fc
18 changed files with 398 additions and 31 deletions
+16 -2
View File
@@ -238,8 +238,15 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (any, *e
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
utcOffset, err := c.GetClientTimezoneOffset()
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone offset, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid
}
uid := c.GetCurrentUid()
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(c, uid, statisticReq.StartTime, statisticReq.EndTime)
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(c, uid, statisticReq.StartTime, statisticReq.EndTime, utcOffset, statisticReq.UseTransactionTimezone)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
@@ -292,6 +299,13 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs
return nil, errs.ErrQueryItemsTooMuch
}
utcOffset, err := c.GetClientTimezoneOffset()
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionAmountsHandler] cannot get client timezone offset, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid
}
uid := c.GetCurrentUid()
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
@@ -307,7 +321,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (any, *errs
for i := 0; i < len(requestItems); i++ {
requestItem := requestItems[i]
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime)
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, utcOffset, transactionAmountsReq.UseTransactionTimezone)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
+5 -3
View File
@@ -135,13 +135,15 @@ type TransactionListInMonthByPageRequest struct {
// TransactionStatisticRequest represents all parameters of transaction statistic request
type TransactionStatisticRequest struct {
StartTime int64 `form:"start_time" binding:"min=0"`
EndTime int64 `form:"end_time" binding:"min=0"`
StartTime int64 `form:"start_time" binding:"min=0"`
EndTime int64 `form:"end_time" binding:"min=0"`
UseTransactionTimezone bool `form:"use_transaction_timezone"`
}
// TransactionAmountsRequest represents all parameters of transaction amounts request
type TransactionAmountsRequest struct {
Query string `form:"query"`
Query string `form:"query"`
UseTransactionTimezone bool `form:"use_transaction_timezone"`
}
// TransactionAmountsRequestItem represents an item of transaction amounts request
+151 -19
View File
@@ -15,6 +15,8 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/uuid"
)
const pageCountForLoadTransactionAmounts = 1000
// TransactionService represents transaction service
type TransactionService struct {
ServiceUsingDB
@@ -1004,45 +1006,112 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
}
// GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64) (map[int64]int64, map[int64]int64, error) {
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64, utcOffset int16, useTransactionTimezone bool) (map[int64]int64, map[int64]int64, error) {
if uid <= 0 {
return nil, nil, errs.ErrUserIdInvalid
}
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation)
endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation)
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
var transactionTotalAmounts []*models.Transaction
err := s.UserDataDB(uid).NewSession(c).Select("type, account_id, SUM(amount) as amount").Where("uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?", uid, false, models.TRANSACTION_DB_TYPE_INCOME, models.TRANSACTION_DB_TYPE_EXPENSE, startTransactionTime, endTransactionTime).GroupBy("type, account_id").Find(&transactionTotalAmounts)
condition := "uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?"
conditionParams := make([]any, 0, 4)
conditionParams = append(conditionParams, uid)
conditionParams = append(conditionParams, false)
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE)
if err != nil {
return nil, nil, err
minTransactionTime := startTransactionTime
maxTransactionTime := endTransactionTime
var allTransactions []*models.Transaction
for maxTransactionTime > 0 {
var transactions []*models.Transaction
finalConditionParams := make([]any, 0, 6)
finalConditionParams = append(finalConditionParams, conditionParams...)
finalConditionParams = append(finalConditionParams, minTransactionTime)
finalConditionParams = append(finalConditionParams, maxTransactionTime)
err := s.UserDataDB(uid).NewSession(c).Select("type, account_id, transaction_time, timezone_utc_offset, amount").Where(condition, finalConditionParams...).Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
if err != nil {
return nil, nil, err
}
allTransactions = append(allTransactions, transactions...)
if len(transactions) < pageCountForLoadTransactionAmounts {
maxTransactionTime = 0
break
}
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
}
incomeAmounts := make(map[int64]int64)
expenseAmounts := make(map[int64]int64)
for i := 0; i < len(transactionTotalAmounts); i++ {
transactionTotalAmount := transactionTotalAmounts[i]
for i := 0; i < len(allTransactions); i++ {
transaction := allTransactions[i]
timeZone := clientLocation
if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_INCOME {
incomeAmounts[transactionTotalAmount.AccountId] = transactionTotalAmount.Amount
} else if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
expenseAmounts[transactionTotalAmount.AccountId] = transactionTotalAmount.Amount
if useTransactionTimezone {
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
}
localDateTime := utils.FormatUnixTimeToNumericLocalDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), timeZone)
if localDateTime < startLocalDateTime || localDateTime > endLocalDateTime {
continue
}
var amountsMap map[int64]int64
if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
amountsMap = incomeAmounts
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
amountsMap = expenseAmounts
}
totalAmounts, exists := amountsMap[transaction.AccountId]
if !exists {
totalAmounts = 0
}
totalAmounts += transaction.Amount
amountsMap[transaction.AccountId] = totalAmounts
}
return incomeAmounts, expenseAmounts, nil
}
// GetAccountsAndCategoriesTotalIncomeAndExpense returns the every accounts and categories total income and expense amount by specific date range
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64) ([]*models.Transaction, error) {
func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *core.Context, uid int64, startUnixTime int64, endUnixTime int64, utcOffset int16, useTransactionTimezone bool) ([]*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation)
endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation)
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
condition := "uid=? AND deleted=? AND (type=? OR type=?)"
conditionParams := make([]any, 0, 8)
conditionParams := make([]any, 0, 4)
conditionParams = append(conditionParams, uid)
conditionParams = append(conditionParams, false)
conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME)
@@ -1050,19 +1119,82 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(c *co
if startUnixTime > 0 {
condition = condition + " AND transaction_time>=?"
conditionParams = append(conditionParams, utils.GetMinTransactionTimeFromUnixTime(startUnixTime))
}
if endUnixTime > 0 {
condition = condition + " AND transaction_time<=?"
conditionParams = append(conditionParams, utils.GetMaxTransactionTimeFromUnixTime(endUnixTime))
}
var transactionTotalAmounts []*models.Transaction
err := s.UserDataDB(uid).NewSession(c).Select("category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
minTransactionTime := startTransactionTime
maxTransactionTime := endTransactionTime
var allTransactions []*models.Transaction
if err != nil {
return nil, err
for maxTransactionTime > 0 {
var transactions []*models.Transaction
finalConditionParams := make([]any, 0, 6)
finalConditionParams = append(finalConditionParams, conditionParams...)
if startUnixTime > 0 {
finalConditionParams = append(finalConditionParams, minTransactionTime)
}
if endUnixTime > 0 {
finalConditionParams = append(finalConditionParams, maxTransactionTime)
}
err := s.UserDataDB(uid).NewSession(c).Select("category_id, account_id, transaction_time, timezone_utc_offset, amount").Where(condition, finalConditionParams...).Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
if err != nil {
return nil, err
}
allTransactions = append(allTransactions, transactions...)
if len(transactions) < pageCountForLoadTransactionAmounts {
maxTransactionTime = 0
break
}
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
}
transactionTotalAmountsMap := make(map[string]*models.Transaction)
for i := 0; i < len(allTransactions); i++ {
transaction := allTransactions[i]
timeZone := clientLocation
if useTransactionTimezone {
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
}
localDateTime := utils.FormatUnixTimeToNumericLocalDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), timeZone)
if localDateTime < startLocalDateTime || localDateTime > endLocalDateTime {
continue
}
groupKey := fmt.Sprintf("%d_%d", transaction.CategoryId, transaction.AccountId)
totalAmounts, exists := transactionTotalAmountsMap[groupKey]
if !exists {
totalAmounts = &models.Transaction{
CategoryId: transaction.CategoryId,
AccountId: transaction.AccountId,
Amount: 0,
}
transactionTotalAmountsMap[groupKey] = totalAmounts
}
totalAmounts.Amount += transaction.Amount
}
transactionTotalAmounts := make([]*models.Transaction, 0, len(transactionTotalAmountsMap))
for _, totalAmounts := range transactionTotalAmountsMap {
transactionTotalAmounts = append(transactionTotalAmounts, totalAmounts)
}
return transactionTotalAmounts, nil
+28
View File
@@ -44,6 +44,34 @@ func FormatUnixTimeToYearMonth(unixTime int64, timezone *time.Location) string {
return t.Format(yearMonthDateTimeFormat)
}
// FormatUnixTimeToNumericLocalDateTime returns numeric year, month, day, hour, minute and second of specified unix time
func FormatUnixTimeToNumericLocalDateTime(unixTime int64, timezone *time.Location) int64 {
t := parseFromUnixTime(unixTime)
if timezone != nil {
t = t.In(timezone)
}
localDateTime := int64(t.Year())
localDateTime = localDateTime*100 + int64(t.Month())
localDateTime = localDateTime*100 + int64(t.Day())
localDateTime = localDateTime*100 + int64(t.Hour())
localDateTime = localDateTime*100 + int64(t.Minute())
localDateTime = localDateTime*100 + int64(t.Second())
return localDateTime
}
// GetMinUnixTimeWithSameLocalDateTime returns the minimum UnixTime for date with the same local date
func GetMinUnixTimeWithSameLocalDateTime(unixTime int64, currentUtcOffset int16) int64 {
return unixTime + int64(currentUtcOffset)*60 - easternmostTimezoneUtcOffset*60
}
// GetMaxUnixTimeWithSameLocalDateTime returns the maximum UnixTime for date with the same local date
func GetMaxUnixTimeWithSameLocalDateTime(unixTime int64, currentUtcOffset int16) int64 {
return unixTime + int64(currentUtcOffset)*60 - westernmostTimezoneUtcOffset*60
}
// ParseFromLongDateTimeToMinUnixTime parses a formatted string in long date time format to minimal unix time (the westernmost timezone)
func ParseFromLongDateTimeToMinUnixTime(t string) (time.Time, error) {
timezone := time.FixedZone("Timezone", easternmostTimezoneUtcOffset*60)
+32
View File
@@ -35,6 +35,38 @@ func TestFormatUnixTimeToYearMonth(t *testing.T) {
assert.Equal(t, expectedValue, actualValue)
}
func TestFormatUnixTimeToNumericLocalDateTime(t *testing.T) {
unixTime := int64(1617228083)
utcTimezone := time.FixedZone("Test Timezone", 0) // UTC
utc8Timezone := time.FixedZone("Test Timezone", 28800) // UTC+8
expectedValue := int64(20210331220123)
actualValue := FormatUnixTimeToNumericLocalDateTime(unixTime, utcTimezone)
assert.Equal(t, expectedValue, actualValue)
expectedValue = int64(20210401060123)
actualValue = FormatUnixTimeToNumericLocalDateTime(unixTime, utc8Timezone)
assert.Equal(t, expectedValue, actualValue)
}
func TestGetMinUnixTimeWithSameLocalDateTime(t *testing.T) {
expectedValue := int64(1690797600)
actualValue := GetMinUnixTimeWithSameLocalDateTime(1690819200, 480)
assert.Equal(t, expectedValue, actualValue)
actualValue = GetMinUnixTimeWithSameLocalDateTime(1690873200, -420)
assert.Equal(t, expectedValue, actualValue)
}
func TestGetMaxUnixTimeWithSameLocalDateTime(t *testing.T) {
expectedValue := int64(1690891200)
actualValue := GetMaxUnixTimeWithSameLocalDateTime(1690819200, 480)
assert.Equal(t, expectedValue, actualValue)
actualValue = GetMaxUnixTimeWithSameLocalDateTime(1690873200, -420)
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromLongDateTimeToMinUnixTime(t *testing.T) {
expectedValue := int64(1690797600)
actualTime, err := ParseFromLongDateTimeToMinUnixTime("2023-08-01 00:00:00")
+10 -1
View File
@@ -592,7 +592,16 @@ const allAvailableTimezones = [
}
];
const allTimezoneTypesUsedForStatistics = {
ApplicationTimezone: 0,
TransactionTimezone: 1
};
const defaultTimezoneTypesUsedForStatistics = allTimezoneTypesUsedForStatistics.ApplicationTimezone;
export default {
all: allAvailableTimezones,
utcTimezoneName: 'Etc/GMT'
utcTimezoneName: 'Etc/GMT',
allTimezoneTypesUsedForStatistics: allTimezoneTypesUsedForStatistics,
defaultTimezoneTypesUsedForStatistics: defaultTimezoneTypesUsedForStatistics
};
+16
View File
@@ -762,6 +762,21 @@ function getDateRangeDisplayName(userStore, dateType, startTime, endTime, transl
return `${displayStartTime} ~ ${displayEndTime}`;
}
function getAllTimezoneTypesUsedForStatistics(currentTimezone, translateFn) {
const currentTimezoneOffset = getTimezoneOffset(currentTimezone);
return [
{
displayName: translateFn('Application Timezone') + ` (UTC${currentTimezoneOffset})`,
type: timezone.allTimezoneTypesUsedForStatistics.ApplicationTimezone
},
{
displayName: translateFn('Transaction Timezone'),
type: timezone.allTimezoneTypesUsedForStatistics.TransactionTimezone
}
];
}
function getAllAccountCategories(translateFn) {
const allAccountCategories = [];
@@ -1333,6 +1348,7 @@ export function i18nFunctions(i18nGlobal) {
getAllDateRanges: (includeCustom) => getAllDateRanges(includeCustom, i18nGlobal.t),
getAllRecentMonthDateRanges: (userStore, includeAll, includeCustom) => getAllRecentMonthDateRanges(userStore, includeAll, includeCustom, i18nGlobal.t),
getDateRangeDisplayName: (userStore, dateType, startTime, endTime) => getDateRangeDisplayName(userStore, dateType, startTime, endTime, i18nGlobal.t),
getAllTimezoneTypesUsedForStatistics: (currentTimezone) => getAllTimezoneTypesUsedForStatistics(currentTimezone, i18nGlobal.t),
getAllAccountCategories: () => getAllAccountCategories(i18nGlobal.t),
getAllAccountTypes: () => getAllAccountTypes(i18nGlobal.t),
getAllStatisticsChartDataTypes: () => getAllStatisticsChartDataTypes(i18nGlobal.t),
+4 -4
View File
@@ -283,7 +283,7 @@ export default {
keyword = encodeURIComponent(keyword);
return axios.get(`v1/transactions/list/by_month.json?year=${year}&month=${month}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
},
getTransactionStatistics: ({ startTime, endTime }) => {
getTransactionStatistics: ({ startTime, endTime, useTransactionTimezone }) => {
const queryParams = [];
if (startTime) {
@@ -294,9 +294,9 @@ export default {
queryParams.push(`end_time=${endTime}`);
}
return axios.get('v1/transactions/statistics.json' + (queryParams.length ? '?' + queryParams.join('&') : ''));
return axios.get(`v1/transactions/statistics.json?use_transaction_timezone=${useTransactionTimezone}` + (queryParams.length ? '&' + queryParams.join('&') : ''));
},
getTransactionAmounts: ({ today, thisWeek, thisMonth, thisYear, lastMonth, monthBeforeLastMonth, monthBeforeLast2Months, monthBeforeLast3Months, monthBeforeLast4Months, monthBeforeLast5Months, monthBeforeLast6Months, monthBeforeLast7Months, monthBeforeLast8Months, monthBeforeLast9Months, monthBeforeLast10Months }) => {
getTransactionAmounts: ({ useTransactionTimezone, today, thisWeek, thisMonth, thisYear, lastMonth, monthBeforeLastMonth, monthBeforeLast2Months, monthBeforeLast3Months, monthBeforeLast4Months, monthBeforeLast5Months, monthBeforeLast6Months, monthBeforeLast7Months, monthBeforeLast8Months, monthBeforeLast9Months, monthBeforeLast10Months }) => {
const queryParams = [];
if (today) {
@@ -359,7 +359,7 @@ export default {
queryParams.push(`monthBeforeLast10Months_${monthBeforeLast10Months.startTime}_${monthBeforeLast10Months.endTime}`);
}
return axios.get('v1/transactions/amounts.json' + (queryParams.length ? '?query=' + queryParams.join('|') : ''));
return axios.get(`v1/transactions/amounts.json?use_transaction_timezone=${useTransactionTimezone}` + (queryParams.length ? '&query=' + queryParams.join('|') : ''));
},
getTransaction: ({ id }) => {
return axios.get(`v1/transactions/get.json?id=${id}&trim_account=true&trim_category=true&trim_tag=true`);
+19
View File
@@ -1,4 +1,5 @@
import currencyConstants from '@/consts/currency.js';
import timezoneConstants from '@/consts/timezone.js';
import statisticsConstants from '@/consts/statistics.js';
const settingsLocalStorageKey = 'ebk_app_settings';
@@ -15,6 +16,7 @@ const defaultSettings = {
thousandsSeparator: true,
currencyDisplayMode: currencyConstants.defaultCurrencyDisplayMode,
showAmountInHomePage: true,
timezoneUsedForStatisticsInHomePage: timezoneConstants.defaultTimezoneTypesUsedForStatistics,
itemsCountInTransactionListPage: 15,
showTotalAmountInTransactionListPage: true,
showAccountBalance: true,
@@ -22,6 +24,7 @@ const defaultSettings = {
defaultChartType: statisticsConstants.defaultChartType,
defaultChartDataType: statisticsConstants.defaultChartDataType,
defaultDataRangeType: statisticsConstants.defaultDataRangeType,
defaultTimezoneType: timezoneConstants.defaultTimezoneTypesUsedForStatistics,
defaultAccountFilter: {},
defaultTransactionCategoryFilter: {},
defaultSortingType: statisticsConstants.defaultSortingType
@@ -186,6 +189,14 @@ export function setShowAmountInHomePage(value) {
setOption('showAmountInHomePage', value);
}
export function getTimezoneUsedForStatisticsInHomePage() {
return getOption('timezoneUsedForStatisticsInHomePage');
}
export function setTimezoneUsedForStatisticsInHomePage(value) {
setOption('timezoneUsedForStatisticsInHomePage', value);
}
export function getItemsCountInTransactionListPage() {
return getOption('itemsCountInTransactionListPage');
}
@@ -230,6 +241,14 @@ export function getStatisticsDefaultDateRange() {
return getSubOption('statistics', 'defaultDataRangeType');
}
export function getStatisticsDefaultTimezoneType() {
return getSubOption('statistics', 'defaultTimezoneType');
}
export function setStatisticsDefaultTimezoneType(value) {
setSubOption('statistics', 'defaultTimezoneType', value);
}
export function setStatisticsDefaultDateRange(value) {
setSubOption('statistics', 'defaultDataRangeType', value);
}
+4
View File
@@ -1019,6 +1019,7 @@ export default {
'Default Transaction Category Filter': 'Default Transaction Category Filter',
'Sort Order': 'Sort Order',
'Default Sort Order': 'Default Sort Order',
'Timezone Used for Date Range': 'Timezone Used for Date Range',
'Amount': 'Amount',
'Display Order': 'Display Order',
'Name': 'Name',
@@ -1047,6 +1048,9 @@ export default {
'Hide Account Balance': 'Hide Account Balance',
'Page Settings': 'Page Settings',
'Overview Page': 'Overview Page',
'Timezone Used for Statistics': 'Timezone Used for Statistics',
'Timezone Type': 'Timezone Type',
'Application Timezone': 'Application Timezone',
'Transaction List Page': 'Transaction List Page',
'Transactions Per Page': 'Transactions Per Page',
'Show Monthly Total Amount': 'Show Monthly Total Amount',
+4
View File
@@ -1019,6 +1019,7 @@ export default {
'Default Transaction Category Filter': '默认交易分类过滤',
'Sort Order': '排序方式',
'Default Sort Order': '默认排序方式',
'Timezone Used for Date Range': '时间范围使用的时区',
'Amount': '金额',
'Display Order': '显示顺序',
'Name': '名称',
@@ -1047,6 +1048,9 @@ export default {
'Hide Account Balance': '隐藏账户余额',
'Page Settings': '页面设置',
'Overview Page': '总览页面',
'Timezone Used for Statistics': '统计时使用的时区',
'Timezone Type': '时区类型',
'Application Timezone': '应用时区',
'Transaction List Page': '交易列表页面',
'Transactions Per Page': '每页交易数',
'Show Monthly Total Amount': '显示月度总金额',
+4
View File
@@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import { useSettingsStore } from '@/stores/setting.js';
import { useUserStore } from './user.js';
import { useExchangeRatesStore } from './exchangeRates.js';
@@ -219,6 +220,8 @@ export const useOverviewStore = defineStore('overview', {
this.transactionOverviewStateInvalid = true;
},
loadTransactionOverview({ force, loadLast11Months }) {
const settingsStore = useSettingsStore();
const self = this;
let dateChanged = false;
let rangeChanged = false;
@@ -239,6 +242,7 @@ export const useOverviewStore = defineStore('overview', {
}
const requestParams = {
useTransactionTimezone: settingsStore.appSettings.timezoneUsedForStatisticsInHomePage,
today: self.transactionDataRange.today,
thisWeek: self.transactionDataRange.thisWeek,
thisMonth: self.transactionDataRange.thisMonth,
+10
View File
@@ -17,6 +17,7 @@ export const useSettingsStore = defineStore('settings', {
thousandsSeparator: settings.isEnableThousandsSeparator(),
currencyDisplayMode: settings.getCurrencyDisplayMode(),
showAmountInHomePage: settings.isShowAmountInHomePage(),
timezoneUsedForStatisticsInHomePage: settings.getTimezoneUsedForStatisticsInHomePage(),
itemsCountInTransactionListPage: settings.getItemsCountInTransactionListPage(),
showTotalAmountInTransactionListPage: settings.isShowTotalAmountInTransactionListPage(),
showAccountBalance: settings.isShowAccountBalance(),
@@ -24,6 +25,7 @@ export const useSettingsStore = defineStore('settings', {
defaultChartType: settings.getStatisticsDefaultChartType(),
defaultChartDataType: settings.getStatisticsDefaultChartDataType(),
defaultDataRangeType: settings.getStatisticsDefaultDateRange(),
defaultTimezoneType: settings.getStatisticsDefaultTimezoneType(),
defaultAccountFilter: settings.getStatisticsDefaultAccountFilter(),
defaultTransactionCategoryFilter: settings.getStatisticsDefaultTransactionCategoryFilter(),
defaultSortingType: settings.getStatisticsSortingType()
@@ -76,6 +78,10 @@ export const useSettingsStore = defineStore('settings', {
settings.setShowAmountInHomePage(value);
this.appSettings.showAmountInHomePage = value;
},
setTimezoneUsedForStatisticsInHomePage(value) {
settings.setTimezoneUsedForStatisticsInHomePage(value);
this.appSettings.timezoneUsedForStatisticsInHomePage = value;
},
setItemsCountInTransactionListPage(value) {
settings.setItemsCountInTransactionListPage(value);
this.appSettings.itemsCountInTransactionListPage = value;
@@ -100,6 +106,10 @@ export const useSettingsStore = defineStore('settings', {
settings.setStatisticsDefaultDateRange(value);
this.appSettings.statistics.defaultDataRangeType = value;
},
setStatisticsDefaultTimezoneType(value) {
settings.setStatisticsDefaultTimezoneType(value);
this.appSettings.statistics.defaultTimezoneType = value;
},
setStatisticsDefaultAccountFilter(value) {
settings.setStatisticsDefaultAccountFilter(value);
this.appSettings.statistics.defaultAccountFilter = value;
+3 -1
View File
@@ -580,11 +580,13 @@ export const useStatisticsStore = defineStore('statistics', {
},
loadTransactionStatistics({ force }) {
const self = this;
const settingsStore = useSettingsStore();
return new Promise((resolve, reject) => {
services.getTransactionStatistics({
startTime: self.transactionStatisticsFilter.startTime,
endTime: self.transactionStatisticsFilter.endTime
endTime: self.transactionStatisticsFilter.endTime,
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType
}).then(response => {
const data = response.data;
@@ -109,6 +109,18 @@
v-model="showAmountInHomePage"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
item-value="type"
persistent-placeholder
:label="$t('Timezone Used for Statistics')"
:placeholder="$t('Timezone Used for Statistics')"
:items="allTimezoneTypesUsedForStatistics"
v-model="timezoneUsedForStatisticsInHomePage"
/>
</v-col>
</v-row>
</v-card-text>
</v-form>
@@ -197,6 +209,9 @@ export default {
allCurrencyDisplayModes() {
return currencyConstants.allCurrencyDisplayModes;
},
allTimezoneTypesUsedForStatistics() {
return this.$locale.getAllTimezoneTypesUsedForStatistics(this.timeZone);
},
theme: {
get: function () {
return this.settingsStore.appSettings.theme;
@@ -265,6 +280,15 @@ export default {
this.settingsStore.setShowAmountInHomePage(value);
}
},
timezoneUsedForStatisticsInHomePage: {
get: function () {
return this.settingsStore.appSettings.timezoneUsedForStatisticsInHomePage;
},
set: function (value) {
this.settingsStore.setTimezoneUsedForStatisticsInHomePage(value);
this.overviewStore.updateTransactionOverviewInvalidState(true);
}
},
showTotalAmountInTransactionListPage: {
get: function () {
return this.settingsStore.appSettings.showTotalAmountInTransactionListPage;
@@ -44,6 +44,18 @@
/>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
item-value="type"
persistent-placeholder
:label="$t('Timezone Used for Date Range')"
:placeholder="$t('Timezone Used for Date Range')"
:items="allTimezoneTypesUsedForStatistics"
v-model="defaultTimezoneType"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
@@ -99,6 +111,9 @@ export default {
allDateRanges() {
return this.$locale.getAllDateRanges(false);
},
allTimezoneTypesUsedForStatistics() {
return this.$locale.getAllTimezoneTypesUsedForStatistics();
},
defaultChartType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultChartType;
@@ -123,6 +138,14 @@ export default {
this.settingsStore.setStatisticsDefaultDateRange(value);
}
},
defaultTimezoneType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultTimezoneType;
},
set: function (value) {
this.settingsStore.setStatisticsDefaultTimezoneType(value);
}
},
defaultSortingType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultSortingType;
+24 -1
View File
@@ -8,6 +8,16 @@
<span>{{ $t('Show Amount') }}</span>
<f7-toggle :checked="showAmountInHomePage" @toggle:change="showAmountInHomePage = $event"></f7-toggle>
</f7-list-item>
<f7-list-item
:title="$t('Timezone Used for Statistics')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone Type'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="timezoneUsedForStatisticsInHomePage">
<option :value="timezoneType.type"
:key="timezoneType.type"
v-for="timezoneType in allTimezoneTypesUsedForStatistics">{{ timezoneType.displayName }}</option>
</select>
</f7-list-item>
</f7-list>
<f7-block-title>{{ $t('Transaction List Page') }}</f7-block-title>
@@ -31,10 +41,14 @@
<script>
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/setting.js';
import { useOverviewStore } from '@/stores/overview.js';
export default {
computed: {
...mapStores(useSettingsStore),
...mapStores(useSettingsStore, useOverviewStore),
allTimezoneTypesUsedForStatistics() {
return this.$locale.getAllTimezoneTypesUsedForStatistics();
},
showAmountInHomePage: {
get: function () {
return this.settingsStore.appSettings.showAmountInHomePage;
@@ -43,6 +57,15 @@ export default {
this.settingsStore.setShowAmountInHomePage(value);
}
},
timezoneUsedForStatisticsInHomePage: {
get: function () {
return this.settingsStore.appSettings.timezoneUsedForStatisticsInHomePage;
},
set: function (value) {
this.settingsStore.setTimezoneUsedForStatisticsInHomePage(value);
this.overviewStore.updateTransactionOverviewInvalidState(true);
}
},
showTotalAmountInTransactionListPage: {
get: function () {
return this.settingsStore.appSettings.showTotalAmountInTransactionListPage;
@@ -32,6 +32,16 @@
</select>
</f7-list-item>
<f7-list-item
:title="$t('Timezone Used for Date Range')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone Type'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultTimezoneType">
<option :value="timezoneType.type"
:key="timezoneType.type"
v-for="timezoneType in allTimezoneTypesUsedForStatistics">{{ timezoneType.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item :title="$t('Default Account Filter')" link="/statistic/filter/account?modifyDefault=1"></f7-list-item>
<f7-list-item :title="$t('Default Transaction Category Filter')" link="/statistic/filter/category?modifyDefault=1"></f7-list-item>
@@ -70,6 +80,9 @@ export default {
allDateRanges() {
return this.$locale.getAllDateRanges(false);
},
allTimezoneTypesUsedForStatistics() {
return this.$locale.getAllTimezoneTypesUsedForStatistics();
},
defaultChartType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultChartType;
@@ -94,6 +107,14 @@ export default {
this.settingsStore.setStatisticsDefaultDateRange(value);
}
},
defaultTimezoneType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultTimezoneType;
},
set: function (value) {
this.settingsStore.setStatisticsDefaultTimezoneType(value);
}
},
defaultSortingType: {
get: function () {
return this.settingsStore.appSettings.statistics.defaultSortingType;