add transaction basic api
This commit is contained in:
@@ -83,6 +83,14 @@ func updateAllDatabaseTablesStructure() error {
|
|||||||
log.BootInfof("[database.updateAllDatabaseTablesStructure] account table maintained successfully")
|
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))
|
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionCategory))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+9
-1
@@ -185,6 +185,14 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/accounts/move.json", bindApi(api.Accounts.AccountMoveHandler))
|
apiV1Route.POST("/accounts/move.json", bindApi(api.Accounts.AccountMoveHandler))
|
||||||
apiV1Route.POST("/accounts/delete.json", bindApi(api.Accounts.AccountDeleteHandler))
|
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
|
// Transaction Categories
|
||||||
apiV1Route.GET("/transaction/categories/list.json", bindApi(api.TransactionCategories.CategoryListHandler))
|
apiV1Route.GET("/transaction/categories/list.json", bindApi(api.TransactionCategories.CategoryListHandler))
|
||||||
apiV1Route.GET("/transaction/categories/get.json", bindApi(api.TransactionCategories.CategoryGetHandler))
|
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 {
|
} else if config.Protocol == settings.SCHEME_HTTPS {
|
||||||
log.BootInfof("[server.startWebServer] will run at https://%s", listenAddr)
|
log.BootInfof("[server.startWebServer] will run at https://%s", listenAddr)
|
||||||
err = router.RunTLS(listenAddr, config.CertFile, config.CertKeyFile)
|
err = router.RunTLS(listenAddr, config.CertFile, config.CertKeyFile)
|
||||||
} else {
|
} else {
|
||||||
err = errs.ErrInvalidProtocol
|
err = errs.ErrInvalidProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,6 @@ var (
|
|||||||
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 6, http.StatusBadRequest, "sub account category not equals to parent")
|
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")
|
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")
|
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")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ var (
|
|||||||
ErrCiphertextInvalid = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 3, http.StatusInternalServerError, "ciphertext is invalid")
|
ErrCiphertextInvalid = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 3, http.StatusInternalServerError, "ciphertext is invalid")
|
||||||
ErrNothingWillBeUpdated = NewNormalError(NORMAL_SUBCATEGORY_GLOBAL, 4, http.StatusBadRequest, "nothing will be updated")
|
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")
|
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 {
|
func GetParameterInvalidMessage(field string) string {
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ var (
|
|||||||
ErrSystemError = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 0, http.StatusInternalServerError, "system error")
|
ErrSystemError = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 0, http.StatusInternalServerError, "system error")
|
||||||
ErrApiNotFound = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 1, http.StatusNotFound, "api not found")
|
ErrApiNotFound = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 1, http.StatusNotFound, "api not found")
|
||||||
ErrMethodNotAllowed = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 2, http.StatusMethodNotAllowed, "method not allowed")
|
ErrMethodNotAllowed = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 2, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
|
ErrNotImplemented = NewSystemError(SYSTEM_SUBCATEGORY_DEFAULT, 3, http.StatusNotImplemented, "not implemented")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
)
|
||||||
@@ -8,4 +8,5 @@ var (
|
|||||||
ErrTransactionCategoryTypeInvalid = NewNormalError(NORMAL_SUBCATEGORY_CATEGORY, 2, http.StatusBadRequest, "transaction category type is invalid")
|
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")
|
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")
|
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")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,6 +2,12 @@ package utils
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
const LongDateTimeFormat = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
func FormatToLongDateTime(t time.Time) string {
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export default {
|
|||||||
'error': {
|
'error': {
|
||||||
'system error': 'System Error',
|
'system error': 'System Error',
|
||||||
'api not found': 'Failed to request api',
|
'api not found': 'Failed to request api',
|
||||||
|
'not implemented': 'Not implemented',
|
||||||
'database operation failed': 'Database operation failed',
|
'database operation failed': 'Database operation failed',
|
||||||
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
|
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
|
||||||
'operation failed': 'Operation failed',
|
'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 category not equals to parent': 'Sub account category does not equal to parent',
|
||||||
'sub account type invalid': 'Sub account type is invalid',
|
'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',
|
'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 id is invalid': 'Transaction category ID is invalid',
|
||||||
'transaction category not found': 'Transaction category is not found',
|
'transaction category not found': 'Transaction category is not found',
|
||||||
'transaction category type is invalid': 'Transaction category type is invalid',
|
'transaction category type is invalid': 'Transaction category type is invalid',
|
||||||
'parent transaction category not found': 'Parent transaction category is not found',
|
'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 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 id is invalid': 'Transaction tag ID is invalid',
|
||||||
'transaction tag not found': 'Transaction tag is not found',
|
'transaction tag not found': 'Transaction tag is not found',
|
||||||
'transaction tag name is empty': 'Transaction tag title is empty',
|
'transaction tag name is empty': 'Transaction tag title is empty',
|
||||||
@@ -327,6 +340,17 @@ export default {
|
|||||||
'color': 'Color',
|
'color': 'Color',
|
||||||
'currency': 'Currency',
|
'currency': 'Currency',
|
||||||
'parentId': 'Parent Node ID',
|
'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',
|
'comment': 'Comment',
|
||||||
},
|
},
|
||||||
'parameterizedError': {
|
'parameterizedError': {
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export default {
|
|||||||
'error': {
|
'error': {
|
||||||
'system error': '系统错误',
|
'system error': '系统错误',
|
||||||
'api not found': '接口调用失败',
|
'api not found': '接口调用失败',
|
||||||
|
'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 not equals to parent': '子账户类别与父账户不同',
|
||||||
'sub account type invalid': '子账户类型无效',
|
'sub account type invalid': '子账户类型无效',
|
||||||
'cannot add or delete sub accounts when modify account': '您不能在修改账户时添加或删除子账户',
|
'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 id is invalid': '交易分类ID无效',
|
||||||
'transaction category not found': '交易分类不存在',
|
'transaction category not found': '交易分类不存在',
|
||||||
'transaction category type is invalid': '交易分类类型无效',
|
'transaction category type is invalid': '交易分类类型无效',
|
||||||
'parent transaction category not found': '父级交易分类不存在',
|
'parent transaction category not found': '父级交易分类不存在',
|
||||||
'cannot add to secondary transaction category': '不能在二级交易分类中添加',
|
'cannot add to secondary transaction category': '不能在二级交易分类中添加',
|
||||||
|
'cannot use primary category for transaction category': '交易分类不能使用一级分类',
|
||||||
'transaction tag id is invalid': '交易标签ID无效',
|
'transaction tag id is invalid': '交易标签ID无效',
|
||||||
'transaction tag not found': '交易标签不存在',
|
'transaction tag not found': '交易标签不存在',
|
||||||
'transaction tag name is empty': '交易标签标题不能为空',
|
'transaction tag name is empty': '交易标签标题不能为空',
|
||||||
@@ -327,6 +340,17 @@ export default {
|
|||||||
'color': '颜色',
|
'color': '颜色',
|
||||||
'currency': '货币',
|
'currency': '货币',
|
||||||
'parentId': '父节点ID',
|
'parentId': '父节点ID',
|
||||||
|
'categoryId': '分类ID',
|
||||||
|
'time': '时间',
|
||||||
|
'sourceAccountId': '来演账户ID',
|
||||||
|
'destinationAccountId': '目标账户ID',
|
||||||
|
'sourceAmount': '源金额',
|
||||||
|
'destinationAmount': '目标金额',
|
||||||
|
'maxTime': '最近时间',
|
||||||
|
'year': '年份',
|
||||||
|
'month': '月份',
|
||||||
|
'page': '页码索引',
|
||||||
|
'count': '数量',
|
||||||
'comment': '备注',
|
'comment': '备注',
|
||||||
},
|
},
|
||||||
'parameterizedError': {
|
'parameterizedError': {
|
||||||
|
|||||||
Reference in New Issue
Block a user