From 44f2a651f75a7015067a9016f529750c3a025425 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 21 Mar 2021 21:39:46 +0800 Subject: [PATCH] support aggregating account/category and tag info in transaction list/get api --- pkg/api/transactions.go | 155 +++++++++++++++++++++++++ pkg/models/transaction.go | 73 +++++++----- pkg/services/transaction_categories.go | 21 ++++ pkg/services/transaction_tags.go | 21 ++++ src/lib/services.js | 4 +- 5 files changed, 242 insertions(+), 32 deletions(-) diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index f6e35557..e1b5a979 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -96,6 +96,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, transactionIds := make([]int64, len(transactions)) accountIds := make([]int64, 0, len(transactions)*2) + categoryIds := make([]int64, 0, len(transactions)) for i := 0; i < len(transactions); i++ { transactionId := transactions[i].TransactionId @@ -110,6 +111,8 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { accountIds = append(accountIds, transactions[i].RelatedAccountId) } + + categoryIds = append(categoryIds, transactions[i].CategoryId) } allAccounts, err := a.accounts.GetAccountsByAccountIds(uid, utils.ToUniqueInt64Slice(accountIds)) @@ -128,6 +131,27 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, return nil, errs.ErrOperationFailed } + var categoryMap map[int64]*models.TransactionCategory + var tagMap map[int64]*models.TransactionTag + + if !transactionListReq.TrimCategory { + categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(uid, utils.ToUniqueInt64Slice(categoryIds)) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + + if !transactionListReq.TrimTag { + tagMap, err = a.transactionTags.GetTagsByTagIds(uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds))) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + transactionResps := &models.TransactionInfoPageWrapperResponse{} transactionResps.Items = make(models.TransactionInfoResponseSlice, len(transactions)) @@ -141,6 +165,26 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, transactionEditable := transaction.IsEditable(user, utcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResps.Items[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) + + if !transactionListReq.TrimAccount { + if sourceAccount := allAccounts[transaction.AccountId]; sourceAccount != nil { + transactionResps.Items[i].SourceAccount = sourceAccount.ToAccountInfoResponse() + } + + if destinationAccount := allAccounts[transaction.RelatedAccountId]; destinationAccount != nil { + transactionResps.Items[i].DestinationAccount = destinationAccount.ToAccountInfoResponse() + } + } + + if !transactionListReq.TrimCategory { + if category := categoryMap[transaction.CategoryId]; category != nil { + transactionResps.Items[i].Category = category.ToTransactionCategoryInfoResponse() + } + } + + if !transactionListReq.TrimTag { + transactionResps.Items[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) + } } sort.Sort(transactionResps.Items) @@ -208,6 +252,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac transactionIds := make([]int64, len(transactions)) accountIds := make([]int64, 0, len(transactions)*2) + categoryIds := make([]int64, 0, len(transactions)) for i := 0; i < len(transactions); i++ { transactionId := transactions[i].TransactionId @@ -222,6 +267,8 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { accountIds = append(accountIds, transactions[i].RelatedAccountId) } + + categoryIds = append(categoryIds, transactions[i].CategoryId) } allAccounts, err := a.accounts.GetAccountsByAccountIds(uid, utils.ToUniqueInt64Slice(accountIds)) @@ -240,6 +287,27 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac return nil, errs.ErrOperationFailed } + var categoryMap map[int64]*models.TransactionCategory + var tagMap map[int64]*models.TransactionTag + + if !transactionListReq.TrimCategory { + categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(uid, utils.ToUniqueInt64Slice(categoryIds)) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + + if !transactionListReq.TrimTag { + tagMap, err = a.transactionTags.GetTagsByTagIds(uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds))) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + transactionResps := make([]*models.TransactionInfoResponse, len(transactions)) for i := 0; i < len(transactions); i++ { @@ -252,6 +320,26 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac transactionEditable := transaction.IsEditable(user, utcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResps[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) + + if !transactionListReq.TrimAccount { + if sourceAccount := allAccounts[transaction.AccountId]; sourceAccount != nil { + transactionResps[i].SourceAccount = sourceAccount.ToAccountInfoResponse() + } + + if destinationAccount := allAccounts[transaction.RelatedAccountId]; destinationAccount != nil { + transactionResps[i].DestinationAccount = destinationAccount.ToAccountInfoResponse() + } + } + + if !transactionListReq.TrimCategory { + if category := categoryMap[transaction.CategoryId]; category != nil { + transactionResps[i].Category = category.ToTransactionCategoryInfoResponse() + } + } + + if !transactionListReq.TrimTag { + transactionResps[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) + } } return transactionResps, nil @@ -325,10 +413,51 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * return nil, errs.ErrOperationFailed } + var category *models.TransactionCategory + var tagMap map[int64]*models.TransactionTag + + if !transactionGetReq.TrimCategory { + category, err = a.transactionCategories.GetCategoryByCategoryId(uid, transaction.CategoryId) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions category for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + + if !transactionGetReq.TrimTag { + tagMap, err = a.transactionTags.GetTagsByTagIds(uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds))) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + transactionEditable := transaction.IsEditable(user, utcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) + if !transactionGetReq.TrimAccount { + if sourceAccount := accountMap[transaction.AccountId]; sourceAccount != nil { + transactionResp.SourceAccount = sourceAccount.ToAccountInfoResponse() + } + + if destinationAccount := accountMap[transaction.RelatedAccountId]; destinationAccount != nil { + transactionResp.DestinationAccount = destinationAccount.ToAccountInfoResponse() + } + } + + if !transactionGetReq.TrimCategory { + if category != nil { + transactionResp.Category = category.ToTransactionCategoryInfoResponse() + } + } + + if !transactionGetReq.TrimTag { + transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) + } + return transactionResp, nil } @@ -596,6 +725,32 @@ func (a *TransactionsApi) filterTransactions(c *core.Context, uid int64, transac return finalTransactions } +func (a *TransactionsApi) getTransactionTagIds(allTransactionTagIds map[int64][]int64) []int64 { + allTagIds := make([]int64, 0, len(allTransactionTagIds)) + + for _, tagIds := range allTransactionTagIds { + allTagIds = append(allTagIds, tagIds...) + } + + return allTagIds +} + +func (a *TransactionsApi) getTransactionTagInfoResponses(tagIds []int64, allTransactionTags map[int64]*models.TransactionTag) []*models.TransactionTagInfoResponse { + allTags := make([]*models.TransactionTagInfoResponse, 0, len(tagIds)) + + for i := 0; i < len(tagIds); i++ { + tag := allTransactionTags[tagIds[i]] + + if tag == nil { + continue + } + + allTags = append(allTags, tag.ToTransactionTagInfoResponse()) + } + + return allTags +} + func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreateReq *models.TransactionCreateRequest) *models.Transaction { var transactionDbType models.TransactionDbType diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index bc3307b1..096116a0 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -82,52 +82,65 @@ type TransactionModifyRequest struct { // TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request type TransactionListByMaxTimeRequest struct { - Type TransactionDbType `form:"type" binding:"min=0,max=4"` - CategoryId int64 `form:"category_id" binding:"min=0"` - AccountId int64 `form:"account_id" binding:"min=0"` - Keyword string `form:"keyword"` - MaxTime int64 `form:"max_time" binding:"min=0"` - MinTime int64 `form:"min_time" binding:"min=0"` - Count int `form:"count" binding:"required,min=1,max=50"` + Type TransactionDbType `form:"type" binding:"min=0,max=4"` + CategoryId int64 `form:"category_id" binding:"min=0"` + AccountId int64 `form:"account_id" binding:"min=0"` + Keyword string `form:"keyword"` + MaxTime int64 `form:"max_time" binding:"min=0"` + MinTime int64 `form:"min_time" binding:"min=0"` + Count int `form:"count" binding:"required,min=1,max=50"` + 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 int `form:"year" binding:"required,min=1"` - Month int `form:"month" binding:"required,min=1"` - Type TransactionDbType `form:"type" binding:"min=0,max=4"` - CategoryId int64 `form:"category_id" binding:"min=0"` - AccountId int64 `form:"account_id" binding:"min=0"` - Keyword string `form:"keyword"` - Page int `form:"page" binding:"required,min=1"` - Count int `form:"count" binding:"required,min=1,max=50"` + Year int `form:"year" binding:"required,min=1"` + Month int `form:"month" binding:"required,min=1"` + Type TransactionDbType `form:"type" binding:"min=0,max=4"` + CategoryId int64 `form:"category_id" binding:"min=0"` + AccountId int64 `form:"account_id" binding:"min=0"` + Keyword string `form:"keyword"` + Page int `form:"page" binding:"required,min=1"` + Count int `form:"count" binding:"required,min=1,max=50"` + TrimAccount bool `form:"trim_account"` + TrimCategory bool `form:"trim_category"` + TrimTag bool `form:"trim_tag"` } // TransactionGetRequest represents all parameters of transaction getting request type TransactionGetRequest struct { - Id int64 `form:"id,string" binding:"required,min=1"` + Id int64 `form:"id,string" binding:"required,min=1"` + TrimAccount bool `form:"trim_account"` + TrimCategory bool `form:"trim_category"` + TrimTag bool `form:"trim_tag"` } // TransactionDeleteRequest represents all parameters of transaction deleting request type TransactionDeleteRequest struct { - Id int64 `json:"id,string" binding:"required,min=1"` + Id int64 `json:"id,string" binding:"required,min=1"` } // TransactionInfoResponse represents a view-object of transaction type TransactionInfoResponse struct { - Id int64 `json:"id,string"` - TimeSequenceId int64 `json:"timeSequenceId,string"` - Type TransactionType `json:"type"` - CategoryId int64 `json:"categoryId,string"` - Time int64 `json:"time"` - UtcOffset int16 `json:"utcOffset"` - SourceAccountId int64 `json:"sourceAccountId,string"` - DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"` - SourceAmount int64 `json:"sourceAmount"` - DestinationAmount int64 `json:"destinationAmount,omitempty"` - TagIds []string `json:"tagIds"` - Comment string `json:"comment"` - Editable bool `json:"editable"` + Id int64 `json:"id,string"` + TimeSequenceId int64 `json:"timeSequenceId,string"` + Type TransactionType `json:"type"` + CategoryId int64 `json:"categoryId,string"` + Category *TransactionCategoryInfoResponse `json:"category,omitempty"` + Time int64 `json:"time"` + UtcOffset int16 `json:"utcOffset"` + SourceAccountId int64 `json:"sourceAccountId,string"` + SourceAccount *AccountInfoResponse `json:"sourceAccount,omitempty"` + DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"` + DestinationAccount *AccountInfoResponse `json:"destinationAccount,omitempty"` + SourceAmount int64 `json:"sourceAmount"` + DestinationAmount int64 `json:"destinationAmount,omitempty"` + TagIds []string `json:"tagIds"` + Tags []*TransactionTagInfoResponse `json:"tags,omitempty"` + Comment string `json:"comment"` + Editable bool `json:"editable"` } // TransactionInfoPageWrapperResponse represents a response of transaction which contains items and next id diff --git a/pkg/services/transaction_categories.go b/pkg/services/transaction_categories.go index 7c083a7e..3add5160 100644 --- a/pkg/services/transaction_categories.go +++ b/pkg/services/transaction_categories.go @@ -78,6 +78,27 @@ func (s *TransactionCategoryService) GetCategoryByCategoryId(uid int64, category return category, nil } +// GetCategoriesByCategoryIds returns transaction category models according to transaction category ids +func (s *TransactionCategoryService) GetCategoriesByCategoryIds(uid int64, categoryIds []int64) (map[int64]*models.TransactionCategory, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + if categoryIds == nil { + return nil, errs.ErrTransactionCategoryIdInvalid + } + + var categories []*models.TransactionCategory + err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("category_id", categoryIds).Find(&categories) + + if err != nil { + return nil, err + } + + categoryMap := s.GetCategoryMapByList(categories) + return categoryMap, err +} + // GetMaxDisplayOrder returns the max display order according to transaction category type func (s *TransactionCategoryService) GetMaxDisplayOrder(uid int64, categoryType models.TransactionCategoryType) (int, error) { if uid <= 0 { diff --git a/pkg/services/transaction_tags.go b/pkg/services/transaction_tags.go index 8c7c922d..a6b8ae7d 100644 --- a/pkg/services/transaction_tags.go +++ b/pkg/services/transaction_tags.go @@ -63,6 +63,27 @@ func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.T return tag, nil } +// GetTagsByTagIds returns transaction tag models according to transaction tag ids +func (s *TransactionTagService) GetTagsByTagIds(uid int64, tagIds []int64) (map[int64]*models.TransactionTag, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + if tagIds == nil { + return nil, errs.ErrTransactionTagIdInvalid + } + + var tags []*models.TransactionTag + err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("tag_id", tagIds).Find(&tags) + + if err != nil { + return nil, err + } + + tagMap := s.GetTagMapByList(tags) + return tagMap, err +} + // GetMaxDisplayOrder returns the max display order func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int, error) { if uid <= 0 { diff --git a/src/lib/services.js b/src/lib/services.js index e1f78020..0720eeb3 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -253,10 +253,10 @@ export default { }); }, getTransactions: ({ maxTime, minTime, type, categoryId, accountId, keyword }) => { - return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&count=50`); + return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&count=50&trim_account=true&trim_category=true&trim_tag=true`); }, getTransaction: ({ id }) => { - return axios.get(`v1/transactions/get.json?id=${id}`); + return axios.get(`v1/transactions/get.json?id=${id}&trim_account=true&trim_category=true&trim_tag=true`); }, addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, tagIds, comment, utcOffset }) => { return axios.post('v1/transactions/add.json', {