add transaction pictures api

This commit is contained in:
MaysWind
2024-08-31 01:09:55 +08:00
parent 636ac974b8
commit 772a22a182
12 changed files with 466 additions and 71 deletions
+17 -3
View File
@@ -2,6 +2,7 @@ package api
import ( import (
"fmt" "fmt"
"sort"
"github.com/mayswind/ezbookkeeping/pkg/avatars" "github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker" "github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
@@ -22,9 +23,22 @@ func (a *ApiUsingConfig) CurrentConfig() *settings.Config {
} }
// GetTransactionPictureInfoResponse returns the view-object of transaction picture basic info according to the transaction picture model // GetTransactionPictureInfoResponse returns the view-object of transaction picture basic info according to the transaction picture model
func (a *ApiUsingConfig) GetTransactionPictureInfoResponse(picture *models.TransactionPictureInfo) *models.TransactionPictureInfoBasicResponse { func (a *ApiUsingConfig) GetTransactionPictureInfoResponse(pictureInfo *models.TransactionPictureInfo) *models.TransactionPictureInfoBasicResponse {
originalUrl := fmt.Sprintf(internalTransactionPictureUrlFormat, a.CurrentConfig().RootUrl, picture.PictureId, picture.PictureExtension) originalUrl := fmt.Sprintf(internalTransactionPictureUrlFormat, a.CurrentConfig().RootUrl, pictureInfo.PictureId, pictureInfo.PictureExtension)
return picture.ToTransactionPictureInfoBasicResponse(originalUrl) return pictureInfo.ToTransactionPictureInfoBasicResponse(originalUrl)
}
// GetTransactionPictureInfoResponseList returns the view-object list of transaction picture basic info according to the transaction picture model
func (a *ApiUsingConfig) GetTransactionPictureInfoResponseList(pictureInfos []*models.TransactionPictureInfo) models.TransactionPictureInfoBasicResponseSlice {
pictureInfoResps := make(models.TransactionPictureInfoBasicResponseSlice, len(pictureInfos))
for i := 0; i < len(pictureInfos); i++ {
pictureInfoResps[i] = a.GetTransactionPictureInfoResponse(pictureInfos[i])
}
sort.Sort(pictureInfoResps)
return pictureInfoResps
} }
// GetAfterRegisterNotificationContent returns the notification content displayed each time users register // GetAfterRegisterNotificationContent returns the notification content displayed each time users register
+2 -9
View File
@@ -26,9 +26,9 @@ type DataManagementsApi struct {
users *services.UserService users *services.UserService
accounts *services.AccountService accounts *services.AccountService
transactions *services.TransactionService transactions *services.TransactionService
pictures *services.TransactionPictureService
categories *services.TransactionCategoryService categories *services.TransactionCategoryService
tags *services.TransactionTagService tags *services.TransactionTagService
pictures *services.TransactionPictureService
templates *services.TransactionTemplateService templates *services.TransactionTemplateService
} }
@@ -44,9 +44,9 @@ var (
users: services.Users, users: services.Users,
accounts: services.Accounts, accounts: services.Accounts,
transactions: services.Transactions, transactions: services.Transactions,
pictures: services.TransactionPictures,
categories: services.TransactionCategories, categories: services.TransactionCategories,
tags: services.TransactionTags, tags: services.TransactionTags,
pictures: services.TransactionPictures,
templates: services.TransactionTemplates, templates: services.TransactionTemplates,
} }
) )
@@ -158,13 +158,6 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.WebContext) (any, *errs.Er
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
err = a.pictures.DeleteAllPictures(c, uid)
if err != nil {
log.Errorf(c, "[data_managements.ClearDataHandler] failed to delete all transaction pictures, because %s", err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
err = a.transactions.DeleteAllTransactions(c, uid) err = a.transactions.DeleteAllTransactions(c, uid)
if err != nil { if err != nil {
+1
View File
@@ -147,6 +147,7 @@ func (a *TransactionPicturesApi) TransactionPictureGetHandler(c *core.WebContext
func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo { func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo {
return &models.TransactionPictureInfo{ return &models.TransactionPictureInfo{
Uid: uid, Uid: uid,
TransactionId: models.TransactionPictureNewPictureTransactionId,
PictureExtension: fileExtension, PictureExtension: fileExtension,
CreatedIp: clientIp, CreatedIp: clientIp,
} }
+132 -14
View File
@@ -23,6 +23,7 @@ type TransactionsApi struct {
transactions *services.TransactionService transactions *services.TransactionService
transactionCategories *services.TransactionCategoryService transactionCategories *services.TransactionCategoryService
transactionTags *services.TransactionTagService transactionTags *services.TransactionTagService
transactionPictures *services.TransactionPictureService
accounts *services.AccountService accounts *services.AccountService
users *services.UserService users *services.UserService
} }
@@ -39,6 +40,7 @@ var (
transactions: services.Transactions, transactions: services.Transactions,
transactionCategories: services.TransactionCategories, transactionCategories: services.TransactionCategories,
transactionTags: services.TransactionTags, transactionTags: services.TransactionTags,
transactionPictures: services.TransactionPictures,
accounts: services.Accounts, accounts: services.Accounts,
users: services.Users, users: services.Users,
} }
@@ -177,7 +179,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
transactions = transactions[:transactionListReq.Count] transactions = transactions[:transactionListReq.Count]
} }
transactionResult, err := a.getTransactionListResult(c, user, transactions, utcOffset, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag) transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -260,7 +262,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
transactionResult, err := a.getTransactionListResult(c, user, transactions, utcOffset, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag) transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -567,6 +569,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
var category *models.TransactionCategory var category *models.TransactionCategory
var tagMap map[int64]*models.TransactionTag var tagMap map[int64]*models.TransactionTag
var pictureInfos []*models.TransactionPictureInfo
if !transactionGetReq.TrimCategory { if !transactionGetReq.TrimCategory {
category, err = a.transactionCategories.GetCategoryByCategoryId(c, uid, transaction.CategoryId) category, err = a.transactionCategories.GetCategoryByCategoryId(c, uid, transaction.CategoryId)
@@ -586,6 +589,15 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
} }
} }
if transactionGetReq.WithPictures {
pictureInfos, err = a.transactionPictures.GetPictureInfosByTransactionId(c, uid, transaction.TransactionId)
if err != nil {
log.Errorf(c, "[transactions.TransactionGetHandler] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
}
transactionEditable := transaction.IsEditable(user, utcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId]) transactionEditable := transaction.IsEditable(user, utcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
@@ -610,6 +622,10 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
} }
if transactionGetReq.WithPictures {
transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
}
return transactionResp, nil return transactionResp, nil
} }
@@ -630,6 +646,13 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
return nil, errs.ErrTransactionTagIdInvalid return nil, errs.ErrTransactionTagIdInvalid
} }
pictureIds, err := utils.StringArrayToInt64Array(transactionCreateReq.PictureIds)
if err != nil {
log.Warnf(c, "[transactions.TransactionCreateHandler] parse picture ids failed, because %s", err.Error())
return nil, errs.ErrTransactionPictureIdInvalid
}
if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER { if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER {
log.Warnf(c, "[transactions.TransactionCreateHandler] transaction type is invalid") log.Warnf(c, "[transactions.TransactionCreateHandler] transaction type is invalid")
return nil, errs.ErrTransactionTypeInvalid return nil, errs.ErrTransactionTypeInvalid
@@ -671,6 +694,24 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
} }
var pictureInfos []*models.TransactionPictureInfo
if len(pictureIds) > 0 {
pictureInfos, err = a.transactionPictures.GetNewPictureInfosByPictureIds(c, uid, pictureIds)
if err != nil {
log.Errorf(c, "[transactions.TransactionCreateHandler] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
notExistsPictureIds := utils.Int64SliceMinus(pictureIds, a.transactionPictures.GetTransactionPictureIds(pictureInfos))
if len(notExistsPictureIds) > 0 {
log.Errorf(c, "[transactions.TransactionCreateHandler] some pictures \"ids:%s\" does not exists for user \"uid:%d\"", strings.Join(utils.Int64ArrayToStringArray(notExistsPictureIds), ","), uid)
return nil, errs.ErrTransactionPictureNotFound
}
}
if a.CurrentConfig().EnableDuplicateSubmissionsCheck && transactionCreateReq.ClientSessionId != "" { if a.CurrentConfig().EnableDuplicateSubmissionsCheck && transactionCreateReq.ClientSessionId != "" {
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId) found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId)
@@ -687,13 +728,14 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
} }
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable) transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
return transactionResp, nil return transactionResp, nil
} }
} }
} }
err = a.transactions.CreateTransaction(c, transaction, tagIds) err = a.transactions.CreateTransaction(c, transaction, tagIds, pictureIds)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionCreateHandler] failed to create transaction \"id:%d\" for user \"uid:%d\", because %s", transaction.TransactionId, uid, err.Error()) log.Errorf(c, "[transactions.TransactionCreateHandler] failed to create transaction \"id:%d\" for user \"uid:%d\", because %s", transaction.TransactionId, uid, err.Error())
@@ -704,6 +746,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
a.SetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId, utils.Int64ToString(transaction.TransactionId)) a.SetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION, uid, transactionCreateReq.ClientSessionId, utils.Int64ToString(transaction.TransactionId))
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable) transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
return transactionResp, nil return transactionResp, nil
} }
@@ -725,6 +768,13 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
return nil, errs.ErrTransactionTagIdInvalid return nil, errs.ErrTransactionTagIdInvalid
} }
pictureIds, err := utils.StringArrayToInt64Array(transactionModifyReq.PictureIds)
if err != nil {
log.Warnf(c, "[transactions.TransactionModifyHandler] parse picture ids failed, because %s", err.Error())
return nil, errs.ErrTransactionPictureIdInvalid
}
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
user, err := a.users.GetUserById(c, uid) user, err := a.users.GetUserById(c, uid)
@@ -761,6 +811,15 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
transactionTagIds = make([]int64, 0, 0) transactionTagIds = make([]int64, 0, 0)
} }
transactionPictureInfos, err := a.transactionPictures.GetPictureInfosByTransactionId(c, uid, transaction.TransactionId)
if err != nil {
log.Errorf(c, "[transactions.TransactionModifyHandler] failed to get transaction picture infos for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionPictureIds := a.transactionPictures.GetTransactionPictureIds(transactionPictureInfos)
newTransaction := &models.Transaction{ newTransaction := &models.Transaction{
TransactionId: transaction.TransactionId, TransactionId: transaction.TransactionId,
Uid: uid, Uid: uid,
@@ -794,10 +853,18 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
newTransaction.Comment == transaction.Comment && newTransaction.Comment == transaction.Comment &&
newTransaction.GeoLongitude == transaction.GeoLongitude && newTransaction.GeoLongitude == transaction.GeoLongitude &&
newTransaction.GeoLatitude == transaction.GeoLatitude && newTransaction.GeoLatitude == transaction.GeoLatitude &&
utils.Int64SliceEquals(tagIds, transactionTagIds) { utils.Int64SliceEquals(tagIds, transactionTagIds) &&
utils.Int64SliceEquals(pictureIds, transactionPictureIds) {
return nil, errs.ErrNothingWillBeUpdated return nil, errs.ErrNothingWillBeUpdated
} }
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset)
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset)
if !transactionEditable || !newTransactionEditable {
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
}
var addTransactionTagIds []int64 var addTransactionTagIds []int64
var removeTransactionTagIds []int64 var removeTransactionTagIds []int64
@@ -806,14 +873,46 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
addTransactionTagIds = tagIds addTransactionTagIds = tagIds
} }
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset) addTransactionPictureIds := utils.Int64SliceMinus(pictureIds, transactionPictureIds)
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset) removeTransactionPictureIds := utils.Int64SliceMinus(transactionPictureIds, pictureIds)
var newPictureInfos []*models.TransactionPictureInfo
if !transactionEditable || !newTransactionEditable { if !utils.Int64SliceEquals(pictureIds, transactionPictureIds) {
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime oldAndNewPictureIds := transactionPictureIds
oldAndNewPictureInfoMap := a.transactionPictures.GetPictureInfoMapByList(transactionPictureInfos)
if len(addTransactionPictureIds) > 0 {
addPictureInfos, err := a.transactionPictures.GetNewPictureInfosByPictureIds(c, uid, addTransactionPictureIds)
if err != nil {
log.Errorf(c, "[transactions.TransactionModifyHandler] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
oldAndNewPictureIds = append(oldAndNewPictureIds, a.transactionPictures.GetTransactionPictureIds(addPictureInfos)...)
notExistsPictureIds := utils.Int64SliceMinus(pictureIds, oldAndNewPictureIds)
if len(notExistsPictureIds) > 0 {
log.Errorf(c, "[transactions.TransactionModifyHandler] some pictures \"ids:%s\" does not exists for user \"uid:%d\"", strings.Join(utils.Int64ArrayToStringArray(notExistsPictureIds), ","), uid)
return nil, errs.ErrTransactionPictureNotFound
}
for i := 0; i < len(addPictureInfos); i++ {
oldAndNewPictureInfoMap[addPictureInfos[i].PictureId] = addPictureInfos[i]
}
}
for i := 0; i < len(pictureIds); i++ {
pictureId := pictureIds[i]
pictureInfo, exists := oldAndNewPictureInfoMap[pictureId]
if exists {
newPictureInfos = append(newPictureInfos, pictureInfo)
}
}
} }
err = a.transactions.ModifyTransaction(c, newTransaction, len(transactionTagIds), addTransactionTagIds, removeTransactionTagIds) err = a.transactions.ModifyTransaction(c, newTransaction, len(transactionTagIds), addTransactionTagIds, removeTransactionTagIds, addTransactionPictureIds, removeTransactionPictureIds)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error()) log.Errorf(c, "[transactions.TransactionModifyHandler] failed to update transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
@@ -824,6 +923,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
newTransaction.Type = transaction.Type newTransaction.Type = transaction.Type
newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, transactionEditable) newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, transactionEditable)
newTransactionResp.Pictures = a.GetTransactionPictureInfoResponseList(newPictureInfos)
return newTransactionResp, nil return newTransactionResp, nil
} }
@@ -1053,7 +1153,7 @@ func (a *TransactionsApi) getTransactionTagInfoResponses(tagIds []int64, allTran
return allTags return allTags
} }
func (a *TransactionsApi) getTransactionListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, utcOffset int16, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) { func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, utcOffset int16, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) {
uid := user.Uid uid := user.Uid
transactionIds := make([]int64, len(transactions)) transactionIds := make([]int64, len(transactions))
accountIds := make([]int64, 0, len(transactions)*2) accountIds := make([]int64, 0, len(transactions)*2)
@@ -1079,7 +1179,7 @@ func (a *TransactionsApi) getTransactionListResult(c *core.WebContext, user *mod
allAccounts, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds)) allAccounts, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds))
if err != nil { if err != nil {
log.Errorf(c, "[transactions.getTransactionListResult] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, err return nil, err
} }
@@ -1088,18 +1188,19 @@ func (a *TransactionsApi) getTransactionListResult(c *core.WebContext, user *mod
allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds) allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.getTransactionListResult] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions tag ids for user \"uid:%d\", because %s", uid, err.Error())
return nil, err return nil, err
} }
var categoryMap map[int64]*models.TransactionCategory var categoryMap map[int64]*models.TransactionCategory
var tagMap map[int64]*models.TransactionTag var tagMap map[int64]*models.TransactionTag
var pictureInfoMap map[int64][]*models.TransactionPictureInfo
if !trimCategory { if !trimCategory {
categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(c, uid, utils.ToUniqueInt64Slice(categoryIds)) categoryMap, err = a.transactionCategories.GetCategoriesByCategoryIds(c, uid, utils.ToUniqueInt64Slice(categoryIds))
if err != nil { if err != nil {
log.Errorf(c, "[transactions.getTransactionListResult] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions categories for user \"uid:%d\", because %s", uid, err.Error())
return nil, err return nil, err
} }
} }
@@ -1108,7 +1209,16 @@ func (a *TransactionsApi) getTransactionListResult(c *core.WebContext, user *mod
tagMap, err = a.transactionTags.GetTagsByTagIds(c, uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds))) tagMap, err = a.transactionTags.GetTagsByTagIds(c, uid, utils.ToUniqueInt64Slice(a.getTransactionTagIds(allTransactionTagIds)))
if err != nil { if err != nil {
log.Errorf(c, "[transactions.getTransactionListResult] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions tags for user \"uid:%d\", because %s", uid, err.Error())
return nil, err
}
}
if withPictures {
pictureInfoMap, err = a.transactionPictures.GetPictureInfosByTransactionIds(c, uid, utils.ToUniqueInt64Slice(a.transactions.GetTransactionIds(transactions)))
if err != nil {
log.Errorf(c, "[transactions.getTransactionResponseListResult] failed to get transactions pictures for user \"uid:%d\", because %s", uid, err.Error())
return nil, err return nil, err
} }
} }
@@ -1145,6 +1255,14 @@ func (a *TransactionsApi) getTransactionListResult(c *core.WebContext, user *mod
if !trimTag { if !trimTag {
result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
} }
if withPictures {
pictureInfos, exists := pictureInfoMap[transaction.TransactionId]
if exists {
result[i].Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
}
}
} }
sort.Sort(result) sort.Sort(result)
+25 -19
View File
@@ -73,6 +73,7 @@ type TransactionCreateRequest struct {
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"` HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"` TagIds []string `json:"tagIds"`
PictureIds []string `json:"pictureIds"`
Comment string `json:"comment" binding:"max=255"` Comment string `json:"comment" binding:"max=255"`
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"` GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
ClientSessionId string `json:"clientSessionId"` ClientSessionId string `json:"clientSessionId"`
@@ -90,6 +91,7 @@ type TransactionModifyRequest struct {
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
HideAmount bool `json:"hideAmount"` HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"` TagIds []string `json:"tagIds"`
PictureIds []string `json:"pictureIds"`
Comment string `json:"comment" binding:"max=255"` Comment string `json:"comment" binding:"max=255"`
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"` GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
} }
@@ -119,6 +121,7 @@ type TransactionListByMaxTimeRequest struct {
Page int32 `form:"page" binding:"min=0"` Page int32 `form:"page" binding:"min=0"`
Count int32 `form:"count" binding:"required,min=1,max=50"` Count int32 `form:"count" binding:"required,min=1,max=50"`
WithCount bool `form:"with_count"` WithCount bool `form:"with_count"`
WithPictures bool `form:"with_pictures"`
TrimAccount bool `form:"trim_account"` TrimAccount bool `form:"trim_account"`
TrimCategory bool `form:"trim_category"` TrimCategory bool `form:"trim_category"`
TrimTag bool `form:"trim_tag"` TrimTag bool `form:"trim_tag"`
@@ -134,6 +137,7 @@ type TransactionListInMonthByPageRequest struct {
TagIds string `form:"tag_ids"` TagIds string `form:"tag_ids"`
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"` AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
Keyword string `form:"keyword"` Keyword string `form:"keyword"`
WithPictures bool `form:"with_pictures"`
TrimAccount bool `form:"trim_account"` TrimAccount bool `form:"trim_account"`
TrimCategory bool `form:"trim_category"` TrimCategory bool `form:"trim_category"`
TrimTag bool `form:"trim_tag"` TrimTag bool `form:"trim_tag"`
@@ -168,6 +172,7 @@ type TransactionAmountsRequestItem struct {
// TransactionGetRequest represents all parameters of transaction getting request // TransactionGetRequest represents all parameters of transaction getting request
type TransactionGetRequest struct { type TransactionGetRequest struct {
Id int64 `form:"id,string" binding:"required,min=1"` Id int64 `form:"id,string" binding:"required,min=1"`
WithPictures bool `form:"with_pictures"`
TrimAccount bool `form:"trim_account"` TrimAccount bool `form:"trim_account"`
TrimCategory bool `form:"trim_category"` TrimCategory bool `form:"trim_category"`
TrimTag bool `form:"trim_tag"` TrimTag bool `form:"trim_tag"`
@@ -192,25 +197,26 @@ type TransactionGeoLocationResponse struct {
// TransactionInfoResponse represents a view-object of transaction // TransactionInfoResponse represents a view-object of transaction
type TransactionInfoResponse struct { type TransactionInfoResponse struct {
Id int64 `json:"id,string"` Id int64 `json:"id,string"`
TimeSequenceId int64 `json:"timeSequenceId,string"` TimeSequenceId int64 `json:"timeSequenceId,string"`
Type TransactionType `json:"type"` Type TransactionType `json:"type"`
CategoryId int64 `json:"categoryId,string"` CategoryId int64 `json:"categoryId,string"`
Category *TransactionCategoryInfoResponse `json:"category,omitempty"` Category *TransactionCategoryInfoResponse `json:"category,omitempty"`
Time int64 `json:"time"` Time int64 `json:"time"`
UtcOffset int16 `json:"utcOffset"` UtcOffset int16 `json:"utcOffset"`
SourceAccountId int64 `json:"sourceAccountId,string"` SourceAccountId int64 `json:"sourceAccountId,string"`
SourceAccount *AccountInfoResponse `json:"sourceAccount,omitempty"` SourceAccount *AccountInfoResponse `json:"sourceAccount,omitempty"`
DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"` DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"`
DestinationAccount *AccountInfoResponse `json:"destinationAccount,omitempty"` DestinationAccount *AccountInfoResponse `json:"destinationAccount,omitempty"`
SourceAmount int64 `json:"sourceAmount"` SourceAmount int64 `json:"sourceAmount"`
DestinationAmount int64 `json:"destinationAmount,omitempty"` DestinationAmount int64 `json:"destinationAmount,omitempty"`
HideAmount bool `json:"hideAmount"` HideAmount bool `json:"hideAmount"`
TagIds []string `json:"tagIds"` TagIds []string `json:"tagIds"`
Tags []*TransactionTagInfoResponse `json:"tags,omitempty"` Tags []*TransactionTagInfoResponse `json:"tags,omitempty"`
Comment string `json:"comment"` Pictures TransactionPictureInfoBasicResponseSlice `json:"pictures,omitempty"`
GeoLocation *TransactionGeoLocationResponse `json:"geoLocation,omitempty"` Comment string `json:"comment"`
Editable bool `json:"editable"` GeoLocation *TransactionGeoLocationResponse `json:"geoLocation,omitempty"`
Editable bool `json:"editable"`
} }
// TransactionCountResponse represents transaction count response // TransactionCountResponse represents transaction count response
+20
View File
@@ -1,5 +1,7 @@
package models package models
const TransactionPictureNewPictureTransactionId = int64(0)
// TransactionPictureInfo represents transaction picture file info stored in database // TransactionPictureInfo represents transaction picture file info stored in database
type TransactionPictureInfo struct { type TransactionPictureInfo struct {
Uid int64 `xorm:"INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) INDEX(IDX_transaction_picture_uid_deleted_picture_id) NOT NULL"` Uid int64 `xorm:"INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) INDEX(IDX_transaction_picture_uid_deleted_picture_id) NOT NULL"`
@@ -26,3 +28,21 @@ func (p *TransactionPictureInfo) ToTransactionPictureInfoBasicResponse(originalU
OriginalUrl: originalUrl, OriginalUrl: originalUrl,
} }
} }
// TransactionPictureInfoBasicResponseSlice represents the slice data structure of TransactionPictureInfoBasicResponse
type TransactionPictureInfoBasicResponseSlice []*TransactionPictureInfoBasicResponse
// Len returns the count of items
func (s TransactionPictureInfoBasicResponseSlice) Len() int {
return len(s)
}
// Swap swaps two items
func (s TransactionPictureInfoBasicResponseSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less reports whether the first item is less than the second one
func (s TransactionPictureInfoBasicResponseSlice) Less(i, j int) bool {
return s[i].PictureId < s[j].PictureId
}
+94 -20
View File
@@ -71,6 +71,67 @@ func (s *TransactionPictureService) GetPictureInfoByPictureId(c core.Context, ui
return pictureInfo, nil return pictureInfo, nil
} }
// GetNewPictureInfosByPictureIds returns new transaction picture info models according to transaction picture ids
func (s *TransactionPictureService) GetNewPictureInfosByPictureIds(c core.Context, uid int64, pictureIds []int64) ([]*models.TransactionPictureInfo, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if pictureIds == nil {
return nil, errs.ErrTransactionPictureIdInvalid
}
var pictureInfos []*models.TransactionPictureInfo
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND transaction_id=?", uid, false, models.TransactionPictureNewPictureTransactionId).In("picture_id", pictureIds).OrderBy("picture_id asc").Find(&pictureInfos)
if err != nil {
return nil, err
}
return pictureInfos, nil
}
// GetPictureInfosByTransactionId returns transaction picture info models according to transaction id
func (s *TransactionPictureService) GetPictureInfosByTransactionId(c core.Context, uid int64, transactionId int64) ([]*models.TransactionPictureInfo, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if transactionId <= 0 {
return nil, errs.ErrTransactionIdInvalid
}
var pictureInfos []*models.TransactionPictureInfo
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=? AND transaction_id=?", uid, false, transactionId).OrderBy("picture_id asc").Find(&pictureInfos)
if err != nil {
return nil, err
}
return pictureInfos, nil
}
// GetPictureInfosByTransactionIds returns transaction picture info models according to transaction ids
func (s *TransactionPictureService) GetPictureInfosByTransactionIds(c core.Context, uid int64, transactionIds []int64) (map[int64][]*models.TransactionPictureInfo, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if transactionIds == nil {
return nil, errs.ErrTransactionIdInvalid
}
var pictureInfos []*models.TransactionPictureInfo
err := s.UserDataDB(uid).NewSession(c).Where("uid=? AND deleted=?", uid, false).In("transaction_id", transactionIds).OrderBy("picture_id asc").Find(&pictureInfos)
if err != nil {
return nil, err
}
pictureInfoMap := s.GetPictureInfoListMapByList(pictureInfos)
return pictureInfoMap, err
}
// GetPictureByPictureId returns the transaction picture data according to transaction picture id // GetPictureByPictureId returns the transaction picture data according to transaction picture id
func (s *TransactionPictureService) GetPictureByPictureId(c core.Context, uid int64, pictureId int64, fileExtension string) ([]byte, error) { func (s *TransactionPictureService) GetPictureByPictureId(c core.Context, uid int64, pictureId int64, fileExtension string) ([]byte, error) {
if uid <= 0 { if uid <= 0 {
@@ -150,26 +211,39 @@ func (s *TransactionPictureService) UploadPicture(c core.Context, pictureInfo *m
}) })
} }
// DeleteAllPictures deletes all existed transaction pictures from database // GetPictureInfoMapByList returns a transaction picture info list map by a list
func (s *TransactionPictureService) DeleteAllPictures(c core.Context, uid int64) error { func (s *TransactionPictureService) GetPictureInfoMapByList(pictureInfos []*models.TransactionPictureInfo) map[int64]*models.TransactionPictureInfo {
if uid <= 0 { pictureInfoMap := make(map[int64]*models.TransactionPictureInfo)
return errs.ErrUserIdInvalid
for i := 0; i < len(pictureInfos); i++ {
pictureInfo := pictureInfos[i]
pictureInfoMap[pictureInfo.PictureId] = pictureInfo
} }
now := time.Now().Unix() return pictureInfoMap
}
updateModel := &models.TransactionPictureInfo{
Deleted: true, // GetPictureInfoListMapByList returns a transaction picture info list map by a list
DeletedUnixTime: now, func (s *TransactionPictureService) GetPictureInfoListMapByList(pictureInfos []*models.TransactionPictureInfo) map[int64][]*models.TransactionPictureInfo {
} pictureInfoMap := make(map[int64][]*models.TransactionPictureInfo)
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error { for i := 0; i < len(pictureInfos); i++ {
_, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel) pictureInfo := pictureInfos[i]
if err != nil { pictureInfos, _ := pictureInfoMap[pictureInfo.TransactionId]
return err pictureInfoMap[pictureInfo.TransactionId] = append(pictureInfos, pictureInfo)
} }
return nil return pictureInfoMap
}) }
// GetTransactionPictureIds returns transaction picture ids list
func (s *TransactionPictureService) GetTransactionPictureIds(pictureInfos []*models.TransactionPictureInfo) []int64 {
pictureIds := make([]int64, len(pictureInfos))
for i := 0; i < len(pictureInfos); i++ {
pictureIds[i] = pictureInfos[i].PictureId
}
return pictureIds
} }
+124 -3
View File
@@ -203,7 +203,7 @@ func (s *TransactionService) GetTransactionCount(c core.Context, uid int64, maxT
} }
// CreateTransaction saves a new transaction to database // CreateTransaction saves a new transaction to database
func (s *TransactionService) CreateTransaction(c core.Context, transaction *models.Transaction, tagIds []int64) error { func (s *TransactionService) CreateTransaction(c core.Context, transaction *models.Transaction, tagIds []int64, pictureIds []int64) error {
if transaction.Uid <= 0 { if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -261,6 +261,11 @@ func (s *TransactionService) CreateTransaction(c core.Context, transaction *mode
} }
} }
pictureUpdateModel := &models.TransactionPictureInfo{
TransactionId: transaction.TransactionId,
UpdatedUnixTime: now,
}
return s.UserDataDB(transaction.Uid).DoTransaction(c, func(sess *xorm.Session) error { return s.UserDataDB(transaction.Uid).DoTransaction(c, func(sess *xorm.Session) error {
// Get and verify source and destination account // Get and verify source and destination account
sourceAccount, destinationAccount, err := s.getAccountModels(sess, transaction) sourceAccount, destinationAccount, err := s.getAccountModels(sess, transaction)
@@ -296,6 +301,13 @@ func (s *TransactionService) CreateTransaction(c core.Context, transaction *mode
return err return err
} }
// Get and verify pictures
err = s.isPicturesValid(sess, transaction, pictureIds)
if err != nil {
return err
}
// Verify balance modification transaction and calculate real amount // Verify balance modification transaction and calculate real amount
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
otherTransactionExists, err := sess.Cols("uid", "deleted", "account_id").Where("uid=? AND deleted=? AND account_id=?", transaction.Uid, false, sourceAccount.AccountId).Limit(1).Exist(&models.Transaction{}) otherTransactionExists, err := sess.Cols("uid", "deleted", "account_id").Where("uid=? AND deleted=? AND account_id=?", transaction.Uid, false, sourceAccount.AccountId).Limit(1).Exist(&models.Transaction{})
@@ -376,6 +388,15 @@ func (s *TransactionService) CreateTransaction(c core.Context, transaction *mode
} }
} }
// Update transaction picture
if len(pictureIds) > 0 {
_, err = sess.Cols("transaction_id", "updated_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", transaction.Uid, false, models.TransactionPictureNewPictureTransactionId).In("picture_id", pictureIds).Update(pictureUpdateModel)
if err != nil {
return err
}
}
// Update account table // Update account table
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
sourceAccount.UpdatedUnixTime = time.Now().Unix() sourceAccount.UpdatedUnixTime = time.Now().Unix()
@@ -543,7 +564,7 @@ func (s *TransactionService) CreateScheduledTransactions(c core.Context, current
} }
tagIds := template.GetTagIds() tagIds := template.GetTagIds()
err = s.CreateTransaction(c, transaction, tagIds) err = s.CreateTransaction(c, transaction, tagIds, nil)
if err == nil { if err == nil {
successCount++ successCount++
@@ -560,7 +581,7 @@ func (s *TransactionService) CreateScheduledTransactions(c core.Context, current
} }
// ModifyTransaction saves an existed transaction to database // ModifyTransaction saves an existed transaction to database
func (s *TransactionService) ModifyTransaction(c core.Context, transaction *models.Transaction, currentTagIdsCount int, addTagIds []int64, removeTagIds []int64) error { func (s *TransactionService) ModifyTransaction(c core.Context, transaction *models.Transaction, currentTagIdsCount int, addTagIds []int64, removeTagIds []int64, addPictureIds []int64, removePictureIds []int64) error {
if transaction.Uid <= 0 { if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -736,6 +757,13 @@ func (s *TransactionService) ModifyTransaction(c core.Context, transaction *mode
return err return err
} }
// Get and verify pictures
err = s.isPicturesValid(sess, transaction, addPictureIds)
if err != nil {
return err
}
// 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)
@@ -801,6 +829,35 @@ func (s *TransactionService) ModifyTransaction(c core.Context, transaction *mode
} }
} }
// Update transaction picture
if len(removePictureIds) > 0 {
pictureUpdateModel := &models.TransactionPictureInfo{
Deleted: true,
DeletedUnixTime: now,
}
deletedRows, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", transaction.Uid, false, transaction.TransactionId).In("picture_id", removePictureIds).Update(pictureUpdateModel)
if err != nil {
return err
} else if deletedRows < 1 {
return errs.ErrTransactionPictureNotFound
}
}
if len(addPictureIds) > 0 {
pictureUpdateModel := &models.TransactionPictureInfo{
TransactionId: transaction.TransactionId,
UpdatedUnixTime: now,
}
_, err = sess.Cols("transaction_id", "updated_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", transaction.Uid, false, models.TransactionPictureNewPictureTransactionId).In("picture_id", addPictureIds).Update(pictureUpdateModel)
if err != nil {
return err
}
}
// Update account table // Update account table
if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
if transaction.AccountId != oldTransaction.AccountId { if transaction.AccountId != oldTransaction.AccountId {
@@ -973,6 +1030,11 @@ func (s *TransactionService) DeleteTransaction(c core.Context, uid int64, transa
DeletedUnixTime: now, DeletedUnixTime: now,
} }
pictureUpdateModel := &models.TransactionPictureInfo{
Deleted: true,
DeletedUnixTime: now,
}
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error { return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
// Get and verify current transaction // Get and verify current transaction
oldTransaction := &models.Transaction{} oldTransaction := &models.Transaction{}
@@ -1025,6 +1087,13 @@ func (s *TransactionService) DeleteTransaction(c core.Context, uid int64, transa
return err return err
} }
// Update transaction picture
_, err = sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", uid, false, oldTransaction.TransactionId).Update(pictureUpdateModel)
if err != nil {
return err
}
// Update account table // Update account table
if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
sourceAccount.UpdatedUnixTime = time.Now().Unix() sourceAccount.UpdatedUnixTime = time.Now().Unix()
@@ -1097,6 +1166,11 @@ func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64) er
DeletedUnixTime: now, DeletedUnixTime: now,
} }
pictureUpdateModel := &models.TransactionPictureInfo{
Deleted: true,
DeletedUnixTime: now,
}
accountUpdateModel := &models.Account{ accountUpdateModel := &models.Account{
Balance: 0, Balance: 0,
Deleted: true, Deleted: true,
@@ -1118,6 +1192,13 @@ func (s *TransactionService) DeleteAllTransactions(c core.Context, uid int64) er
return err return err
} }
// Update all transaction picture to deleted
_, err = sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(pictureUpdateModel)
if err != nil {
return err
}
// Update all account table to deleted // Update all account table to deleted
_, err = sess.Cols("balance", "deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(accountUpdateModel) _, err = sess.Cols("balance", "deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(accountUpdateModel)
@@ -1495,6 +1576,17 @@ func (s *TransactionService) GetTransactionMapByList(transactions []*models.Tran
return transactionMap return transactionMap
} }
// GetTransactionIds returns transaction ids list
func (s *TransactionService) GetTransactionIds(transactions []*models.Transaction) []int64 {
transactionIds := make([]int64, len(transactions))
for i := 0; i < len(transactions); i++ {
transactionIds[i] = transactions[i].TransactionId
}
return transactionIds
}
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, amountFilter string, keyword string, noDuplicated bool) (string, []any) { func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, tagIds []int64, amountFilter string, keyword string, noDuplicated bool) (string, []any) {
condition := "uid=? AND deleted=?" condition := "uid=? AND deleted=?"
conditionParams := make([]any, 0, 16) conditionParams := make([]any, 0, 16)
@@ -1924,3 +2016,32 @@ func (s *TransactionService) isTagsValid(sess *xorm.Session, transaction *models
return nil return nil
} }
func (s *TransactionService) isPicturesValid(sess *xorm.Session, transaction *models.Transaction, pictureIds []int64) error {
if len(pictureIds) > 0 {
var pictureInfos []*models.TransactionPictureInfo
err := sess.Where("uid=? AND deleted=?", transaction.Uid, false).In("picture_id", pictureIds).Find(&pictureInfos)
if err != nil {
return err
}
pictureInfoMap := make(map[int64]*models.TransactionPictureInfo)
for i := 0; i < len(pictureInfos); i++ {
if pictureInfos[i].TransactionId != models.TransactionPictureNewPictureTransactionId && pictureInfos[i].TransactionId != transaction.TransactionId {
return errs.ErrTransactionPictureIdInvalid
}
pictureInfoMap[pictureInfos[i].PictureId] = pictureInfos[i]
}
for i := 0; i < len(pictureIds); i++ {
if _, exists := pictureInfoMap[pictureIds[i]]; !exists {
return errs.ErrTransactionPictureNotFound
}
}
}
return nil
}
+11 -3
View File
@@ -394,9 +394,9 @@ export default {
return axios.get(`v1/transactions/amounts.json?use_transaction_timezone=${useTransactionTimezone}` + (queryParams.length ? '&query=' + queryParams.join('|') : '')); return axios.get(`v1/transactions/amounts.json?use_transaction_timezone=${useTransactionTimezone}` + (queryParams.length ? '&query=' + queryParams.join('|') : ''));
}, },
getTransaction: ({ id }) => { getTransaction: ({ id }) => {
return axios.get(`v1/transactions/get.json?id=${id}&trim_account=true&trim_category=true&trim_tag=true`); return axios.get(`v1/transactions/get.json?id=${id}&with_pictures=true&trim_account=true&trim_category=true&trim_tag=true`);
}, },
addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, geoLocation, utcOffset, clientSessionId }) => { addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, pictureIds, comment, geoLocation, utcOffset, clientSessionId }) => {
return axios.post('v1/transactions/add.json', { return axios.post('v1/transactions/add.json', {
type, type,
categoryId, categoryId,
@@ -407,13 +407,14 @@ export default {
destinationAmount, destinationAmount,
hideAmount, hideAmount,
tagIds, tagIds,
pictureIds,
comment, comment,
geoLocation, geoLocation,
utcOffset, utcOffset,
clientSessionId clientSessionId
}); });
}, },
modifyTransaction: ({ id, type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, geoLocation, utcOffset }) => { modifyTransaction: ({ id, type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, pictureIds, comment, geoLocation, utcOffset }) => {
return axios.post('v1/transactions/modify.json', { return axios.post('v1/transactions/modify.json', {
id, id,
type, type,
@@ -425,6 +426,7 @@ export default {
destinationAmount, destinationAmount,
hideAmount, hideAmount,
tagIds, tagIds,
pictureIds,
comment, comment,
geoLocation, geoLocation,
utcOffset utcOffset
@@ -435,6 +437,12 @@ export default {
id id
}); });
}, },
uploadTransactionPicture: ({ pictureFile, clientSessionId }) => {
return axios.postForm('v1/transaction/pictures/upload.json', {
picture: pictureFile,
clientSessionId: clientSessionId
});
},
getAllTransactionCategories: () => { getAllTransactionCategories: () => {
return axios.get('v1/transaction/categories/list.json'); return axios.get('v1/transaction/categories/list.json');
}, },
+2
View File
@@ -1414,6 +1414,7 @@
"Transaction": "Transaction", "Transaction": "Transaction",
"Transactions": "Transactions", "Transactions": "Transactions",
"Transaction Pictures": "Transaction Pictures", "Transaction Pictures": "Transaction Pictures",
"Pictures": "Pictures",
"Add Transaction": "Add Transaction", "Add Transaction": "Add Transaction",
"Edit Transaction": "Edit Transaction", "Edit Transaction": "Edit Transaction",
"Add Transaction Template": "Add Transaction Template", "Add Transaction Template": "Add Transaction Template",
@@ -1465,6 +1466,7 @@
"Unable to save transaction": "Unable to save transaction", "Unable to save transaction": "Unable to save transaction",
"You have added a new transaction": "You have added a new transaction", "You have added a new transaction": "You have added a new transaction",
"You have saved this transaction": "You have saved this transaction", "You have saved this transaction": "You have saved this transaction",
"Unable to upload transaction picture": "Unable to upload transaction picture",
"Search transaction description": "Search transaction description", "Search transaction description": "Search transaction description",
"Unable to retrieve transaction list": "Unable to retrieve transaction list", "Unable to retrieve transaction list": "Unable to retrieve transaction list",
"Custom Date Range": "Custom Date Range", "Custom Date Range": "Custom Date Range",
+2
View File
@@ -1414,6 +1414,7 @@
"Transaction": "交易", "Transaction": "交易",
"Transactions": "交易", "Transactions": "交易",
"Transaction Pictures": "交易图片", "Transaction Pictures": "交易图片",
"Pictures": "图片",
"Add Transaction": "添加交易", "Add Transaction": "添加交易",
"Edit Transaction": "编辑交易", "Edit Transaction": "编辑交易",
"Add Transaction Template": "添加交易模板", "Add Transaction Template": "添加交易模板",
@@ -1465,6 +1466,7 @@
"Unable to save transaction": "无法保存交易", "Unable to save transaction": "无法保存交易",
"You have added a new transaction": "您已经添加新交易", "You have added a new transaction": "您已经添加新交易",
"You have saved this transaction": "您已经保存该交易", "You have saved this transaction": "您已经保存该交易",
"Unable to upload transaction picture": "无法上传交易图片",
"Search transaction description": "搜索交易描述", "Search transaction description": "搜索交易描述",
"Unable to retrieve transaction list": "无法获取交易列表", "Unable to retrieve transaction list": "无法获取交易列表",
"Custom Date Range": "自定义日期范围", "Custom Date Range": "自定义日期范围",
+36
View File
@@ -864,6 +864,18 @@ export const useTransactionsStore = defineStore('transactions', {
return Promise.reject('An error occurred'); return Promise.reject('An error occurred');
} }
if (transaction.pictures && transaction.pictures.length > 0) {
const pictureIds = [];
for (let i = 0; i < transaction.pictures.length; i++) {
if (transaction.pictures[i].pictureId) {
pictureIds.push(transaction.pictures[i].pictureId);
}
}
transaction.pictureIds = pictureIds;
}
if (isEdit) { if (isEdit) {
submitTransaction.id = transaction.id; submitTransaction.id = transaction.id;
} }
@@ -991,6 +1003,30 @@ export const useTransactionsStore = defineStore('transactions', {
}); });
}); });
}, },
uploadTransactionPicture({ pictureFile, clientSessionId }) {
return new Promise((resolve, reject) => {
services.uploadTransactionPicture({ pictureFile, clientSessionId }).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to upload transaction picture' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('Unable to upload transaction picture', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to upload transaction picture' });
} else {
reject(error);
}
});
});
},
collapseMonthInTransactionList({ month, collapse }) { collapseMonthInTransactionList({ month, collapse }) {
if (month) { if (month) {
month.opened = !collapse; month.opened = !collapse;