add transaction tag index

This commit is contained in:
MaysWind
2020-12-13 23:58:20 +08:00
parent d46307de07
commit 45b1cf0176
10 changed files with 271 additions and 19 deletions
+8
View File
@@ -107,5 +107,13 @@ func updateAllDatabaseTablesStructure() error {
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag table maintained successfully") log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag table maintained successfully")
} }
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTagIndex))
if err != nil {
return err
} else {
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag index table maintained successfully")
}
return nil return nil
} }
+1 -1
View File
@@ -203,7 +203,7 @@ func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *er
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
err = a.tags.DeleteTags(uid, []int64{tagDeleteReq.Id}) err = a.tags.DeleteTag(uid, tagDeleteReq.Id)
if err != nil { if err != nil {
log.ErrorfWithRequestId(c, "[transaction_tags.TagDeleteHandler] failed to delete tag \"id:%d\" for user \"uid:%d\", because %s", tagDeleteReq.Id, uid, err.Error()) log.ErrorfWithRequestId(c, "[transaction_tags.TagDeleteHandler] failed to delete tag \"id:%d\" for user \"uid:%d\", because %s", tagDeleteReq.Id, uid, err.Error())
+60 -8
View File
@@ -8,15 +8,18 @@ import (
"github.com/mayswind/lab/pkg/log" "github.com/mayswind/lab/pkg/log"
"github.com/mayswind/lab/pkg/models" "github.com/mayswind/lab/pkg/models"
"github.com/mayswind/lab/pkg/services" "github.com/mayswind/lab/pkg/services"
"github.com/mayswind/lab/pkg/utils"
) )
type TransactionsApi struct { type TransactionsApi struct {
transactions *services.TransactionService transactions *services.TransactionService
transactionTags *services.TransactionTagService
} }
var ( var (
Transactions = &TransactionsApi{ Transactions = &TransactionsApi{
transactions: services.Transactions, transactions: services.Transactions,
transactionTags: services.TransactionTags,
} }
) )
@@ -43,11 +46,25 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
finalCount = len(transactions) finalCount = len(transactions)
} }
transactionIds := make([]int64, finalCount)
for i := 0; i < finalCount; i++ {
transactionIds[i] = transactions[i].TransactionId
}
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, transactionIds)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
transactionResps := &models.TransactionInfoPageWrapperResponse{} transactionResps := &models.TransactionInfoPageWrapperResponse{}
transactionResps.Items = make(models.TransactionInfoResponseSlice, finalCount) transactionResps.Items = make(models.TransactionInfoResponseSlice, finalCount)
for i := 0; i < finalCount; i++ { for i := 0; i < finalCount; i++ {
transactionResps.Items[i] = transactions[i].ToTransactionInfoResponse(nil) transactionTagIds := allTransactionTagIds[transactions[i].TransactionId]
transactionResps.Items[i] = transactions[i].ToTransactionInfoResponse(transactionTagIds)
} }
sort.Sort(transactionResps.Items) sort.Sort(transactionResps.Items)
@@ -76,10 +93,24 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
return nil, errs.ErrOperationFailed return nil, errs.ErrOperationFailed
} }
transactionIds := make([]int64, len(transactions))
for i := 0; i < len(transactions); i++ {
transactionIds[i] = transactions[i].TransactionId
}
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, transactionIds)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
transactionResps := make([]*models.TransactionInfoResponse, len(transactions)) transactionResps := make([]*models.TransactionInfoResponse, len(transactions))
for i := 0; i < len(transactions); i++ { for i := 0; i < len(transactions); i++ {
transactionResps[i] = transactions[i].ToTransactionInfoResponse(nil) transactionTagIds := allTransactionTagIds[transactions[i].TransactionId]
transactionResps[i] = transactions[i].ToTransactionInfoResponse(transactionTagIds)
} }
return transactionResps, nil return transactionResps, nil
@@ -102,7 +133,15 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
return nil, errs.ErrOperationFailed return nil, errs.ErrOperationFailed
} }
transactionResp := transaction.ToTransactionInfoResponse(nil) allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId})
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds)
return transactionResp, nil return transactionResp, nil
} }
@@ -142,7 +181,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
transaction := a.createNewTransactionModel(uid, &transactionCreateReq) transaction := a.createNewTransactionModel(uid, &transactionCreateReq)
err = a.transactions.CreateTransaction(transaction) err = a.transactions.CreateTransaction(transaction, transactionCreateReq.TagIds)
if err != nil { if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to create transaction \"id:%d\" for user \"uid:%d\", because %s", transaction.TransactionId, uid, err.Error()) log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to create transaction \"id:%d\" for user \"uid:%d\", because %s", transaction.TransactionId, uid, err.Error())
@@ -173,6 +212,17 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId})
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
addTransactionTagIds := utils.Int64SliceMinus(transactionModifyReq.TagIds, transactionTagIds)
removeTransactionTagIds := utils.Int64SliceMinus(transactionTagIds, transactionModifyReq.TagIds)
newTransaction := &models.Transaction{ newTransaction := &models.Transaction{
TransactionId: transaction.TransactionId, TransactionId: transaction.TransactionId,
Uid: uid, Uid: uid,
@@ -191,11 +241,13 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
newTransaction.DestinationAccountId == transaction.DestinationAccountId && newTransaction.DestinationAccountId == transaction.DestinationAccountId &&
newTransaction.SourceAmount == transaction.SourceAmount && newTransaction.SourceAmount == transaction.SourceAmount &&
newTransaction.DestinationAmount == transaction.DestinationAmount && newTransaction.DestinationAmount == transaction.DestinationAmount &&
newTransaction.Comment == transaction.Comment { newTransaction.Comment == transaction.Comment &&
len(addTransactionTagIds) < 1 &&
len(removeTransactionTagIds) < 1 {
return nil, errs.ErrNothingWillBeUpdated return nil, errs.ErrNothingWillBeUpdated
} }
err = a.transactions.ModifyTransaction(newTransaction) err = a.transactions.ModifyTransaction(newTransaction, addTransactionTagIds, removeTransactionTagIds)
if err != nil { if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error()) log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
+5 -4
View File
@@ -3,8 +3,9 @@ package errs
import "net/http" import "net/http"
var ( var (
ErrTransactionTagIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_TAG, 0, http.StatusBadRequest, "transaction tag id is invalid") ErrTransactionTagIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_TAG, 0, http.StatusBadRequest, "transaction tag id is invalid")
ErrTransactionTagNotFound = NewNormalError(NORMAL_SUBCATEGORY_TAG, 1, http.StatusBadRequest, "transaction tag not found") ErrTransactionTagNotFound = NewNormalError(NORMAL_SUBCATEGORY_TAG, 1, http.StatusBadRequest, "transaction tag not found")
ErrTransactionTagNameIsEmpty = NewNormalError(NORMAL_SUBCATEGORY_TAG, 2, http.StatusBadRequest, "transaction tag name is empty") ErrTransactionTagNameIsEmpty = NewNormalError(NORMAL_SUBCATEGORY_TAG, 2, http.StatusBadRequest, "transaction tag name is empty")
ErrTransactionTagNameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_TAG, 3, http.StatusBadRequest, "transaction tag name already exists") ErrTransactionTagNameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_TAG, 3, http.StatusBadRequest, "transaction tag name already exists")
ErrTransactionTagInUseCannotBeDeleted = NewNormalError(NORMAL_SUBCATEGORY_TAG, 4, http.StatusBadRequest, "transaction tag is in use and cannot be deleted")
) )
+10
View File
@@ -0,0 +1,10 @@
package models
type TransactionTagIndex struct {
Uid int64 `xorm:"PK INDEX(IDX_transaction_tag_index_uid_tag_id_transaction_time) INDEX(IDX_transaction_tag_index_uid_transaction_id)"`
TagId int64 `xorm:"PK INDEX(IDX_transaction_tag_index_uid_tag_id_transaction_time)"`
TransactionId int64 `xorm:"PK INDEX(IDX_transaction_tag_index_uid_transaction_id)"`
TransactionTime int64 `xorm:"INDEX(IDX_transaction_tag_index_uid_tag_id_transaction_time) NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
}
+34 -2
View File
@@ -78,6 +78,32 @@ func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int, error) {
} }
} }
func (s *TransactionTagService) GetAllTagIdsOfTransactions(uid int64, transactionIds []int64) (map[int64][]int64, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
var tagIndexs []*models.TransactionTagIndex
err := s.UserDataDB(uid).Where("uid=?", uid).In("transaction_id", transactionIds).Find(&tagIndexs)
allTransactionTagIds := make(map[int64][]int64)
for i := 0; i < len(tagIndexs); i++ {
tagIndex := tagIndexs[i]
var transactionTagIds []int64
if _, exists := allTransactionTagIds[tagIndex.TransactionId]; exists {
transactionTagIds = allTransactionTagIds[tagIndex.TransactionId]
}
transactionTagIds = append(transactionTagIds, tagIndex.TagId)
allTransactionTagIds[tagIndex.TransactionId] = transactionTagIds
}
return allTransactionTagIds, err
}
func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error { func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error {
if tag.Uid <= 0 { if tag.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
@@ -180,13 +206,19 @@ func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models
}) })
} }
func (s *TransactionTagService) DeleteTags(uid int64, ids []int64) error { func (s *TransactionTagService) DeleteTag(uid int64, tagId int64) error {
if uid <= 0 { if uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
deletedRows, err := sess.In("tag_id", ids).Where("uid=?", uid).Delete(&models.TransactionTag{}) exists, err := sess.Cols("uid", "tag_id").Where("uid=? AND tag_id=?", uid, tagId).Limit(1).Exist(&models.TransactionTagIndex{})
if exists {
return errs.ErrTransactionTagInUseCannotBeDeleted
}
deletedRows, err := sess.ID(tagId).Where("uid=?", uid).Delete(&models.TransactionTag{})
if err != nil { if err != nil {
return err return err
+114 -4
View File
@@ -122,7 +122,7 @@ func (s *TransactionService) GetMonthTransactionCount(uid int64, year int64, mon
return s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time<?", uid, false, startUnixTime, endUnixTime).Count(&models.Transaction{}) return s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time<?", uid, false, startUnixTime, endUnixTime).Count(&models.Transaction{})
} }
func (s *TransactionService) CreateTransaction(transaction *models.Transaction) error { func (s *TransactionService) CreateTransaction(transaction *models.Transaction, tagIds []int64) error {
if transaction.Uid <= 0 { if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -143,11 +143,26 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction)
return errs.ErrTransactionTypeInvalid return errs.ErrTransactionTypeInvalid
} }
now := time.Now().Unix()
transaction.TransactionId = s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION) transaction.TransactionId = s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION)
transaction.TransactionTime = utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)) transaction.TransactionTime = utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))
transaction.CreatedUnixTime = time.Now().Unix() transaction.CreatedUnixTime = now
transaction.UpdatedUnixTime = time.Now().Unix() transaction.UpdatedUnixTime = now
tagIds = utils.ToUniqueInt64Slice(tagIds)
transactionTagIndexs := make([]*models.TransactionTagIndex, len(tagIds))
for i := 0; i < len(tagIds); i++ {
transactionTagIndexs[i] = &models.TransactionTagIndex{
Uid: transaction.Uid,
TagId: tagIds[i],
TransactionId: transaction.TransactionId,
CreatedUnixTime: now,
UpdatedUnixTime: now,
}
}
return s.UserDataDB(transaction.Uid).DoTransaction(func(sess *xorm.Session) error { return s.UserDataDB(transaction.Uid).DoTransaction(func(sess *xorm.Session) error {
// Get and verify source and destination account // Get and verify source and destination account
@@ -204,6 +219,28 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction)
return errs.ErrTransactionCategoryTypeInvalid return errs.ErrTransactionCategoryTypeInvalid
} }
// Get and verify tags
if len(transactionTagIndexs) > 0 {
var tags []*models.TransactionTag
err := sess.Where("uid=? AND deleted=?", transaction.Uid, false).In("tag_ids", tagIds).Find(&tags)
if err != nil {
return err
}
tagMap := make(map[int64]*models.TransactionTag)
for i := 0; i < len(tags); i++ {
tagMap[tags[i].TagId] = tags[i]
}
for i := 0; i < len(transactionTagIndexs); i++ {
if _, exists := tagMap[transactionTagIndexs[i].TagId]; !exists {
return errs.ErrTransactionTagNotFound
}
}
}
// Verify balance modification transaction and calculate real amount // Verify balance modification transaction and calculate real amount
if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
otherTransactionExists, err := sess.Where("uid=? AND deleted=? AND destination_account_id=?", transaction.Uid, false, destinationAccount.AccountId).Limit(1).Exist(&models.Transaction{}) otherTransactionExists, err := sess.Where("uid=? AND deleted=? AND destination_account_id=?", transaction.Uid, false, destinationAccount.AccountId).Limit(1).Exist(&models.Transaction{})
@@ -245,6 +282,18 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction)
} }
} }
// Insert transaction tag index
if len(transactionTagIndexs) > 0 {
for i := 0; i < len(transactionTagIndexs); i++ {
transactionTagIndex := transactionTagIndexs[i]
_, err := sess.Insert(transactionTagIndex)
if err != nil {
return err
}
}
}
// Update account table // Update account table
if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
destinationAccount.UpdatedUnixTime = time.Now().Unix() destinationAccount.UpdatedUnixTime = time.Now().Unix()
@@ -297,7 +346,7 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction)
}) })
} }
func (s *TransactionService) ModifyTransaction(transaction *models.Transaction) error { func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, addTagIds []int64, removeTagIds []int64) error {
if transaction.Uid <= 0 { if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -310,6 +359,21 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction)
transaction.UpdatedUnixTime = now transaction.UpdatedUnixTime = now
updateCols = append(updateCols, "updated_unix_time") updateCols = append(updateCols, "updated_unix_time")
addTagIds = utils.ToUniqueInt64Slice(addTagIds)
removeTagIds = utils.ToUniqueInt64Slice(removeTagIds)
transactionTagIndexs := make([]*models.TransactionTagIndex, len(addTagIds))
for i := 0; i < len(addTagIds); i++ {
transactionTagIndexs[i] = &models.TransactionTagIndex{
Uid: transaction.Uid,
TagId: addTagIds[i],
TransactionId: transaction.TransactionId,
CreatedUnixTime: now,
UpdatedUnixTime: now,
}
}
err := s.UserDB().DoTransaction(func(sess *xorm.Session) error { err := s.UserDB().DoTransaction(func(sess *xorm.Session) error {
// Get and verify current transaction // Get and verify current transaction
oldTransaction := &models.Transaction{} oldTransaction := &models.Transaction{}
@@ -435,6 +499,28 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction)
updateCols = append(updateCols, "comment") updateCols = append(updateCols, "comment")
} }
// Get and verify tags
if len(transactionTagIndexs) > 0 {
var tags []*models.TransactionTag
err := sess.Where("uid=? AND deleted=?", transaction.Uid, false).In("tag_ids", addTagIds).Find(&tags)
if err != nil {
return err
}
tagMap := make(map[int64]*models.TransactionTag)
for i := 0; i < len(tags); i++ {
tagMap[tags[i].TagId] = tags[i]
}
for i := 0; i < len(transactionTagIndexs); i++ {
if _, exists := tagMap[transactionTagIndexs[i].TagId]; !exists {
return errs.ErrTransactionTagNotFound
}
}
}
// Update transaction row // Update transaction row
updatedRows, err := sess.ID(transaction.TransactionId).Cols(updateCols...).Where("uid=? AND deleted=?", transaction.Uid, false).Update(transaction) updatedRows, err := sess.ID(transaction.TransactionId).Cols(updateCols...).Where("uid=? AND deleted=?", transaction.Uid, false).Update(transaction)
@@ -444,6 +530,30 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction)
return errs.ErrTransactionNotFound return errs.ErrTransactionNotFound
} }
// Update transaction tag index
if len(removeTagIds) > 0 {
deletedRows, err := sess.Where("uid=?", transaction.Uid).In("tag_id", removeTagIds).Delete(&models.TransactionTagIndex{})
if err != nil {
return err
} else if deletedRows < 1 {
return errs.ErrTransactionTagNotFound
}
return err
}
if len(transactionTagIndexs) > 0 {
for i := 0; i < len(transactionTagIndexs); i++ {
transactionTagIndex := transactionTagIndexs[i]
_, err := sess.Insert(transactionTagIndex)
if err != nil {
return err
}
}
}
// Update account table // Update account table
if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
if transaction.SourceAccountId != oldTransaction.SourceAccountId { if transaction.SourceAccountId != oldTransaction.SourceAccountId {
+37
View File
@@ -17,3 +17,40 @@ func IsStringSliceEuqals(s1, s2 []string) bool {
return true return true
} }
func Int64SliceMinus(s1, s2 []int64) []int64 {
if s1 == nil {
return nil
}
s2ItemsMap := make(map[int64]bool)
var ret []int64
for i := 0; i < len(s2); i++ {
s2ItemsMap[s2[i]] = true
}
for i := 0; i < len(s1); i++ {
if _, exists := s2ItemsMap[s1[i]]; !exists {
ret = append(ret, s1[i])
}
}
return ret
}
func ToUniqueInt64Slice(items []int64) []int64 {
var uniqueItems []int64
itemExistMap := make(map[int64]bool)
for i := 0; i < len(items); i++ {
item := items[i]
if _, exists := itemExistMap[item]; !exists {
uniqueItems = append(uniqueItems, item)
itemExistMap[item] = true
}
}
return uniqueItems
}
+1
View File
@@ -329,6 +329,7 @@ export default {
'transaction tag not found': 'Transaction tag is not found', 'transaction tag not found': 'Transaction tag is not found',
'transaction tag name is empty': 'Transaction tag title is empty', 'transaction tag name is empty': 'Transaction tag title is empty',
'transaction tag name already exists': 'Transaction tag title already exists', 'transaction tag name already exists': 'Transaction tag title already exists',
'transaction tag is in use and cannot be deleted': 'Transaction tag is in use and it cannot be deleted',
}, },
'parameter': { 'parameter': {
'id': 'ID', 'id': 'ID',
+1
View File
@@ -329,6 +329,7 @@ export default {
'transaction tag not found': '交易标签不存在', 'transaction tag not found': '交易标签不存在',
'transaction tag name is empty': '交易标签标题不能为空', 'transaction tag name is empty': '交易标签标题不能为空',
'transaction tag name already exists': '交易标签标题已经存在', 'transaction tag name already exists': '交易标签标题已经存在',
'transaction tag is in use and cannot be deleted': '交易标签正在被使用,无法删除',
}, },
'parameter': { 'parameter': {
'id': 'ID', 'id': 'ID',