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