support setting timezone type for the time range of statistical data
This commit is contained in:
+16
-2
@@ -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())
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
@@ -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`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '显示月度总金额',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user