tag filter supports selecting both included and excluded tags simultaneously

This commit is contained in:
MaysWind
2025-11-24 02:12:44 +08:00
parent 45be96cf68
commit 6430a52027
45 changed files with 1151 additions and 706 deletions
+5 -5
View File
@@ -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())
+26 -26
View File
@@ -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())
+5
View File
@@ -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)
}
+2 -2
View File
@@ -153,14 +153,14 @@ func (h *mcpQueryTransactionsToolHandler) Handle(c *core.WebContext, callToolReq
}
}
totalCount, err := services.GetTransactionService().GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", queryTransactionsRequest.Keyword)
totalCount, err := services.GetTransactionService().GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, filterCategoryIds, filterAccountIds, nil, false, "", queryTransactionsRequest.Keyword)
if err != nil {
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 {
+8 -9
View File
@@ -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
}
+97 -48
View File
@@ -104,6 +104,9 @@ func (t TransactionDbType) ToTransactionRelatedAccountType() (TransactionRelated
}
}
// TransactionTagFilterValue represents transaction tag filter value for no tag
const TransactionNoTagFilterValue = "none"
// TransactionTagFilterType represents transaction tag filter type
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) {
+95
View File
@@ -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",
+55 -42
View File
@@ -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
+2
View File
@@ -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
View File
@@ -0,0 +1,26 @@
package validators
import (
"github.com/go-playground/validator/v10"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
// ValidTagFilter returns whether the given tag filter is valid
func ValidTagFilter(fl validator.FieldLevel) bool {
if value, ok := fl.Field().Interface().(string); ok {
if value == "" {
return true
}
if value == models.TransactionNoTagFilterValue {
return true
}
_, err := models.ParseTransactionTagFilter(value)
return err == nil
}
return false
}
+104
View File
@@ -0,0 +1,104 @@
package validators
import (
"testing"
"github.com/go-playground/validator/v10"
"github.com/stretchr/testify/assert"
)
func TestEmptyTagFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("", "validTagFilter")
assert.Nil(t, err)
}
func TestNoTag(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("none", "validTagFilter")
assert.Nil(t, err)
}
func TestNoValidFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var(";", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var(";;", "validTagFilter")
assert.NotNil(t, err)
}
func TestValidOneFilterInTagFilters(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:1", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("0:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("1:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("2:1,2,3", "validTagFilter")
assert.Nil(t, err)
err = validate.Var("3:1,2,3", "validTagFilter")
assert.Nil(t, err)
}
func TestInvalidTagFilterType(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("a:1,2,3", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("-1:1,2,3", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("4:1,2,3", "validTagFilter")
assert.NotNil(t, err)
}
func TestNoTagIdsInFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0", "validTagFilter")
assert.NotNil(t, err)
err = validate.Var("0:", "validTagFilter")
assert.NotNil(t, err)
}
func TestInvalidTagIdsInFilter(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:abc", "validTagFilter")
assert.NotNil(t, err)
}
func TestValidTwoFilterInTagFilters(t *testing.T) {
validate := validator.New()
err := validate.RegisterValidation("validTagFilter", ValidTagFilter)
assert.Nil(t, err)
err = validate.Var("0:1,2,3;2:4,5,6", "validTagFilter")
assert.Nil(t, err)
}