tag filter supports selecting both included and excluded tags simultaneously

This commit is contained in:
MaysWind
2025-11-24 02:12:44 +08:00
parent 45be96cf68
commit 6430a52027
45 changed files with 1151 additions and 706 deletions
+1
View File
@@ -116,6 +116,7 @@ func startWebServer(c *core.CliContext) error {
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency) _ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor) _ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter) _ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
_ = v.RegisterValidation("validTagFilter", validators.ValidTagFilter)
_ = v.RegisterValidation("validFiscalYearStart", validators.ValidateFiscalYearStart) _ = v.RegisterValidation("validFiscalYearStart", validators.ValidateFiscalYearStart)
} }
+5 -5
View File
@@ -372,14 +372,14 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
return nil, "", errs.Or(err, errs.ErrOperationFailed) return nil, "", errs.Or(err, errs.ErrOperationFailed)
} }
var allTagIds []int64 noTags := exportTransactionDataReq.TagFilter == models.TransactionNoTagFilterValue
noTags := exportTransactionDataReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.tags.GetTagIds(exportTransactionDataReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(exportTransactionDataReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[data_managements.getExportedFileContent] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[data_managements.getExportedFileContent] parse transaction tag filters error, because %s", err.Error())
return nil, "", errs.Or(err, errs.ErrOperationFailed) return nil, "", errs.Or(err, errs.ErrOperationFailed)
} }
} }
@@ -395,7 +395,7 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(exportTransactionDataReq.MinTime) minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(exportTransactionDataReq.MinTime)
} }
allTransactions, err := a.transactions.GetAllSpecifiedTransactions(c, uid, maxTransactionTime, minTransactionTime, exportTransactionDataReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, exportTransactionDataReq.TagFilterType, exportTransactionDataReq.AmountFilter, exportTransactionDataReq.Keyword, pageCountForDataExport, true) allTransactions, err := a.transactions.GetAllSpecifiedTransactions(c, uid, maxTransactionTime, minTransactionTime, exportTransactionDataReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, exportTransactionDataReq.AmountFilter, exportTransactionDataReq.Keyword, pageCountForDataExport, true)
if err != nil { if err != nil {
log.Errorf(c, "[data_managements.getExportedFileContent] failed to all transactions user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[data_managements.getExportedFileContent] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
+26 -26
View File
@@ -83,19 +83,19 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.WebContext) (any, *err
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
var allTagIds []int64 noTags := transactionCountReq.TagFilter == models.TransactionNoTagFilterValue
noTags := transactionCountReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.transactionTags.GetTagIds(transactionCountReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(transactionCountReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionCountHandler] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionCountHandler] parse transaction filters error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
} }
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionCountReq.TagFilterType, transactionCountReq.AmountFilter, transactionCountReq.Keyword) totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionCountReq.AmountFilter, transactionCountReq.Keyword)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
@@ -151,14 +151,14 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
var allTagIds []int64 noTags := transactionListReq.TagFilter == models.TransactionNoTagFilterValue
noTags := transactionListReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.transactionTags.GetTagIds(transactionListReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(transactionListReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionListHandler] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionListHandler] parse transaction tag filters error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
} }
@@ -166,7 +166,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
var totalCount int64 var totalCount int64
if transactionListReq.WithCount { if transactionListReq.WithCount {
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword) totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
@@ -174,7 +174,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
} }
} }
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true) transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error()) log.Errorf(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
@@ -254,19 +254,19 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
var allTagIds []int64 noTags := transactionListReq.TagFilter == models.TransactionNoTagFilterValue
noTags := transactionListReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.transactionTags.GetTagIds(transactionListReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(transactionListReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionMonthListHandler] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionMonthListHandler] parse transaction tag filters error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
} }
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, allTagIds, noTags, transactionListReq.TagFilterType, transactionListReq.AmountFilter, transactionListReq.Keyword) transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, tagFilters, noTags, transactionListReq.AmountFilter, transactionListReq.Keyword)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error()) log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
@@ -413,20 +413,20 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any,
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
var allTagIds []int64 noTags := statisticReq.TagFilter == models.TransactionNoTagFilterValue
noTags := statisticReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.transactionTags.GetTagIds(statisticReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(statisticReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionStatisticsHandler] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionStatisticsHandler] parse transaction tag filters error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, allTagIds, noTags, statisticReq.TagFilterType, statisticReq.Keyword, utcOffset, statisticReq.UseTransactionTimezone) totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, tagFilters, noTags, statisticReq.Keyword, utcOffset, statisticReq.UseTransactionTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
@@ -481,20 +481,20 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
var allTagIds []int64 noTags := statisticTrendsReq.TagFilter == models.TransactionNoTagFilterValue
noTags := statisticTrendsReq.TagIds == "none" var tagFilters []*models.TransactionTagFilter
if !noTags { if !noTags {
allTagIds, err = a.transactionTags.GetTagIds(statisticTrendsReq.TagIds) tagFilters, err = models.ParseTransactionTagFilter(statisticTrendsReq.TagFilter)
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] get transaction tag ids error, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] parse transaction tag filters error, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, allTagIds, noTags, statisticTrendsReq.TagFilterType, statisticTrendsReq.Keyword, utcOffset, statisticTrendsReq.UseTransactionTimezone) allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, tagFilters, noTags, statisticTrendsReq.Keyword, utcOffset, statisticTrendsReq.UseTransactionTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionStatisticsTrendsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionStatisticsTrendsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
+5
View File
@@ -94,3 +94,8 @@ func GetParameterInvalidHexRGBColorMessage(field string) string {
func GetParameterInvalidAmountFilterMessage(field string) string { func GetParameterInvalidAmountFilterMessage(field string) string {
return fmt.Sprintf("parameter \"%s\" is invalid amount filter", field) return fmt.Sprintf("parameter \"%s\" is invalid amount filter", field)
} }
// GetParameterInvalidTagFilterMessage returns specific error message for invalid tag filter parameter error
func GetParameterInvalidTagFilterMessage(field string) string {
return fmt.Sprintf("parameter \"%s\" is invalid tag filter", field)
}
+2 -2
View File
@@ -153,14 +153,14 @@ func (h *mcpQueryTransactionsToolHandler) Handle(c *core.WebContext, callToolReq
} }
} }
totalCount, err := services.GetTransactionService().GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", queryTransactionsRequest.Keyword) totalCount, err := services.GetTransactionService().GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, "", queryTransactionsRequest.Keyword)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
return nil, nil, err return nil, nil, err
} }
transactions, err := services.GetTransactionService().GetTransactionsByMaxTime(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", queryTransactionsRequest.Keyword, queryTransactionsRequest.Page, queryTransactionsRequest.Count, false, true) transactions, err := services.GetTransactionService().GetTransactionsByMaxTime(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, "", queryTransactionsRequest.Keyword, queryTransactionsRequest.Page, queryTransactionsRequest.Count, false, true)
structuredResponse, response, err := h.createNewMCPQueryTransactionsResponse(c, &queryTransactionsRequest, transactions, totalCount, services.GetAccountService().GetAccountMapByList(allAccounts), services.GetTransactionCategoryService().GetCategoryMapByList(allCategories)) structuredResponse, response, err := h.createNewMCPQueryTransactionsResponse(c, &queryTransactionsRequest, transactions, totalCount, services.GetAccountService().GetAccountMapByList(allAccounts), services.GetTransactionCategoryService().GetCategoryMapByList(allCategories))
if err != nil { if err != nil {
+1 -2
View File
@@ -27,8 +27,7 @@ type ExportTransactionDataRequest struct {
Type TransactionType `form:"type" binding:"min=0,max=4"` Type TransactionType `form:"type" binding:"min=0,max=4"`
CategoryIds string `form:"category_ids"` CategoryIds string `form:"category_ids"`
AccountIds string `form:"account_ids"` AccountIds string `form:"account_ids"`
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"` AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
MaxTime int64 `form:"max_time" binding:"min=0"` // Unix timestamp in seconds MaxTime int64 `form:"max_time" binding:"min=0"` // Unix timestamp in seconds
+59 -10
View File
@@ -104,6 +104,9 @@ func (t TransactionDbType) ToTransactionRelatedAccountType() (TransactionRelated
} }
} }
// TransactionTagFilterValue represents transaction tag filter value for no tag
const TransactionNoTagFilterValue = "none"
// TransactionTagFilterType represents transaction tag filter type // TransactionTagFilterType represents transaction tag filter type
type TransactionTagFilterType byte type TransactionTagFilterType byte
@@ -199,13 +202,17 @@ type TransactionImportProcessRequest struct {
ClientSessionId string `form:"client_session_id"` ClientSessionId string `form:"client_session_id"`
} }
type TransactionTagFilter struct {
TagIds []int64
Type TransactionTagFilterType
}
// TransactionCountRequest represents transaction count request // TransactionCountRequest represents transaction count request
type TransactionCountRequest struct { type TransactionCountRequest struct {
Type TransactionType `form:"type" binding:"min=0,max=4"` Type TransactionType `form:"type" binding:"min=0,max=4"`
CategoryIds string `form:"category_ids"` CategoryIds string `form:"category_ids"`
AccountIds string `form:"account_ids"` AccountIds string `form:"account_ids"`
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"` AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
MaxTime int64 `form:"max_time" binding:"min=0"` // Transaction time sequence id MaxTime int64 `form:"max_time" binding:"min=0"` // Transaction time sequence id
@@ -217,8 +224,7 @@ type TransactionListByMaxTimeRequest struct {
Type TransactionType `form:"type" binding:"min=0,max=4"` Type TransactionType `form:"type" binding:"min=0,max=4"`
CategoryIds string `form:"category_ids"` CategoryIds string `form:"category_ids"`
AccountIds string `form:"account_ids"` AccountIds string `form:"account_ids"`
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"` AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
MaxTime int64 `form:"max_time" binding:"min=0"` // Transaction time sequence id MaxTime int64 `form:"max_time" binding:"min=0"` // Transaction time sequence id
@@ -239,8 +245,7 @@ type TransactionListInMonthByPageRequest struct {
Type TransactionType `form:"type" binding:"min=0,max=4"` Type TransactionType `form:"type" binding:"min=0,max=4"`
CategoryIds string `form:"category_ids"` CategoryIds string `form:"category_ids"`
AccountIds string `form:"account_ids"` AccountIds string `form:"account_ids"`
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"` AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
WithPictures bool `form:"with_pictures"` WithPictures bool `form:"with_pictures"`
@@ -260,8 +265,7 @@ type TransactionReconciliationStatementRequest struct {
type TransactionStatisticRequest struct { type TransactionStatisticRequest struct {
StartTime int64 `form:"start_time" binding:"min=0"` StartTime int64 `form:"start_time" binding:"min=0"`
EndTime int64 `form:"end_time" binding:"min=0"` EndTime int64 `form:"end_time" binding:"min=0"`
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
UseTransactionTimezone bool `form:"use_transaction_timezone"` UseTransactionTimezone bool `form:"use_transaction_timezone"`
} }
@@ -269,8 +273,7 @@ type TransactionStatisticRequest struct {
// TransactionStatisticTrendsRequest represents all parameters of transaction statistic trends request // TransactionStatisticTrendsRequest represents all parameters of transaction statistic trends request
type TransactionStatisticTrendsRequest struct { type TransactionStatisticTrendsRequest struct {
YearMonthRangeRequest YearMonthRangeRequest
TagIds string `form:"tag_ids"` TagFilter string `form:"tag_filter" binding:"validTagFilter"`
TagFilterType TransactionTagFilterType `form:"tag_filter_type" binding:"min=0,max=3"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
UseTransactionTimezone bool `form:"use_transaction_timezone"` UseTransactionTimezone bool `form:"use_transaction_timezone"`
} }
@@ -445,6 +448,52 @@ type TransactionAmountsResponseItemAmountInfo struct {
ExpenseAmount int64 `json:"expenseAmount"` ExpenseAmount int64 `json:"expenseAmount"`
} }
// ParseTransactionTagFilter parses transaction tag filter from string
func ParseTransactionTagFilter(tagFilterStr string) ([]*TransactionTagFilter, error) {
if tagFilterStr == "" || tagFilterStr == TransactionNoTagFilterValue {
return []*TransactionTagFilter{}, nil
}
filters := strings.Split(tagFilterStr, ";")
transactionTagFilters := make([]*TransactionTagFilter, 0, len(filters))
for _, filter := range filters {
tagFilterItem := strings.Split(filter, ":")
if len(tagFilterItem) != 2 {
return nil, errs.ErrFormatInvalid
}
tagFilterType, err := utils.StringToInt(tagFilterItem[0])
if err != nil || (tagFilterType < int(TRANSACTION_TAG_FILTER_HAS_ANY) || tagFilterType > int(TRANSACTION_TAG_FILTER_NOT_HAS_ALL)) {
return nil, errs.ErrFormatInvalid
}
textualTagIds := strings.Split(tagFilterItem[1], ",")
tagIds := make([]int64, 0, len(textualTagIds))
for _, tagIdStr := range textualTagIds {
tagId, err := utils.StringToInt64(tagIdStr)
if err != nil {
return nil, errs.ErrTransactionTagIdInvalid
}
tagIds = append(tagIds, tagId)
}
transactionTagFilter := &TransactionTagFilter{
TagIds: tagIds,
Type: TransactionTagFilterType(tagFilterType),
}
transactionTagFilters = append(transactionTagFilters, transactionTagFilter)
}
return transactionTagFilters, nil
}
// IsEditable returns whether this transaction can be edited // IsEditable returns whether this transaction can be edited
func (t *Transaction) IsEditable(currentUser *User, utcOffset int16, account *Account, relatedAccount *Account) bool { func (t *Transaction) IsEditable(currentUser *User, utcOffset int16, account *Account, relatedAccount *Account) bool {
if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) { if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) {
+95
View File
@@ -9,6 +9,101 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/errs"
) )
func TestParseTransactionTagFilter_EmptyTagFilter(t *testing.T) {
actualValue, err := ParseTransactionTagFilter("")
assert.Nil(t, err)
assert.Equal(t, 0, len(actualValue))
}
func TestParseTransactionTagFilter_NoTag(t *testing.T) {
actualValue, err := ParseTransactionTagFilter("none")
assert.Nil(t, err)
assert.Equal(t, 0, len(actualValue))
}
func TestParseTransactionTagFilter_NoValidFilter(t *testing.T) {
_, err := ParseTransactionTagFilter(";")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
_, err = ParseTransactionTagFilter(";;")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
}
func TestParseTransactionTagFilter_ValidOneFilterInTagFilters(t *testing.T) {
actualValue, err := ParseTransactionTagFilter("0:1")
assert.Nil(t, err)
assert.Equal(t, 1, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_HAS_ANY, actualValue[0].Type)
assert.Equal(t, 1, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1}, actualValue[0].TagIds)
actualValue, err = ParseTransactionTagFilter("0:1,2,3")
assert.Nil(t, err)
assert.Equal(t, 1, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_HAS_ANY, actualValue[0].Type)
assert.Equal(t, 3, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1, 2, 3}, actualValue[0].TagIds)
actualValue, err = ParseTransactionTagFilter("1:1,2,3")
assert.Nil(t, err)
assert.Equal(t, 1, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_HAS_ALL, actualValue[0].Type)
assert.Equal(t, 3, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1, 2, 3}, actualValue[0].TagIds)
actualValue, err = ParseTransactionTagFilter("2:1,2,3")
assert.Nil(t, err)
assert.Equal(t, 1, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_NOT_HAS_ANY, actualValue[0].Type)
assert.Equal(t, 3, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1, 2, 3}, actualValue[0].TagIds)
actualValue, err = ParseTransactionTagFilter("3:1,2,3")
assert.Nil(t, err)
assert.Equal(t, 1, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_NOT_HAS_ALL, actualValue[0].Type)
assert.Equal(t, 3, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1, 2, 3}, actualValue[0].TagIds)
}
func TestParseTransactionTagFilter_InvalidTagFilterType(t *testing.T) {
_, err := ParseTransactionTagFilter("a:1,2,3")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
_, err = ParseTransactionTagFilter("-1:1,2,3")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
_, err = ParseTransactionTagFilter("4:1,2,3")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
}
func TestParseTransactionTagFilter_NoTagIdsInFilter(t *testing.T) {
_, err := ParseTransactionTagFilter("0")
assert.EqualError(t, err, errs.ErrFormatInvalid.Message)
_, err = ParseTransactionTagFilter("0:")
assert.EqualError(t, err, errs.ErrTransactionTagIdInvalid.Message)
}
func TestParseTransactionTagFilter_InvalidTagIdsInFilter(t *testing.T) {
_, err := ParseTransactionTagFilter("0:abc")
assert.EqualError(t, err, errs.ErrTransactionTagIdInvalid.Message)
}
func TestParseTransactionTagFilter_ValidTwoFilterInTagFilters(t *testing.T) {
actualValue, err := ParseTransactionTagFilter("0:1,2,3;2:4,5,6")
assert.Nil(t, err)
assert.Equal(t, 2, len(actualValue))
assert.Equal(t, TRANSACTION_TAG_FILTER_HAS_ANY, actualValue[0].Type)
assert.Equal(t, 3, len(actualValue[0].TagIds))
assert.Equal(t, []int64{1, 2, 3}, actualValue[0].TagIds)
assert.Equal(t, TRANSACTION_TAG_FILTER_NOT_HAS_ANY, actualValue[1].Type)
assert.Equal(t, 3, len(actualValue[1].TagIds))
assert.Equal(t, []int64{4, 5, 6}, actualValue[1].TagIds)
}
func TestTransactionAmountsRequestGetTransactionAmountsRequestItems(t *testing.T) { func TestTransactionAmountsRequestGetTransactionAmountsRequestItems(t *testing.T) {
transactionAmountsRequest := &TransactionAmountsRequest{ transactionAmountsRequest := &TransactionAmountsRequest{
Query: "name1_1234567890_1234567891|name2_1234567900_1234567901", Query: "name1_1234567890_1234567891|name2_1234567900_1234567901",
+43 -30
View File
@@ -76,11 +76,11 @@ func (s *TransactionService) GetAllTransactions(c core.Context, uid int64, pageC
// GetAllTransactionsByMaxTime returns all transactions before given time // GetAllTransactionsByMaxTime returns all transactions before given time
func (s *TransactionService) GetAllTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) { func (s *TransactionService) GetAllTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, count, false, noDuplicated) return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, "", "", 1, count, false, noDuplicated)
} }
// GetAllSpecifiedTransactions returns all transactions that match given conditions // GetAllSpecifiedTransactions returns all transactions that match given conditions
func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string, pageCount int32, noDuplicated bool) ([]*models.Transaction, error) { func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagFilters []*models.TransactionTagFilter, noTags bool, amountFilter string, keyword string, pageCount int32, noDuplicated bool) ([]*models.Transaction, error) {
if maxTransactionTime <= 0 { if maxTransactionTime <= 0 {
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix()) maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
} }
@@ -88,7 +88,7 @@ func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int
var allTransactions []*models.Transaction var allTransactions []*models.Transaction
for maxTransactionTime > 0 { for maxTransactionTime > 0 {
transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, tagIds, noTags, tagFilterType, amountFilter, keyword, 1, pageCount, false, noDuplicated) transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, tagFilters, noTags, amountFilter, keyword, 1, pageCount, false, noDuplicated)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -116,7 +116,7 @@ func (s *TransactionService) GetAllTransactionsInOneAccountWithAccountBalanceByM
var allTransactions []*models.Transaction var allTransactions []*models.Transaction
for maxTransactionTime > 0 { for maxTransactionTime > 0 {
transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, []int64{accountId}, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, pageCount, false, true) transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, []int64{accountId}, nil, false, "", "", 1, pageCount, false, true)
if err != nil { if err != nil {
return nil, 0, 0, 0, 0, err return nil, 0, 0, 0, 0, err
@@ -207,7 +207,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.
var allTransactions []*models.Transaction var allTransactions []*models.Transaction
for maxTransactionTime > 0 { for maxTransactionTime > 0 {
transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, pageCountForLoadTransactionAmounts, false, false) transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, "", "", 1, pageCountForLoadTransactionAmounts, false, false)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -324,7 +324,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.
} }
// GetTransactionsByMaxTime returns transactions before given time // GetTransactionsByMaxTime returns transactions before given time
func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) { func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagFilters []*models.TransactionTagFilter, noTags bool, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
@@ -358,9 +358,9 @@ func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64,
actualCount++ actualCount++
} }
condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagIds, amountFilter, keyword, noDuplicated) condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagFilters, amountFilter, keyword, noDuplicated)
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...) sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagIds, noTags, tagFilterType) sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagFilters, noTags)
err = sess.Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions) err = sess.Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
@@ -368,7 +368,7 @@ func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64,
} }
// GetTransactionsInMonthByPage returns all transactions in given year and month // GetTransactionsInMonthByPage returns all transactions in given year and month
func (s *TransactionService) GetTransactionsInMonthByPage(c core.Context, uid int64, year int32, month int32, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string) ([]*models.Transaction, error) { func (s *TransactionService) GetTransactionsInMonthByPage(c core.Context, uid int64, year int32, month int32, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagFilters []*models.TransactionTagFilter, noTags bool, amountFilter string, keyword string) ([]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
@@ -392,9 +392,9 @@ func (s *TransactionService) GetTransactionsInMonthByPage(c core.Context, uid in
var transactions []*models.Transaction var transactions []*models.Transaction
condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagIds, amountFilter, keyword, true) condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagFilters, amountFilter, keyword, true)
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...) sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagIds, noTags, tagFilterType) sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagFilters, noTags)
err = sess.OrderBy("transaction_time desc").Find(&transactions) err = sess.OrderBy("transaction_time desc").Find(&transactions)
@@ -437,11 +437,11 @@ func (s *TransactionService) GetTransactionByTransactionId(c core.Context, uid i
// GetAllTransactionCount returns total count of transactions // GetAllTransactionCount returns total count of transactions
func (s *TransactionService) GetAllTransactionCount(c core.Context, uid int64) (int64, error) { func (s *TransactionService) GetAllTransactionCount(c core.Context, uid int64) (int64, error) {
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "") return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, nil, false, "", "")
} }
// GetTransactionCount returns count of transactions // GetTransactionCount returns count of transactions
func (s *TransactionService) GetTransactionCount(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string) (int64, error) { func (s *TransactionService) GetTransactionCount(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagFilters []*models.TransactionTagFilter, noTags bool, amountFilter string, keyword string) (int64, error) {
if uid <= 0 { if uid <= 0 {
return 0, errs.ErrUserIdInvalid return 0, errs.ErrUserIdInvalid
} }
@@ -457,9 +457,9 @@ func (s *TransactionService) GetTransactionCount(c core.Context, uid int64, maxT
} }
} }
condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagIds, amountFilter, keyword, true) condition, conditionParams := s.buildTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionDbType, categoryIds, accountIds, tagFilters, amountFilter, keyword, true)
sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...) sess := s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...)
sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagIds, noTags, tagFilterType) sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagFilters, noTags)
return sess.Count(&models.Transaction{}) return sess.Count(&models.Transaction{})
} }
@@ -1730,7 +1730,7 @@ func (s *TransactionService) DeleteAllTransactionsOfAccount(c core.Context, uid
return errs.ErrAccountIdInvalid return errs.ErrAccountIdInvalid
} }
transactions, err := s.GetAllSpecifiedTransactions(c, uid, 0, 0, 0, nil, []int64{accountId}, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", pageCount, true) transactions, err := s.GetAllSpecifiedTransactions(c, uid, 0, 0, 0, nil, []int64{accountId}, nil, false, "", "", pageCount, true)
if err != nil { if err != nil {
return err return err
@@ -1923,7 +1923,7 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c core.Context, ui
} }
// GetAccountsAndCategoriesTotalInflowAndOutflow returns the every accounts and categories total inflows and outflows amount by specific date range // GetAccountsAndCategoriesTotalInflowAndOutflow returns the every accounts and categories total inflows and outflows amount by specific date range
func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, keyword string, utcOffset int16, useTransactionTimezone bool) ([]*models.Transaction, error) { func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, utcOffset int16, useTransactionTimezone bool) ([]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
@@ -1979,7 +1979,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c cor
} }
sess := s.UserDataDB(uid).NewSession(c).Select("type, category_id, account_id, related_account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...) sess := s.UserDataDB(uid).NewSession(c).Select("type, category_id, account_id, related_account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...)
sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagIds, noTags, tagFilterType) sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagFilters, noTags)
err := sess.Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions) err := sess.Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
@@ -2046,7 +2046,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c cor
} }
// GetAccountsAndCategoriesMonthlyInflowAndOutflow returns the every accounts monthly inflows and outflows amount by specific date range // GetAccountsAndCategoriesMonthlyInflowAndOutflow returns the every accounts monthly inflows and outflows amount by specific date range
func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c core.Context, uid int64, startYear int32, startMonth int32, endYear int32, endMonth int32, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, keyword string, utcOffset int16, useTransactionTimezone bool) (map[int32][]*models.Transaction, error) { func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c core.Context, uid int64, startYear int32, startMonth int32, endYear int32, endMonth int32, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, utcOffset int16, useTransactionTimezone bool) (map[int32][]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
@@ -2107,7 +2107,7 @@ func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c c
} }
sess := s.UserDataDB(uid).NewSession(c).Select("type, category_id, account_id, related_account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...) sess := s.UserDataDB(uid).NewSession(c).Select("type, category_id, account_id, related_account_id, transaction_time, timezone_utc_offset, amount").Where(finalCondition, finalConditionParams...)
sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagIds, noTags, tagFilterType) sess = s.appendFilterTagIdsConditionToQuery(sess, uid, maxTransactionTime, minTransactionTime, tagFilters, noTags)
err := sess.Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions) err := sess.Limit(pageCountForLoadTransactionAmounts, 0).OrderBy("transaction_time desc").Find(&transactions)
@@ -2460,7 +2460,7 @@ func (s *TransactionService) doCreateTransaction(c core.Context, database *datas
return err return err
} }
func (s *TransactionService) buildTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionDbType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, amountFilter string, keyword string, noDuplicated bool) (string, []any) { func (s *TransactionService) buildTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionDbType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagFilters []*models.TransactionTagFilter, amountFilter string, keyword string, noDuplicated bool) (string, []any) {
condition := "uid=? AND deleted=?" condition := "uid=? AND deleted=?"
conditionParams := make([]any, 0, 16) conditionParams := make([]any, 0, 16)
conditionParams = append(conditionParams, uid) conditionParams = append(conditionParams, uid)
@@ -2616,7 +2616,8 @@ func (s *TransactionService) buildTransactionQueryCondition(uid int64, maxTransa
return condition, conditionParams return condition, conditionParams
} }
func (s *TransactionService) appendFilterTagIdsConditionToQuery(sess *xorm.Session, uid int64, maxTransactionTime int64, minTransactionTime int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType) *xorm.Session { func (s *TransactionService) appendFilterTagIdsConditionToQuery(sess *xorm.Session, uid int64, maxTransactionTime int64, minTransactionTime int64, tagFilters []*models.TransactionTagFilter, noTags bool) *xorm.Session {
if noTags {
subQueryCondition := builder.And(builder.Eq{"uid": uid}, builder.Eq{"deleted": false}) subQueryCondition := builder.And(builder.Eq{"uid": uid}, builder.Eq{"deleted": false})
if maxTransactionTime > 0 { if maxTransactionTime > 0 {
@@ -2627,28 +2628,40 @@ func (s *TransactionService) appendFilterTagIdsConditionToQuery(sess *xorm.Sessi
subQueryCondition = subQueryCondition.And(builder.Gte{"transaction_time": minTransactionTime}) subQueryCondition = subQueryCondition.And(builder.Gte{"transaction_time": minTransactionTime})
} }
if noTags {
subQuery := builder.Select("transaction_id").From("transaction_tag_index").Where(subQueryCondition) subQuery := builder.Select("transaction_id").From("transaction_tag_index").Where(subQueryCondition)
sess.NotIn("transaction_id", subQuery).NotIn("related_id", subQuery) sess.NotIn("transaction_id", subQuery).NotIn("related_id", subQuery)
return sess return sess
} }
if len(tagIds) < 1 { if len(tagFilters) < 1 {
return sess return sess
} }
subQueryCondition = subQueryCondition.And(builder.In("tag_id", tagIds)) for i := 0; i < len(tagFilters); i++ {
subQuery := builder.Select("transaction_id").From("transaction_tag_index").Where(subQueryCondition) tagFilter := tagFilters[i]
subQueryCondition := builder.And(builder.Eq{"uid": uid}, builder.Eq{"deleted": false})
if tagFilterType == models.TRANSACTION_TAG_FILTER_HAS_ALL || tagFilterType == models.TRANSACTION_TAG_FILTER_NOT_HAS_ALL { if maxTransactionTime > 0 {
subQuery = subQuery.GroupBy("transaction_id").Having(fmt.Sprintf("COUNT(DISTINCT tag_id) >= %d", len(tagIds))) subQueryCondition = subQueryCondition.And(builder.Lte{"transaction_time": maxTransactionTime})
} }
if tagFilterType == models.TRANSACTION_TAG_FILTER_HAS_ANY || tagFilterType == models.TRANSACTION_TAG_FILTER_HAS_ALL { if minTransactionTime > 0 {
subQueryCondition = subQueryCondition.And(builder.Gte{"transaction_time": minTransactionTime})
}
subQueryCondition = subQueryCondition.And(builder.In("tag_id", tagFilter.TagIds))
subQuery := builder.Select("transaction_id").From("transaction_tag_index").Where(subQueryCondition)
if tagFilter.Type == models.TRANSACTION_TAG_FILTER_HAS_ALL || tagFilter.Type == models.TRANSACTION_TAG_FILTER_NOT_HAS_ALL {
subQuery = subQuery.GroupBy("transaction_id").Having(fmt.Sprintf("COUNT(DISTINCT tag_id) >= %d", len(tagFilter.TagIds)))
}
if tagFilter.Type == models.TRANSACTION_TAG_FILTER_HAS_ANY || tagFilter.Type == models.TRANSACTION_TAG_FILTER_HAS_ALL {
sess.And(builder.Or(builder.In("transaction_id", subQuery), builder.In("related_id", subQuery))) sess.And(builder.Or(builder.In("transaction_id", subQuery), builder.In("related_id", subQuery)))
} else if tagFilterType == models.TRANSACTION_TAG_FILTER_NOT_HAS_ANY || tagFilterType == models.TRANSACTION_TAG_FILTER_NOT_HAS_ALL { } else if tagFilter.Type == models.TRANSACTION_TAG_FILTER_NOT_HAS_ANY || tagFilter.Type == models.TRANSACTION_TAG_FILTER_NOT_HAS_ALL {
sess.NotIn("transaction_id", subQuery).NotIn("related_id", subQuery) sess.NotIn("transaction_id", subQuery).NotIn("related_id", subQuery)
} }
}
return sess return sess
} }
+2
View File
@@ -186,6 +186,8 @@ func getValidationErrorText(err validator.FieldError) string {
return errs.GetParameterInvalidHexRGBColorMessage(fieldName) return errs.GetParameterInvalidHexRGBColorMessage(fieldName)
case "validAmountFilter": case "validAmountFilter":
return errs.GetParameterInvalidAmountFilterMessage(fieldName) return errs.GetParameterInvalidAmountFilterMessage(fieldName)
case "validTagFilter":
return errs.GetParameterInvalidTagFilterMessage(fieldName)
} }
return errs.GetParameterInvalidMessage(fieldName) return errs.GetParameterInvalidMessage(fieldName)
+26
View File
@@ -0,0 +1,26 @@
package validators
import (
"github.com/go-playground/validator/v10"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
// ValidTagFilter returns whether the given tag filter is valid
func ValidTagFilter(fl validator.FieldLevel) bool {
if value, ok := fl.Field().Interface().(string); ok {
if value == "" {
return true
}
if value == models.TransactionNoTagFilterValue {
return true
}
_, err := models.ParseTransactionTagFilter(value)
return err == nil
}
return false
}
+104
View File
@@ -0,0 +1,104 @@
package validators
import (
"testing"
"github.com/go-playground/validator/v10"
"github.com/stretchr/testify/assert"
)
func TestEmptyTagFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("", "validTagFilter")
assert.Nil(t, err)
}
func TestNoTag(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("none", "validTagFilter")
assert.Nil(t, err)
}
func TestNoValidFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var(";", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var(";;", "validTagFilter")
assert.NotNil(t, err)
}
func TestValidOneFilterInTagFilters(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:1", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("0:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("1:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("2:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("3:1,2,3", "validTagFilter")
assert.Nil(t, err)
}
func TestInvalidTagFilterType(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("a:1,2,3", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("-1:1,2,3", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("4:1,2,3", "validTagFilter")
assert.NotNil(t, err)
}
func TestNoTagIdsInFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("0:", "validTagFilter")
assert.NotNil(t, err)
}
func TestInvalidTagIdsInFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:abc", "validTagFilter")
assert.NotNil(t, err)
}
func TestValidTwoFilterInTagFilters(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:1,2,3;2:4,5,6", "validTagFilter")
assert.Nil(t, err)
}
+8
View File
@@ -162,5 +162,13 @@ export const PARAMETERIZED_ERRORS: ParameterizedError[] = [
field: 'parameter', field: 'parameter',
localized: true localized: true
}] }]
},
{
localeKey: 'parameter invalid tag filter',
regex: /^parameter "(\w+)" is invalid tag filter$/,
parameters: [{
field: 'parameter',
localized: true
}]
} }
]; ];
+10 -6
View File
@@ -40,13 +40,12 @@ export class TransactionEditScopeType implements TypeAndName {
export class TransactionTagFilterType implements TypeAndName { export class TransactionTagFilterType implements TypeAndName {
private static readonly allInstances: TransactionTagFilterType[] = []; private static readonly allInstances: TransactionTagFilterType[] = [];
private static readonly allInstancesByType: Record<number, TransactionTagFilterType> = {};
public static readonly HasAny = new TransactionTagFilterType(0, 'With Any Selected Tags'); public static readonly HasAny = new TransactionTagFilterType(0, 'Include Any Selected Tags');
public static readonly HasAll = new TransactionTagFilterType(1, 'With All Selected Tags'); public static readonly HasAll = new TransactionTagFilterType(1, 'Include All Selected Tags');
public static readonly NotHasAny = new TransactionTagFilterType(2, 'Without Any Selected Tags'); public static readonly NotHasAny = new TransactionTagFilterType(2, 'Exclude Any Selected Tags');
public static readonly NotHasAll = new TransactionTagFilterType(3, 'Without All Selected Tags'); public static readonly NotHasAll = new TransactionTagFilterType(3, 'Exclude All Selected Tags');
public static readonly Default = TransactionTagFilterType.HasAny;
public readonly type: number; public readonly type: number;
public readonly name: string; public readonly name: string;
@@ -56,9 +55,14 @@ export class TransactionTagFilterType implements TypeAndName {
this.name = name; this.name = name;
TransactionTagFilterType.allInstances.push(this); TransactionTagFilterType.allInstances.push(this);
TransactionTagFilterType.allInstancesByType[type] = this;
} }
public static values(): TransactionTagFilterType[] { public static values(): TransactionTagFilterType[] {
return TransactionTagFilterType.allInstances; return TransactionTagFilterType.allInstances;
} }
public static parse(type: number): TransactionTagFilterType | undefined {
return TransactionTagFilterType.allInstancesByType[type];
}
} }
+11 -16
View File
@@ -417,11 +417,12 @@ export default {
let params = ''; let params = '';
if (req) { if (req) {
const tagFilter = encodeURIComponent(req.tagFilter);
const amountFilter = encodeURIComponent(req.amountFilter); const amountFilter = encodeURIComponent(req.amountFilter);
const keyword = encodeURIComponent(req.keyword); const keyword = encodeURIComponent(req.keyword);
params = `max_time=${req.maxTime}&min_time=${req.minTime}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_ids=${req.tagIds}&tag_filter_type=${req.tagFilterType}&amount_filter=${amountFilter}&keyword=${keyword}`; params = `max_time=${req.maxTime}&min_time=${req.minTime}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_filter=${tagFilter}&amount_filter=${amountFilter}&keyword=${keyword}`;
} else { } else {
params = 'max_time=0&min_time=0&type=0&category_ids=&account_ids=&tag_ids=&tag_filter_type=0&amount_filter=&keyword='; params = 'max_time=0&min_time=0&type=0&category_ids=&account_ids=&tag_filter=&amount_filter=&keyword=';
} }
if (fileType === 'csv') { if (fileType === 'csv') {
@@ -476,14 +477,16 @@ export default {
return axios.post<ApiResponse<boolean>>('v1/accounts/sub_account/delete.json', req); return axios.post<ApiResponse<boolean>>('v1/accounts/sub_account/delete.json', req);
}, },
getTransactions: (req: TransactionListByMaxTimeRequest): ApiResponsePromise<TransactionInfoPageWrapperResponse> => { getTransactions: (req: TransactionListByMaxTimeRequest): ApiResponsePromise<TransactionInfoPageWrapperResponse> => {
const tagFilter = encodeURIComponent(req.tagFilter);
const amountFilter = encodeURIComponent(req.amountFilter); const amountFilter = encodeURIComponent(req.amountFilter);
const keyword = encodeURIComponent(req.keyword); const keyword = encodeURIComponent(req.keyword);
return axios.get<ApiResponse<TransactionInfoPageWrapperResponse>>(`v1/transactions/list.json?max_time=${req.maxTime}&min_time=${req.minTime}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_ids=${req.tagIds}&tag_filter_type=${req.tagFilterType}&amount_filter=${amountFilter}&keyword=${keyword}&count=${req.count}&page=${req.page}&with_count=${req.withCount}&trim_account=true&trim_category=true&trim_tag=true`); return axios.get<ApiResponse<TransactionInfoPageWrapperResponse>>(`v1/transactions/list.json?max_time=${req.maxTime}&min_time=${req.minTime}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_filter=${tagFilter}&amount_filter=${amountFilter}&keyword=${keyword}&count=${req.count}&page=${req.page}&with_count=${req.withCount}&trim_account=true&trim_category=true&trim_tag=true`);
}, },
getAllTransactionsByMonth: (req: TransactionListInMonthByPageRequest): ApiResponsePromise<TransactionInfoPageWrapperResponse2> => { getAllTransactionsByMonth: (req: TransactionListInMonthByPageRequest): ApiResponsePromise<TransactionInfoPageWrapperResponse2> => {
const tagFilter = encodeURIComponent(req.tagFilter);
const amountFilter = encodeURIComponent(req.amountFilter); const amountFilter = encodeURIComponent(req.amountFilter);
const keyword = encodeURIComponent(req.keyword); const keyword = encodeURIComponent(req.keyword);
return axios.get<ApiResponse<TransactionInfoPageWrapperResponse2>>(`v1/transactions/list/by_month.json?year=${req.year}&month=${req.month}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_ids=${req.tagIds}&tag_filter_type=${req.tagFilterType}&amount_filter=${amountFilter}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`); return axios.get<ApiResponse<TransactionInfoPageWrapperResponse2>>(`v1/transactions/list/by_month.json?year=${req.year}&month=${req.month}&type=${req.type}&category_ids=${req.categoryIds}&account_ids=${req.accountIds}&tag_filter=${tagFilter}&amount_filter=${amountFilter}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
}, },
getReconciliationStatements: (req: TransactionReconciliationStatementRequest): ApiResponsePromise<TransactionReconciliationStatementResponse> => { getReconciliationStatements: (req: TransactionReconciliationStatementRequest): ApiResponsePromise<TransactionReconciliationStatementResponse> => {
return axios.get<ApiResponse<TransactionReconciliationStatementResponse>>(`v1/transactions/reconciliation_statements.json?account_id=${req.accountId}&start_time=${req.startTime}&end_time=${req.endTime}`); return axios.get<ApiResponse<TransactionReconciliationStatementResponse>>(`v1/transactions/reconciliation_statements.json?account_id=${req.accountId}&start_time=${req.startTime}&end_time=${req.endTime}`);
@@ -499,12 +502,8 @@ export default {
queryParams.push(`end_time=${req.endTime}`); queryParams.push(`end_time=${req.endTime}`);
} }
if (req.tagIds) { if (req.tagFilter) {
queryParams.push(`tag_ids=${req.tagIds}`); queryParams.push(`tag_filter=${encodeURIComponent(req.tagFilter)}`);
}
if (req.tagFilterType) {
queryParams.push(`tag_filter_type=${req.tagFilterType}`);
} }
if (req.keyword) { if (req.keyword) {
@@ -524,12 +523,8 @@ export default {
queryParams.push(`end_year_month=${req.endYearMonth}`); queryParams.push(`end_year_month=${req.endYearMonth}`);
} }
if (req.tagIds) { if (req.tagFilter) {
queryParams.push(`tag_ids=${req.tagIds}`); queryParams.push(`tag_filter=${encodeURIComponent(req.tagFilter)}`);
}
if (req.tagFilterType) {
queryParams.push(`tag_filter_type=${req.tagFilterType}`);
} }
if (req.keyword) { if (req.keyword) {
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Saldozeit", "balanceTime": "Saldozeit",
"startTime": "Startzeit", "startTime": "Startzeit",
"endTime": "Endzeit", "endTime": "Endzeit",
"tagFilterType": "Tag-Filtertyp", "tagFilter": "Tag Filter",
"amountFilter": "Betragsfilter", "amountFilter": "Betragsfilter",
"sourceAccountId": "Quellkonto-ID", "sourceAccountId": "Quellkonto-ID",
"destinationAccountId": "Zielkonto-ID", "destinationAccountId": "Zielkonto-ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} hat ein ungültiges Format", "parameter invalid email format": "{parameter} hat ein ungültiges Format",
"parameter invalid currency": "{parameter} hat ein ungültiges Format", "parameter invalid currency": "{parameter} hat ein ungültiges Format",
"parameter invalid color": "{parameter} hat ein ungültiges Format", "parameter invalid color": "{parameter} hat ein ungültiges Format",
"parameter invalid amount filter": "{parameter} hat ein ungültiges Format" "parameter invalid amount filter": "{parameter} hat ein ungültiges Format",
"parameter invalid tag filter": "{parameter} hat ein ungültiges Format"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Auto detect", "Auto detect": "Auto detect",
"Miscellaneous": "Verschiedenes", "Miscellaneous": "Verschiedenes",
"Default": "Standard", "Default": "Standard",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Fertig", "Done": "Fertig",
"Continue": "Weiter", "Continue": "Weiter",
"Previous": "Zurück", "Previous": "Zurück",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Auswahl auf dieser Seite umkehren", "Invert Selection in This Page": "Auswahl auf dieser Seite umkehren",
"Select All Valid Items": "Alle gültigen Elemente auswählen", "Select All Valid Items": "Alle gültigen Elemente auswählen",
"Select All Invalid Items": "Alle ungültigen Elemente auswählen", "Select All Invalid Items": "Alle ungültigen Elemente auswählen",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Zurück", "Back": "Zurück",
"Load More": "Mehr laden", "Load More": "Mehr laden",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Zielkonto", "Destination Account": "Zielkonto",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Ohne Tags", "Without Tags": "Ohne Tags",
"With Any Selected Tags": "Mit ausgewählten Tags", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Mit allen ausgewählten Tags", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Ohne ausgewählte Tags", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Ohne alle ausgewählten Tags", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Mehrere Tags", "Multiple Tags": "Mehrere Tags",
"Transaction Time": "Transaktionszeit", "Transaction Time": "Transaktionszeit",
"Scheduled Transaction Frequency": "Häufigkeit der geplanten Transaktion", "Scheduled Transaction Frequency": "Häufigkeit der geplanten Transaktion",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Balance Time", "balanceTime": "Balance Time",
"startTime": "Start Time", "startTime": "Start Time",
"endTime": "End Time", "endTime": "End Time",
"tagFilterType": "Tag Filter Type", "tagFilter": "Tag Filter",
"amountFilter": "Amount Filter", "amountFilter": "Amount Filter",
"sourceAccountId": "Source Account ID", "sourceAccountId": "Source Account ID",
"destinationAccountId": "Destination Account ID", "destinationAccountId": "Destination Account ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} is invalid format", "parameter invalid email format": "{parameter} is invalid format",
"parameter invalid currency": "{parameter} is invalid format", "parameter invalid currency": "{parameter} is invalid format",
"parameter invalid color": "{parameter} is invalid format", "parameter invalid color": "{parameter} is invalid format",
"parameter invalid amount filter": "{parameter} is invalid format" "parameter invalid amount filter": "{parameter} is invalid format",
"parameter invalid tag filter": "{parameter} is invalid format"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Auto detect", "Auto detect": "Auto detect",
"Miscellaneous": "Miscellaneous", "Miscellaneous": "Miscellaneous",
"Default": "Default", "Default": "Default",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Done", "Done": "Done",
"Continue": "Continue", "Continue": "Continue",
"Previous": "Previous", "Previous": "Previous",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Invert Selection in This Page", "Invert Selection in This Page": "Invert Selection in This Page",
"Select All Valid Items": "Select All Valid Items", "Select All Valid Items": "Select All Valid Items",
"Select All Invalid Items": "Select All Invalid Items", "Select All Invalid Items": "Select All Invalid Items",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Back", "Back": "Back",
"Load More": "Load More", "Load More": "Load More",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Destination Account", "Destination Account": "Destination Account",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Without Tags", "Without Tags": "Without Tags",
"With Any Selected Tags": "With Any Selected Tags", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "With All Selected Tags", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Without Any Selected Tags", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Without All Selected Tags", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Multiple Tags", "Multiple Tags": "Multiple Tags",
"Transaction Time": "Transaction Time", "Transaction Time": "Transaction Time",
"Scheduled Transaction Frequency": "Scheduled Transaction Frequency", "Scheduled Transaction Frequency": "Scheduled Transaction Frequency",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Tiempo de equilibrio", "balanceTime": "Tiempo de equilibrio",
"startTime": "Hora de inicio", "startTime": "Hora de inicio",
"endTime": "Hora de finalización", "endTime": "Hora de finalización",
"tagFilterType": "Tipo de filtro de etiquetas", "tagFilter": "Tag Filter",
"amountFilter": "Filtro de cantidad", "amountFilter": "Filtro de cantidad",
"sourceAccountId": "ID de cuenta de origen", "sourceAccountId": "ID de cuenta de origen",
"destinationAccountId": "ID de cuenta de destino", "destinationAccountId": "ID de cuenta de destino",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} es un formato no válido", "parameter invalid email format": "{parameter} es un formato no válido",
"parameter invalid currency": "{parameter} es un formato no válido", "parameter invalid currency": "{parameter} es un formato no válido",
"parameter invalid color": "{parameter} es un formato no válido", "parameter invalid color": "{parameter} es un formato no válido",
"parameter invalid amount filter": "{parameter} es un formato no válido" "parameter invalid amount filter": "{parameter} es un formato no válido",
"parameter invalid tag filter": "{parameter} es un formato no válido"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Auto detect", "Auto detect": "Auto detect",
"Miscellaneous": "Misceláneas", "Miscellaneous": "Misceláneas",
"Default": "Por defecto", "Default": "Por defecto",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Hecho", "Done": "Hecho",
"Continue": "Continuar", "Continue": "Continuar",
"Previous": "Anterior", "Previous": "Anterior",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Invertir selección en esta página", "Invert Selection in This Page": "Invertir selección en esta página",
"Select All Valid Items": "Seleccionar todos los artículos válidos", "Select All Valid Items": "Seleccionar todos los artículos válidos",
"Select All Invalid Items": "Seleccionar todos los artículos no válidos", "Select All Invalid Items": "Seleccionar todos los artículos no válidos",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Atrás", "Back": "Atrás",
"Load More": "Cargar más", "Load More": "Cargar más",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Cuenta de destino", "Destination Account": "Cuenta de destino",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Sin Etiquetas", "Without Tags": "Sin Etiquetas",
"With Any Selected Tags": "Con alguna de las etiquetas seleccionada", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Con todas las etiquetas seleccionadas", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Sin alguna de las etiquetas seleccionadas", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Sin todas las etiquetas seleccionadas", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Múltiples etiquetas", "Multiple Tags": "Múltiples etiquetas",
"Transaction Time": "Tiempo de transacción", "Transaction Time": "Tiempo de transacción",
"Scheduled Transaction Frequency": "Frecuencia de transacciones programadas", "Scheduled Transaction Frequency": "Frecuencia de transacciones programadas",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Heure du solde", "balanceTime": "Heure du solde",
"startTime": "Heure de début", "startTime": "Heure de début",
"endTime": "Heure de fin", "endTime": "Heure de fin",
"tagFilterType": "Type de filtre d'étiquette", "tagFilter": "Tag Filter",
"amountFilter": "Filtre de montant", "amountFilter": "Filtre de montant",
"sourceAccountId": "ID du compte source", "sourceAccountId": "ID du compte source",
"destinationAccountId": "ID du compte de destination", "destinationAccountId": "ID du compte de destination",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} a un format invalide", "parameter invalid email format": "{parameter} a un format invalide",
"parameter invalid currency": "{parameter} a un format invalide", "parameter invalid currency": "{parameter} a un format invalide",
"parameter invalid color": "{parameter} a un format invalide", "parameter invalid color": "{parameter} a un format invalide",
"parameter invalid amount filter": "{parameter} a un format invalide" "parameter invalid amount filter": "{parameter} a un format invalide",
"parameter invalid tag filter": "{parameter} a un format invalide"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Détection automatique", "Auto detect": "Détection automatique",
"Miscellaneous": "Divers", "Miscellaneous": "Divers",
"Default": "Par défaut", "Default": "Par défaut",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Terminé", "Done": "Terminé",
"Continue": "Continuer", "Continue": "Continuer",
"Previous": "Précédent", "Previous": "Précédent",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Inverser la sélection dans cette page", "Invert Selection in This Page": "Inverser la sélection dans cette page",
"Select All Valid Items": "Sélectionner tous les éléments valides", "Select All Valid Items": "Sélectionner tous les éléments valides",
"Select All Invalid Items": "Sélectionner tous les éléments invalides", "Select All Invalid Items": "Sélectionner tous les éléments invalides",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Retour", "Back": "Retour",
"Load More": "Charger plus", "Load More": "Charger plus",
"Export Results": "Exporter les résultats", "Export Results": "Exporter les résultats",
@@ -1802,10 +1811,10 @@
"Destination Account": "Compte de destination", "Destination Account": "Compte de destination",
"Transaction Tag": "Étiquette de transaction", "Transaction Tag": "Étiquette de transaction",
"Without Tags": "Sans étiquettes", "Without Tags": "Sans étiquettes",
"With Any Selected Tags": "Avec n'importe laquelle des étiquettes sélectionnées", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Avec toutes les étiquettes sélectionnées", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Sans aucune des étiquettes sélectionnées", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Sans toutes les étiquettes sélectionnées", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Étiquettes multiples", "Multiple Tags": "Étiquettes multiples",
"Transaction Time": "Heure de transaction", "Transaction Time": "Heure de transaction",
"Scheduled Transaction Frequency": "Fréquence de transaction programmée", "Scheduled Transaction Frequency": "Fréquence de transaction programmée",
+1 -3
View File
@@ -122,8 +122,7 @@ import {
} from '@/core/category.ts'; } from '@/core/category.ts';
import { import {
TransactionEditScopeType, TransactionEditScopeType
TransactionTagFilterType
} from '@/core/transaction.ts'; } from '@/core/transaction.ts';
import { import {
@@ -2357,7 +2356,6 @@ export function useI18n() {
getAllStatisticsDateAggregationTypes: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, true), getAllStatisticsDateAggregationTypes: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, true),
getAllStatisticsDateAggregationTypesWithShortName: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, false), getAllStatisticsDateAggregationTypesWithShortName: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, false),
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()), getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()), getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
getAllImportTransactionColumnTypes: () => getLocalizedDisplayNameAndType(ImportTransactionColumnType.values()), getAllImportTransactionColumnTypes: () => getLocalizedDisplayNameAndType(ImportTransactionColumnType.values()),
getAllTransactionDefaultCategories, getAllTransactionDefaultCategories,
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Ora saldo", "balanceTime": "Ora saldo",
"startTime": "Ora di inizio", "startTime": "Ora di inizio",
"endTime": "Ora di fine", "endTime": "Ora di fine",
"tagFilterType": "Tipo filtro tag", "tagFilter": "Tag Filter",
"amountFilter": "Filtro importo", "amountFilter": "Filtro importo",
"sourceAccountId": "ID conto di origine", "sourceAccountId": "ID conto di origine",
"destinationAccountId": "ID conto di destinazione", "destinationAccountId": "ID conto di destinazione",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} ha un formato non valido", "parameter invalid email format": "{parameter} ha un formato non valido",
"parameter invalid currency": "{parameter} ha un formato non valido", "parameter invalid currency": "{parameter} ha un formato non valido",
"parameter invalid color": "{parameter} ha un formato non valido", "parameter invalid color": "{parameter} ha un formato non valido",
"parameter invalid amount filter": "{parameter} ha un formato non valido" "parameter invalid amount filter": "{parameter} ha un formato non valido",
"parameter invalid tag filter": "{parameter} ha un formato non valido"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Rilevamento automatico", "Auto detect": "Rilevamento automatico",
"Miscellaneous": "Varie", "Miscellaneous": "Varie",
"Default": "Predefinito", "Default": "Predefinito",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Fatto", "Done": "Fatto",
"Continue": "Continua", "Continue": "Continua",
"Previous": "Precedente", "Previous": "Precedente",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Inverti selezione in questa pagina", "Invert Selection in This Page": "Inverti selezione in questa pagina",
"Select All Valid Items": "Seleziona tutti gli elementi validi", "Select All Valid Items": "Seleziona tutti gli elementi validi",
"Select All Invalid Items": "Seleziona tutti gli elementi non validi", "Select All Invalid Items": "Seleziona tutti gli elementi non validi",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Indietro", "Back": "Indietro",
"Load More": "Carica altro", "Load More": "Carica altro",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Conto di destinazione", "Destination Account": "Conto di destinazione",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Senza tag", "Without Tags": "Senza tag",
"With Any Selected Tags": "Con qualsiasi tag selezionato", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Con tutti i tag selezionati", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Senza alcun tag selezionato", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Senza tutti i tag selezionati", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Tag multipli", "Multiple Tags": "Tag multipli",
"Transaction Time": "Ora transazione", "Transaction Time": "Ora transazione",
"Scheduled Transaction Frequency": "Frequenza transazione pianificata", "Scheduled Transaction Frequency": "Frequenza transazione pianificata",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "残高時間", "balanceTime": "残高時間",
"startTime": "開始時間", "startTime": "開始時間",
"endTime": "終了時間", "endTime": "終了時間",
"tagFilterType": "タグフィルタータイプ", "tagFilter": "Tag Filter",
"amountFilter": "金額フィルター", "amountFilter": "金額フィルター",
"sourceAccountId": "元口座ID", "sourceAccountId": "元口座ID",
"destinationAccountId": "宛先口座ID", "destinationAccountId": "宛先口座ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter}は無効な形式です", "parameter invalid email format": "{parameter}は無効な形式です",
"parameter invalid currency": "{parameter}は無効な形式です", "parameter invalid currency": "{parameter}は無効な形式です",
"parameter invalid color": "{parameter}は無効な形式です", "parameter invalid color": "{parameter}は無効な形式です",
"parameter invalid amount filter": "{parameter}は無効な形式です" "parameter invalid amount filter": "{parameter}は無効な形式です",
"parameter invalid tag filter": "{parameter}は無効な形式です"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "自動検出", "Auto detect": "自動検出",
"Miscellaneous": "その他", "Miscellaneous": "その他",
"Default": "デフォルト", "Default": "デフォルト",
"Included": "Included",
"Excluded": "Excluded",
"Done": "完了", "Done": "完了",
"Continue": "続ける", "Continue": "続ける",
"Previous": "前", "Previous": "前",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "このページの選択を反転", "Invert Selection in This Page": "このページの選択を反転",
"Select All Valid Items": "すべての有効なアイテムを選択", "Select All Valid Items": "すべての有効なアイテムを選択",
"Select All Invalid Items": "すべての無効なアイテムを選択します", "Select All Invalid Items": "すべての無効なアイテムを選択します",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "戻る", "Back": "戻る",
"Load More": "さらに読み込む", "Load More": "さらに読み込む",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "宛先口座", "Destination Account": "宛先口座",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "タグなし", "Without Tags": "タグなし",
"With Any Selected Tags": "選択したタグを含む", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "選択したすべてのタグを含む", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "タグを選択しない", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "全てのタグを選択しない", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "複数のタグ", "Multiple Tags": "複数のタグ",
"Transaction Time": "取引時間", "Transaction Time": "取引時間",
"Scheduled Transaction Frequency": "スケジュールされた取引の頻度", "Scheduled Transaction Frequency": "スケジュールされた取引の頻度",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "잔액 시간", "balanceTime": "잔액 시간",
"startTime": "시작 시간", "startTime": "시작 시간",
"endTime": "종료 시간", "endTime": "종료 시간",
"tagFilterType": "태그 필터 유형", "tagFilter": "Tag Filter",
"amountFilter": "금액 필터", "amountFilter": "금액 필터",
"sourceAccountId": "출발 계좌 ID", "sourceAccountId": "출발 계좌 ID",
"destinationAccountId": "도착 계좌 ID", "destinationAccountId": "도착 계좌 ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter}는 유효하지 않은 형식입니다", "parameter invalid email format": "{parameter}는 유효하지 않은 형식입니다",
"parameter invalid currency": "{parameter}는 유효하지 않은 형식입니다", "parameter invalid currency": "{parameter}는 유효하지 않은 형식입니다",
"parameter invalid color": "{parameter}는 유효하지 않은 형식입니다", "parameter invalid color": "{parameter}는 유효하지 않은 형식입니다",
"parameter invalid amount filter": "{parameter}는 유효하지 않은 형식입니다" "parameter invalid amount filter": "{parameter}는 유효하지 않은 형식입니다",
"parameter invalid tag filter": "{parameter}는 유효하지 않은 형식입니다"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "자동 감지", "Auto detect": "자동 감지",
"Miscellaneous": "기타", "Miscellaneous": "기타",
"Default": "기본값", "Default": "기본값",
"Included": "Included",
"Excluded": "Excluded",
"Done": "완료", "Done": "완료",
"Continue": "계속", "Continue": "계속",
"Previous": "이전", "Previous": "이전",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "현재 페이지 선택 반전", "Invert Selection in This Page": "현재 페이지 선택 반전",
"Select All Valid Items": "유효한 항목 전체 선택", "Select All Valid Items": "유효한 항목 전체 선택",
"Select All Invalid Items": "유효하지 않은 항목 전체 선택", "Select All Invalid Items": "유효하지 않은 항목 전체 선택",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "뒤로", "Back": "뒤로",
"Load More": "더 불러오기", "Load More": "더 불러오기",
"Export Results": "결과 내보내기", "Export Results": "결과 내보내기",
@@ -1802,10 +1811,10 @@
"Destination Account": "입금 계좌", "Destination Account": "입금 계좌",
"Transaction Tag": "거래 태그", "Transaction Tag": "거래 태그",
"Without Tags": "태그 없음", "Without Tags": "태그 없음",
"With Any Selected Tags": "선택한 태그 중 하나와 함께", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "선택한 모든 태그와 함께", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "선택한 태그 없음", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "선택한 모든 태그 없음", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "다중 태그", "Multiple Tags": "다중 태그",
"Transaction Time": "거래 시간", "Transaction Time": "거래 시간",
"Scheduled Transaction Frequency": "예약 거래 빈도", "Scheduled Transaction Frequency": "예약 거래 빈도",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Saldo-tijdstip", "balanceTime": "Saldo-tijdstip",
"startTime": "Starttijd", "startTime": "Starttijd",
"endTime": "Eindtijd", "endTime": "Eindtijd",
"tagFilterType": "Tag-filtertype", "tagFilter": "Tag Filter",
"amountFilter": "Bedragfilter", "amountFilter": "Bedragfilter",
"sourceAccountId": "Bronrekening-ID", "sourceAccountId": "Bronrekening-ID",
"destinationAccountId": "Bestemmingsrekening-ID", "destinationAccountId": "Bestemmingsrekening-ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} heeft een ongeldig formaat", "parameter invalid email format": "{parameter} heeft een ongeldig formaat",
"parameter invalid currency": "{parameter} heeft een ongeldig formaat", "parameter invalid currency": "{parameter} heeft een ongeldig formaat",
"parameter invalid color": "{parameter} heeft een ongeldig formaat", "parameter invalid color": "{parameter} heeft een ongeldig formaat",
"parameter invalid amount filter": "{parameter} heeft een ongeldig formaat" "parameter invalid amount filter": "{parameter} heeft een ongeldig formaat",
"parameter invalid tag filter": "{parameter} heeft een ongeldig formaat"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Automatisch detecteren", "Auto detect": "Automatisch detecteren",
"Miscellaneous": "Diversen", "Miscellaneous": "Diversen",
"Default": "Standaard", "Default": "Standaard",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Klaar", "Done": "Klaar",
"Continue": "Doorgaan", "Continue": "Doorgaan",
"Previous": "Vorige", "Previous": "Vorige",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Selectie op deze pagina omkeren", "Invert Selection in This Page": "Selectie op deze pagina omkeren",
"Select All Valid Items": "Alle geldige items selecteren", "Select All Valid Items": "Alle geldige items selecteren",
"Select All Invalid Items": "Alle ongeldige items selecteren", "Select All Invalid Items": "Alle ongeldige items selecteren",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Terug", "Back": "Terug",
"Load More": "Meer laden", "Load More": "Meer laden",
"Export Results": "Resultaten exporteren", "Export Results": "Resultaten exporteren",
@@ -1802,10 +1811,10 @@
"Destination Account": "Bestemmingsrekening", "Destination Account": "Bestemmingsrekening",
"Transaction Tag": "Transactietag", "Transaction Tag": "Transactietag",
"Without Tags": "Zonder tags", "Without Tags": "Zonder tags",
"With Any Selected Tags": "Met willekeurige geselecteerde tags", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Met alle geselecteerde tags", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Zonder geselecteerde tags", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Zonder alle geselecteerde tags", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Meerdere tags", "Multiple Tags": "Meerdere tags",
"Transaction Time": "Transactietijd", "Transaction Time": "Transactietijd",
"Scheduled Transaction Frequency": "Frequentie geplande transactie", "Scheduled Transaction Frequency": "Frequentie geplande transactie",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Hora do Saldo", "balanceTime": "Hora do Saldo",
"startTime": "Hora de Início", "startTime": "Hora de Início",
"endTime": "Hora de Término", "endTime": "Hora de Término",
"tagFilterType": "Tipo de Filtro de Tag", "tagFilter": "Tag Filter",
"amountFilter": "Filtro de Quantia", "amountFilter": "Filtro de Quantia",
"sourceAccountId": "ID da Conta de Origem", "sourceAccountId": "ID da Conta de Origem",
"destinationAccountId": "ID da Conta de Destino", "destinationAccountId": "ID da Conta de Destino",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} está em formato inválido", "parameter invalid email format": "{parameter} está em formato inválido",
"parameter invalid currency": "{parameter} está em formato inválido", "parameter invalid currency": "{parameter} está em formato inválido",
"parameter invalid color": "{parameter} está em formato inválido", "parameter invalid color": "{parameter} está em formato inválido",
"parameter invalid amount filter": "{parameter} está em formato inválido" "parameter invalid amount filter": "{parameter} está em formato inválido",
"parameter invalid tag filter": "{parameter} está em formato inválido"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Detecção automática", "Auto detect": "Detecção automática",
"Miscellaneous": "Diversos", "Miscellaneous": "Diversos",
"Default": "Padrão", "Default": "Padrão",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Concluído", "Done": "Concluído",
"Continue": "Continuar", "Continue": "Continuar",
"Previous": "Anterior", "Previous": "Anterior",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Inverter Seleção nesta Página", "Invert Selection in This Page": "Inverter Seleção nesta Página",
"Select All Valid Items": "Selecionar Todos os Itens Válidos", "Select All Valid Items": "Selecionar Todos os Itens Válidos",
"Select All Invalid Items": "Selecionar Todos os Itens Inválidos", "Select All Invalid Items": "Selecionar Todos os Itens Inválidos",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Voltar", "Back": "Voltar",
"Load More": "Carregar Mais", "Load More": "Carregar Mais",
"Export Results": "Exportar Resultados", "Export Results": "Exportar Resultados",
@@ -1802,10 +1811,10 @@
"Destination Account": "Conta de Destino", "Destination Account": "Conta de Destino",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Sem Tags", "Without Tags": "Sem Tags",
"With Any Selected Tags": "Com Quaisquer Tags Selecionadas", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Com Todas as Tags Selecionadas", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Sem Quaisquer Tags Selecionadas", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Sem Todas as Tags Selecionadas", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Várias Tags", "Multiple Tags": "Várias Tags",
"Transaction Time": "Horário da Transação", "Transaction Time": "Horário da Transação",
"Scheduled Transaction Frequency": "Frequência da Transação Agendada", "Scheduled Transaction Frequency": "Frequência da Transação Agendada",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Время баланса", "balanceTime": "Время баланса",
"startTime": "Время начала", "startTime": "Время начала",
"endTime": "Время окончания", "endTime": "Время окончания",
"tagFilterType": "Тип фильтра по тегам", "tagFilter": "Tag Filter",
"amountFilter": "Фильтр по сумме", "amountFilter": "Фильтр по сумме",
"sourceAccountId": "ID исходного счета", "sourceAccountId": "ID исходного счета",
"destinationAccountId": "ID целевого счета", "destinationAccountId": "ID целевого счета",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} имеет неверный формат", "parameter invalid email format": "{parameter} имеет неверный формат",
"parameter invalid currency": "{parameter} имеет неверный формат", "parameter invalid currency": "{parameter} имеет неверный формат",
"parameter invalid color": "{parameter} имеет неверный формат", "parameter invalid color": "{parameter} имеет неверный формат",
"parameter invalid amount filter": "{parameter} имеет неверный формат" "parameter invalid amount filter": "{parameter} имеет неверный формат",
"parameter invalid tag filter": "{parameter} имеет неверный формат"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Auto detect", "Auto detect": "Auto detect",
"Miscellaneous": "Разное", "Miscellaneous": "Разное",
"Default": "По умолчанию", "Default": "По умолчанию",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Готово", "Done": "Готово",
"Continue": "Продолжить", "Continue": "Продолжить",
"Previous": "Предыдущий", "Previous": "Предыдущий",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Инвертировать выбор на этой странице", "Invert Selection in This Page": "Инвертировать выбор на этой странице",
"Select All Valid Items": "Выбрать все действительные элементы", "Select All Valid Items": "Выбрать все действительные элементы",
"Select All Invalid Items": "Выбрать все недействительные элементы", "Select All Invalid Items": "Выбрать все недействительные элементы",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Назад", "Back": "Назад",
"Load More": "Загрузить еще", "Load More": "Загрузить еще",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Целевой счет", "Destination Account": "Целевой счет",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Без тегов", "Without Tags": "Без тегов",
"With Any Selected Tags": "С любыми выбранными тегами", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "Со всеми выбранными тегами", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Без любых выбранных тегов", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Без всех выбранных тегов", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Несколько тегов", "Multiple Tags": "Несколько тегов",
"Transaction Time": "Время транзакции", "Transaction Time": "Время транзакции",
"Scheduled Transaction Frequency": "Частота запланированных транзакций", "Scheduled Transaction Frequency": "Частота запланированных транзакций",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "เวลาแสดงยอด", "balanceTime": "เวลาแสดงยอด",
"startTime": "เวลาเริ่ม", "startTime": "เวลาเริ่ม",
"endTime": "เวลาสิ้นสุด", "endTime": "เวลาสิ้นสุด",
"tagFilterType": "ประเภทตัวกรองแท็ก", "tagFilter": "Tag Filter",
"amountFilter": "ตัวกรองจำนวนเงิน", "amountFilter": "ตัวกรองจำนวนเงิน",
"sourceAccountId": "รหัสบัญชีต้นทาง", "sourceAccountId": "รหัสบัญชีต้นทาง",
"destinationAccountId": "รหัสบัญชีปลายทาง", "destinationAccountId": "รหัสบัญชีปลายทาง",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "รูปแบบของ {parameter} ไม่ถูกต้อง", "parameter invalid email format": "รูปแบบของ {parameter} ไม่ถูกต้อง",
"parameter invalid currency": "รูปแบบของ {parameter} ไม่ถูกต้อง", "parameter invalid currency": "รูปแบบของ {parameter} ไม่ถูกต้อง",
"parameter invalid color": "รูปแบบของ {parameter} ไม่ถูกต้อง", "parameter invalid color": "รูปแบบของ {parameter} ไม่ถูกต้อง",
"parameter invalid amount filter": "รูปแบบของ {parameter} ไม่ถูกต้อง" "parameter invalid amount filter": "รูปแบบของ {parameter} ไม่ถูกต้อง",
"parameter invalid tag filter": "รูปแบบของ {parameter} ไม่ถูกต้อง"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "ตรวจสอบอัตโนมัติ", "Auto detect": "ตรวจสอบอัตโนมัติ",
"Miscellaneous": "อื่น ๆ", "Miscellaneous": "อื่น ๆ",
"Default": "ค่าเริ่มต้น", "Default": "ค่าเริ่มต้น",
"Included": "Included",
"Excluded": "Excluded",
"Done": "เสร็จสิ้น", "Done": "เสร็จสิ้น",
"Continue": "ดำเนินการต่อ", "Continue": "ดำเนินการต่อ",
"Previous": "ก่อนหน้า", "Previous": "ก่อนหน้า",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "สลับการเลือกในหน้านี้", "Invert Selection in This Page": "สลับการเลือกในหน้านี้",
"Select All Valid Items": "เลือกทุกไอเทมที่ถูกต้อง", "Select All Valid Items": "เลือกทุกไอเทมที่ถูกต้อง",
"Select All Invalid Items": "เลือกทุกไอเทมที่ไม่ถูกต้อง", "Select All Invalid Items": "เลือกทุกไอเทมที่ไม่ถูกต้อง",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "กลับ", "Back": "กลับ",
"Load More": "โหลดเพิ่มเติม", "Load More": "โหลดเพิ่มเติม",
"Export Results": "ส่งออกผลลัพธ์", "Export Results": "ส่งออกผลลัพธ์",
@@ -1802,10 +1811,10 @@
"Destination Account": "บัญชีปลายทาง", "Destination Account": "บัญชีปลายทาง",
"Transaction Tag": "แท็กรายการ", "Transaction Tag": "แท็กรายการ",
"Without Tags": "ไม่มีแท็ก", "Without Tags": "ไม่มีแท็ก",
"With Any Selected Tags": "มีแท็กที่เลือกใด ๆ", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "มีแท็กทั้งหมดที่เลือก", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "ไม่มีแท็กที่เลือกใด ๆ", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "ไม่มีแท็กทั้งหมดที่เลือก", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "หลายแท็ก", "Multiple Tags": "หลายแท็ก",
"Transaction Time": "เวลาธุรกรรม", "Transaction Time": "เวลาธุรกรรม",
"Scheduled Transaction Frequency": "ความถี่รายการที่กำหนดเวลา", "Scheduled Transaction Frequency": "ความถี่รายการที่กำหนดเวลา",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Час балансу", "balanceTime": "Час балансу",
"startTime": "Час початку", "startTime": "Час початку",
"endTime": "Час завершення", "endTime": "Час завершення",
"tagFilterType": "Тип фільтра за тегами", "tagFilter": "Tag Filter",
"amountFilter": "Фільтр за сумою", "amountFilter": "Фільтр за сумою",
"sourceAccountId": "ID вихідного рахунку", "sourceAccountId": "ID вихідного рахунку",
"destinationAccountId": "ID цільового рахунку", "destinationAccountId": "ID цільового рахунку",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} має некоректний формат", "parameter invalid email format": "{parameter} має некоректний формат",
"parameter invalid currency": "{parameter} має некоректний формат", "parameter invalid currency": "{parameter} має некоректний формат",
"parameter invalid color": "{parameter} має некоректний формат", "parameter invalid color": "{parameter} має некоректний формат",
"parameter invalid amount filter": "{parameter} має некоректний формат" "parameter invalid amount filter": "{parameter} має некоректний формат",
"parameter invalid tag filter": "{parameter} має некоректний формат"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Автовизначення", "Auto detect": "Автовизначення",
"Miscellaneous": "Різне", "Miscellaneous": "Різне",
"Default": "По замовчуванню", "Default": "По замовчуванню",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Готово", "Done": "Готово",
"Continue": "Продовжити", "Continue": "Продовжити",
"Previous": "Назад", "Previous": "Назад",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Інвертувати вибір на цій сторінці", "Invert Selection in This Page": "Інвертувати вибір на цій сторінці",
"Select All Valid Items": "Вибрати всі дійсні елементи", "Select All Valid Items": "Вибрати всі дійсні елементи",
"Select All Invalid Items": "Вибрати всі недійсні елементи", "Select All Invalid Items": "Вибрати всі недійсні елементи",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Назад", "Back": "Назад",
"Load More": "Завантажити ще", "Load More": "Завантажити ще",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Цільовий рахунок", "Destination Account": "Цільовий рахунок",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Без тегів", "Without Tags": "Без тегів",
"With Any Selected Tags": "З будь-якими вибраними тегами", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "З усіма вибраними тегами", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Без будь-якого з вибраних тегів", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Без усіх вибраних тегів", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Кілька тегів", "Multiple Tags": "Кілька тегів",
"Transaction Time": "Час транзакції", "Transaction Time": "Час транзакції",
"Scheduled Transaction Frequency": "Частота запланованої транзакції", "Scheduled Transaction Frequency": "Частота запланованої транзакції",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "Thời gian số dư", "balanceTime": "Thời gian số dư",
"startTime": "Thời gian bắt đầu", "startTime": "Thời gian bắt đầu",
"endTime": "Thời gian kết thúc", "endTime": "Thời gian kết thúc",
"tagFilterType": "Tag Filter Type", "tagFilter": "Tag Filter",
"amountFilter": "Bộ lọc số tiền", "amountFilter": "Bộ lọc số tiền",
"sourceAccountId": "ID tài khoản nguồn", "sourceAccountId": "ID tài khoản nguồn",
"destinationAccountId": "ID tài khoản đích", "destinationAccountId": "ID tài khoản đích",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter} có định dạng không hợp lệ", "parameter invalid email format": "{parameter} có định dạng không hợp lệ",
"parameter invalid currency": "{parameter} có định dạng không hợp lệ", "parameter invalid currency": "{parameter} có định dạng không hợp lệ",
"parameter invalid color": "{parameter} có định dạng không hợp lệ", "parameter invalid color": "{parameter} có định dạng không hợp lệ",
"parameter invalid amount filter": "{parameter} có định dạng không hợp lệ" "parameter invalid amount filter": "{parameter} có định dạng không hợp lệ",
"parameter invalid tag filter": "{parameter} có định dạng không hợp lệ"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "Auto detect", "Auto detect": "Auto detect",
"Miscellaneous": "Linh tinh", "Miscellaneous": "Linh tinh",
"Default": "Mặc định", "Default": "Mặc định",
"Included": "Included",
"Excluded": "Excluded",
"Done": "Hoàn tất", "Done": "Hoàn tất",
"Continue": "Tiếp tục", "Continue": "Tiếp tục",
"Previous": "Trước", "Previous": "Trước",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "Đảo ngược lựa chọn trong trang này", "Invert Selection in This Page": "Đảo ngược lựa chọn trong trang này",
"Select All Valid Items": "Chọn tất cả các mục hợp lệ", "Select All Valid Items": "Chọn tất cả các mục hợp lệ",
"Select All Invalid Items": "Chọn tất cả các mục không hợp lệ", "Select All Invalid Items": "Chọn tất cả các mục không hợp lệ",
"Set All to Included": "Set All to Included",
"Set All to Default": "Set All to Default",
"Set All to Excluded": "Set All to Excluded",
"Set All Visible Items to Included": "Set All Visible Items to Included",
"Set All Visible Items to Default": "Set All Visible Items to Default",
"Set All Visible Items to Excluded": "Set All Visible Items to Excluded",
"Back": "Quay lại", "Back": "Quay lại",
"Load More": "Tải thêm", "Load More": "Tải thêm",
"Export Results": "Export Results", "Export Results": "Export Results",
@@ -1802,10 +1811,10 @@
"Destination Account": "Tài khoản đích", "Destination Account": "Tài khoản đích",
"Transaction Tag": "Transaction Tag", "Transaction Tag": "Transaction Tag",
"Without Tags": "Không có thẻ", "Without Tags": "Không có thẻ",
"With Any Selected Tags": "With Any Selected Tags", "Include Any Selected Tags": "Include Any Selected Tags",
"With All Selected Tags": "With All Selected Tags", "Include All Selected Tags": "Include All Selected Tags",
"Without Any Selected Tags": "Without Any Selected Tags", "Exclude Any Selected Tags": "Exclude Any Selected Tags",
"Without All Selected Tags": "Without All Selected Tags", "Exclude All Selected Tags": "Exclude All Selected Tags",
"Multiple Tags": "Nhiều thẻ", "Multiple Tags": "Nhiều thẻ",
"Transaction Time": "Thời gian giao dịch", "Transaction Time": "Thời gian giao dịch",
"Scheduled Transaction Frequency": "Tần suất giao dịch theo lịch trình", "Scheduled Transaction Frequency": "Tần suất giao dịch theo lịch trình",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "余额时间", "balanceTime": "余额时间",
"startTime": "开始时间", "startTime": "开始时间",
"endTime": "结束时间", "endTime": "结束时间",
"tagFilterType": "标签过滤类型", "tagFilter": "标签过滤",
"amountFilter": "金额过滤", "amountFilter": "金额过滤",
"sourceAccountId": "来源账户ID", "sourceAccountId": "来源账户ID",
"destinationAccountId": "目标账户ID", "destinationAccountId": "目标账户ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter}格式错误", "parameter invalid email format": "{parameter}格式错误",
"parameter invalid currency": "{parameter}格式错误", "parameter invalid currency": "{parameter}格式错误",
"parameter invalid color": "{parameter}格式错误", "parameter invalid color": "{parameter}格式错误",
"parameter invalid amount filter": "{parameter}格式错误" "parameter invalid amount filter": "{parameter}格式错误",
"parameter invalid tag filter": "{parameter}格式错误"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "自动检测", "Auto detect": "自动检测",
"Miscellaneous": "杂项", "Miscellaneous": "杂项",
"Default": "默认", "Default": "默认",
"Included": "包含",
"Excluded": "排除",
"Done": "完成", "Done": "完成",
"Continue": "继续", "Continue": "继续",
"Previous": "上一步", "Previous": "上一步",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "本页反选", "Invert Selection in This Page": "本页反选",
"Select All Valid Items": "选择全部有效项目", "Select All Valid Items": "选择全部有效项目",
"Select All Invalid Items": "选择全部无效项目", "Select All Invalid Items": "选择全部无效项目",
"Set All to Included": "全部设置为包含",
"Set All to Default": "全部设置为默认",
"Set All to Excluded": "全部设置为排除",
"Set All Visible Items to Included": "全部可见项目设置为包含",
"Set All Visible Items to Default": "全部可见项目设置为默认",
"Set All Visible Items to Excluded": "全部可见项目设置为排除",
"Back": "返回", "Back": "返回",
"Load More": "加载更多", "Load More": "加载更多",
"Export Results": "导出结果", "Export Results": "导出结果",
@@ -1802,10 +1811,10 @@
"Destination Account": "目标账户", "Destination Account": "目标账户",
"Transaction Tag": "交易标签", "Transaction Tag": "交易标签",
"Without Tags": "没有标签", "Without Tags": "没有标签",
"With Any Selected Tags": "包含任意选中的标签", "Include Any Selected Tags": "包含任意选标签",
"With All Selected Tags": "包含全部选中的标签", "Include All Selected Tags": "包含所有已选标签",
"Without Any Selected Tags": "不包含任意选中的标签", "Exclude Any Selected Tags": "排除任意已选标签",
"Without All Selected Tags": "不包含全部选中的标签", "Exclude All Selected Tags": "排除所有已选标签",
"Multiple Tags": "多个标签", "Multiple Tags": "多个标签",
"Transaction Time": "交易时间", "Transaction Time": "交易时间",
"Scheduled Transaction Frequency": "定时交易周期", "Scheduled Transaction Frequency": "定时交易周期",
+15 -6
View File
@@ -1300,7 +1300,7 @@
"balanceTime": "餘額時間", "balanceTime": "餘額時間",
"startTime": "開始時間", "startTime": "開始時間",
"endTime": "結束時間", "endTime": "結束時間",
"tagFilterType": "標籤篩選類型", "tagFilter": "標籤篩選",
"amountFilter": "金額篩選", "amountFilter": "金額篩選",
"sourceAccountId": "來源帳戶ID", "sourceAccountId": "來源帳戶ID",
"destinationAccountId": "目標帳戶ID", "destinationAccountId": "目標帳戶ID",
@@ -1328,7 +1328,8 @@
"parameter invalid email format": "{parameter}格式錯誤", "parameter invalid email format": "{parameter}格式錯誤",
"parameter invalid currency": "{parameter}格式錯誤", "parameter invalid currency": "{parameter}格式錯誤",
"parameter invalid color": "{parameter}格式錯誤", "parameter invalid color": "{parameter}格式錯誤",
"parameter invalid amount filter": "{parameter}格式錯誤" "parameter invalid amount filter": "{parameter}格式錯誤",
"parameter invalid tag filter": "{parameter}格式錯誤"
}, },
"encoding": { "encoding": {
"utf-8": "UTF-8", "utf-8": "UTF-8",
@@ -1442,6 +1443,8 @@
"Auto detect": "自動偵測", "Auto detect": "自動偵測",
"Miscellaneous": "雜項", "Miscellaneous": "雜項",
"Default": "預設", "Default": "預設",
"Included": "包含",
"Excluded": "排除",
"Done": "完成", "Done": "完成",
"Continue": "繼續", "Continue": "繼續",
"Previous": "上一步", "Previous": "上一步",
@@ -1549,6 +1552,12 @@
"Invert Selection in This Page": "本頁反向選擇", "Invert Selection in This Page": "本頁反向選擇",
"Select All Valid Items": "選擇全部有效項目", "Select All Valid Items": "選擇全部有效項目",
"Select All Invalid Items": "選擇全部無效項目", "Select All Invalid Items": "選擇全部無效項目",
"Set All to Included": "全部設為包含",
"Set All to Default": "全部設為預設",
"Set All to Excluded": "全部設為排除",
"Set All Visible Items to Included": "全部可見項目設為包含",
"Set All Visible Items to Default": "全部可見項目設為預設",
"Set All Visible Items to Excluded": "全部可見項目設為排除",
"Back": "返回", "Back": "返回",
"Load More": "載入更多", "Load More": "載入更多",
"Export Results": "匯出結果", "Export Results": "匯出結果",
@@ -1802,10 +1811,10 @@
"Destination Account": "目標帳戶", "Destination Account": "目標帳戶",
"Transaction Tag": "交易標籤", "Transaction Tag": "交易標籤",
"Without Tags": "沒有標籤", "Without Tags": "沒有標籤",
"With Any Selected Tags": "包含任意選中的標籤", "Include Any Selected Tags": "包含任一選取的標籤",
"With All Selected Tags": "包含全部選中的標籤", "Include All Selected Tags": "包含所有選取的標籤",
"Without Any Selected Tags": "不包含任意選中的標籤", "Exclude Any Selected Tags": "排除任一選取的標籤",
"Without All Selected Tags": "不包含全部選中的標籤", "Exclude All Selected Tags": "排除所有選取的標籤",
"Multiple Tags": "多個標籤", "Multiple Tags": "多個標籤",
"Transaction Time": "交易時間", "Transaction Time": "交易時間",
"Scheduled Transaction Frequency": "排程交易週期", "Scheduled Transaction Frequency": "排程交易週期",
+1 -2
View File
@@ -4,8 +4,7 @@ export interface ExportTransactionDataRequest {
readonly type: number; readonly type: number;
readonly categoryIds: string; readonly categoryIds: string;
readonly accountIds: string; readonly accountIds: string;
readonly tagIds: string; readonly tagFilter: string;
readonly tagFilterType: number;
readonly amountFilter: string; readonly amountFilter: string;
readonly keyword: string; readonly keyword: string;
} }
+75 -9
View File
@@ -1,7 +1,7 @@
import { type PartialRecord, itemAndIndex } from '@/core/base.ts'; import { type PartialRecord, itemAndIndex } from '@/core/base.ts';
import type { TextualYearMonthDay, Year1BasedMonth, YearMonthDay, StartEndTime, WeekDay } from '@/core/datetime.ts'; import type { TextualYearMonthDay, Year1BasedMonth, YearMonthDay, StartEndTime, WeekDay } from '@/core/datetime.ts';
import { type Coordinate, getNormalizedCoordinate } from '@/core/coordinate.ts'; import { type Coordinate, getNormalizedCoordinate } from '@/core/coordinate.ts';
import { TransactionType } from '@/core/transaction.ts'; import { TransactionType, TransactionTagFilterType } from '@/core/transaction.ts';
import { Account, type AccountInfoResponse } from './account.ts'; import { Account, type AccountInfoResponse } from './account.ts';
import { TransactionCategory, type TransactionCategoryInfoResponse } from './transaction_category.ts'; import { TransactionCategory, type TransactionCategoryInfoResponse } from './transaction_category.ts';
@@ -437,6 +437,76 @@ export class TransactionGeoLocation implements TransactionGeoLocationRequest {
} }
} }
export class TransactionTagFilter {
public readonly tagIds: string[]
public readonly type: TransactionTagFilterType;
public static readonly TransactionNoTagFilterValue: string = 'none';
private constructor(tagIds: string[], type: TransactionTagFilterType) {
this.tagIds = tagIds;
this.type = type;
}
public static create(type: TransactionTagFilterType): TransactionTagFilter {
return new TransactionTagFilter([], type);
}
public static of(tagId: string): TransactionTagFilter {
return new TransactionTagFilter([tagId], TransactionTagFilterType.HasAny);
}
public static parse(tagFilter: string): TransactionTagFilter[] {
const ret: TransactionTagFilter[] = [];
if (!tagFilter || tagFilter === TransactionTagFilter.TransactionNoTagFilterValue) {
return ret;
}
const filters: string[] = tagFilter.split(';');
for (const filter of filters) {
const tagFilterItem: string[] = filter.split(':');
if (tagFilterItem.length !== 2) {
continue;
}
const tagFilterTypeValue: number = parseInt(tagFilterItem[0] as string, 10);
if (Number.isNaN(tagFilterTypeValue) || !Number.isFinite(tagFilterTypeValue)) {
continue;
}
const tagFilterType: TransactionTagFilterType | undefined = TransactionTagFilterType.parse(tagFilterTypeValue);
if (!tagFilterType) {
continue;
}
const tagIds: string[] = (tagFilterItem[1] as string).split(',');
const tagFilter: TransactionTagFilter = new TransactionTagFilter(tagIds, tagFilterType);
ret.push(tagFilter);
}
return ret;
}
public static toTextualTagFilters(tagFilters: TransactionTagFilter[]): string {
const textualTagFilters: string[] = [];
for (const tagFilter of tagFilters) {
textualTagFilters.push(tagFilter.toTextualTagFilter());
}
return textualTagFilters.join(';');
}
public toTextualTagFilter(): string {
return `${this.type.type}:${this.tagIds.join(',')}`;
}
}
export interface TransactionDraft { export interface TransactionDraft {
readonly type?: number; readonly type?: number;
readonly categoryId?: string; readonly categoryId?: string;
@@ -511,8 +581,7 @@ export interface TransactionListByMaxTimeRequest {
readonly type: number; readonly type: number;
readonly categoryIds: string; readonly categoryIds: string;
readonly accountIds: string; readonly accountIds: string;
readonly tagIds: string; readonly tagFilter: string;
readonly tagFilterType: number;
readonly amountFilter: string; readonly amountFilter: string;
readonly keyword: string; readonly keyword: string;
} }
@@ -523,8 +592,7 @@ export interface TransactionListInMonthByPageRequest {
readonly type: number; readonly type: number;
readonly categoryIds: string; readonly categoryIds: string;
readonly accountIds: string; readonly accountIds: string;
readonly tagIds: string; readonly tagFilter: string;
readonly tagFilterType: number;
readonly amountFilter: string; readonly amountFilter: string;
readonly keyword: string; readonly keyword: string;
} }
@@ -563,8 +631,7 @@ export interface TransactionInfoResponse {
export interface TransactionStatisticRequest { export interface TransactionStatisticRequest {
readonly startTime: number; readonly startTime: number;
readonly endTime: number; readonly endTime: number;
readonly tagIds: string; readonly tagFilter: string;
readonly tagFilterType: number;
readonly keyword: string; readonly keyword: string;
readonly useTransactionTimezone: boolean; readonly useTransactionTimezone: boolean;
} }
@@ -575,8 +642,7 @@ export interface YearMonthRangeRequest {
} }
export interface TransactionStatisticTrendsRequest extends YearMonthRangeRequest { export interface TransactionStatisticTrendsRequest extends YearMonthRangeRequest {
readonly tagIds: string; readonly tagFilter: string;
readonly tagFilterType: number;
readonly keyword: string; readonly keyword: string;
readonly useTransactionTimezone: boolean; readonly useTransactionTimezone: boolean;
} }
+2 -4
View File
@@ -111,8 +111,7 @@ const router = createRouter({
initType: route.query['type'], initType: route.query['type'],
initCategoryIds: route.query['categoryIds'], initCategoryIds: route.query['categoryIds'],
initAccountIds: route.query['accountIds'], initAccountIds: route.query['accountIds'],
initTagIds: route.query['tagIds'], initTagFilter: route.query['tagFilter'],
initTagFilterType: route.query['tagFilterType'],
initAmountFilter: route.query['amountFilter'], initAmountFilter: route.query['amountFilter'],
initKeyword: route.query['keyword'] initKeyword: route.query['keyword']
}) })
@@ -130,8 +129,7 @@ const router = createRouter({
initEndTime: route.query['endTime'], initEndTime: route.query['endTime'],
initFilterAccountIds: route.query['filterAccountIds'], initFilterAccountIds: route.query['filterAccountIds'],
initFilterCategoryIds: route.query['filterCategoryIds'], initFilterCategoryIds: route.query['filterCategoryIds'],
initTagIds: route.query['tagIds'], initTagFilter: route.query['tagFilter'],
initTagFilterType: route.query['tagFilterType'],
initKeyword: route.query['keyword'], initKeyword: route.query['keyword'],
initSortingType: route.query['sortingType'], initSortingType: route.query['sortingType'],
initTrendDateAggregationType: route.query['trendDateAggregationType'], initTrendDateAggregationType: route.query['trendDateAggregationType'],
+16 -42
View File
@@ -12,8 +12,7 @@ import { type DateTime, type TextualYearMonth, type TimeRangeAndDateType, DateRa
import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
import { CategoryType } from '@/core/category.ts'; import { CategoryType } from '@/core/category.ts';
import { import {
TransactionRelatedAccountType, TransactionRelatedAccountType
TransactionTagFilterType
} from '@/core/transaction.ts'; } from '@/core/transaction.ts';
import { import {
StatisticsAnalysisType, StatisticsAnalysisType,
@@ -135,8 +134,7 @@ export interface TransactionStatisticsPartialFilter {
assetTrendsChartEndTime?: number; assetTrendsChartEndTime?: number;
filterAccountIds?: Record<string, boolean>; filterAccountIds?: Record<string, boolean>;
filterCategoryIds?: Record<string, boolean>; filterCategoryIds?: Record<string, boolean>;
tagIds?: string; tagFilter?: string;
tagFilterType?: number;
keyword?: string; keyword?: string;
sortingType?: number; sortingType?: number;
} }
@@ -157,8 +155,7 @@ export interface TransactionStatisticsFilter extends TransactionStatisticsPartia
assetTrendsChartEndTime: number; assetTrendsChartEndTime: number;
filterAccountIds: Record<string, boolean>; filterAccountIds: Record<string, boolean>;
filterCategoryIds: Record<string, boolean>; filterCategoryIds: Record<string, boolean>;
tagIds: string; tagFilter: string;
tagFilterType: number;
keyword: string; keyword: string;
sortingType: number; sortingType: number;
} }
@@ -186,8 +183,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
assetTrendsChartEndTime: 0, assetTrendsChartEndTime: 0,
filterAccountIds: {}, filterAccountIds: {},
filterCategoryIds: {}, filterCategoryIds: {},
tagIds: '', tagFilter: '',
tagFilterType: TransactionTagFilterType.Default.type,
keyword: '', keyword: '',
sortingType: ChartSortingType.Default.type sortingType: ChartSortingType.Default.type
}); });
@@ -1326,8 +1322,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
transactionStatisticsFilter.value.assetTrendsChartEndTime = 0; transactionStatisticsFilter.value.assetTrendsChartEndTime = 0;
transactionStatisticsFilter.value.filterAccountIds = {}; transactionStatisticsFilter.value.filterAccountIds = {};
transactionStatisticsFilter.value.filterCategoryIds = {}; transactionStatisticsFilter.value.filterCategoryIds = {};
transactionStatisticsFilter.value.tagIds = ''; transactionStatisticsFilter.value.tagFilter = '';
transactionStatisticsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
transactionStatisticsFilter.value.keyword = ''; transactionStatisticsFilter.value.keyword = '';
transactionCategoryStatisticsData.value = null; transactionCategoryStatisticsData.value = null;
transactionCategoryTrendsData.value = []; transactionCategoryTrendsData.value = [];
@@ -1502,16 +1497,10 @@ export const useStatisticsStore = defineStore('statistics', () => {
transactionStatisticsFilter.value.filterCategoryIds = settingsStore.appSettings.statistics.defaultTransactionCategoryFilter || {}; transactionStatisticsFilter.value.filterCategoryIds = settingsStore.appSettings.statistics.defaultTransactionCategoryFilter || {};
} }
if (filter && isString(filter.tagIds)) { if (filter && isString(filter.tagFilter)) {
transactionStatisticsFilter.value.tagIds = filter.tagIds; transactionStatisticsFilter.value.tagFilter = filter.tagFilter;
} else { } else {
transactionStatisticsFilter.value.tagIds = ''; transactionStatisticsFilter.value.tagFilter = '';
}
if (filter && isInteger(filter.tagFilterType)) {
transactionStatisticsFilter.value.tagFilterType = filter.tagFilterType;
} else {
transactionStatisticsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
} }
if (filter && isString(filter.keyword)) { if (filter && isString(filter.keyword)) {
@@ -1613,13 +1602,8 @@ export const useStatisticsStore = defineStore('statistics', () => {
changed = true; changed = true;
} }
if (filter && isString(filter.tagIds) && transactionStatisticsFilter.value.tagIds !== filter.tagIds) { if (filter && isString(filter.tagFilter) && transactionStatisticsFilter.value.tagFilter !== filter.tagFilter) {
transactionStatisticsFilter.value.tagIds = filter.tagIds; transactionStatisticsFilter.value.tagFilter = filter.tagFilter;
changed = true;
}
if (filter && isInteger(filter.tagFilterType) && transactionStatisticsFilter.value.tagFilterType !== filter.tagFilterType) {
transactionStatisticsFilter.value.tagFilterType = filter.tagFilterType;
changed = true; changed = true;
} }
@@ -1692,12 +1676,8 @@ export const useStatisticsStore = defineStore('statistics', () => {
} }
} }
if (transactionStatisticsFilter.value.tagIds) { if (transactionStatisticsFilter.value.tagFilter) {
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds); querys.push('tagFilter=' + transactionStatisticsFilter.value.tagFilter);
}
if (transactionStatisticsFilter.value.tagFilterType) {
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
} }
if (transactionStatisticsFilter.value.keyword) { if (transactionStatisticsFilter.value.keyword) {
@@ -1798,12 +1778,8 @@ export const useStatisticsStore = defineStore('statistics', () => {
} }
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis || analysisType === StatisticsAnalysisType.TrendAnalysis) { if (analysisType === StatisticsAnalysisType.CategoricalAnalysis || analysisType === StatisticsAnalysisType.TrendAnalysis) {
if (transactionStatisticsFilter.value.tagIds) { if (transactionStatisticsFilter.value.tagFilter) {
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds); querys.push('tagFilter=' + transactionStatisticsFilter.value.tagFilter);
}
if (transactionStatisticsFilter.value.tagFilterType) {
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
} }
if (transactionStatisticsFilter.value.keyword) { if (transactionStatisticsFilter.value.keyword) {
@@ -1834,8 +1810,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
services.getTransactionStatistics({ services.getTransactionStatistics({
startTime: transactionStatisticsFilter.value.categoricalChartStartTime, startTime: transactionStatisticsFilter.value.categoricalChartStartTime,
endTime: transactionStatisticsFilter.value.categoricalChartEndTime, endTime: transactionStatisticsFilter.value.categoricalChartEndTime,
tagIds: transactionStatisticsFilter.value.tagIds, tagFilter: transactionStatisticsFilter.value.tagFilter,
tagFilterType: transactionStatisticsFilter.value.tagFilterType,
keyword: transactionStatisticsFilter.value.keyword, keyword: transactionStatisticsFilter.value.keyword,
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type
}).then(response => { }).then(response => {
@@ -1877,8 +1852,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
services.getTransactionStatisticsTrends({ services.getTransactionStatisticsTrends({
startYearMonth: transactionStatisticsFilter.value.trendChartStartYearMonth, startYearMonth: transactionStatisticsFilter.value.trendChartStartYearMonth,
endYearMonth: transactionStatisticsFilter.value.trendChartEndYearMonth, endYearMonth: transactionStatisticsFilter.value.trendChartEndYearMonth,
tagIds: transactionStatisticsFilter.value.tagIds, tagFilter: transactionStatisticsFilter.value.tagFilter,
tagFilterType: transactionStatisticsFilter.value.tagFilterType,
keyword: transactionStatisticsFilter.value.keyword, keyword: transactionStatisticsFilter.value.keyword,
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type
}).then(response => { }).then(response => {
+39 -38
View File
@@ -21,6 +21,7 @@ import {
type TransactionPageWrapper, type TransactionPageWrapper,
type TransactionReconciliationStatementResponse, type TransactionReconciliationStatementResponse,
Transaction, Transaction,
TransactionTagFilter,
EMPTY_TRANSACTION_RESULT EMPTY_TRANSACTION_RESULT
} from '@/models/transaction.ts'; } from '@/models/transaction.ts';
import type { import type {
@@ -47,6 +48,7 @@ import {
isNumber, isNumber,
isString, isString,
isArray1SubsetOfArray2, isArray1SubsetOfArray2,
getObjectOwnFieldCount,
splitItemsToMap, splitItemsToMap,
countSplitItems countSplitItems
} from '@/lib/common.ts'; } from '@/lib/common.ts';
@@ -69,8 +71,7 @@ export interface TransactionListPartialFilter {
type?: number; type?: number;
categoryIds?: string; categoryIds?: string;
accountIds?: string; accountIds?: string;
tagIds?: string; tagFilter?: string;
tagFilterType?: number;
amountFilter?: string; amountFilter?: string;
keyword?: string; keyword?: string;
} }
@@ -82,8 +83,7 @@ export interface TransactionListFilter extends TransactionListPartialFilter {
type: number; type: number;
categoryIds: string; categoryIds: string;
accountIds: string; accountIds: string;
tagIds: string; tagFilter: string;
tagFilterType: number;
amountFilter: string; amountFilter: string;
keyword: string; keyword: string;
} }
@@ -123,8 +123,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
type: 0, type: 0,
categoryIds: '', categoryIds: '',
accountIds: '', accountIds: '',
tagIds: '', tagFilter: '',
tagFilterType: TransactionTagFilterType.Default.type,
amountFilter: '', amountFilter: '',
keyword: '' keyword: ''
}); });
@@ -136,11 +135,32 @@ export const useTransactionsStore = defineStore('transactions', () => {
const allFilterCategoryIds = computed<Record<string, boolean>>(() => splitItemsToMap(transactionsFilter.value.categoryIds, ',')); const allFilterCategoryIds = computed<Record<string, boolean>>(() => splitItemsToMap(transactionsFilter.value.categoryIds, ','));
const allFilterAccountIds = computed<Record<string, boolean>>(() => splitItemsToMap(transactionsFilter.value.accountIds, ',')); const allFilterAccountIds = computed<Record<string, boolean>>(() => splitItemsToMap(transactionsFilter.value.accountIds, ','));
const allFilterTagIds = computed<Record<string, boolean>>(() => splitItemsToMap(transactionsFilter.value.tagIds, ',')); const allFilterTagIds = computed<Record<string, boolean>>(() => {
const tagFilters: TransactionTagFilter[] = TransactionTagFilter.parse(transactionsFilter.value.tagFilter);
const allTagIdsMap: Record<string, boolean> = {};
for (const tagFilter of tagFilters) {
let state: boolean = true;
if (tagFilter.type === TransactionTagFilterType.HasAny || tagFilter.type === TransactionTagFilterType.HasAll) {
state = true;
} else if (tagFilter.type === TransactionTagFilterType.NotHasAny || tagFilter.type === TransactionTagFilterType.NotHasAll) {
state = false;
} else {
continue;
}
for (const tagId of tagFilter.tagIds) {
allTagIdsMap[tagId] = state;
}
}
return allTagIdsMap;
});
const allFilterCategoryIdsCount = computed<number>(() => countSplitItems(transactionsFilter.value.categoryIds, ',')); const allFilterCategoryIdsCount = computed<number>(() => countSplitItems(transactionsFilter.value.categoryIds, ','));
const allFilterAccountIdsCount = computed<number>(() => countSplitItems(transactionsFilter.value.accountIds, ',')); const allFilterAccountIdsCount = computed<number>(() => countSplitItems(transactionsFilter.value.accountIds, ','));
const allFilterTagIdsCount = computed<number>(() => countSplitItems(transactionsFilter.value.tagIds, ',')); const allFilterTagIdsCount = computed<number>(() => getObjectOwnFieldCount(allFilterTagIds.value));
const noTransaction = computed<boolean>(() => { const noTransaction = computed<boolean>(() => {
for (const transactionMonthList of transactions.value) { for (const transactionMonthList of transactions.value) {
@@ -587,8 +607,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
transactionsFilter.value.type = 0; transactionsFilter.value.type = 0;
transactionsFilter.value.categoryIds = ''; transactionsFilter.value.categoryIds = '';
transactionsFilter.value.accountIds = ''; transactionsFilter.value.accountIds = '';
transactionsFilter.value.tagIds = ''; transactionsFilter.value.tagFilter = '';
transactionsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
transactionsFilter.value.amountFilter = ''; transactionsFilter.value.amountFilter = '';
transactionsFilter.value.keyword = ''; transactionsFilter.value.keyword = '';
transactions.value = []; transactions.value = [];
@@ -640,16 +659,10 @@ export const useTransactionsStore = defineStore('transactions', () => {
transactionsFilter.value.accountIds = ''; transactionsFilter.value.accountIds = '';
} }
if (filter && isString(filter.tagIds)) { if (filter && isString(filter.tagFilter)) {
transactionsFilter.value.tagIds = filter.tagIds; transactionsFilter.value.tagFilter = filter.tagFilter;
} else { } else {
transactionsFilter.value.tagIds = ''; transactionsFilter.value.tagFilter = '';
}
if (filter && isNumber(filter.tagFilterType)) {
transactionsFilter.value.tagFilterType = filter.tagFilterType;
} else {
transactionsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
} }
if (filter && isString(filter.amountFilter)) { if (filter && isString(filter.amountFilter)) {
@@ -703,13 +716,8 @@ export const useTransactionsStore = defineStore('transactions', () => {
changed = true; changed = true;
} }
if (filter && isString(filter.tagIds) && transactionsFilter.value.tagIds !== filter.tagIds) { if (filter && isString(filter.tagFilter) && transactionsFilter.value.tagFilter !== filter.tagFilter) {
transactionsFilter.value.tagIds = filter.tagIds; transactionsFilter.value.tagFilter = filter.tagFilter;
changed = true;
}
if (filter && isNumber(filter.tagFilterType) && transactionsFilter.value.tagFilterType !== filter.tagFilterType) {
transactionsFilter.value.tagFilterType = filter.tagFilterType;
changed = true; changed = true;
} }
@@ -743,12 +751,8 @@ export const useTransactionsStore = defineStore('transactions', () => {
querys.push('categoryIds=' + transactionsFilter.value.categoryIds); querys.push('categoryIds=' + transactionsFilter.value.categoryIds);
} }
if (transactionsFilter.value.tagIds) { if (transactionsFilter.value.tagFilter) {
querys.push('tagIds=' + transactionsFilter.value.tagIds); querys.push('tagFilter=' + transactionsFilter.value.tagFilter);
}
if (transactionsFilter.value.tagFilterType) {
querys.push('tagFilterType=' + transactionsFilter.value.tagFilterType);
} }
querys.push('dateType=' + transactionsFilter.value.dateType); querys.push('dateType=' + transactionsFilter.value.dateType);
@@ -776,8 +780,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
type: transactionsFilter.value.type, type: transactionsFilter.value.type,
categoryIds: transactionsFilter.value.categoryIds, categoryIds: transactionsFilter.value.categoryIds,
accountIds: transactionsFilter.value.accountIds, accountIds: transactionsFilter.value.accountIds,
tagIds: transactionsFilter.value.tagIds, tagFilter: transactionsFilter.value.tagFilter,
tagFilterType: transactionsFilter.value.tagFilterType,
amountFilter: transactionsFilter.value.amountFilter, amountFilter: transactionsFilter.value.amountFilter,
keyword: transactionsFilter.value.keyword keyword: transactionsFilter.value.keyword
}; };
@@ -802,8 +805,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
type: transactionsFilter.value.type, type: transactionsFilter.value.type,
categoryIds: transactionsFilter.value.categoryIds, categoryIds: transactionsFilter.value.categoryIds,
accountIds: transactionsFilter.value.accountIds, accountIds: transactionsFilter.value.accountIds,
tagIds: transactionsFilter.value.tagIds, tagFilter: transactionsFilter.value.tagFilter,
tagFilterType: transactionsFilter.value.tagFilterType,
amountFilter: transactionsFilter.value.amountFilter, amountFilter: transactionsFilter.value.amountFilter,
keyword: transactionsFilter.value.keyword keyword: transactionsFilter.value.keyword
}).then(response => { }).then(response => {
@@ -882,8 +884,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
type: transactionsFilter.value.type, type: transactionsFilter.value.type,
categoryIds: transactionsFilter.value.categoryIds, categoryIds: transactionsFilter.value.categoryIds,
accountIds: transactionsFilter.value.accountIds, accountIds: transactionsFilter.value.accountIds,
tagIds: transactionsFilter.value.tagIds, tagFilter: transactionsFilter.value.tagFilter,
tagFilterType: transactionsFilter.value.tagFilterType,
amountFilter: transactionsFilter.value.amountFilter, amountFilter: transactionsFilter.value.amountFilter,
keyword: transactionsFilter.value.keyword keyword: transactionsFilter.value.keyword
}).then(response => { }).then(response => {
+27
View File
@@ -259,6 +259,33 @@ html[dir="rtl"] .bidirectional-switch {
} }
} }
.toggle-buttons {
&.v-btn-toggle {
height: auto !important;
padding: 0 !important;
border: none !important;
}
&.v-btn-toggle > .v-btn:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
}
&.v-btn-toggle > .v-btn:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&.v-btn-toggle > .v-btn {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
}
&.v-btn-toggle button.v-btn {
width: auto !important;
}
}
.v-theme--dark { .v-theme--dark {
.v-btn--variant-elevated, .v-btn--variant-elevated,
.v-btn--variant-flat { .v-btn--variant-flat {
@@ -1,26 +1,35 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { useTransactionsStore } from '@/stores/transaction.ts'; import { useTransactionsStore } from '@/stores/transaction.ts';
import { useStatisticsStore } from '@/stores/statistics.ts'; import { useStatisticsStore } from '@/stores/statistics.ts';
import { type TypeAndDisplayName, keys, keysIfValueEquals, values } from '@/core/base.ts'; import { entries, values } from '@/core/base.ts';
import { TransactionTagFilterType } from '@/core/transaction.ts'; import { TransactionTagFilterType } from '@/core/transaction.ts';
import type { TransactionTag } from '@/models/transaction_tag.ts'; import type { TransactionTag } from '@/models/transaction_tag.ts';
import { TransactionTagFilter } from '@/models/transaction.ts';
import { objectFieldWithValueToArrayItem } from '@/lib/common.ts';
export enum TransactionTagFilterState {
Default = 0,
Include = 1,
Exclude = 2
}
export function useTransactionTagFilterSettingPageBase(type?: string) { export function useTransactionTagFilterSettingPageBase(type?: string) {
const { getAllTransactionTagFilterTypes } = useI18n();
const transactionTagsStore = useTransactionTagsStore(); const transactionTagsStore = useTransactionTagsStore();
const transactionsStore = useTransactionsStore(); const transactionsStore = useTransactionsStore();
const statisticsStore = useStatisticsStore(); const statisticsStore = useStatisticsStore();
const loading = ref<boolean>(true); const loading = ref<boolean>(true);
const showHidden = ref<boolean>(false); const showHidden = ref<boolean>(false);
const filterTagIds = ref<Record<string, boolean>>({}); const filterTagIds = ref<Record<string, TransactionTagFilterState>>({});
const tagFilterType = ref<number>(TransactionTagFilterType.Default.type); const includeTagFilterType = ref<number>(TransactionTagFilterType.HasAny.type);
const excludeTagFilterType = ref<number>(TransactionTagFilterType.NotHasAny.type);
const includeTagsCount = computed<number>(() => objectFieldWithValueToArrayItem(filterTagIds.value, TransactionTagFilterState.Include).length);
const excludeTagsCount = computed<number>(() => objectFieldWithValueToArrayItem(filterTagIds.value, TransactionTagFilterState.Exclude).length);
const title = computed<string>(() => { const title = computed<string>(() => {
return 'Filter Transaction Tags'; return 'Filter Transaction Tags';
@@ -31,7 +40,6 @@ export function useTransactionTagFilterSettingPageBase(type?: string) {
}); });
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags); const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
const allTagFilterTypes = computed<TypeAndDisplayName[]>(() => getAllTransactionTagFilterTypes());
const hasAnyAvailableTag = computed<boolean>(() => transactionTagsStore.allAvailableTagsCount > 0); const hasAnyAvailableTag = computed<boolean>(() => transactionTagsStore.allAvailableTagsCount > 0);
const hasAnyVisibleTag = computed<boolean>(() => { const hasAnyVisibleTag = computed<boolean>(() => {
if (showHidden.value) { if (showHidden.value) {
@@ -42,67 +50,76 @@ export function useTransactionTagFilterSettingPageBase(type?: string) {
}); });
function loadFilterTagIds(): boolean { function loadFilterTagIds(): boolean {
const allTransactionTagIds: Record<string, boolean> = {}; let tagFilters: TransactionTagFilter[] = [];
for (const transactionTag of values(transactionTagsStore.allTransactionTagsMap)) {
allTransactionTagIds[transactionTag.id] = true;
}
if (type === 'statisticsCurrent') { if (type === 'statisticsCurrent') {
const transactionTagIds = statisticsStore.transactionStatisticsFilter.tagIds ? statisticsStore.transactionStatisticsFilter.tagIds.split(',') : []; tagFilters = TransactionTagFilter.parse(statisticsStore.transactionStatisticsFilter.tagFilter);
for (const transactionTagId of transactionTagIds) {
const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId];
if (transactionTag) {
allTransactionTagIds[transactionTag.id] = false;
}
}
filterTagIds.value = allTransactionTagIds;
tagFilterType.value = statisticsStore.transactionStatisticsFilter.tagFilterType;
return true;
} else if (type === 'transactionListCurrent') { } else if (type === 'transactionListCurrent') {
for (const transactionTagId of keysIfValueEquals(transactionsStore.allFilterTagIds, true)) { tagFilters = TransactionTagFilter.parse(transactionsStore.transactionsFilter.tagFilter);
const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId];
if (transactionTag) {
allTransactionTagIds[transactionTag.id] = false;
}
}
filterTagIds.value = allTransactionTagIds;
return true;
} else { } else {
return false; return false;
} }
const allTagIdsMap: Record<string, TransactionTagFilterState> = {};
for (const transactionTag of values(transactionTagsStore.allTransactionTagsMap)) {
allTagIdsMap[transactionTag.id] = TransactionTagFilterState.Default;
}
for (const tagFilter of tagFilters) {
let state: TransactionTagFilterState = TransactionTagFilterState.Default;
if (tagFilter.type === TransactionTagFilterType.HasAny || tagFilter.type === TransactionTagFilterType.HasAll) {
state = TransactionTagFilterState.Include;
includeTagFilterType.value = tagFilter.type.type;
} else if (tagFilter.type === TransactionTagFilterType.NotHasAny || tagFilter.type === TransactionTagFilterType.NotHasAll) {
state = TransactionTagFilterState.Exclude;
excludeTagFilterType.value = tagFilter.type.type;
} else {
continue;
}
for (const tagId of tagFilter.tagIds) {
allTagIdsMap[tagId] = state;
}
}
filterTagIds.value = allTagIdsMap;
return true;
} }
function saveFilterTagIds(): boolean { function saveFilterTagIds(): boolean {
const filteredTagIds: Record<string, boolean> = {}; const includeTagFilter: TransactionTagFilter = TransactionTagFilter.create(TransactionTagFilterType.parse(includeTagFilterType.value) ?? TransactionTagFilterType.HasAny);
let finalTagIds = ''; const excludeTagFilter: TransactionTagFilter = TransactionTagFilter.create(TransactionTagFilterType.parse(excludeTagFilterType.value) ?? TransactionTagFilterType.NotHasAny);
let changed = true; let changed = true;
for (const transactionTagId of keys(filterTagIds.value)) { for (const [transactionTagId, state] of entries(filterTagIds.value)) {
const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId]; const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId];
if (!transactionTag) { if (!transactionTag) {
continue; continue;
} }
if (filterTagIds.value[transactionTag.id]) { if (state === TransactionTagFilterState.Include) {
filteredTagIds[transactionTag.id] = true; includeTagFilter.tagIds.push(transactionTag.id);
} else { } else if (state === TransactionTagFilterState.Exclude) {
if (finalTagIds.length > 0) { excludeTagFilter.tagIds.push(transactionTag.id);
finalTagIds += ','; }
} }
finalTagIds += transactionTag.id; const tagFilters: TransactionTagFilter[] = [];
if (includeTagFilter.tagIds.length > 0) {
tagFilters.push(includeTagFilter);
} }
if (excludeTagFilter.tagIds.length > 0) {
tagFilters.push(excludeTagFilter);
} }
if (type === 'statisticsCurrent') { if (type === 'statisticsCurrent') {
changed = statisticsStore.updateTransactionStatisticsFilter({ changed = statisticsStore.updateTransactionStatisticsFilter({
tagIds: finalTagIds, tagFilter: TransactionTagFilter.toTextualTagFilters(tagFilters)
tagFilterType: tagFilterType.value
}); });
if (changed) { if (changed) {
@@ -110,7 +127,7 @@ export function useTransactionTagFilterSettingPageBase(type?: string) {
} }
} else if (type === 'transactionListCurrent') { } else if (type === 'transactionListCurrent') {
changed = transactionsStore.updateTransactionListFilter({ changed = transactionsStore.updateTransactionListFilter({
tagIds: finalTagIds tagFilter: TransactionTagFilter.toTextualTagFilters(tagFilters)
}); });
if (changed) { if (changed) {
@@ -126,12 +143,14 @@ export function useTransactionTagFilterSettingPageBase(type?: string) {
loading, loading,
showHidden, showHidden,
filterTagIds, filterTagIds,
tagFilterType, includeTagFilterType,
excludeTagFilterType,
// computed states // computed states
includeTagsCount,
excludeTagsCount,
title, title,
applyText, applyText,
allTags, allTags,
allTagFilterTypes,
hasAnyAvailableTag, hasAnyAvailableTag,
hasAnyVisibleTag, hasAnyVisibleTag,
// functions // functions
@@ -9,7 +9,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { type TransactionListFilter, type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts'; import { type TransactionListFilter, type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts';
import { type TypeAndName, entries } from '@/core/base.ts'; import { type TypeAndName, keys, entries } from '@/core/base.ts';
import type { NumeralSystem } from '@/core/numeral.ts'; import type { NumeralSystem } from '@/core/numeral.ts';
import { type TextualYearMonthDay, type Year0BasedMonth, type LocalizedDateRange, type WeekDayValue, DateRange, DateRangeScene } from '@/core/datetime.ts'; import { type TextualYearMonthDay, type Year0BasedMonth, type LocalizedDateRange, type WeekDayValue, DateRange, DateRangeScene } from '@/core/datetime.ts';
import { AccountType } from '@/core/account.ts'; import { AccountType } from '@/core/account.ts';
@@ -19,7 +19,7 @@ import { DISPLAY_HIDDEN_AMOUNT, INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numera
import type { Account } from '@/models/account.ts'; import type { Account } from '@/models/account.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts';
import type { TransactionTag } from '@/models/transaction_tag.ts'; import type { TransactionTag } from '@/models/transaction_tag.ts';
import type { Transaction } from '@/models/transaction.ts'; import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
@@ -196,7 +196,7 @@ export function useTransactionListPageBase() {
}); });
const queryTagName = computed<string>(() => { const queryTagName = computed<string>(() => {
if (query.value.tagIds === 'none') { if (query.value.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue) {
return tt('Without Tags'); return tt('Without Tags');
} }
@@ -204,7 +204,15 @@ export function useTransactionListPageBase() {
return tt('Multiple Tags'); return tt('Multiple Tags');
} }
return allTransactionTags.value[query.value.tagIds]?.name || tt('Tags'); for (const tagId of keys(queryAllFilterTagIds.value)) {
const tagName = allTransactionTags.value[tagId]?.name;
if (tagName) {
return tagName;
}
}
return tt('Tags');
}); });
const queryAmount = computed<string>(() => { const queryAmount = computed<string>(() => {
@@ -11,22 +11,30 @@
<v-menu activator="parent"> <v-menu activator="parent">
<v-list> <v-list>
<v-list-item :prepend-icon="mdiSelectAll" <v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Select All')" :title="tt('Set All to Included')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectAllTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Include)"></v-list-item>
<v-list-item :prepend-icon="mdiSelect" <v-list-item :prepend-icon="mdiSelect"
:title="tt('Select None')" :title="tt('Set All to Default')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectNoneTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Default)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectInverse" <v-list-item :prepend-icon="mdiSelectInverse"
:title="tt('Invert Selection')" :title="tt('Set All to Excluded')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectInvertTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Exclude)"></v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiSelectAll" <v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Select All Visible')" :title="tt('Set All Visible Items to Included')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectAllVisibleTransactionTags"></v-list-item> @click="setAllToState(true, TransactionTagFilterState.Include)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Set All Visible Items to Default')"
:disabled="!hasAnyVisibleTag"
@click="setAllToState(true, TransactionTagFilterState.Default)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Set All Visible Items to Excluded')"
:disabled="!hasAnyVisibleTag"
@click="setAllToState(true, TransactionTagFilterState.Exclude)"></v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiEyeOutline" <v-list-item :prepend-icon="mdiEyeOutline"
:title="tt('Show Hidden Transaction Tags')" :title="tt('Show Hidden Transaction Tags')"
@@ -47,22 +55,30 @@
<v-menu activator="parent"> <v-menu activator="parent">
<v-list> <v-list>
<v-list-item :prepend-icon="mdiSelectAll" <v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Select All')" :title="tt('Set All to Included')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectAllTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Include)"></v-list-item>
<v-list-item :prepend-icon="mdiSelect" <v-list-item :prepend-icon="mdiSelect"
:title="tt('Select None')" :title="tt('Set All to Default')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectNoneTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Default)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectInverse" <v-list-item :prepend-icon="mdiSelectInverse"
:title="tt('Invert Selection')" :title="tt('Set All to Excluded')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectInvertTransactionTags"></v-list-item> @click="setAllToState(false, TransactionTagFilterState.Exclude)"></v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiSelectAll" <v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Select All Visible')" :title="tt('Set All Visible Items to Included')"
:disabled="!hasAnyVisibleTag" :disabled="!hasAnyVisibleTag"
@click="selectAllVisibleTransactionTags"></v-list-item> @click="setAllToState(true, TransactionTagFilterState.Include)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Set All Visible Items to Default')"
:disabled="!hasAnyVisibleTag"
@click="setAllToState(true, TransactionTagFilterState.Default)"></v-list-item>
<v-list-item :prepend-icon="mdiSelectAll"
:title="tt('Set All Visible Items to Excluded')"
:disabled="!hasAnyVisibleTag"
@click="setAllToState(true, TransactionTagFilterState.Exclude)"></v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiEyeOutline" <v-list-item :prepend-icon="mdiEyeOutline"
:title="tt('Show Hidden Transaction Tags')" :title="tt('Show Hidden Transaction Tags')"
@@ -86,15 +102,24 @@
</v-card-text> </v-card-text>
<v-card-text :class="{ 'mt-0 mt-sm-2 mt-md-4': dialogMode }" v-else-if="!loading && hasAnyVisibleTag"> <v-card-text :class="{ 'mt-0 mt-sm-2 mt-md-4': dialogMode }" v-else-if="!loading && hasAnyVisibleTag">
<div class="tag-filter-types d-flex flex-column mb-4" v-if="type === 'statisticsCurrent'"> <div class="mb-4" v-if="includeTagsCount > 1">
<v-btn border class="justify-start" :key="filterType.type" <v-btn-toggle class="toggle-buttons" density="compact" variant="outlined"
:color="tagFilterType === filterType.type ? 'primary' : 'default'" mandatory="force" divided
:variant="tagFilterType === filterType.type ? 'tonal' : 'outlined'" :model-value="includeTagFilterType"
:append-icon="(tagFilterType === filterType.type ? mdiCheck : undefined)" @update:model-value="updateTransactionTagIncludeType($event)">
v-for="filterType in allTagFilterTypes" <v-btn :value="TransactionTagFilterType.HasAny.type">{{ tt(TransactionTagFilterType.HasAny.name) }}</v-btn>
@click="tagFilterType = filterType.type"> <v-btn :value="TransactionTagFilterType.HasAll.type">{{ tt(TransactionTagFilterType.HasAll.name) }}</v-btn>
{{ filterType.displayName }} </v-btn-toggle>
</v-btn> </div>
<div class="mb-4" v-if="excludeTagsCount > 1">
<v-btn-toggle class="toggle-buttons" density="compact" variant="outlined"
mandatory="force" divided
:model-value="excludeTagFilterType"
@update:model-value="updateTransactionTagExcludeType($event)">
<v-btn :value="TransactionTagFilterType.NotHasAny.type">{{ tt(TransactionTagFilterType.NotHasAny.name) }}</v-btn>
<v-btn :value="TransactionTagFilterType.NotHasAll.type">{{ tt(TransactionTagFilterType.NotHasAll.name) }}</v-btn>
</v-btn-toggle>
</div> </div>
<v-expansion-panels class="tag-categories" multiple v-model="expandTagCategories"> <v-expansion-panels class="tag-categories" multiple v-model="expandTagCategories">
@@ -108,9 +133,6 @@
v-for="transactionTag in allTags"> v-for="transactionTag in allTags">
<v-list-item v-if="showHidden || !transactionTag.hidden"> <v-list-item v-if="showHidden || !transactionTag.hidden">
<template #prepend> <template #prepend>
<v-checkbox :model-value="!filterTagIds[transactionTag.id]"
@update:model-value="updateTransactionTagSelected(transactionTag, $event)">
<template #label>
<v-badge class="right-bottom-icon" color="secondary" <v-badge class="right-bottom-icon" color="secondary"
location="bottom right" offset-x="2" offset-y="2" :icon="mdiEyeOffOutline" location="bottom right" offset-x="2" offset-y="2" :icon="mdiEyeOffOutline"
v-if="transactionTag.hidden"> v-if="transactionTag.hidden">
@@ -119,7 +141,15 @@
<v-icon size="24" :icon="mdiPound" v-else-if="!transactionTag.hidden"/> <v-icon size="24" :icon="mdiPound" v-else-if="!transactionTag.hidden"/>
<span class="ms-3">{{ transactionTag.name }}</span> <span class="ms-3">{{ transactionTag.name }}</span>
</template> </template>
</v-checkbox> <template #append>
<v-btn-toggle class="toggle-buttons" density="compact" variant="outlined"
mandatory="force" divided
:model-value="filterTagIds[transactionTag.id]"
@update:model-value="updateTransactionTagState(transactionTag, $event)">
<v-btn :value="TransactionTagFilterState.Include">{{ tt('Included') }}</v-btn>
<v-btn :value="TransactionTagFilterState.Default">{{ tt('Default') }}</v-btn>
<v-btn :value="TransactionTagFilterState.Exclude">{{ tt('Excluded') }}</v-btn>
</v-btn-toggle>
</template> </template>
</v-list-item> </v-list-item>
</template> </template>
@@ -146,19 +176,16 @@ import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, useTemplateRef } from 'vue'; import { ref, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { useTransactionTagFilterSettingPageBase } from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts'; import {
useTransactionTagFilterSettingPageBase,
TransactionTagFilterState
} from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { TransactionTagFilterType } from '@/core/transaction.ts';
import type { TransactionTag } from '@/models/transaction_tag.ts'; import type { TransactionTag } from '@/models/transaction_tag.ts';
import {
selectAllVisible,
selectAll,
selectNone,
selectInvert
} from '@/lib/common.ts';
import { import {
mdiSelectAll, mdiSelectAll,
mdiSelect, mdiSelect,
@@ -166,7 +193,6 @@ import {
mdiEyeOutline, mdiEyeOutline,
mdiEyeOffOutline, mdiEyeOffOutline,
mdiDotsVertical, mdiDotsVertical,
mdiCheck,
mdiPound mdiPound
} from '@mdi/js'; } from '@mdi/js';
@@ -188,11 +214,13 @@ const {
loading, loading,
showHidden, showHidden,
filterTagIds, filterTagIds,
tagFilterType, includeTagFilterType,
excludeTagFilterType,
includeTagsCount,
excludeTagsCount,
title, title,
applyText, applyText,
allTags, allTags,
allTagFilterTypes,
hasAnyAvailableTag, hasAnyAvailableTag,
hasAnyVisibleTag, hasAnyVisibleTag,
loadFilterTagIds, loadFilterTagIds,
@@ -223,40 +251,38 @@ function init(): void {
}); });
} }
function updateTransactionTagSelected(transactionTag: TransactionTag, value: boolean | null): void { function updateTransactionTagState(transactionTag: TransactionTag, value: TransactionTagFilterState): void {
filterTagIds.value[transactionTag.id] = !value; filterTagIds.value[transactionTag.id] = value;
if (props.autoSave) { if (props.autoSave) {
save(); save();
} }
} }
function selectAllTransactionTags(): void { function updateTransactionTagIncludeType(value: number): void {
selectAll(filterTagIds.value, transactionTagsStore.allTransactionTagsMap); includeTagFilterType.value = value;
if (props.autoSave) { if (props.autoSave) {
save(); save();
} }
} }
function selectNoneTransactionTags(): void { function updateTransactionTagExcludeType(value: number): void {
selectNone(filterTagIds.value, transactionTagsStore.allTransactionTagsMap); excludeTagFilterType.value = value;
if (props.autoSave) { if (props.autoSave) {
save(); save();
} }
} }
function selectInvertTransactionTags(): void { function setAllToState(onlyVisible: boolean, value: TransactionTagFilterState): void {
selectInvert(filterTagIds.value, transactionTagsStore.allTransactionTagsMap); for (const tag of allTags.value) {
if (onlyVisible && !showHidden.value && tag.hidden) {
if (props.autoSave) { continue;
save();
} }
}
function selectAllVisibleTransactionTags(): void { filterTagIds.value[tag.id] = value;
selectAllVisible(filterTagIds.value, transactionTagsStore.allTransactionTagsMap); }
if (props.autoSave) { if (props.autoSave) {
save(); save();
@@ -276,15 +302,9 @@ init();
</script> </script>
<style> <style>
.tag-filter-types .v-btn:not(:first-child) { .tag-categories .tag-filter-state-toggle {
border-top-left-radius: inherit; overflow-x: auto;
border-top-right-radius: inherit; white-space: nowrap;
}
.tag-filter-types .v-btn:not(:last-child) {
border-bottom: 0;
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
} }
.tag-categories .v-expansion-panel-text__wrapper { .tag-categories .v-expansion-panel-text__wrapper {
@@ -576,8 +576,7 @@ interface TransactionStatisticsProps {
initEndTime?: TextualYearMonth | '', initEndTime?: TextualYearMonth | '',
initFilterAccountIds?: string, initFilterAccountIds?: string,
initFilterCategoryIds?: string, initFilterCategoryIds?: string,
initTagIds?: string, initTagFilter?: string,
initTagFilterType?: string,
initKeyword?: string; initKeyword?: string;
initSortingType?: string, initSortingType?: string,
initTrendDateAggregationType?: string initTrendDateAggregationType?: string
@@ -757,8 +756,7 @@ function init(initProps: TransactionStatisticsProps): void {
chartDataType: initProps.initChartDataType ? parseInt(initProps.initChartDataType) : undefined, chartDataType: initProps.initChartDataType ? parseInt(initProps.initChartDataType) : undefined,
filterAccountIds: initProps.initFilterAccountIds ? arrayItemToObjectField(initProps.initFilterAccountIds.split(','), true) : {}, filterAccountIds: initProps.initFilterAccountIds ? arrayItemToObjectField(initProps.initFilterAccountIds.split(','), true) : {},
filterCategoryIds: initProps.initFilterCategoryIds ? arrayItemToObjectField(initProps.initFilterCategoryIds.split(','), true) : {}, filterCategoryIds: initProps.initFilterCategoryIds ? arrayItemToObjectField(initProps.initFilterCategoryIds.split(','), true) : {},
tagIds: initProps.initTagIds, tagFilter: initProps.initTagFilter,
tagFilterType: initProps.initTagFilterType && parseInt(initProps.initTagFilterType) >= 0 ? parseInt(initProps.initTagFilterType) : undefined,
keyword: initProps.initKeyword, keyword: initProps.initKeyword,
sortingType: initProps.initSortingType ? parseInt(initProps.initSortingType) : undefined sortingType: initProps.initSortingType ? parseInt(initProps.initSortingType) : undefined
}; };
@@ -1314,8 +1312,7 @@ onBeforeRouteUpdate((to) => {
initEndTime: (to.query['endTime'] as TextualYearMonth | null) || undefined, initEndTime: (to.query['endTime'] as TextualYearMonth | null) || undefined,
initFilterAccountIds: (to.query['filterAccountIds'] as string | null) || undefined, initFilterAccountIds: (to.query['filterAccountIds'] as string | null) || undefined,
initFilterCategoryIds: (to.query['filterCategoryIds'] as string | null) || undefined, initFilterCategoryIds: (to.query['filterCategoryIds'] as string | null) || undefined,
initTagIds: (to.query['tagIds'] as string | null) || undefined, initTagFilter: (to.query['tagFilter'] as string | null) || undefined,
initTagFilterType: (to.query['tagFilterType'] as string | null) || undefined,
initKeyword: (to.query['keyword'] as string | null) || undefined, initKeyword: (to.query['keyword'] as string | null) || undefined,
initSortingType: (to.query['sortingType'] as string | null) || undefined, initSortingType: (to.query['sortingType'] as string | null) || undefined,
initTrendDateAggregationType: (to.query['trendDateAggregationType'] as string | null) || undefined, initTrendDateAggregationType: (to.query['trendDateAggregationType'] as string | null) || undefined,
+38 -92
View File
@@ -431,15 +431,15 @@
@update:model-value="scrollTagMenuToSelectedItem"> @update:model-value="scrollTagMenuToSelectedItem">
<template #activator="{ props }"> <template #activator="{ props }">
<div class="d-flex align-center cursor-pointer" <div class="d-flex align-center cursor-pointer"
:class="{ 'readonly': loading, 'text-primary': query.tagIds }" v-bind="props"> :class="{ 'readonly': loading, 'text-primary': query.tagFilter }" v-bind="props">
<span>{{ queryTagName }}</span> <span>{{ queryTagName }}</span>
<v-icon :icon="mdiMenuDown" /> <v-icon :icon="mdiMenuDown" />
</div> </div>
</template> </template>
<v-list :selected="[queryAllSelectedFilterTagIds]"> <v-list :selected="[queryAllSelectedFilterTagIds]">
<v-list-item key="" value="" class="text-sm" density="compact" <v-list-item key="" value="" class="text-sm" density="compact"
:class="{ 'list-item-selected': !query.tagIds }" :class="{ 'list-item-selected': !query.tagFilter }"
:append-icon="(!query.tagIds ? mdiCheck : undefined)"> :append-icon="(!query.tagFilter ? mdiCheck : undefined)">
<v-list-item-title class="cursor-pointer" <v-list-item-title class="cursor-pointer"
@click="changeTagFilter('')"> @click="changeTagFilter('')">
<div class="d-flex align-center"> <div class="d-flex align-center">
@@ -448,11 +448,13 @@
</div> </div>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item key="none" value="none" class="text-sm" density="compact" <v-list-item class="text-sm" density="compact"
:class="{ 'list-item-selected': query.tagIds === 'none' }" :key="TransactionTagFilter.TransactionNoTagFilterValue"
:append-icon="(query.tagIds === 'none' ? mdiCheck : undefined)"> :value="TransactionTagFilter.TransactionNoTagFilterValue"
:class="{ 'list-item-selected': query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue }"
:append-icon="(query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue ? mdiCheck : undefined)">
<v-list-item-title class="cursor-pointer" <v-list-item-title class="cursor-pointer"
@click="changeTagFilter('none')"> @click="changeTagFilter(TransactionTagFilter.TransactionNoTagFilterValue)">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon :icon="mdiBorderNoneVariant" /> <v-icon :icon="mdiBorderNoneVariant" />
<span class="text-sm ms-3">{{ tt('Without Tags') }}</span> <span class="text-sm ms-3">{{ tt('Without Tags') }}</span>
@@ -460,8 +462,8 @@
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item key="multiple" value="multiple" class="text-sm" density="compact" <v-list-item key="multiple" value="multiple" class="text-sm" density="compact"
:class="{ 'list-item-selected': query.tagIds && queryAllFilterTagIdsCount > 1 }" :class="{ 'list-item-selected': query.tagFilter && queryAllFilterTagIdsCount > 1 }"
:append-icon="(query.tagIds && queryAllFilterTagIdsCount > 1 ? mdiCheck : undefined)" :append-icon="(query.tagFilter && queryAllFilterTagIdsCount > 1 ? mdiCheck : undefined)"
v-if="allAvailableTagsCount > 0"> v-if="allAvailableTagsCount > 0">
<v-list-item-title class="cursor-pointer" <v-list-item-title class="cursor-pointer"
@click="showFilterTagDialog = true"> @click="showFilterTagDialog = true">
@@ -472,34 +474,18 @@
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-divider v-if="query.tagIds && query.tagIds !== 'none'" /> <v-divider v-if="query.tagFilter && query.tagFilter !== TransactionTagFilter.TransactionNoTagFilterValue" />
<template v-if="query.tagIds && query.tagIds !== 'none'">
<v-list-item class="text-sm" density="compact"
:key="filterType.type"
:value="filterType.type"
:append-icon="(query.tagFilterType === filterType.type ? mdiCheck : undefined)"
v-for="filterType in allTransactionTagFilterTypes">
<v-list-item-title class="cursor-pointer"
@click="changeTagFilterType(filterType.type)">
<div class="d-flex align-center">
<v-icon size="24" :icon="filterType.icon"/>
<span class="text-sm ms-3">{{ filterType.displayName }}</span>
</div>
</v-list-item-title>
</v-list-item>
</template>
<template :key="transactionTag.id" <template :key="transactionTag.id"
v-for="transactionTag in allTransactionTags"> v-for="transactionTag in allTransactionTags">
<v-divider v-if="!transactionTag.hidden || query.tagIds === transactionTag.id" /> <v-divider v-if="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])" />
<v-list-item class="text-sm" density="compact" <v-list-item class="text-sm" density="compact"
:value="transactionTag.id" :value="transactionTag.id"
:class="{ 'list-item-selected': query.tagIds === transactionTag.id, 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && queryAllFilterTagIds[transactionTag.id] }" :class="{ 'list-item-selected': queryAllFilterTagIdsCount === 1 && isDefined(queryAllFilterTagIds[transactionTag.id]), 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && isDefined(queryAllFilterTagIds[transactionTag.id]) }"
:append-icon="(query.tagIds === transactionTag.id ? mdiCheck : undefined)" :append-icon="(queryAllFilterTagIds[transactionTag.id] === true ? mdiCheck : (queryAllFilterTagIds[transactionTag.id] === false ? mdiClose : undefined))"
v-if="!transactionTag.hidden || query.tagIds === transactionTag.id"> v-if="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])">
<v-list-item-title class="cursor-pointer" <v-list-item-title class="cursor-pointer"
@click="changeTagFilter(transactionTag.id)"> @click="changeTagFilter(TransactionTagFilter.of(transactionTag.id).toTextualTagFilter())">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon size="24" :icon="mdiPound"/> <v-icon size="24" :icon="mdiPound"/>
<span class="text-sm ms-3">{{ transactionTag.name }}</span> <span class="text-sm ms-3">{{ transactionTag.name }}</span>
@@ -678,7 +664,6 @@ import { useDesktopPageStore } from '@/stores/desktopPage.ts';
import { import {
type NameNumeralValue, type NameNumeralValue,
type TypeAndDisplayName,
keys keys
} from '@/core/base.ts'; } from '@/core/base.ts';
import { import {
@@ -690,16 +675,18 @@ import {
} from '@/core/datetime.ts'; } from '@/core/datetime.ts';
import { type NumeralSystem, AmountFilterType } from '@/core/numeral.ts'; import { type NumeralSystem, AmountFilterType } from '@/core/numeral.ts';
import { ThemeType } from '@/core/theme.ts'; import { ThemeType } from '@/core/theme.ts';
import { TransactionType, TransactionTagFilterType } from '@/core/transaction.ts'; import { TransactionType } from '@/core/transaction.ts';
import { TemplateType } from '@/core/template.ts'; import { TemplateType } from '@/core/template.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts';
import type { Transaction } from '@/models/transaction.ts'; import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
import type { TransactionTemplate } from '@/models/transaction_template.ts'; import type { TransactionTemplate } from '@/models/transaction_template.ts';
import { import {
isDefined,
isObject, isObject,
isString, isString,
isNumber isNumber,
objectFieldWithValueToArrayItem
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import {
getCurrentUnixTime, getCurrentUnixTime,
@@ -731,6 +718,7 @@ import logger from '@/lib/logger.ts';
import { import {
mdiMagnify, mdiMagnify,
mdiCheck, mdiCheck,
mdiClose,
mdiViewGridOutline, mdiViewGridOutline,
mdiBorderNoneVariant, mdiBorderNoneVariant,
mdiVectorArrangeBelow, mdiVectorArrangeBelow,
@@ -740,10 +728,6 @@ import {
mdiPencilBoxOutline, mdiPencilBoxOutline,
mdiArrowLeft, mdiArrowLeft,
mdiArrowRight, mdiArrowRight,
mdiPlusBoxMultipleOutline,
mdiCheckboxMultipleOutline,
mdiMinusBoxMultipleOutline,
mdiCloseBoxMultipleOutline,
mdiPound, mdiPound,
mdiMagicStaff, mdiMagicStaff,
mdiTextBoxOutline mdiTextBoxOutline
@@ -757,8 +741,7 @@ interface TransactionListProps {
initType?: string, initType?: string,
initCategoryIds?: string, initCategoryIds?: string,
initAccountIds?: string, initAccountIds?: string,
initTagIds?: string, initTagFilter?: string,
initTagFilterType?: string,
initAmountFilter?: string, initAmountFilter?: string,
initKeyword?: string initKeyword?: string
} }
@@ -771,12 +754,6 @@ type EditDialogType = InstanceType<typeof EditDialog>;
type AIImageRecognitionDialogType = InstanceType<typeof AIImageRecognitionDialog>; type AIImageRecognitionDialogType = InstanceType<typeof AIImageRecognitionDialog>;
type ImportDialogType = InstanceType<typeof ImportDialog>; type ImportDialogType = InstanceType<typeof ImportDialog>;
interface TransactionTemplateWithIcon {
type: number;
displayName: string;
icon: string;
}
interface TransactionListDisplayTotalAmount { interface TransactionListDisplayTotalAmount {
income: string; income: string;
expense: string; expense: string;
@@ -789,7 +766,6 @@ const theme = useTheme();
const { const {
tt, tt,
getAllRecentMonthDateRanges, getAllRecentMonthDateRanges,
getAllTransactionTagFilterTypes,
getWeekdayLongName, getWeekdayLongName,
getCurrentNumeralSystemType getCurrentNumeralSystemType
} = useI18n(); } = useI18n();
@@ -852,13 +828,6 @@ const transactionsStore = useTransactionsStore();
const transactionTemplatesStore = useTransactionTemplatesStore(); const transactionTemplatesStore = useTransactionTemplatesStore();
const desktopPageStore = useDesktopPageStore(); const desktopPageStore = useDesktopPageStore();
const tagFilterIconMap: Record<number, string> = {
[TransactionTagFilterType.HasAny.type]: mdiPlusBoxMultipleOutline,
[TransactionTagFilterType.HasAll.type]: mdiCheckboxMultipleOutline,
[TransactionTagFilterType.NotHasAny.type]: mdiMinusBoxMultipleOutline,
[TransactionTagFilterType.NotHasAll.type]: mdiCloseBoxMultipleOutline
};
const timeFilterMenu = useTemplateRef<VMenu>('timeFilterMenu'); const timeFilterMenu = useTemplateRef<VMenu>('timeFilterMenu');
const categoryFilterMenu = useTemplateRef<VMenu>('categoryFilterMenu'); const categoryFilterMenu = useTemplateRef<VMenu>('categoryFilterMenu');
const amountFilterMenu = useTemplateRef<VMenu>('amountFilterMenu'); const amountFilterMenu = useTemplateRef<VMenu>('amountFilterMenu');
@@ -912,21 +881,6 @@ const allTransactionTemplates = computed<TransactionTemplate[]>(() => {
return allTemplates[TemplateType.Normal.type] || []; return allTemplates[TemplateType.Normal.type] || [];
}); });
const allTransactionTagFilterTypes = computed<TransactionTemplateWithIcon[]>(() => {
const allTagFilterTypes: TypeAndDisplayName[] = getAllTransactionTagFilterTypes();
const allTagFilterTypesWithIcon: TransactionTemplateWithIcon[] = [];
for (const tagFilterType of allTagFilterTypes) {
allTagFilterTypesWithIcon.push({
type: tagFilterType.type,
displayName: tagFilterType.displayName,
icon: tagFilterIconMap[tagFilterType.type] ?? ''
});
}
return allTagFilterTypesWithIcon;
});
const allowCategoryTypes = computed<string>(() => { const allowCategoryTypes = computed<string>(() => {
if (TransactionType.Income <= query.value.type && query.value.type <= TransactionType.Transfer) { if (TransactionType.Income <= query.value.type && query.value.type <= TransactionType.Transfer) {
return transactionTypeToCategoryType(query.value.type)?.toString() ?? ''; return transactionTypeToCategoryType(query.value.type)?.toString() ?? '';
@@ -1018,10 +972,16 @@ const queryAllSelectedFilterAccountIds = computed<string>(() => {
}); });
const queryAllSelectedFilterTagIds = computed<string>(() => { const queryAllSelectedFilterTagIds = computed<string>(() => {
if (queryAllFilterTagIdsCount.value === 0) { if (query.value.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue) {
return TransactionTagFilter.TransactionNoTagFilterValue;
} else if (queryAllFilterTagIdsCount.value === 0) {
return ''; return '';
} else if (queryAllFilterTagIdsCount.value === 1) { } else if (queryAllFilterTagIdsCount.value === 1) {
return query.value.tagIds; for (const tagId of keys(queryAllFilterTagIds.value)) {
return tagId;
}
return '';
} else { // queryAllFilterTagIdsCount.value > 1 } else { // queryAllFilterTagIdsCount.value > 1
return 'multiple'; return 'multiple';
} }
@@ -1147,8 +1107,7 @@ function init(initProps: TransactionListProps): void {
type: initProps.initType && parseInt(initProps.initType) > 0 ? parseInt(initProps.initType) : undefined, type: initProps.initType && parseInt(initProps.initType) > 0 ? parseInt(initProps.initType) : undefined,
categoryIds: initProps.initCategoryIds, categoryIds: initProps.initCategoryIds,
accountIds: initProps.initAccountIds, accountIds: initProps.initAccountIds,
tagIds: initProps.initTagIds, tagFilter: initProps.initTagFilter,
tagFilterType: initProps.initTagFilterType && parseInt(initProps.initTagFilterType) >= 0 ? parseInt(initProps.initTagFilterType) : undefined,
amountFilter: initProps.initAmountFilter || '', amountFilter: initProps.initAmountFilter || '',
keyword: initProps.initKeyword || '' keyword: initProps.initKeyword || ''
}); });
@@ -1490,13 +1449,13 @@ function changeMultipleAccountsFilter(changed: boolean): void {
updateUrlWhenChanged(changed); updateUrlWhenChanged(changed);
} }
function changeTagFilter(tagIds: string): void { function changeTagFilter(tagFilter: string): void {
if (query.value.tagIds === tagIds) { if (query.value.tagFilter === tagFilter) {
return; return;
} }
const changed = transactionsStore.updateTransactionListFilter({ const changed = transactionsStore.updateTransactionListFilter({
tagIds: tagIds tagFilter: tagFilter
}); });
updateUrlWhenChanged(changed); updateUrlWhenChanged(changed);
@@ -1508,18 +1467,6 @@ function changeMultipleTagsFilter(changed: boolean): void {
updateUrlWhenChanged(changed); updateUrlWhenChanged(changed);
} }
function changeTagFilterType(filterType: number): void {
if (query.value.tagFilterType === filterType) {
return;
}
const changed = transactionsStore.updateTransactionListFilter({
tagFilterType: filterType
});
updateUrlWhenChanged(changed);
}
function changeKeywordFilter(keyword: string): void { function changeKeywordFilter(keyword: string): void {
if (query.value.keyword === keyword) { if (query.value.keyword === keyword) {
return; return;
@@ -1592,7 +1539,7 @@ function add(template?: TransactionTemplate): void {
type: query.value.type, type: query.value.type,
categoryId: queryAllFilterCategoryIdsCount.value === 1 ? query.value.categoryIds : '', categoryId: queryAllFilterCategoryIdsCount.value === 1 ? query.value.categoryIds : '',
accountId: queryAllFilterAccountIdsCount.value === 1 ? query.value.accountIds : '', accountId: queryAllFilterAccountIdsCount.value === 1 ? query.value.accountIds : '',
tagIds: query.value.tagIds || '', tagIds: objectFieldWithValueToArrayItem(queryAllFilterTagIds.value, true).join(',') || '',
template: template template: template
}).then(result => { }).then(result => {
if (result && result.message) { if (result && result.message) {
@@ -1765,8 +1712,7 @@ onBeforeRouteUpdate((to) => {
initType: (to.query['type'] as string | null) || undefined, initType: (to.query['type'] as string | null) || undefined,
initCategoryIds: (to.query['categoryIds'] as string | null) || undefined, initCategoryIds: (to.query['categoryIds'] as string | null) || undefined,
initAccountIds: (to.query['accountIds'] as string | null) || undefined, initAccountIds: (to.query['accountIds'] as string | null) || undefined,
initTagIds: (to.query['tagIds'] as string | null) || undefined, initTagFilter: (to.query['tagFilter'] as string | null) || undefined,
initTagFilterType: (to.query['tagFilterType'] as string | null) || undefined,
initAmountFilter: (to.query['amountFilter'] as string | null) || undefined, initAmountFilter: (to.query['amountFilter'] as string | null) || undefined,
initKeyword: (to.query['keyword'] as string | null) || undefined initKeyword: (to.query['keyword'] as string | null) || undefined
}); });
@@ -57,7 +57,7 @@
v-for="typeName in parsedFileAllTransactionTypes"> v-for="typeName in parsedFileAllTransactionTypes">
<td>{{ typeName }}</td> <td>{{ typeName }}</td>
<td> <td>
<v-btn-toggle class="transaction-types-toggle" density="compact" variant="outlined" <v-btn-toggle class="toggle-buttons" density="compact" variant="outlined"
mandatory="force" divided mandatory="force" divided
v-model="parsedFileDataColumnMapping.transactionTypeMapping[typeName]"> v-model="parsedFileDataColumnMapping.transactionTypeMapping[typeName]">
<v-btn :value="undefined">{{ tt('None') }}</v-btn> <v-btn :value="undefined">{{ tt('None') }}</v-btn>
@@ -166,14 +166,14 @@
v-for="separator in allSeparators"> v-for="separator in allSeparators">
<td>{{ separator.name }} ({{separator.value}})</td> <td>{{ separator.name }} ({{separator.value}})</td>
<td> <td>
<v-btn-toggle class="transaction-types-toggle" density="compact" variant="outlined" <v-btn-toggle class="toggle-buttons" density="compact" variant="outlined"
mandatory="force" divided mandatory="force" divided
v-model="parsedFileDataColumnMapping.geoLocationOrder" v-model="parsedFileDataColumnMapping.geoLocationOrder"
v-if="parsedFileDataColumnMapping.geoLocationSeparator === separator.value"> v-if="parsedFileDataColumnMapping.geoLocationSeparator === separator.value">
<v-btn value="latlon">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn> <v-btn value="latlon">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn>
<v-btn value="lonlat">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn> <v-btn value="lonlat">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn>
</v-btn-toggle> </v-btn-toggle>
<v-btn-group class="transaction-types-toggle" density="compact" variant="outlined" <v-btn-group class="toggle-buttons" density="compact" variant="outlined"
divided v-if="parsedFileDataColumnMapping.geoLocationSeparator !== separator.value"> divided v-if="parsedFileDataColumnMapping.geoLocationSeparator !== separator.value">
<v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'latlon')">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn> <v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'latlon')">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn>
<v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'lonlat')">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn> <v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'lonlat')">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn>
@@ -552,39 +552,3 @@ defineExpose({
saveColumnMappingFile saveColumnMappingFile
}); });
</script> </script>
<style>
.transaction-types-popup-menu .transaction-types-toggle {
overflow-x: auto;
white-space: nowrap;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle {
height: auto !important;
padding: 0;
border: none;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle button.v-btn {
width: auto !important;
}
</style>
@@ -40,14 +40,27 @@
</f7-list> </f7-list>
<f7-block class="combination-list-wrapper margin-vertical" key="default" v-show="!loading && hasAnyVisibleTag"> <f7-block class="combination-list-wrapper margin-vertical" key="default" v-show="!loading && hasAnyVisibleTag">
<f7-list class="margin-top-half margin-bottom" strong inset dividers v-if="query['type'] === 'statisticsCurrent'"> <f7-list class="margin-top-half margin-bottom" strong inset dividers>
<f7-list-item radio <f7-list-item radio
:title="filterType.displayName" :title="tt(filterType.name)"
:value="filterType.type"
:checked="tagFilterType === filterType.type"
:key="filterType.type" :key="filterType.type"
v-for="filterType in allTagFilterTypes" :value="filterType.type"
@change="tagFilterType = filterType.type"> :checked="includeTagFilterType === filterType.type"
v-for="filterType in [TransactionTagFilterType.HasAny, TransactionTagFilterType.HasAll]"
@change="includeTagFilterType = filterType.type"
v-if="includeTagsCount > 1">
</f7-list-item>
</f7-list>
<f7-list class="margin-top-half margin-bottom" strong inset dividers>
<f7-list-item radio
:title="tt(filterType.name)"
:key="filterType.type"
:value="filterType.type"
:checked="excludeTagFilterType === filterType.type"
v-for="filterType in [TransactionTagFilterType.NotHasAny, TransactionTagFilterType.NotHasAll]"
@change="excludeTagFilterType = filterType.type"
v-if="excludeTagsCount > 1">
</f7-list-item> </f7-list-item>
</f7-list> </f7-list>
@@ -68,14 +81,15 @@
</f7-block-title> </f7-block-title>
<f7-accordion-content :style="{ height: collapseStates['default']!.opened ? 'auto' : '' }"> <f7-accordion-content :style="{ height: collapseStates['default']!.opened ? 'auto' : '' }">
<f7-list strong inset dividers accordion-list class="combination-list-content"> <f7-list strong inset dividers accordion-list class="combination-list-content">
<f7-list-item checkbox <f7-list-item link="#"
popover-open=".tag-filter-state-popover-menu"
:title="transactionTag.name" :title="transactionTag.name"
:value="transactionTag.id" :value="transactionTag.id"
:checked="!filterTagIds[transactionTag.id]"
:key="transactionTag.id" :key="transactionTag.id"
:after="tt(filterTagIds[transactionTag.id] === TransactionTagFilterState.Include ? 'Included' : filterTagIds[transactionTag.id] === TransactionTagFilterState.Exclude ? 'Excluded' : 'Default')"
v-for="transactionTag in allTags" v-for="transactionTag in allTags"
v-show="showHidden || !transactionTag.hidden" v-show="showHidden || !transactionTag.hidden"
@change="updateTransactionTagSelected"> @click="currentTransactionTagId = transactionTag.id">
<template #media> <template #media>
<f7-icon class="transaction-tag-icon" f7="number"> <f7-icon class="transaction-tag-icon" f7="number">
<f7-badge color="gray" class="right-bottom-icon" v-if="transactionTag.hidden"> <f7-badge color="gray" class="right-bottom-icon" v-if="transactionTag.hidden">
@@ -89,14 +103,35 @@
</f7-accordion-item> </f7-accordion-item>
</f7-block> </f7-block>
<f7-popover class="tag-filter-state-popover-menu"
v-model:opened="showTagFilterStatePopover">
<f7-list dividers>
<f7-list-item :title="state.displayName"
:class="{ 'list-item-selected': filterTagIds[currentTransactionTagId] === state.type }"
:key="state.type"
v-for="state in [
{ type: TransactionTagFilterState.Include, displayName: tt('Included') },
{ type: TransactionTagFilterState.Default, displayName: tt('Default') },
{ type: TransactionTagFilterState.Exclude, displayName: tt('Excluded') }
]"
@click="updateCurrentTransactionTagState(state.type)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="filterTagIds[currentTransactionTagId] === state.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false"> <f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group> <f7-actions-group>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectAllTransactionTags">{{ tt('Select All') }}</f7-actions-button> <f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Include)">{{ tt('Set All to Included') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectNoneTransactionTags">{{ tt('Select None') }}</f7-actions-button> <f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Default)">{{ tt('Set All to Default') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectInvertTransactionTags">{{ tt('Invert Selection') }}</f7-actions-button> <f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Exclude)">{{ tt('Set All to Excluded') }}</f7-actions-button>
</f7-actions-group> </f7-actions-group>
<f7-actions-group> <f7-actions-group>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectAllVisibleTransactionTags">{{ tt('Select All Visible') }}</f7-actions-button> <f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Include)">{{ tt('Set All Visible Items to Included') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Default)">{{ tt('Set All Visible Items to Default') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Exclude)">{{ tt('Set All Visible Items to Excluded') }}</f7-actions-button>
</f7-actions-group> </f7-actions-group>
<f7-actions-group> <f7-actions-group>
<f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Tags') }}</f7-actions-button> <f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Tags') }}</f7-actions-button>
@@ -115,16 +150,14 @@ import type { Router } from 'framework7/types';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { useI18nUIComponents } from '@/lib/ui/mobile.ts'; import { useI18nUIComponents } from '@/lib/ui/mobile.ts';
import { useTransactionTagFilterSettingPageBase } from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts'; import {
useTransactionTagFilterSettingPageBase,
TransactionTagFilterState
} from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { import { TransactionTagFilterType } from '@/core/transaction.ts';
selectAllVisible,
selectAll,
selectNone,
selectInvert
} from '@/lib/common.ts';
interface CollapseState { interface CollapseState {
opened: boolean; opened: boolean;
@@ -144,11 +177,13 @@ const {
loading, loading,
showHidden, showHidden,
filterTagIds, filterTagIds,
tagFilterType, includeTagFilterType,
excludeTagFilterType,
includeTagsCount,
excludeTagsCount,
title, title,
applyText, applyText,
allTags, allTags,
allTagFilterTypes,
hasAnyAvailableTag, hasAnyAvailableTag,
hasAnyVisibleTag, hasAnyVisibleTag,
loadFilterTagIds, loadFilterTagIds,
@@ -158,6 +193,8 @@ const {
const transactionTagsStore = useTransactionTagsStore(); const transactionTagsStore = useTransactionTagsStore();
const loadingError = ref<unknown | null>(null); const loadingError = ref<unknown | null>(null);
const currentTransactionTagId = ref<string>('');
const showTagFilterStatePopover = ref<boolean>(false);
const showMoreActionSheet = ref<boolean>(false); const showMoreActionSheet = ref<boolean>(false);
const collapseStates = ref<Record<string, CollapseState>>({ const collapseStates = ref<Record<string, CollapseState>>({
@@ -186,32 +223,20 @@ function init(): void {
}); });
} }
function updateTransactionTagSelected(e: Event): void { function updateCurrentTransactionTagState(state: number): void {
const target = e.target as HTMLInputElement; filterTagIds.value[currentTransactionTagId.value] = state;
const transactionTagId = target.value; showTagFilterStatePopover.value = false;
const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId]; currentTransactionTagId.value = '';
}
if (!transactionTag) { function setAllToState(onlyVisible: boolean, value: TransactionTagFilterState): void {
return; for (const tag of allTags.value) {
if (onlyVisible && !showHidden.value && tag.hidden) {
continue;
} }
filterTagIds.value[transactionTag.id] = !target.checked; filterTagIds.value[tag.id] = value;
} }
function selectAllTransactionTags(): void {
selectAll(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
}
function selectNoneTransactionTags(): void {
selectNone(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
}
function selectInvertTransactionTags(): void {
selectInvert(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
}
function selectAllVisibleTransactionTags(): void {
selectAllVisible(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
} }
function save(): void { function save(): void {
+23 -56
View File
@@ -62,7 +62,7 @@
<span :class="{ 'tabbar-item-changed': query.accountIds }">{{ queryAccountName }}</span> <span :class="{ 'tabbar-item-changed': query.accountIds }">{{ queryAccountName }}</span>
</f7-link> </f7-link>
<f7-link popover-open=".more-popover-menu"> <f7-link popover-open=".more-popover-menu">
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter || query.tagIds }"></f7-icon> <f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter || query.tagFilter }"></f7-icon>
</f7-link> </f7-link>
</f7-toolbar> </f7-toolbar>
@@ -509,52 +509,37 @@
<f7-list-item group-title> <f7-list-item group-title>
<small>{{ tt('Tags') }}</small> <small>{{ tt('Tags') }}</small>
</f7-list-item> </f7-list-item>
<f7-list-item :class="{ 'list-item-selected': !query.tagIds }" :title="tt('All')" @click="changeTagFilter('')"> <f7-list-item :class="{ 'list-item-selected': !query.tagFilter }" :title="tt('All')" @click="changeTagFilter('')">
<template #after> <template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.tagIds"></f7-icon> <f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.tagFilter"></f7-icon>
</template> </template>
</f7-list-item> </f7-list-item>
<f7-list-item :class="{ 'list-item-selected': query.tagIds === 'none' }" :title="tt('Without Tags')" @click="changeTagFilter('none')"> <f7-list-item :class="{ 'list-item-selected': query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue }" :title="tt('Without Tags')" @click="changeTagFilter(TransactionTagFilter.TransactionNoTagFilterValue)">
<template #after> <template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagIds === 'none'"></f7-icon> <f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue"></f7-icon>
</template> </template>
</f7-list-item> </f7-list-item>
<f7-list-item :class="{ 'list-item-selected': query.tagIds && queryAllFilterTagIdsCount > 1 }" <f7-list-item :class="{ 'list-item-selected': query.tagFilter && queryAllFilterTagIdsCount > 1 }"
:title="tt('Multiple Tags')" @click="filterMultipleTags()" v-if="allAvailableTagsCount > 0"> :title="tt('Multiple Tags')" @click="filterMultipleTags()" v-if="allAvailableTagsCount > 0">
<template #after> <template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagIds && queryAllFilterTagIdsCount > 1"></f7-icon> <f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagFilter && queryAllFilterTagIdsCount > 1"></f7-icon>
</template> </template>
</f7-list-item> </f7-list-item>
<template v-if="query.tagIds && query.tagIds !== 'none'">
<f7-list-item :title="filterType.displayName"
:key="filterType.type"
v-for="filterType in allTransactionTagFilterTypes"
@click="changeTagFilterType(filterType.type)"
>
<template #after>
<f7-icon class="list-item-checked-icon"
f7="checkmark_alt"
v-if="query.tagFilterType === filterType.type">
</f7-icon>
</template>
</f7-list-item>
</template>
<f7-list-item :title="transactionTag.name" <f7-list-item :title="transactionTag.name"
:class="{ 'list-item-selected': query.tagIds === transactionTag.id, 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && queryAllFilterTagIds[transactionTag.id] }" :class="{ 'list-item-selected': queryAllFilterTagIdsCount === 1 && isDefined(queryAllFilterTagIds[transactionTag.id]), 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && isDefined(queryAllFilterTagIds[transactionTag.id]) }"
:key="transactionTag.id" :key="transactionTag.id"
v-for="transactionTag in allTransactionTags" v-for="transactionTag in allTransactionTags"
v-show="!transactionTag.hidden || query.tagIds === transactionTag.id" v-show="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])"
@click="changeTagFilter(transactionTag.id)" @click="changeTagFilter(TransactionTagFilter.of(transactionTag.id).toTextualTagFilter())"
> >
<template #before-title> <template #before-title>
<f7-icon class="transaction-tag-name transaction-tag-icon" f7="number"></f7-icon> <f7-icon class="transaction-tag-name transaction-tag-icon" f7="number"></f7-icon>
</template> </template>
<template #after> <template #after>
<f7-icon class="list-item-checked-icon" <f7-icon class="list-item-checked-icon"
f7="checkmark_alt" :f7="queryAllFilterTagIds[transactionTag.id] === true ? 'checkmark_alt' : (queryAllFilterTagIds[transactionTag.id] === false ? 'multiply' : undefined)"
v-if="query.tagIds === transactionTag.id"> v-if="isDefined(queryAllFilterTagIds[transactionTag.id])">
</f7-icon> </f7-icon>
</template> </template>
</f7-list-item> </f7-list-item>
@@ -597,7 +582,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts'; import { type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts';
import { type TypeAndDisplayName, keys } from '@/core/base.ts'; import { keys } from '@/core/base.ts';
import { TextDirection } from '@/core/text.ts'; import { TextDirection } from '@/core/text.ts';
import { import {
type TextualYearMonth, type TextualYearMonth,
@@ -609,10 +594,12 @@ import {
import { AmountFilterType } from '@/core/numeral.ts'; import { AmountFilterType } from '@/core/numeral.ts';
import { TransactionType } from '@/core/transaction.ts'; import { TransactionType } from '@/core/transaction.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts';
import type { Transaction } from '@/models/transaction.ts'; import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
import { import {
isNumber isDefined,
isNumber,
objectFieldWithValueToArrayItem
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import {
getCurrentUnixTime, getCurrentUnixTime,
@@ -644,7 +631,6 @@ const props = defineProps<{
const { const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
getAllTransactionTagFilterTypes,
getWeekdayShortName, getWeekdayShortName,
getCalendarDisplayDayOfMonthFromUnixTime getCalendarDisplayDayOfMonthFromUnixTime
} = useI18n(); } = useI18n();
@@ -723,8 +709,6 @@ const showDeleteActionSheet = ref<boolean>(false);
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection()); const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false); const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
const allTransactionTagFilterTypes = computed<TypeAndDisplayName[]>(() => getAllTransactionTagFilterTypes());
const transactions = computed<TransactionMonthList[]>(() => { const transactions = computed<TransactionMonthList[]>(() => {
if (loading.value) { if (loading.value) {
return []; return [];
@@ -925,8 +909,7 @@ function init(): void {
type: initQuery['type'] && parseInt(initQuery['type']) > 0 ? parseInt(initQuery['type']) : undefined, type: initQuery['type'] && parseInt(initQuery['type']) > 0 ? parseInt(initQuery['type']) : undefined,
categoryIds: initQuery['categoryIds'], categoryIds: initQuery['categoryIds'],
accountIds: initQuery['accountIds'], accountIds: initQuery['accountIds'],
tagIds: initQuery['tagIds'], tagFilter: initQuery['tagFilter'],
tagFilterType: initQuery['tagFilterType'] && parseInt(initQuery['tagFilterType']) >= 0 ? parseInt(initQuery['tagFilterType']) : undefined,
keyword: initQuery['keyword'] keyword: initQuery['keyword']
}); });
@@ -1277,13 +1260,13 @@ function filterMultipleAccounts(): void {
props.f7router.navigate('/settings/filter/account?type=transactionListCurrent'); props.f7router.navigate('/settings/filter/account?type=transactionListCurrent');
} }
function changeTagFilter(tagIds: string): void { function changeTagFilter(tagFilter: string): void {
if (query.value.tagIds === tagIds) { if (query.value.tagFilter === tagFilter) {
return; return;
} }
const changed = transactionsStore.updateTransactionListFilter({ const changed = transactionsStore.updateTransactionListFilter({
tagIds: tagIds tagFilter: tagFilter
}); });
showMorePopover.value = false; showMorePopover.value = false;
@@ -1297,22 +1280,6 @@ function filterMultipleTags(): void {
props.f7router.navigate('/settings/filter/tag?type=transactionListCurrent'); props.f7router.navigate('/settings/filter/tag?type=transactionListCurrent');
} }
function changeTagFilterType(filterType: number): void {
if (query.value.tagFilterType === filterType) {
return;
}
const changed = transactionsStore.updateTransactionListFilter({
tagFilterType: filterType
});
showMorePopover.value = false;
if (changed) {
reload();
}
}
function changeKeywordFilter(keyword: string): void { function changeKeywordFilter(keyword: string): void {
if (query.value.keyword === keyword) { if (query.value.keyword === keyword) {
return; return;
@@ -1383,8 +1350,8 @@ function add(): void {
params.push(`accountId=${query.value.accountIds}`); params.push(`accountId=${query.value.accountIds}`);
} }
if (query.value.tagIds) { if (query.value.tagFilter) {
params.push(`tagIds=${query.value.tagIds}`); params.push(`tagIds=${objectFieldWithValueToArrayItem(queryAllFilterTagIds.value, true).join(',') || ''}`);
} }
props.f7router.navigate(`/transaction/add?${params.join('&')}`); props.f7router.navigate(`/transaction/add?${params.join('&')}`);