mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 00:12:11 +08:00
tag filter supports selecting both included and excluded tags simultaneously
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
+97
-48
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user