diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index be308c8b..2a8cfe43 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -16,6 +16,7 @@ type TransactionsApi struct { transactions *services.TransactionService transactionCategories *services.TransactionCategoryService transactionTags *services.TransactionTagService + accounts *services.AccountService } // Initialize a transaction api singleton instance @@ -24,6 +25,7 @@ var ( transactions: services.Transactions, transactionCategories: services.TransactionCategories, transactionTags: services.TransactionTags, + accounts: services.Accounts, } ) @@ -64,21 +66,19 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, return nil, errs.ErrOperationFailed } - finalCount := transactionListReq.Count - - if len(transactions) < finalCount { - finalCount = len(transactions) - } - hasMore := false + var nextTimeSequenceId *int64 - if finalCount < len(transactions) { + if len(transactions) > transactionListReq.Count { hasMore = true + nextTimeSequenceId = &transactions[transactionListReq.Count].TransactionTime + transactions = transactions[:transactionListReq.Count] } - transactionIds := make([]int64, finalCount) + transactionIds := make([]int64, len(transactions)) + accountIds := make([]int64, 0, len(transactions)*2) - for i := 0; i < finalCount; i++ { + for i := 0; i < len(transactions); i++ { transactionId := transactions[i].TransactionId if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { @@ -86,8 +86,22 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, } transactionIds[i] = transactionId + accountIds = append(accountIds, transactions[i].AccountId) + + 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) + } } + allAccounts, err := a.accounts.GetAccountsByAccountIds(uid, utils.ToUniqueInt64Slice(accountIds)) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + transactions = a.filterTransactions(c, uid, transactions, allAccounts) + allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, transactionIds) if err != nil { @@ -96,23 +110,34 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, } transactionResps := &models.TransactionInfoPageWrapperResponse{} - transactionResps.Items = make(models.TransactionInfoResponseSlice, finalCount) + transactionResps.Items = make(models.TransactionInfoResponseSlice, len(transactions)) - for i := 0; i < finalCount; i++ { + for i := 0; i < len(transactions); i++ { transaction := transactions[i] + transactionEditable := true if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) } + if allAccounts[transaction.AccountId].Hidden { + transactionEditable = false + } + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + if allAccounts[transaction.RelatedAccountId].Hidden { + transactionEditable = false + } + } + transactionTagIds := allTransactionTagIds[transaction.TransactionId] - transactionResps.Items[i] = transaction.ToTransactionInfoResponse(transactionTagIds) + transactionResps.Items[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) } sort.Sort(transactionResps.Items) if hasMore { - transactionResps.NextTimeSequenceId = &transactions[finalCount].TransactionTime + transactionResps.NextTimeSequenceId = nextTimeSequenceId } return transactionResps, nil @@ -156,6 +181,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac } transactionIds := make([]int64, len(transactions)) + accountIds := make([]int64, 0, len(transactions)*2) for i := 0; i < len(transactions); i++ { transactionId := transactions[i].TransactionId @@ -165,8 +191,22 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac } transactionIds[i] = transactionId + accountIds = append(accountIds, transactions[i].AccountId) + + 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) + } } + allAccounts, err := a.accounts.GetAccountsByAccountIds(uid, utils.ToUniqueInt64Slice(accountIds)) + + if err != nil { + log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + transactions = a.filterTransactions(c, uid, transactions, allAccounts) + allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, transactionIds) if err != nil { @@ -178,13 +218,24 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac for i := 0; i < len(transactions); i++ { transaction := transactions[i] + transactionEditable := true if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) } + if allAccounts[transaction.AccountId].Hidden { + transactionEditable = false + } + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + if allAccounts[transaction.RelatedAccountId].Hidden { + transactionEditable = false + } + } + transactionTagIds := allTransactionTagIds[transaction.TransactionId] - transactionResps[i] = transaction.ToTransactionInfoResponse(transactionTagIds) + transactionResps[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) } return transactionResps, nil @@ -202,6 +253,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * uid := c.GetCurrentUid() transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionGetReq.Id) + transactionEditable := true if err != nil { log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionGetReq.Id, uid, err.Error()) @@ -212,6 +264,32 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) } + accountIds := make([]int64, 0, 2) + accountIds = append(accountIds, transaction.AccountId) + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + accountIds = append(accountIds, transaction.RelatedAccountId) + accountIds = utils.ToUniqueInt64Slice(accountIds) + } + + accountMap, err := a.accounts.GetAccountsByAccountIds(uid, accountIds) + + if _, exists := accountMap[transaction.AccountId]; !exists { + log.WarnfWithRequestId(c, "[transactions.TransactionGetHandler] account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transaction.TransactionId, uid) + return nil, errs.ErrTransactionNotFound + } else if accountMap[transaction.AccountId].Hidden { + transactionEditable = false + } + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + if _, exists := accountMap[transaction.RelatedAccountId]; !exists { + log.WarnfWithRequestId(c, "[transactions.TransactionGetHandler] related account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transaction.TransactionId, uid) + return nil, errs.ErrTransactionNotFound + } else if accountMap[transaction.RelatedAccountId].Hidden { + transactionEditable = false + } + } + allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId}) if err != nil { @@ -220,7 +298,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * } transactionTagIds := allTransactionTagIds[transaction.TransactionId] - transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds) + transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) return transactionResp, nil } @@ -277,7 +355,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{} log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId) - transactionResp := transaction.ToTransactionInfoResponse(nil) + transactionResp := transaction.ToTransactionInfoResponse(nil, true) return transactionResp, nil } @@ -365,7 +443,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} log.InfofWithRequestId(c, "[transactions.TransactionModifyHandler] user \"uid:%d\" has updated transaction \"id:%d\" successfully", uid, transactionModifyReq.Id) newTransaction.Type = transaction.Type - newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds) + newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, true) return newTransactionResp, nil } @@ -392,6 +470,30 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{} return true, nil } +func (a *TransactionsApi) filterTransactions(c *core.Context, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account) []*models.Transaction { + finalTransactions := make([]*models.Transaction, 0, len(transactions)) + + for i := 0; i < len(transactions); i++ { + transaction := transactions[i] + + if _, exists := accountMap[transaction.AccountId]; !exists { + log.WarnfWithRequestId(c, "[transactions.filterTransactions] account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transaction.TransactionId, uid) + continue + } + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + if _, exists := accountMap[transaction.RelatedAccountId]; !exists { + log.WarnfWithRequestId(c, "[transactions.filterTransactions] related account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transaction.TransactionId, uid) + continue + } + } + + finalTransactions = append(finalTransactions, transaction) + } + + return finalTransactions +} + 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 a3fb9cff..ef7a9874 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -123,6 +123,7 @@ type TransactionInfoResponse struct { DestinationAmount int64 `json:"destinationAmount,omitempty"` TagIds []string `json:"tagIds"` Comment string `json:"comment"` + Editable bool `json:"editable"` } // TransactionInfoPageWrapperResponse represents a response of transaction which contains items and next id @@ -132,7 +133,7 @@ type TransactionInfoPageWrapperResponse struct { } // ToTransactionInfoResponse returns a view-object according to database model -func (c *Transaction) ToTransactionInfoResponse(tagIds []int64) *TransactionInfoResponse { +func (c *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *TransactionInfoResponse { var transactionType TransactionType if c.Type == TRANSACTION_DB_TYPE_MODIFY_BALANCE { @@ -178,6 +179,7 @@ func (c *Transaction) ToTransactionInfoResponse(tagIds []int64) *TransactionInfo DestinationAmount: destinationAmount, TagIds: utils.Int64ArrayToStringArray(tagIds), Comment: c.Comment, + Editable: editable, } } diff --git a/pkg/services/accounts.go b/pkg/services/accounts.go index b2d8f06d..33a73e72 100644 --- a/pkg/services/accounts.go +++ b/pkg/services/accounts.go @@ -58,6 +58,27 @@ func (s *AccountService) GetAccountAndSubAccountsByAccountId(uid int64, accountI return accounts, err } +// GetAccountsByAccountIds returns account models according to account ids +func (s *AccountService) GetAccountsByAccountIds(uid int64, accountIds []int64) (map[int64]*models.Account, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + if accountIds == nil { + return nil, errs.ErrAccountIdInvalid + } + + var accounts []*models.Account + err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).In("account_id", accountIds).Find(&accounts) + + if err != nil { + return nil, err + } + + accountMap := s.GetAccountMapByList(accounts) + return accountMap, err +} + // GetMaxDisplayOrder returns the max display order according to account category func (s *AccountService) GetMaxDisplayOrder(uid int64, category models.AccountCategory) (int, error) { if uid <= 0 { diff --git a/src/views/mobile/transactions/List.vue b/src/views/mobile/transactions/List.vue index a11cbb83..b31f9f43 100644 --- a/src/views/mobile/transactions/List.vue +++ b/src/views/mobile/transactions/List.vue @@ -211,9 +211,11 @@ @click="duplicate(transaction)"> - +