add transaction basic api

This commit is contained in:
MaysWind
2020-12-07 01:06:57 +08:00
parent 33814af667
commit 128d064df4
13 changed files with 1026 additions and 2 deletions
+8
View File
@@ -83,6 +83,14 @@ func updateAllDatabaseTablesStructure() error {
log.BootInfof("[database.updateAllDatabaseTablesStructure] account table maintained successfully")
}
err = datastore.Container.UserDataStore.SyncStructs(new(models.Transaction))
if err != nil {
return err
} else {
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction table maintained successfully")
}
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionCategory))
if err != nil {
+9 -1
View File
@@ -185,6 +185,14 @@ func startWebServer(c *cli.Context) error {
apiV1Route.POST("/accounts/move.json", bindApi(api.Accounts.AccountMoveHandler))
apiV1Route.POST("/accounts/delete.json", bindApi(api.Accounts.AccountDeleteHandler))
// Transactions
apiV1Route.GET("/transactions/list.json", bindApi(api.Transactions.TransactionListHandler))
apiV1Route.GET("/transactions/list/by_month.json", bindApi(api.Transactions.TransactionListHandler))
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler))
// Transaction Categories
apiV1Route.GET("/transaction/categories/list.json", bindApi(api.TransactionCategories.CategoryListHandler))
apiV1Route.GET("/transaction/categories/get.json", bindApi(api.TransactionCategories.CategoryGetHandler))
@@ -220,7 +228,7 @@ func startWebServer(c *cli.Context) error {
} else if config.Protocol == settings.SCHEME_HTTPS {
log.BootInfof("[server.startWebServer] will run at https://%s", listenAddr)
err = router.RunTLS(listenAddr, config.CertFile, config.CertKeyFile)
} else {
} else {
err = errs.ErrInvalidProtocol
}
+235
View File
@@ -0,0 +1,235 @@
package api
import (
"sort"
"github.com/mayswind/lab/pkg/core"
"github.com/mayswind/lab/pkg/errs"
"github.com/mayswind/lab/pkg/log"
"github.com/mayswind/lab/pkg/models"
"github.com/mayswind/lab/pkg/services"
)
type TransactionsApi struct {
transactions *services.TransactionService
}
var (
Transactions = &TransactionsApi{
transactions: services.Transactions,
}
)
func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionListReq models.TransactionListByMaxTimeRequest
err := c.ShouldBindQuery(&transactionListReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionListHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, transactionListReq.Count + 1)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
return nil, errs.ErrOperationFailed
}
finalCount := transactionListReq.Count
if len(transactions) < finalCount {
finalCount = len(transactions)
}
transactionResps := &models.TransactionInfoPageWrapperResponse{}
transactionResps.Items = make(models.TransactionInfoResponseSlice, finalCount)
for i := 0; i < finalCount; i++ {
transactionResps.Items[i] = transactions[i].ToTransactionInfoResponse(nil)
}
sort.Sort(transactionResps.Items)
if finalCount < len(transactions) {
transactionResps.NextTime = &transactions[finalCount].TransactionTime
}
return transactionResps, nil
}
func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionListReq models.TransactionListInMonthByPageRequest
err := c.ShouldBindQuery(&transactionListReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionMonthListHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Page, transactionListReq.Count)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
return nil, errs.ErrOperationFailed
}
transactionResps := make([]*models.TransactionInfoResponse, len(transactions))
for i := 0; i < len(transactions); i++ {
transactionResps[i] = transactions[i].ToTransactionInfoResponse(nil)
}
return transactionResps, nil
}
func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionGetReq models.TransactionGetRequest
err := c.ShouldBindQuery(&transactionGetReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionGetHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionGetReq.Id)
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())
return nil, errs.ErrOperationFailed
}
transactionResp := transaction.ToTransactionInfoResponse(nil)
return transactionResp, nil
}
func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionCreateReq models.TransactionCreateRequest
err := c.ShouldBindJSON(&transactionCreateReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER {
log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] transaction type is invalid")
return nil, errs.ErrTransactionTypeInvalid
}
if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE && transactionCreateReq.CategoryId > 0 {
return nil, errs.ErrBalanceModificationTransactionCannotSetCategory
}
if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.SourceAccountId != transactionCreateReq.DestinationAccountId {
return nil, errs.ErrTransactionSourceAndDestinationIdNotEqual
} else if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.SourceAmount != transactionCreateReq.DestinationAmount {
return nil, errs.ErrTransactionSourceAndDestinationAmountNotEqual
}
uid := c.GetCurrentUid()
transaction := a.createNewTransactionModel(uid, &transactionCreateReq)
err = a.transactions.CreateTransaction(transaction)
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())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId)
transactionResp := transaction.ToTransactionInfoResponse(nil)
return transactionResp, nil
}
func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionModifyReq models.TransactionModifyRequest
err := c.ShouldBindJSON(&transactionModifyReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionModifyHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionModifyReq.Id)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get transaction \"id:%d\" for user \"uid:%d\", because %s", transactionModifyReq.Id, uid, err.Error())
return nil, errs.ErrOperationFailed
}
newTransaction := &models.Transaction{
TransactionId: transaction.TransactionId,
Uid: uid,
CategoryId: transactionModifyReq.CategoryId,
TransactionTime: transactionModifyReq.Time,
SourceAccountId: transactionModifyReq.SourceAccountId,
DestinationAccountId: transactionModifyReq.DestinationAccountId,
SourceAmount: transactionModifyReq.SourceAmount,
DestinationAmount: transactionModifyReq.DestinationAmount,
Comment: transactionModifyReq.Comment,
}
if newTransaction.CategoryId == transaction.CategoryId &&
newTransaction.TransactionTime / 1000 == transaction.TransactionTime / 1000 &&
newTransaction.SourceAccountId == transaction.SourceAccountId &&
newTransaction.DestinationAccountId == transaction.DestinationAccountId &&
newTransaction.SourceAmount == transaction.SourceAmount &&
newTransaction.DestinationAmount == transaction.DestinationAmount &&
newTransaction.Comment == transaction.Comment {
return nil, errs.ErrNothingWillBeUpdated
}
err = a.transactions.ModifyTransaction(newTransaction)
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())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.InfofWithRequestId(c, "[transactions.TransactionModifyHandler] user \"uid:%d\" has updated transaction \"id:%d\" successfully", uid, transactionModifyReq.Id)
return true, nil
}
func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
var transactionDeleteReq models.TransactionDeleteRequest
err := c.ShouldBindJSON(&transactionDeleteReq)
if err != nil {
log.WarnfWithRequestId(c, "[transactions.TransactionDeleteHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
err = a.transactions.DeleteTransaction(uid, transactionDeleteReq.Id)
if err != nil {
log.ErrorfWithRequestId(c, "[transactions.TransactionDeleteHandler] failed to delete transaction \"id:%d\" for user \"uid:%d\", because %s", transactionDeleteReq.Id, uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.InfofWithRequestId(c, "[transactions.TransactionDeleteHandler] user \"uid:%d\" has deleted transaction \"id:%d\"", uid, transactionDeleteReq.Id)
return true, nil
}
func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreateReq *models.TransactionCreateRequest) *models.Transaction {
return &models.Transaction{
Uid: uid,
Type: transactionCreateReq.Type,
CategoryId: transactionCreateReq.CategoryId,
TransactionTime: transactionCreateReq.Time,
SourceAccountId: transactionCreateReq.SourceAccountId,
DestinationAccountId: transactionCreateReq.DestinationAccountId,
SourceAmount: transactionCreateReq.SourceAmount,
DestinationAmount: transactionCreateReq.DestinationAmount,
Comment: transactionCreateReq.Comment,
}
}
+2
View File
@@ -12,4 +12,6 @@ var (
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 6, http.StatusBadRequest, "sub account category not equals to parent")
ErrSubAccountTypeInvalid = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 7, http.StatusBadRequest, "sub account type invalid")
ErrCannotAddOrDeleteSubAccountsWhenModify = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 8, http.StatusBadRequest, "cannot add or delete sub accounts when modify account")
ErrSourceAccountNotFound = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 9, http.StatusBadRequest, "source account not found")
ErrDestinationAccountNotFound = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 10, http.StatusBadRequest, "destination account not found")
)
+2
View File
@@ -12,6 +12,8 @@ var (
ErrCiphertextInvalid = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 3, http.StatusInternalServerError, "ciphertext is invalid")
ErrNothingWillBeUpdated = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 4, http.StatusBadRequest, "nothing will be updated")
ErrFailedToRequestRemoteApi = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 5, http.StatusBadRequest, "failed to request third party api")
ErrPageIndexInvalid = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 6, http.StatusBadRequest, "page index is invalid")
ErrPageCountInvalid = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 7, http.StatusBadRequest, "page count is invalid")
)
func GetParameterInvalidMessage(field string) string {
+1
View File
@@ -6,4 +6,5 @@ var (
ErrSystemError = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 0, http.StatusInternalServerError, "system error")
ErrApiNotFound = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 1, http.StatusNotFound, "api not found")
ErrMethodNotAllowed = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 2, http.StatusMethodNotAllowed, "method not allowed")
ErrNotImplemented = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 3, http.StatusNotImplemented, "not implemented")
)
+15
View File
@@ -0,0 +1,15 @@
package errs
import "net/http"
var (
ErrTransactionIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 0, http.StatusBadRequest, "transaction id is invalid")
ErrTransactionNotFound = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 1, http.StatusBadRequest, "transaction not found")
ErrTransactionTypeInvalid = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 2, http.StatusBadRequest, "transaction type is invalid")
ErrTransactionSourceAndDestinationIdNotEqual = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 3, http.StatusBadRequest, "transaction source and destination account id not equal")
ErrTransactionSourceAndDestinationIdCannotBeEqual = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 4, http.StatusBadRequest, "transaction source and destination account id cannot be equal")
ErrTransactionSourceAndDestinationAmountNotEqual = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 5, http.StatusBadRequest, "transaction source and destination amount not equal")
ErrTooMuchTransactionInOneSecond = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 6, http.StatusBadRequest, "too much transaction in one second")
ErrBalanceModificationTransactionCannotSetCategory = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 7, http.StatusBadRequest, "balance modification transaction cannot set category")
ErrBalanceModificationTransactionCannotChangeAccountId = NewNormalError(NORMAL_SUBCATEGORY_TRANSACTION, 8, http.StatusBadRequest, "balance modification transaction cannot change account id")
)
+1
View File
@@ -8,4 +8,5 @@ var (
ErrTransactionCategoryTypeInvalid = NewNormalError(NORMAL_SUBCATEGORY_CATEGORY, 2, http.StatusBadRequest, "transaction category type is invalid")
ErrParentTransactionCategoryNotFound = NewNormalError(NORMAL_SUBCATEGORY_CATEGORY, 3, http.StatusBadRequest, "parent transaction category not found")
ErrCannotAddToSecondaryTransactionCategory = NewNormalError(NORMAL_SUBCATEGORY_CATEGORY, 4, http.StatusBadRequest, "cannot add to secondary transaction category")
ErrCannotUsePrimaryCategoryForTransaction = NewNormalError(NORMAL_SUBCATEGORY_CATEGORY, 5, http.StatusBadRequest, "cannot use primary category for transaction category")
)
+118
View File
@@ -0,0 +1,118 @@
package models
type TransactionType byte
const (
TRANSACTION_TYPE_MODIFY_BALANCE TransactionType = 1
TRANSACTION_TYPE_INCOME TransactionType = 2
TRANSACTION_TYPE_EXPENSE TransactionType = 3
TRANSACTION_TYPE_TRANSFER TransactionType = 4
)
type Transaction struct {
TransactionId int64 `xorm:"PK"`
Uid int64 `xorm:"UNIQUE(IDX_transaction_uid_transaction_time) INDEX(IDX_transaction_uid_deleted_transaction_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_transaction_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
Type TransactionType `xorm:"INDEX(IDX_transaction_uid_deleted_type_time) NOT NULL"`
CategoryId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
TransactionTime int64 `xorm:"UNIQUE(IDX_transaction_uid_transaction_time) INDEX(IDX_transaction_uid_deleted_transaction_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
SourceAccountId int64 `xorm:"NOT NULL"`
DestinationAccountId int64 `xorm:"NOT NULL"`
SourceAmount int64 `xorm:"NOT NULL"`
DestinationAmount int64 `xorm:"NOT NULL"`
Comment string `xorm:"VARCHAR(255) NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
}
type TransactionCreateRequest struct {
Type TransactionType `json:"type" binding:"required"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"required,min=1"`
SourceAmount int64 `json:"sourceAmount"`
DestinationAmount int64 `json:"destinationAmount"`
TagIds []int64 `json:"tagIds,string"`
Comment string `json:"comment" binding:"max=255"`
}
type TransactionModifyRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time" binding:"required,min=1"`
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"required,min=1"`
SourceAmount int64 `json:"sourceAmount"`
DestinationAmount int64 `json:"destinationAmount"`
TagIds []int64 `json:"tagIds,string"`
Comment string `json:"comment" binding:"max=255"`
}
type TransactionListByMaxTimeRequest struct {
MaxTime int64 `form:"max_time" binding:"required,min=1"`
Count int `form:"count" binding:"required,min=1,max=50"`
}
type TransactionListInMonthByPageRequest struct {
Year int `form:"year" binding:"required,min=1"`
Month int `form:"month" binding:"required,min=1"`
Page int `form:"page" binding:"required,min=1"`
Count int `form:"count" binding:"required,min=1,max=50"`
}
type TransactionGetRequest struct {
Id int64 `form:"id,string" binding:"required,min=1"`
}
type TransactionDeleteRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
}
type TransactionInfoResponse struct {
Id int64 `json:"id,string"`
Type TransactionType `json:"type"`
CategoryId int64 `json:"categoryId,string"`
Time int64 `json:"time"`
SourceAccountId int64 `json:"sourceAccountId,string"`
DestinationAccountId int64 `json:"destinationAccountId,string"`
SourceAmount int64 `json:"sourceAmount"`
DestinationAmount int64 `json:"destinationAmount"`
TagIds []int64 `json:"tagIds,string"`
Comment string `json:"comment"`
}
type TransactionInfoPageWrapperResponse struct {
Items TransactionInfoResponseSlice `json:"items"`
NextTime *int64 `json:"nextTime,string"`
}
func (c *Transaction) ToTransactionInfoResponse(tagIds []int64) *TransactionInfoResponse {
return &TransactionInfoResponse{
Id: c.TransactionId,
Type: c.Type,
CategoryId: c.CategoryId,
Time: c.TransactionTime,
SourceAccountId: c.SourceAccountId,
DestinationAccountId: c.DestinationAccountId,
SourceAmount: c.SourceAmount,
DestinationAmount: c.DestinationAmount,
TagIds: tagIds,
Comment: c.Comment,
}
}
type TransactionInfoResponseSlice []*TransactionInfoResponse
func (c TransactionInfoResponseSlice) Len() int {
return len(c)
}
func (c TransactionInfoResponseSlice) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c TransactionInfoResponseSlice) Less(i, j int) bool {
return c[i].Time < c[j].Time
}
+580
View File
@@ -0,0 +1,580 @@
package services
import (
"fmt"
"time"
"xorm.io/xorm"
"github.com/mayswind/lab/pkg/datastore"
"github.com/mayswind/lab/pkg/errs"
"github.com/mayswind/lab/pkg/models"
"github.com/mayswind/lab/pkg/utils"
"github.com/mayswind/lab/pkg/uuid"
)
type TransactionService struct {
ServiceUsingDB
ServiceUsingUuid
}
var (
Transactions = &TransactionService{
ServiceUsingDB: ServiceUsingDB{
container: datastore.Container,
},
ServiceUsingUuid: ServiceUsingUuid{
container: uuid.Container,
},
}
)
func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTime int64, count int) ([]*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if count < 1 {
return nil, errs.ErrPageCountInvalid
}
var transactions []*models.Transaction
err := s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time<=?", uid, false, maxTime).Limit(count, 0).OrderBy("transaction_time desc").Find(&transactions)
return transactions, err
}
func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, month int, page int, count int) ([]*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if page < 1 {
return nil, errs.ErrPageIndexInvalid
}
if count < 1 {
return nil, errs.ErrPageCountInvalid
}
startTime, err := utils.ParseFromLongDateTime(fmt.Sprintf("%d-%d-01 00:00:00", year, month))
if err != nil {
return nil, errs.ErrSystemError
}
endTime := startTime.AddDate(0, 1, 0)
startUnixTime := startTime.Unix()
endUnixTime := endTime.Unix()
var transactions []*models.Transaction
err = s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time<?", uid, false, startUnixTime, endUnixTime).Limit(count, count * (page - 1)).OrderBy("transaction_time desc").Find(&transactions)
return transactions, err
}
func (s *TransactionService) GetTransactionByTransactionId(uid int64, transactionId int64) (*models.Transaction, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if transactionId <= 0 {
return nil, errs.ErrTransactionIdInvalid
}
transaction := &models.Transaction{}
has, err := s.UserDataDB(uid).ID(transactionId).Where("uid=? AND deleted=?", uid, false).Get(transaction)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrTransactionNotFound
}
return transaction, nil
}
func (s *TransactionService) GetAllTransactionCount(uid int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
return s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Count(&models.Transaction{})
}
func (s *TransactionService) GetMonthTransactionCount(uid int64, year int64, month int64) (int64, error) {
if uid <= 0 {
return 0, errs.ErrUserIdInvalid
}
startTime, err := utils.ParseFromLongDateTime(fmt.Sprintf("%d-%d-01 00:00:00", year, month))
if err != nil {
return 0, errs.ErrSystemError
}
endTime := startTime.AddDate(0, 1, 0)
startUnixTime := startTime.Unix()
endUnixTime := endTime.Unix()
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 {
if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid
}
if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE ||
transaction.Type == models.TRANSACTION_TYPE_INCOME ||
transaction.Type == models.TRANSACTION_TYPE_EXPENSE {
if transaction.SourceAccountId != transaction.DestinationAccountId {
return errs.ErrTransactionSourceAndDestinationIdNotEqual
} else if transaction.SourceAmount != transaction.DestinationAmount {
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
}
} else if transaction.Type == models.TRANSACTION_TYPE_TRANSFER {
if transaction.SourceAccountId == transaction.DestinationAccountId {
return errs.ErrTransactionSourceAndDestinationIdCannotBeEqual
}
} else {
return errs.ErrTransactionTypeInvalid
}
transaction.TransactionId = s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION)
transaction.TransactionTime = (transaction.TransactionTime / 1000) * 1000
transaction.CreatedUnixTime = time.Now().Unix()
transaction.UpdatedUnixTime = time.Now().Unix()
return s.UserDataDB(transaction.Uid).DoTransaction(func(sess *xorm.Session) error {
sourceAccount := &models.Account{}
destinationAccount := &models.Account{}
has, err := sess.ID(transaction.SourceAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(sourceAccount)
if err != nil {
return err
} else if !has {
return errs.ErrSourceAccountNotFound
}
category := &models.TransactionCategory{}
if transaction.Type != models.TRANSACTION_TYPE_MODIFY_BALANCE {
has, err = sess.ID(transaction.CategoryId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(category)
if err != nil {
return err
} else if !has {
return errs.ErrTransactionCategoryNotFound
}
if category.ParentCategoryId < 1 {
return errs.ErrCannotUsePrimaryCategoryForTransaction
}
}
if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
transaction.DestinationAmount = transaction.SourceAmount - sourceAccount.Balance
} else if transaction.Type == models.TRANSACTION_TYPE_INCOME {
if category.Type != models.CATEGORY_TYPE_INCOME {
return errs.ErrTransactionCategoryTypeInvalid
}
} else if transaction.Type == models.TRANSACTION_TYPE_EXPENSE {
if category.Type != models.CATEGORY_TYPE_EXPENSE {
return errs.ErrTransactionCategoryTypeInvalid
}
} else if transaction.Type == models.TRANSACTION_TYPE_TRANSFER {
if category.Type != models.CATEGORY_TYPE_TRANSFER {
return errs.ErrTransactionCategoryTypeInvalid
}
has, err := sess.ID(transaction.DestinationAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(destinationAccount)
if err != nil {
return err
} else if !has {
return errs.ErrDestinationAccountNotFound
}
if sourceAccount.Currency == destinationAccount.Currency && transaction.SourceAmount != transaction.DestinationAmount {
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
}
}
createdRows, err := sess.Insert(transaction)
if err != nil || createdRows < 1 { // maybe another transaction has same time
sameSecondLatestTransaction := &models.Transaction{}
currentSecondUnixtime := (transaction.TransactionTime / 1000) * 1000
nextSecondUnixtime := currentSecondUnixtime + 1000
has, err = sess.Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time<?", transaction.Uid, false, currentSecondUnixtime, nextSecondUnixtime).OrderBy("transaction_time desc").Limit(1).Get(sameSecondLatestTransaction)
if err != nil {
return err
} else if !has {
return errs.ErrDatabaseOperationFailed
} else if sameSecondLatestTransaction.TransactionTime == nextSecondUnixtime - 1 {
return errs.ErrTooMuchTransactionInOneSecond
}
transaction.TransactionTime = sameSecondLatestTransaction.TransactionTime + 1
createdRows, err := sess.Insert(transaction)
if err != nil {
return err
} else if createdRows < 1 {
return errs.ErrDatabaseOperationFailed
}
}
if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if transaction.Type == models.TRANSACTION_TYPE_INCOME {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if transaction.Type == models.TRANSACTION_TYPE_EXPENSE {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", transaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if transaction.Type == models.TRANSACTION_TYPE_TRANSFER {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedSourceRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", transaction.SourceAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedSourceRows < 1 {
return errs.ErrDatabaseOperationFailed
}
destinationAccount.UpdatedUnixTime = time.Now().Unix()
updatedDestinationRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", destinationAccount.Uid, false).Update(destinationAccount)
if err != nil {
return err
} else if updatedDestinationRows < 1 {
return errs.ErrDatabaseOperationFailed
}
}
return err
})
}
func (s *TransactionService) ModifyTransaction(transaction *models.Transaction) error {
if transaction.Uid <= 0 {
return errs.ErrUserIdInvalid
}
var updateCols []string
now := time.Now().Unix()
transaction.TransactionTime = (transaction.TransactionTime / 1000) * 1000
transaction.UpdatedUnixTime = now
updateCols = append(updateCols, "updated_unix_time")
err := s.UserDB().DoTransaction(func(sess *xorm.Session) error {
oldTransaction := &models.Transaction{}
has, err := sess.ID(transaction.TransactionId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldTransaction)
if err != nil {
return err
} else if !has {
return errs.ErrTransactionNotFound
}
if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE ||
oldTransaction.Type == models.TRANSACTION_TYPE_INCOME ||
oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE {
if transaction.SourceAccountId != transaction.DestinationAccountId {
return errs.ErrTransactionSourceAndDestinationIdNotEqual
} else if transaction.SourceAmount != transaction.DestinationAmount {
return errs.ErrTransactionSourceAndDestinationAmountNotEqual
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER {
if transaction.SourceAccountId == transaction.DestinationAccountId {
return errs.ErrTransactionSourceAndDestinationIdCannotBeEqual
}
} else {
return errs.ErrTransactionTypeInvalid
}
if transaction.CategoryId != oldTransaction.CategoryId {
if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
if transaction.CategoryId > 0 {
return errs.ErrBalanceModificationTransactionCannotSetCategory
}
} else {
category := &models.TransactionCategory{}
has, err = sess.ID(transaction.CategoryId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(category)
if err != nil {
return err
} else if !has {
return errs.ErrTransactionCategoryNotFound
}
if category.ParentCategoryId < 1 {
return errs.ErrCannotUsePrimaryCategoryForTransaction
}
if (oldTransaction.Type == models.TRANSACTION_TYPE_INCOME && category.Type != models.CATEGORY_TYPE_INCOME) ||
(oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE && category.Type != models.CATEGORY_TYPE_EXPENSE) ||
(oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER && category.Type != models.CATEGORY_TYPE_TRANSFER) {
return errs.ErrTransactionCategoryTypeInvalid
}
}
updateCols = append(updateCols, "category_id")
}
if transaction.TransactionTime / 1000 != oldTransaction.TransactionTime / 1000 {
sameSecondLatestTransaction := &models.Transaction{}
currentSecondUnixtime := (transaction.TransactionTime / 1000) * 1000
nextSecondUnixtime := currentSecondUnixtime + 1000
has, err = sess.Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time<?", transaction.Uid, false, currentSecondUnixtime, nextSecondUnixtime).OrderBy("transaction_time desc").Limit(1).Get(sameSecondLatestTransaction)
if has && sameSecondLatestTransaction.TransactionTime < nextSecondUnixtime - 1 {
transaction.TransactionTime = sameSecondLatestTransaction.TransactionTime + 1
} else if has && sameSecondLatestTransaction.TransactionTime == nextSecondUnixtime - 1 {
return errs.ErrTooMuchTransactionInOneSecond
}
updateCols = append(updateCols, "transaction_time")
}
sourceAccount := &models.Account{}
destinationAccount := &models.Account{}
if transaction.SourceAccountId != oldTransaction.SourceAccountId {
has, err := sess.ID(transaction.SourceAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(sourceAccount)
if err != nil {
return err
} else if !has {
return errs.ErrSourceAccountNotFound
}
updateCols = append(updateCols, "source_account_id")
}
if transaction.DestinationAccountId != oldTransaction.DestinationAccountId {
has, err := sess.ID(transaction.DestinationAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(destinationAccount)
if err != nil {
return err
} else if !has {
return errs.ErrDestinationAccountNotFound
}
updateCols = append(updateCols, "destination_account_id")
}
if transaction.SourceAmount != oldTransaction.SourceAmount {
updateCols = append(updateCols, "source_amount")
}
if transaction.DestinationAmount != oldTransaction.DestinationAmount {
updateCols = append(updateCols, "destination_amount")
}
if transaction.Comment != oldTransaction.Comment {
updateCols = append(updateCols, "comment")
}
updatedRows, err := sess.ID(transaction.TransactionId).Cols(updateCols...).Where("uid=? AND deleted=?", transaction.Uid, false).Update(transaction)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrTransactionNotFound
}
if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
if transaction.SourceAccountId != oldTransaction.SourceAccountId {
return errs.ErrBalanceModificationTransactionCannotChangeAccountId
}
if transaction.SourceAmount != oldTransaction.SourceAmount {
transaction.DestinationAmount = transaction.SourceAmount - sourceAccount.Balance
// TODO: implement
return errs.ErrNotImplemented
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_INCOME {
if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.DestinationAmount == oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId == oldTransaction.SourceAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE {
if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.DestinationAmount == oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId == oldTransaction.SourceAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER {
if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.SourceAmount != oldTransaction.SourceAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId != oldTransaction.SourceAccountId && transaction.SourceAmount == oldTransaction.SourceAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.SourceAccountId == oldTransaction.SourceAccountId && transaction.SourceAmount != oldTransaction.SourceAmount {
// TODO: implement
return errs.ErrNotImplemented
}
if transaction.DestinationAccountId != oldTransaction.DestinationAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.DestinationAccountId != oldTransaction.DestinationAccountId && transaction.DestinationAmount == oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
} else if transaction.DestinationAccountId == oldTransaction.DestinationAccountId && transaction.DestinationAmount != oldTransaction.DestinationAmount {
// TODO: implement
return errs.ErrNotImplemented
}
}
return nil
})
if err != nil {
return err
}
return nil
}
func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) error {
if uid <= 0 {
return errs.ErrUserIdInvalid
}
now := time.Now().Unix()
updateModel := &models.Transaction{
Deleted: true,
DeletedUnixTime: now,
}
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
oldTransaction := &models.Transaction{}
has, err := sess.ID(transactionId).Where("uid=? AND deleted=?", uid, false).Get(oldTransaction)
if err != nil {
return err
} else if !has {
return errs.ErrTransactionNotFound
}
sourceAccount := &models.Account{}
has, err = sess.ID(oldTransaction.SourceAccountId).Where("uid=? AND deleted=?", oldTransaction.Uid, false).Get(sourceAccount)
if err != nil {
return err
} else if !has {
return errs.ErrSourceAccountNotFound
}
deletedRows, err := sess.ID(transactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
if err != nil {
return err
} else if deletedRows < 1 {
return errs.ErrTransactionNotFound
}
if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_INCOME {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE {
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", oldTransaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrDatabaseOperationFailed
}
} else if oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER {
destinationAccount := &models.Account{}
has, err := sess.ID(oldTransaction.DestinationAccountId).Where("uid=? AND deleted=?", oldTransaction.Uid, false).Get(destinationAccount)
if err != nil {
return err
} else if !has {
return errs.ErrDestinationAccountNotFound
}
sourceAccount.UpdatedUnixTime = time.Now().Unix()
updatedSourceRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", oldTransaction.SourceAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount)
if err != nil {
return err
} else if updatedSourceRows < 1 {
return errs.ErrDatabaseOperationFailed
}
destinationAccount.UpdatedUnixTime = time.Now().Unix()
updatedDestinationRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", destinationAccount.Uid, false).Update(destinationAccount)
if err != nil {
return err
} else if updatedDestinationRows < 1 {
return errs.ErrDatabaseOperationFailed
}
}
return err
})
}
+7 -1
View File
@@ -2,6 +2,12 @@ package utils
import "time"
const LongDateTimeFormat = "2006-01-02 15:04:05"
func FormatToLongDateTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
return t.Format(LongDateTimeFormat)
}
func ParseFromLongDateTime(t string) (time.Time, error) {
return time.Parse(LongDateTimeFormat, t)
}
+24
View File
@@ -261,6 +261,7 @@ export default {
'error': {
'system error': 'System Error',
'api not found': 'Failed to request api',
'not implemented': 'Not implemented',
'database operation failed': 'Database operation failed',
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
'operation failed': 'Operation failed',
@@ -302,11 +303,23 @@ export default {
'sub account category not equals to parent': 'Sub account category does not equal to parent',
'sub account type invalid': 'Sub account type is invalid',
'cannot add or delete sub accounts when modify account': 'You cannot add or delete sub accounts when modify account',
'source account not found': 'Source account is not found',
'destination account not found': 'Destination account is not found',
'transaction id is invalid': 'Transaction ID is invalid',
'transaction not found': 'Transaction is not found',
'transaction type is invalid': 'Transaction type is invalid',
'transaction source and destination account id not equal': 'Source account ID and destination account ID do not equal',
'transaction source and destination account id cannot be equal': 'Source account ID and destination account ID cannot be equal',
'transaction source and destination amount not equal': 'Source amount and destination source do not equal',
'too much transaction in one second': 'There are too much transaction in one second, please choose another time',
'balance modification transaction cannot set category': 'You cannot set category for balance modification transaction',
'balance modification transaction cannot change account id': 'You cannot change account ID for balance modification transaction',
'transaction category id is invalid': 'Transaction category ID is invalid',
'transaction category not found': 'Transaction category is not found',
'transaction category type is invalid': 'Transaction category type is invalid',
'parent transaction category not found': 'Parent transaction category is not found',
'cannot add to secondary transaction category': 'Cannot add transaction category to secondary transaction category',
'cannot use primary category for transaction category': 'Cannot use primary category for transaction category',
'transaction tag id is invalid': 'Transaction tag ID is invalid',
'transaction tag not found': 'Transaction tag is not found',
'transaction tag name is empty': 'Transaction tag title is empty',
@@ -327,6 +340,17 @@ export default {
'color': 'Color',
'currency': 'Currency',
'parentId': 'Parent Node ID',
'categoryId': 'Category ID',
'time': 'Time',
'sourceAccountId': 'Source Account ID',
'destinationAccountId': 'Destination Account ID',
'sourceAmount': 'Source Amount',
'destinationAmount': 'Destination Amount',
'maxTime': 'Latest Time',
'year': 'Year',
'month': 'Month',
'page': 'Page Index',
'count': 'Count',
'comment': 'Comment',
},
'parameterizedError': {
+24
View File
@@ -261,6 +261,7 @@ export default {
'error': {
'system error': '系统错误',
'api not found': '接口调用失败',
'not implemented': '未实现',
'database operation failed': '数据库操作失败',
'incomplete or incorrect submission': '提交不完整或不正确',
'operation failed': '操作失败',
@@ -302,11 +303,23 @@ export default {
'sub account category not equals to parent': '子账户类别与父账户不同',
'sub account type invalid': '子账户类型无效',
'cannot add or delete sub accounts when modify account': '您不能在修改账户时添加或删除子账户',
'source account not found': '来源账户不存在',
'destination account not found': '目标账户不存在',
'transaction id is invalid': '交易ID无效',
'transaction not found': '交易不存',
'transaction type is invalid': '交易类型无效',
'transaction source and destination account id not equal': '来源账户和目标账户不一致',
'transaction source and destination account id cannot be equal': '来源账户和目标账户不能相同',
'transaction source and destination amount not equal': '源金额和目标金额不一致',
'too much transaction in one second': '一秒钟内交易太多,请选择其他时间',
'balance modification transaction cannot set category': '您无法对修改余额的交易设置分类',
'balance modification transaction cannot change account id': '您无法对修改余额的交易修改账户ID',
'transaction category id is invalid': '交易分类ID无效',
'transaction category not found': '交易分类不存在',
'transaction category type is invalid': '交易分类类型无效',
'parent transaction category not found': '父级交易分类不存在',
'cannot add to secondary transaction category': '不能在二级交易分类中添加',
'cannot use primary category for transaction category': '交易分类不能使用一级分类',
'transaction tag id is invalid': '交易标签ID无效',
'transaction tag not found': '交易标签不存在',
'transaction tag name is empty': '交易标签标题不能为空',
@@ -327,6 +340,17 @@ export default {
'color': '颜色',
'currency': '货币',
'parentId': '父节点ID',
'categoryId': '分类ID',
'time': '时间',
'sourceAccountId': '来演账户ID',
'destinationAccountId': '目标账户ID',
'sourceAmount': '源金额',
'destinationAmount': '目标金额',
'maxTime': '最近时间',
'year': '年份',
'month': '月份',
'page': '页码索引',
'count': '数量',
'comment': '备注',
},
'parameterizedError': {