diff --git a/cmd/webserver.go b/cmd/webserver.go index 9ae38f77..df293433 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -353,6 +353,7 @@ func startWebServer(c *core.CliContext) error { 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/move/all.json", bindApi(api.Transactions.TransactionMoveAllBetweenAccountsHandler)) apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler)) if config.EnableDataImport { diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index 7240641c..4f0c6147 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -1114,6 +1114,63 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er return newTransactionResp, nil } +// TransactionMoveAllBetweenAccountsHandler moves all transactions from one account to another account for current user +func (a *TransactionsApi) TransactionMoveAllBetweenAccountsHandler(c *core.WebContext) (any, *errs.Error) { + var transactionMoveReq models.TransactionMoveBetweenAccountsRequest + err := c.ShouldBindJSON(&transactionMoveReq) + + if err != nil { + log.Warnf(c, "[transactions.TransactionMoveAllBetweenAccountsHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + if transactionMoveReq.FromAccountId == transactionMoveReq.ToAccountId { + return nil, errs.ErrCannotMoveTransactionToSameAccount + } + + uid := c.GetCurrentUid() + accountMap, err := a.accounts.GetAccountsByAccountIds(c, uid, []int64{transactionMoveReq.FromAccountId, transactionMoveReq.ToAccountId}) + + if err != nil { + log.Errorf(c, "[transactions.TransactionMoveAllBetweenAccountsHandler] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + fromAccount, exists := accountMap[transactionMoveReq.FromAccountId] + + if !exists { + return nil, errs.ErrSourceAccountNotFound + } + + toAccount, exists := accountMap[transactionMoveReq.ToAccountId] + + if !exists { + return nil, errs.ErrDestinationAccountNotFound + } + + if fromAccount.Hidden || toAccount.Hidden { + return nil, errs.ErrCannotMoveTransactionFromOrToHiddenAccount + } + + if fromAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || toAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS { + return nil, errs.ErrCannotMoveTransactionFromOrToParentAccount + } + + if fromAccount.Currency != toAccount.Currency { + return nil, errs.ErrCannotMoveTransactionBetweenAccountsWithDifferentCurrencies + } + + err = a.transactions.MoveAllTransactionsBetweenAccounts(c, uid, transactionMoveReq.FromAccountId, transactionMoveReq.ToAccountId) + + if err != nil { + log.Errorf(c, "[transactions.TransactionMoveAllBetweenAccountsHandler] failed to move all transactions from account \"id:%d\" to account \"id:%d\" for user \"uid:%d\", because %s", transactionMoveReq.FromAccountId, transactionMoveReq.ToAccountId, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.Infof(c, "[transactions.TransactionMoveAllBetweenAccountsHandler] user \"uid:%d\" has moved all transactions from account \"id:%d\" to account \"id:%d\" successfully", uid, transactionMoveReq.FromAccountId, transactionMoveReq.ToAccountId) + return true, nil +} + // TransactionDeleteHandler deletes an existed transaction by request parameters for current user func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *errs.Error) { var transactionDeleteReq models.TransactionDeleteRequest diff --git a/pkg/errs/transaction.go b/pkg/errs/transaction.go index e312beaf..725a6b79 100644 --- a/pkg/errs/transaction.go +++ b/pkg/errs/transaction.go @@ -4,41 +4,45 @@ import "net/http" // Error codes related to transaction var ( - ErrTransactionIdInvalid = NewNormalError(NormalSubcategoryTransaction, 0, http.StatusBadRequest, "transaction id is invalid") - ErrTransactionNotFound = NewNormalError(NormalSubcategoryTransaction, 1, http.StatusBadRequest, "transaction not found") - ErrTransactionTypeInvalid = NewNormalError(NormalSubcategoryTransaction, 2, http.StatusBadRequest, "transaction type is invalid") - ErrTransactionSourceAndDestinationIdCannotBeEqual = NewNormalError(NormalSubcategoryTransaction, 3, http.StatusBadRequest, "transaction source and destination account id cannot be equal") - ErrTransactionSourceAndDestinationAmountNotEqual = NewNormalError(NormalSubcategoryTransaction, 4, http.StatusBadRequest, "transaction source and destination amount not equal") - ErrTransactionDestinationAccountCannotBeSet = NewNormalError(NormalSubcategoryTransaction, 5, http.StatusBadRequest, "transaction destination account cannot be set") - ErrTransactionDestinationAmountCannotBeSet = NewNormalError(NormalSubcategoryTransaction, 6, http.StatusBadRequest, "transaction destination amount cannot be set") - ErrTooMuchTransactionInOneSecond = NewNormalError(NormalSubcategoryTransaction, 7, http.StatusBadRequest, "too much transaction in one second") - ErrBalanceModificationTransactionCannotSetCategory = NewNormalError(NormalSubcategoryTransaction, 8, http.StatusBadRequest, "balance modification transaction cannot set category") - ErrBalanceModificationTransactionCannotChangeAccountId = NewNormalError(NormalSubcategoryTransaction, 9, http.StatusBadRequest, "balance modification transaction cannot change account id") - ErrBalanceModificationTransactionCannotAddWhenNotEmpty = NewNormalError(NormalSubcategoryTransaction, 10, http.StatusBadRequest, "balance modification transaction cannot add when other transaction exists") - ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot add transaction to hidden account") - ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot modify transaction of hidden account") - ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 13, http.StatusBadRequest, "cannot delete transaction in hidden account") - ErrCannotAddTransactionToParentAccount = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction to parent account") - ErrCannotModifyTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot modify transaction of parent account") - ErrCannotDeleteTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 16, http.StatusBadRequest, "cannot delete transaction in parent account") - ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 17, http.StatusBadRequest, "cannot add transaction with this transaction time") - ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 18, http.StatusBadRequest, "cannot modify transaction with this transaction time") - ErrCannotDeleteTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 19, http.StatusBadRequest, "cannot delete transaction with this transaction time") - ErrCannotUseHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 20, http.StatusBadRequest, "cannot use hidden account") - ErrCannotUseHiddenTransactionCategory = NewNormalError(NormalSubcategoryTransaction, 21, http.StatusBadRequest, "cannot use hidden transaction category") - ErrCannotUseHiddenTransactionTag = NewNormalError(NormalSubcategoryTransaction, 22, http.StatusBadRequest, "cannot use hidden transaction tag") - ErrTransactionHasTooManyTags = NewNormalError(NormalSubcategoryTransaction, 23, http.StatusBadRequest, "transaction has too many tags") - ErrTransactionHasTooManyPictures = NewNormalError(NormalSubcategoryTransaction, 24, http.StatusBadRequest, "transaction has too many pictures") - ErrImportFileTypeIsEmpty = NewNormalError(NormalSubcategoryTransaction, 25, http.StatusBadRequest, "import file type is empty") - ErrImportFileTypeNotSupported = NewNormalError(NormalSubcategoryTransaction, 26, http.StatusBadRequest, "import file type not supported") - ErrNoDataToImport = NewNormalError(NormalSubcategoryTransaction, 27, http.StatusBadRequest, "no data to import") - ErrCannotAddTransactionBeforeBalanceModificationTransaction = NewNormalError(NormalSubcategoryTransaction, 28, http.StatusBadRequest, "cannot add transaction before balance modification transaction") - ErrBalanceModificationTransactionCannotModifyTime = NewNormalError(NormalSubcategoryTransaction, 29, http.StatusBadRequest, "balance modification transaction cannot modify transaction time") - ErrTransferTransactionAmountCannotBeLessThanZero = NewNormalError(NormalSubcategoryTransaction, 30, http.StatusBadRequest, "transfer transaction amount cannot be less than zero") - ErrImportFileEncodingIsEmpty = NewNormalError(NormalSubcategoryTransaction, 31, http.StatusBadRequest, "import file encoding is empty") - ErrImportFileEncodingNotSupported = NewNormalError(NormalSubcategoryTransaction, 32, http.StatusBadRequest, "import file encoding not supported") - ErrImportFileColumnMappingInvalid = NewNormalError(NormalSubcategoryTransaction, 33, http.StatusBadRequest, "column mapping invalid") - ErrImportFileTransactionTypeMappingInvalid = NewNormalError(NormalSubcategoryTransaction, 34, http.StatusBadRequest, "transaction type mapping invalid") - ErrImportFileTransactionTimeFormatInvalid = NewNormalError(NormalSubcategoryTransaction, 35, http.StatusBadRequest, "transaction time format invalid") - ErrImportFileTransactionTimezoneFormatInvalid = NewNormalError(NormalSubcategoryTransaction, 36, http.StatusBadRequest, "transaction time zone format invalid") + ErrTransactionIdInvalid = NewNormalError(NormalSubcategoryTransaction, 0, http.StatusBadRequest, "transaction id is invalid") + ErrTransactionNotFound = NewNormalError(NormalSubcategoryTransaction, 1, http.StatusBadRequest, "transaction not found") + ErrTransactionTypeInvalid = NewNormalError(NormalSubcategoryTransaction, 2, http.StatusBadRequest, "transaction type is invalid") + ErrTransactionSourceAndDestinationIdCannotBeEqual = NewNormalError(NormalSubcategoryTransaction, 3, http.StatusBadRequest, "transaction source and destination account id cannot be equal") + ErrTransactionSourceAndDestinationAmountNotEqual = NewNormalError(NormalSubcategoryTransaction, 4, http.StatusBadRequest, "transaction source and destination amount not equal") + ErrTransactionDestinationAccountCannotBeSet = NewNormalError(NormalSubcategoryTransaction, 5, http.StatusBadRequest, "transaction destination account cannot be set") + ErrTransactionDestinationAmountCannotBeSet = NewNormalError(NormalSubcategoryTransaction, 6, http.StatusBadRequest, "transaction destination amount cannot be set") + ErrTooMuchTransactionInOneSecond = NewNormalError(NormalSubcategoryTransaction, 7, http.StatusBadRequest, "too much transaction in one second") + ErrBalanceModificationTransactionCannotSetCategory = NewNormalError(NormalSubcategoryTransaction, 8, http.StatusBadRequest, "balance modification transaction cannot set category") + ErrBalanceModificationTransactionCannotChangeAccountId = NewNormalError(NormalSubcategoryTransaction, 9, http.StatusBadRequest, "balance modification transaction cannot change account id") + ErrBalanceModificationTransactionCannotAddWhenNotEmpty = NewNormalError(NormalSubcategoryTransaction, 10, http.StatusBadRequest, "balance modification transaction cannot add when other transaction exists") + ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot add transaction to hidden account") + ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot modify transaction of hidden account") + ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 13, http.StatusBadRequest, "cannot delete transaction in hidden account") + ErrCannotAddTransactionToParentAccount = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction to parent account") + ErrCannotModifyTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot modify transaction of parent account") + ErrCannotDeleteTransactionInParentAccount = NewNormalError(NormalSubcategoryTransaction, 16, http.StatusBadRequest, "cannot delete transaction in parent account") + ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 17, http.StatusBadRequest, "cannot add transaction with this transaction time") + ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 18, http.StatusBadRequest, "cannot modify transaction with this transaction time") + ErrCannotDeleteTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 19, http.StatusBadRequest, "cannot delete transaction with this transaction time") + ErrCannotUseHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 20, http.StatusBadRequest, "cannot use hidden account") + ErrCannotUseHiddenTransactionCategory = NewNormalError(NormalSubcategoryTransaction, 21, http.StatusBadRequest, "cannot use hidden transaction category") + ErrCannotUseHiddenTransactionTag = NewNormalError(NormalSubcategoryTransaction, 22, http.StatusBadRequest, "cannot use hidden transaction tag") + ErrTransactionHasTooManyTags = NewNormalError(NormalSubcategoryTransaction, 23, http.StatusBadRequest, "transaction has too many tags") + ErrTransactionHasTooManyPictures = NewNormalError(NormalSubcategoryTransaction, 24, http.StatusBadRequest, "transaction has too many pictures") + ErrImportFileTypeIsEmpty = NewNormalError(NormalSubcategoryTransaction, 25, http.StatusBadRequest, "import file type is empty") + ErrImportFileTypeNotSupported = NewNormalError(NormalSubcategoryTransaction, 26, http.StatusBadRequest, "import file type not supported") + ErrNoDataToImport = NewNormalError(NormalSubcategoryTransaction, 27, http.StatusBadRequest, "no data to import") + ErrCannotAddTransactionBeforeBalanceModificationTransaction = NewNormalError(NormalSubcategoryTransaction, 28, http.StatusBadRequest, "cannot add transaction before balance modification transaction") + ErrBalanceModificationTransactionCannotModifyTime = NewNormalError(NormalSubcategoryTransaction, 29, http.StatusBadRequest, "balance modification transaction cannot modify transaction time") + ErrTransferTransactionAmountCannotBeLessThanZero = NewNormalError(NormalSubcategoryTransaction, 30, http.StatusBadRequest, "transfer transaction amount cannot be less than zero") + ErrImportFileEncodingIsEmpty = NewNormalError(NormalSubcategoryTransaction, 31, http.StatusBadRequest, "import file encoding is empty") + ErrImportFileEncodingNotSupported = NewNormalError(NormalSubcategoryTransaction, 32, http.StatusBadRequest, "import file encoding not supported") + ErrImportFileColumnMappingInvalid = NewNormalError(NormalSubcategoryTransaction, 33, http.StatusBadRequest, "column mapping invalid") + ErrImportFileTransactionTypeMappingInvalid = NewNormalError(NormalSubcategoryTransaction, 34, http.StatusBadRequest, "transaction type mapping invalid") + ErrImportFileTransactionTimeFormatInvalid = NewNormalError(NormalSubcategoryTransaction, 35, http.StatusBadRequest, "transaction time format invalid") + ErrImportFileTransactionTimezoneFormatInvalid = NewNormalError(NormalSubcategoryTransaction, 36, http.StatusBadRequest, "transaction time zone format invalid") + ErrCannotMoveTransactionToSameAccount = NewNormalError(NormalSubcategoryTransaction, 37, http.StatusBadRequest, "cannot move transaction to same account") + ErrCannotMoveTransactionFromOrToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 38, http.StatusBadRequest, "cannot move transaction from or to hidden account") + ErrCannotMoveTransactionFromOrToParentAccount = NewNormalError(NormalSubcategoryTransaction, 39, http.StatusBadRequest, "cannot move transaction from or to parent account") + ErrCannotMoveTransactionBetweenAccountsWithDifferentCurrencies = NewNormalError(NormalSubcategoryTransaction, 40, http.StatusBadRequest, "cannot move transaction between accounts with different currencies") ) diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index 64bce9bf..ce678e56 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -279,6 +279,12 @@ type TransactionGetRequest struct { TrimTag bool `form:"trim_tag"` } +// TransactionMoveBetweenAccountsRequest represents all parameters of moving all transactions between accounts request +type TransactionMoveBetweenAccountsRequest struct { + FromAccountId int64 `json:"fromAccountId,string" binding:"required,min=1"` + ToAccountId int64 `json:"toAccountId,string" binding:"required,min=1"` +} + // TransactionDeleteRequest represents all parameters of transaction deleting request type TransactionDeleteRequest struct { Id int64 `json:"id,string" binding:"required,min=1"` diff --git a/pkg/services/transactions.go b/pkg/services/transactions.go index 1ed7f36d..327a1b50 100644 --- a/pkg/services/transactions.go +++ b/pkg/services/transactions.go @@ -1168,6 +1168,219 @@ func (s *TransactionService) ModifyTransaction(c core.Context, transaction *mode return nil } +func (s *TransactionService) MoveAllTransactionsBetweenAccounts(c core.Context, uid int64, fromAccountId int64, toAccountId int64) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + if fromAccountId <= 0 || toAccountId <= 0 { + return errs.ErrAccountIdInvalid + } + + if fromAccountId == toAccountId { + return errs.ErrCannotMoveTransactionToSameAccount + } + + return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error { + // get and verify from and to account + fromAccount := &models.Account{} + has, err := sess.ID(fromAccountId).Where("uid=? AND deleted=?", uid, false).Get(fromAccount) + + if err != nil { + return err + } else if !has { + return errs.ErrAccountNotFound + } + + toAccount := &models.Account{} + has, err = sess.ID(toAccountId).Where("uid=? AND deleted=?", uid, false).Get(toAccount) + + if err != nil { + return err + } else if !has { + return errs.ErrAccountNotFound + } + + if fromAccount.Hidden || toAccount.Hidden { + return errs.ErrCannotMoveTransactionFromOrToHiddenAccount + } + + if fromAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS || toAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS { + return errs.ErrCannotMoveTransactionFromOrToParentAccount + } + + if fromAccount.Currency != toAccount.Currency { + return errs.ErrCannotMoveTransactionBetweenAccountsWithDifferentCurrencies + } + + // combine balance modification transaction + var balanceModificationTransactions []*models.Transaction + err = sess.Where("uid=? AND deleted=? AND type=? AND (account_id=? OR account_id=?)", uid, false, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, fromAccountId, toAccountId).Find(&balanceModificationTransactions) + + if err != nil { + return err + } + + if len(balanceModificationTransactions) > 2 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has more than 2 balance modification transactions in account \"id:%d\" and account \"id:%d\", cannot combine balance modification transaction", uid, fromAccountId, toAccountId) + return errs.ErrOperationFailed + } else if len(balanceModificationTransactions) == 2 && balanceModificationTransactions[0].AccountId != balanceModificationTransactions[1].AccountId { + // if two balance modification transactions exist, merge the amounts into the earlier one and delete the later transaction + var earlierTransaction *models.Transaction + var laterTransaction *models.Transaction + + if balanceModificationTransactions[0].TransactionTime < balanceModificationTransactions[1].TransactionTime { + earlierTransaction = balanceModificationTransactions[0] + laterTransaction = balanceModificationTransactions[1] + } else { + earlierTransaction = balanceModificationTransactions[1] + laterTransaction = balanceModificationTransactions[0] + } + + earlierTransaction.Amount += laterTransaction.Amount + earlierTransaction.RelatedAccountAmount += laterTransaction.RelatedAccountAmount + earlierTransaction.UpdatedUnixTime = time.Now().Unix() + + updatedRows, err := sess.ID(earlierTransaction.TransactionId).Cols("amount", "related_account_amount", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(earlierTransaction) + + if err != nil { + return err + } else if updatedRows < 1 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] failed to update earlier balance modification transaction") + return errs.ErrDatabaseOperationFailed + } + + laterTransaction.Deleted = true + laterTransaction.DeletedUnixTime = time.Now().Unix() + + deletedRows, err := sess.ID(laterTransaction.TransactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(laterTransaction) + + if err != nil { + return err + } else if deletedRows < 1 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] failed to delete later balance modification transaction") + return errs.ErrDatabaseOperationFailed + } + + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has combined two balance modification transactions \"id:%d\" and \"id:%d\", retained transaction is \"id:%d\"", uid, earlierTransaction.TransactionId, laterTransaction.TransactionId, earlierTransaction.TransactionId) + } else if len(balanceModificationTransactions) == 1 { + // when merging a new balance modification transaction, if its date is later than the account's earliest transaction, update the balance modification transaction time accordingly + anotherAccountId := int64(0) + + if balanceModificationTransactions[0].AccountId == fromAccountId { + anotherAccountId = toAccountId + } else if balanceModificationTransactions[0].AccountId == toAccountId { + anotherAccountId = fromAccountId + } else { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has a balance modification transaction \"id:%d\" which account id is neither \"%d\" nor \"%d\"", uid, balanceModificationTransactions[0].TransactionId, fromAccountId, toAccountId) + return errs.ErrOperationFailed + } + + earliestTransaction := &models.Transaction{} + has, err := sess.Where("uid=? AND deleted=? AND account_id=?", uid, false, anotherAccountId).OrderBy("transaction_time asc").Limit(1).Get(earliestTransaction) + + if err != nil { + return err + } else if has && balanceModificationTransactions[0].TransactionTime > earliestTransaction.TransactionTime { + balanceModificationTransaction := balanceModificationTransactions[0] + balanceModificationTransaction.TransactionTime = utils.GetMinTransactionTimeFromUnixTime(utils.GetUnixTimeFromTransactionTime(earliestTransaction.TransactionTime) - 1) + balanceModificationTransaction.UpdatedUnixTime = time.Now().Unix() + + if balanceModificationTransaction.TransactionTime < 0 { + balanceModificationTransaction.TransactionTime = 0 + } + + updatedRows, err := sess.ID(balanceModificationTransaction.TransactionId).Cols("transaction_time", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(balanceModificationTransaction) + + if err != nil { + return err + } else if updatedRows < 1 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] failed to update balance modification transaction time") + return errs.ErrDatabaseOperationFailed + } + + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has updated balance modification transaction \"id:%d\" time to %d, because earliest transaction time in account \"id:%d\" is %d", uid, balanceModificationTransaction.TransactionId, balanceModificationTransaction.TransactionTime, toAccountId, earliestTransaction.TransactionTime) + } + } + + // update all transactions of from account + updateModel := &models.Transaction{ + AccountId: toAccountId, + UpdatedUnixTime: time.Now().Unix(), + } + + updatedRows, err := sess.Cols("account_id", "updated_unix_time").Where("uid=? AND deleted=? AND account_id=?", uid, false, fromAccountId).Update(updateModel) + + if err != nil { + return err + } + + if updatedRows > 0 { + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has moved %d transactions from account \"id:%d\" to account \"id:%d\"", uid, updatedRows, fromAccountId, toAccountId) + } + + // update all related transactions of from account + updateRelatedModel := &models.Transaction{ + RelatedAccountId: toAccountId, + UpdatedUnixTime: time.Now().Unix(), + } + + relatedUpdatedRows, err := sess.Cols("related_account_id", "updated_unix_time").Where("uid=? AND deleted=? AND related_account_id=?", uid, false, fromAccountId).Update(updateRelatedModel) + + if err != nil { + return err + } + + if updatedRows > 0 { + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has moved %d related transactions from account \"id:%d\" to account \"id:%d\"", uid, relatedUpdatedRows, fromAccountId, toAccountId) + } + + // delete all transfer transactions which related account id and account id are both + deletedModel := &models.Transaction{ + Deleted: true, + DeletedUnixTime: time.Now().Unix(), + } + + deletedRows, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND (type=? OR type=?) AND account_id=? AND related_account_id=?", uid, false, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, models.TRANSACTION_DB_TYPE_TRANSFER_IN, toAccountId, toAccountId).Update(deletedModel) + + if err != nil { + return err + } + + if deletedRows > 0 { + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has deleted %d transactions which account id and related account id are both \"%d\"", uid, deletedRows, toAccountId) + } + + // update account balance + if fromAccount.Balance != 0 { + toAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(toAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", fromAccount.Balance)).Cols("updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(toAccount) + + if err != nil { + return err + } else if updatedRows < 1 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] failed to update to account balance") + return errs.ErrDatabaseOperationFailed + } + + log.Infof(c, "[transactions.MoveAllTransactionsBetweenAccounts] user \"uid:%d\" has updated account \"id:%d\" balance from %d to %d", uid, toAccountId, toAccount.Balance, toAccount.Balance+fromAccount.Balance) + + fromAccount.Balance = 0 + fromAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err = sess.ID(fromAccount.AccountId).Cols("balance", "updated_unix_time").Where("uid=? AND deleted=?", fromAccount.Uid, false).Update(fromAccount) + + if err != nil { + return err + } else if updatedRows < 1 { + log.Errorf(c, "[transactions.MoveAllTransactionsBetweenAccounts] failed to update from account balance") + return errs.ErrDatabaseOperationFailed + } + } + + return nil + }) +} + // DeleteTransaction deletes an existed transaction from database func (s *TransactionService) DeleteTransaction(c core.Context, uid int64, transactionId int64) error { if uid <= 0 { diff --git a/src/lib/services.ts b/src/lib/services.ts index d0f18f06..7ef874b9 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -61,6 +61,7 @@ import type { import type { TransactionCreateRequest, TransactionModifyRequest, + TransactionMoveBetweenAccountsRequest, TransactionDeleteRequest, TransactionImportRequest, TransactionListByMaxTimeRequest, @@ -528,6 +529,9 @@ export default { modifyTransaction: (req: TransactionModifyRequest): ApiResponsePromise => { return axios.post>('v1/transactions/modify.json', req); }, + moveAllTransactionsBetweenAccounts: (req: TransactionMoveBetweenAccountsRequest): ApiResponsePromise => { + return axios.post>('v1/transactions/move/all.json', req); + }, deleteTransaction: (req: TransactionDeleteRequest): ApiResponsePromise => { return axios.post>('v1/transactions/delete.json', req); }, diff --git a/src/locales/de.json b/src/locales/de.json index 746f4e2b..21e7c000 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Sind Sie sicher, dass Sie {count} Transaktionen importieren möchten?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Sie haben {count} Transaktionen erfolgreich importiert.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Ein Aktivierungslink wurde an Ihre E-Mail-Adresse gesendet: {email}. Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail erneut zu senden.", "resendValidationEmailTip": "Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail an: {email} erneut zu senden." @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transaction type mapping is invalid", "transaction time format invalid": "Transaction time format is invalid", "transaction time zone format invalid": "Transaction time zone format is invalid", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "Transaktionskategorie-ID ist ungültig", "transaction category not found": "Transaktionskategorie nicht gefunden", "transaction category type is invalid": "Transaktionskategorietyp ist ungültig", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "Konto kann nicht gelöscht werden", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "Ungültiges Konto", "Target Account": "Zielkonto", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Ungültiges Tag", "Target Tag": "Ziel-Tag", "Remove Tag": "Remove Tag", diff --git a/src/locales/en.json b/src/locales/en.json index ccb51203..d530fb41 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Are you sure you want to import {count} transactions?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "You have imported {count} transactions successfully.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Account activation link has been sent to your email address: {email}, If you don't receive the mail, please fill password again and click the button below to resend the validation mail.", "resendValidationEmailTip": "If you don't receive the mail, please fill password again and click the button below to resend the validation mail to: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transaction type mapping is invalid", "transaction time format invalid": "Transaction time format is invalid", "transaction time zone format invalid": "Transaction time zone format is invalid", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "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", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "Unable to delete this account", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "Invalid Account", "Target Account": "Target Account", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Invalid Tag", "Target Tag": "Target Tag", "Remove Tag": "Remove Tag", diff --git a/src/locales/es.json b/src/locales/es.json index c4c97ef3..653d5661 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "¿Está seguro de que desea importar {count} transacciones?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Ha importado {count} transacciones correctamente.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "El enlace de activación de la cuenta se envió a su dirección de correo electrónico: {email}. Si no recibe el correo, ingrese la contraseña nuevamente y haga clic en el botón a continuación para reenviar el correo de validación.", "resendValidationEmailTip": "Si no recibe el correo, complete nuevamente la contraseña y haga clic en el botón a continuación para reenviar el correo de validación a: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transaction type mapping is invalid", "transaction time format invalid": "Transaction time format is invalid", "transaction time zone format invalid": "Transaction time zone format is invalid", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "El ID de categoría de transacción no es válido", "transaction category not found": "No se encuentra la categoría de transacción", "transaction category type is invalid": "El tipo de categoría de transacción no es válido", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "No se puede eliminar esta cuenta", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "Cuenta no válida", "Target Account": "Cuenta de destino", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Etiqueta no válida", "Target Tag": "Etiqueta de destino", "Remove Tag": "Remove Tag", diff --git a/src/locales/fr.json b/src/locales/fr.json index 4eda5ba9..9b4aabca 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Êtes-vous sûr de vouloir importer {count} transactions ?", "importingTransactions": "Importation ({process}%)", "importTransactionResult": "Vous avez importé {count} transactions avec succès.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Le lien d'activation du compte a été envoyé à votre adresse e-mail : {email}, Si vous ne recevez pas le mail, veuillez remplir à nouveau le mot de passe et cliquer sur le bouton ci-dessous pour renvoyer l'e-mail de validation.", "resendValidationEmailTip": "Si vous ne recevez pas le mail, veuillez remplir à nouveau le mot de passe et cliquer sur le bouton ci-dessous pour renvoyer l'e-mail de validation à : {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Le mappage du type de transaction est invalide", "transaction time format invalid": "Le format d'heure de transaction est invalide", "transaction time zone format invalid": "Le format de fuseau horaire de transaction est invalide", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "L'ID de catégorie de transaction est invalide", "transaction category not found": "Catégorie de transaction non trouvée", "transaction category type is invalid": "Le type de catégorie de transaction est invalide", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Êtes-vous sûr de vouloir supprimer ce sous-compte ?", "Unable to delete this account": "Impossible de supprimer ce compte", "Unable to delete this sub-account": "Impossible de supprimer ce sous-compte", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Relevé de rapprochement", "Account Balance Trends": "Tendances du solde du compte", "Update Closing Balance": "Mettre à jour le solde de clôture", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Catégorie de virement par défaut", "Invalid Account": "Compte invalide", "Target Account": "Compte cible", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Étiquette invalide", "Target Tag": "Étiquette cible", "Remove Tag": "Supprimer l'étiquette", diff --git a/src/locales/it.json b/src/locales/it.json index 65b64c93..48e6f664 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Sei sicuro di voler importare {count} transazioni?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Hai importato {count} transazioni.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Abbiamo inviato un link per l'attivazione del tuo account all'indirizzo {email}. Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio.", "resendValidationEmailTip": "Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio all'indirizzo: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Mappatura del tipo di transazione non valida", "transaction time format invalid": "Formato dell'ora della transazione non valido", "transaction time zone format invalid": "Formato del fuso orario della transazione non valido", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "ID categoria transazione non valido", "transaction category not found": "Categoria transazione non trovata", "transaction category type is invalid": "Tipo di categoria transazione non valido", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Sei sicuro di voler eliminare questo sotto-account?", "Unable to delete this account": "Impossibile eliminare questo account", "Unable to delete this sub-account": "Impossibile eliminare questo sotto-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Categoria di trasferimento predefinita", "Invalid Account": "Conto non valido", "Target Account": "Conto di destinazione", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Tag non valido", "Target Tag": "Tag di destinazione", "Remove Tag": "Remove Tag", diff --git a/src/locales/ja.json b/src/locales/ja.json index 1295695e..f5e7d747 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "本当に{count}件の取引をインポートしますか?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "{count}件の取引を正常にインポートしました。", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "アカウントの有効化リンクがメールアドレスに送信されました:{email}、メールが届かない場合はパスワードをもう一度入力して下のボタンをクリックして認証メールを再送信してください。", "resendValidationEmailTip": "メールが届かない場合は、パスワードをもう一度入力の上、以下のボタンをクリックして検証メールを再送信してください: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "取引タイプのマッピングが無効です", "transaction time format invalid": "取引時間の形式が無効です", "transaction time zone format invalid": "取引のタイムゾーン形式が無効です", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "取引カテゴリIDは無効です", "transaction category not found": "取引カテゴリは見つかりません", "transaction category type is invalid": "取引カテゴリタイプは無効です", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "この口座を削除できません", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "無効な口座", "Target Account": "対象口座", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "無効なタグ", "Target Tag": "対象タグ", "Remove Tag": "Remove Tag", diff --git a/src/locales/ko.json b/src/locales/ko.json index 1801eda2..8ac1fd19 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "{count}개의 거래를 가져오시겠습니까?", "importingTransactions": "가져오는 중 ({process}%)", "importTransactionResult": "성공적으로 {count}개의 거래를 가져왔습니다.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "이 작업은 되돌릴 수 없습니다. {account}의 거래 데이터를 지웁니다. 계속하시려면 현재 비밀번호를 입력하세요.", "accountActivationAndResendValidationEmailTip": "계정 활성화 링크가 귀하의 이메일 주소({email})로 전송되었습니다. 메일을 받지 못하신 경우, 비밀번호를 다시 입력하고 아래 버튼을 클릭하여 확인 메일을 재전송하십시오.", "resendValidationEmailTip": "메일을 받지 못하신 경우, 비밀번호를 다시 입력하고 아래 버튼을 클릭하여 확인 메일을 {email}로 재전송하십시오." @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "거래 유형 매핑이 유효하지 않습니다.", "transaction time format invalid": "거래 시간 형식이 유효하지 않습니다.", "transaction time zone format invalid": "거래 시간대 형식이 유효하지 않습니다.", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "거래 카테고리 ID가 유효하지 않습니다.", "transaction category not found": "거래 카테고리를 찾을 수 없습니다.", "transaction category type is invalid": "거래 카테고리 유형이 유효하지 않습니다.", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "이 하위계좌를 삭제하시겠습니까?", "Unable to delete this account": "이 계좌를 삭제할 수 없습니다.", "Unable to delete this sub-account": "이 하위계좌를 삭제할 수 없습니다.", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "조정 명세서", "Account Balance Trends": "계좌 잔액 추세", "Update Closing Balance": "마감 잔액 업데이트", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "기본 이체 카테고리", "Invalid Account": "유효하지 않은 계좌", "Target Account": "대상 계좌", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "유효하지 않은 태그", "Target Tag": "대상 태그", "Remove Tag": "태그 제거", diff --git a/src/locales/nl.json b/src/locales/nl.json index 6de09a95..e1e50651 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Weet je zeker dat je {count} transacties wilt importeren?", "importingTransactions": "Bezig met importeren ({process}%)", "importTransactionResult": "Je hebt {count} transacties succesvol geïmporteerd.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Een activatielink is verzonden naar je e-mailadres: {email}. Als je de e-mail niet ontvangt, vul dan je wachtwoord opnieuw in en klik op de knop hieronder om de validatiemail opnieuw te verzenden.", "resendValidationEmailTip": "Als je de e-mail niet ontvangt, vul dan je wachtwoord opnieuw in en klik op de knop hieronder om de validatiemail opnieuw te verzenden naar: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transactietypetoewijzing is ongeldig", "transaction time format invalid": "Formaat van transactietijd is ongeldig", "transaction time zone format invalid": "Formaat van transactietijdzone is ongeldig", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "Transactiecategorie-ID is ongeldig", "transaction category not found": "Transactiecategorie niet gevonden", "transaction category type is invalid": "Type transactiecategorie is ongeldig", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Weet je zeker dat je deze subrekening wilt verwijderen?", "Unable to delete this account": "Kan deze rekening niet verwijderen", "Unable to delete this sub-account": "Kan deze subrekening niet verwijderen", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Afstemmingsrapport", "Account Balance Trends": "Rekening-saldotrends", "Update Closing Balance": "Eindsaldo bijwerken", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Standaard overboekingscategorie", "Invalid Account": "Ongeldige rekening", "Target Account": "Doelrekening", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Ongeldige tag", "Target Tag": "Doeltag", "Remove Tag": "Remove Tag", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index e2876ded..17282c34 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Tem certeza de que deseja importar {count} transações?", "importingTransactions": "Importando ({process}%)", "importTransactionResult": "Você importou {count} transações com sucesso.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "O link de ativação da conta foi enviado para seu endereço de e-mail: {email}. Se você não receber o e-mail, por favor preencha a senha novamente e clique no botão abaixo para reenviar o e-mail de validação.", "resendValidationEmailTip": "Se você não receber o e-mail, por favor preencha a senha novamente e clique no botão abaixo para reenviar o e-mail de validação para: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Mapeamento de tipo de transação é inválido", "transaction time format invalid": "Formato de tempo da transação é inválido", "transaction time zone format invalid": "Formato de fuso horário da transação é inválido", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "ID de categoria de transação é inválido", "transaction category not found": "Categoria de transação não encontrada", "transaction category type is invalid": "Tipo de categoria de transação é inválido", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Tem certeza de que deseja deletar esta subconta?", "Unable to delete this account": "Não foi possível deletar esta conta", "Unable to delete this sub-account": "Não foi possível deletar esta subconta", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Categoria Padrão de Transferência", "Invalid Account": "Conta Inválida", "Target Account": "Conta Alvo", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Tag Inválida", "Target Tag": "Tag Alvo", "Remove Tag": "Remove Tag", diff --git a/src/locales/ru.json b/src/locales/ru.json index a1bd3e6d..cbe7dc01 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Вы уверены, что хотите импортировать {count} транзакций?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Вы успешно импортировали {count} транзакций.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Ссылка для активации учетной записи была отправлена на ваш электронный адрес: {email}. Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно.", "resendValidationEmailTip": "Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно на: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transaction type mapping is invalid", "transaction time format invalid": "Transaction time format is invalid", "transaction time zone format invalid": "Transaction time zone format is invalid", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "ID категории транзакции недействителен", "transaction category not found": "Категория транзакции не найдена", "transaction category type is invalid": "Тип категории транзакции недействителен", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "Не удалось удалить этот счет", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "Недействительный счет", "Target Account": "Целевой счет", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Недействительный тег", "Target Tag": "Целевой тег", "Remove Tag": "Remove Tag", diff --git a/src/locales/th.json b/src/locales/th.json index 37a4dad8..a5ee55e0 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "คุณแน่ใจหรือไม่ว่าต้องการนำเข้าธุรกรรม {count} รายการ?", "importingTransactions": "กำลังนำเข้า ({process}%)", "importTransactionResult": "คุณได้นำเข้าธุรกรรม {count} รายการเรียบร้อยแล้ว", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "คุณไม่สามารถยกเลิกการกระทำนี้ได้ การกระทำนี้จะลบข้อมูลธุรกรรมทั้งหมดใน {account} โปรดป้อนรหัสผ่านปัจจุบันเพื่อยืนยัน", "accountActivationAndResendValidationEmailTip": "ลิงก์สำหรับเปิดใช้งานบัญชีได้ถูกส่งไปยังอีเมลของคุณแล้ว: {email} หากคุณไม่ได้รับอีเมล โปรดกรอกรหัสผ่านอีกครั้งแล้วกดปุ่มด้านล่างเพื่อส่งอีเมลยืนยันอีกครั้ง", "resendValidationEmailTip": "หากคุณไม่ได้รับอีเมล โปรดกรอกรหัสผ่านอีกครั้งแล้วกดปุ่มด้านล่างเพื่อส่งอีเมลยืนยันไปยัง: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "การแมปประเภทธุรกรรมไม่ถูกต้อง", "transaction time format invalid": "รูปแบบเวลาธุรกรรมไม่ถูกต้อง", "transaction time zone format invalid": "รูปแบบโซนเวลาธุรกรรมไม่ถูกต้อง", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "รหัสหมวดหมู่ธุรกรรมไม่ถูกต้อง", "transaction category not found": "ไม่พบหมวดหมู่ธุรกรรม", "transaction category type is invalid": "ประเภทหมวดหมู่ธุรกรรมไม่ถูกต้อง", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "คุณแน่ใจหรือว่าต้องการลบบัญชีย่อยนี้?", "Unable to delete this account": "ไม่สามารถลบบัญชีนี้ได้", "Unable to delete this sub-account": "ไม่สามารถลบบัญชีย่อยนี้ได้", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "งบกระทบยอด", "Account Balance Trends": "แนวโน้มยอดบัญชี", "Update Closing Balance": "อัปเดตยอดปิดบัญชี", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "หมวดโอนเริ่มต้น", "Invalid Account": "บัญชีไม่ถูกต้อง", "Target Account": "บัญชีเป้าหมาย", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "แท็กไม่ถูกต้อง", "Target Tag": "แท็กเป้าหมาย", "Remove Tag": "ลบแท็ก", diff --git a/src/locales/uk.json b/src/locales/uk.json index 56801bb6..5b064f85 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Ви впевнені, що хочете імпортувати {count} транзакцій?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Ви успішно імпортували {count} транзакцій.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Посилання для активації облікового запису було надіслано на вашу електронну адресу: {email}. Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно.", "resendValidationEmailTip": "Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно на адресу: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Некоректне зіставлення типу транзакції", "transaction time format invalid": "Неправильний формат часу транзакції", "transaction time zone format invalid": "Неправильний формат часового поясу транзакції", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "ID категорії транзакції недійсний", "transaction category not found": "Категорію транзакції не знайдено", "transaction category type is invalid": "Тип категорії транзакції недійсний", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Ви впевнені, що хочете видалити цей субрахунок?", "Unable to delete this account": "Не вдалося видалити цей рахунок", "Unable to delete this sub-account": "Не вдалося видалити цей субрахунок", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Категорія переказів за замовчуванням", "Invalid Account": "Недійсний рахунок", "Target Account": "Цільовий рахунок", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Недійсний тег", "Target Tag": "Цільовий тег", "Remove Tag": "Remove Tag", diff --git a/src/locales/vi.json b/src/locales/vi.json index 760bc771..3a3eb81f 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "Bạn có chắc chắn muốn nhập {count} giao dịch không?", "importingTransactions": "Importing ({process}%)", "importTransactionResult": "Bạn đã nhập thành công {count} giao dịch.", + "moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.", "clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.", "accountActivationAndResendValidationEmailTip": "Liên kết kích hoạt tài khoản đã được gửi tới email của bạn: {email}. Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận.", "resendValidationEmailTip": "Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận tới: {email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "Transaction type mapping is invalid", "transaction time format invalid": "Transaction time format is invalid", "transaction time zone format invalid": "Transaction time zone format is invalid", + "cannot move transaction to same account": "Cannot move transaction to the same account", + "cannot move transaction from or to hidden account": "Cannot move transaction from or to hidden account", + "cannot move transaction from or to parent account": "Cannot move transaction from or to parent account", + "cannot move transaction between accounts with different currencies": "Cannot move transaction between accounts with different currencies", "transaction category id is invalid": "ID danh mục giao dịch không hợp lệ", "transaction category not found": "Không tìm thấy danh mục giao dịch", "transaction category type is invalid": "Loại danh mục giao dịch không hợp lệ", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "Are you sure you want to delete this sub-account?", "Unable to delete this account": "Không thể xóa tài khoản này", "Unable to delete this sub-account": "Unable to delete this sub-account", + "Move All Transactions": "Move All Transactions", + "Are you sure you want to move all transactions?": "Are you sure you want to move all transactions?", + "Unable to move transactions": "Unable to move transactions", + "All transactions in this account has been moved.": "All transactions in this account has been moved.", "Reconciliation Statement": "Reconciliation Statement", "Account Balance Trends": "Account Balance Trends", "Update Closing Balance": "Update Closing Balance", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "Default Transfer Category", "Invalid Account": "Tài khoản không hợp lệ", "Target Account": "Tài khoản mục tiêu", + "Confirm Target Account Name": "Confirm Target Account Name", + "Please re-enter the target account name to confirm": "Please re-enter the target account name to confirm", "Invalid Tag": "Thẻ không hợp lệ", "Target Tag": "Thẻ mục tiêu", "Remove Tag": "Remove Tag", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index f3cdc030..5442b58a 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "您确定要导入 {count} 个交易?", "importingTransactions": "正在导入 ({process}%)", "importTransactionResult": "您已经成功导入 {count} 个交易。", + "moveTransactionsInAccountTip": "您不能撤销该操作。该操作将会把 {fromAccount} 账户中所有的交易数据移动到 {toAccount}。", "clearTransactionsInAccountTip": "您不能撤销该操作。该操作将会清除您在 {account} 账户中的交易数据。请输入您当前的密码以确认。", "accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。", "resendValidationEmailTip": "如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件到:{email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "交易类型映射无效", "transaction time format invalid": "交易时间格式无效", "transaction time zone format invalid": "交易时区格式无效", + "cannot move transaction to same account": "不能将交易移动到相同的账户", + "cannot move transaction from or to hidden account": "不能从隐藏账户移动交易或移动交易到隐藏账户", + "cannot move transaction from or to parent account": "不能从父账户移动交易或移动交易到父账户", + "cannot move transaction between accounts with different currencies": "不能在不同货币的账户之间移动交易", "transaction category id is invalid": "交易分类ID无效", "transaction category not found": "交易分类不存在", "transaction category type is invalid": "交易分类类型无效", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "您确定要删除该子账户?", "Unable to delete this account": "无法删除该账户", "Unable to delete this sub-account": "无法删除该子账户", + "Move All Transactions": "移动所有交易", + "Are you sure you want to move all transactions?": "您确定要移动所有交易?", + "Unable to move transactions": "无法移动交易", + "All transactions in this account has been moved.": "该账户中的所有交易已被移动。", "Reconciliation Statement": "对账单", "Account Balance Trends": "账户余额趋势", "Update Closing Balance": "更新期末余额", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "默认转账分类", "Invalid Account": "无效账户", "Target Account": "目标账户", + "Confirm Target Account Name": "确认目标账户名称", + "Please re-enter the target account name to confirm": "请重新输入目标账户名称以确认", "Invalid Tag": "无效标签", "Target Tag": "目标标签", "Remove Tag": "删除标签", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index bade45f3..affdfe38 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -126,6 +126,7 @@ "confirmImportTransactions": "您確定要匯入 {count} 個交易?", "importingTransactions": "正在匯入 ({process}%)", "importTransactionResult": "您已經成功匯入 {count} 個交易。", + "moveTransactionsInAccountTip": "您不能還原此操作。此操作將會把 {fromAccount} 帳戶中的所有交易資料移動到 {toAccount}。", "clearTransactionsInAccountTip": "您不能還原此操作。此操作將會清除您在 {account} 帳戶中的交易資料。請輸入您目前的密碼以確認。", "accountActivationAndResendValidationEmailTip": "帳號啟用連結已經傳送到您的信箱地址:{email},如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件。", "resendValidationEmailTip": "如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件到:{email}" @@ -1163,6 +1164,10 @@ "transaction type mapping invalid": "交易類型對應無效", "transaction time format invalid": "交易時間格式無效", "transaction time zone format invalid": "交易時區格式無效", + "cannot move transaction to same account": "不能將交易移動到相同的帳戶", + "cannot move transaction from or to hidden account": "不能從隱藏帳戶移動交易或移動交易到隱藏帳戶", + "cannot move transaction from or to parent account": "不能從父帳戶移動交易或移動交易到父帳戶", + "cannot move transaction between accounts with different currencies": "不能在不同幣別的帳戶之間移動交易", "transaction category id is invalid": "交易分類ID無效", "transaction category not found": "交易分類不存在", "transaction category type is invalid": "交易分類類型無效", @@ -1704,6 +1709,10 @@ "Are you sure you want to delete this sub-account?": "您確定要刪除這個子帳戶?", "Unable to delete this account": "無法刪除此帳戶", "Unable to delete this sub-account": "無法刪除此子帳戶", + "Move All Transactions": "移動所有交易", + "Are you sure you want to move all transactions?": "您確定要移動所有交易?", + "Unable to move transactions": "無法移動交易", + "All transactions in this account has been moved.": "此帳戶中的所有交易已被移動。", "Reconciliation Statement": "對帳單", "Account Balance Trends": "帳戶餘額趨勢", "Update Closing Balance": "更新期末餘額", @@ -1886,6 +1895,8 @@ "Default Transfer Category": "預設轉帳分類", "Invalid Account": "無效帳戶", "Target Account": "目標帳戶", + "Confirm Target Account Name": "確認目標帳戶名稱", + "Please re-enter the target account name to confirm": "請再次輸入目標帳戶名稱以確認", "Invalid Tag": "無效標籤", "Target Tag": "目標標籤", "Remove Tag": "移除標籤", diff --git a/src/models/transaction.ts b/src/models/transaction.ts index 1adc9576..a74db6d8 100644 --- a/src/models/transaction.ts +++ b/src/models/transaction.ts @@ -488,6 +488,11 @@ export interface TransactionModifyRequest { readonly geoLocation?: TransactionGeoLocationRequest; } +export interface TransactionMoveBetweenAccountsRequest { + readonly fromAccountId: string; + readonly toAccountId: string; +} + export interface TransactionDeleteRequest { readonly id: string; } diff --git a/src/router/mobile.ts b/src/router/mobile.ts index 8bd28013..993f3582 100644 --- a/src/router/mobile.ts +++ b/src/router/mobile.ts @@ -14,6 +14,7 @@ import TransactionAmountFilterPage from '@/views/mobile/transactions/AmountFilte import AccountListPage from '@/views/mobile/accounts/ListPage.vue'; import AccountEditPage from '@/views/mobile/accounts/EditPage.vue'; import AccountReconciliationStatementPage from '@/views/mobile/accounts/ReconciliationStatementPage.vue'; +import AccountMoveAllTransactionsPage from '@/views/mobile/accounts/MoveAllTransactionsPage.vue'; import StatisticsTransactionPage from '@/views/mobile/statistics/TransactionPage.vue'; import StatisticsSettingsPage from '@/views/mobile/statistics/SettingsPage.vue'; @@ -197,6 +198,11 @@ const routes: Router.RouteParameters[] = [ async: asyncResolve(AccountReconciliationStatementPage), beforeEnter: [checkLogin] }, + { + path: '/account/move_all_transactions', + async: asyncResolve(AccountMoveAllTransactionsPage), + beforeEnter: [checkLogin] + }, { path: '/statistic/transaction', async: asyncResolve(StatisticsTransactionPage), diff --git a/src/stores/transaction.ts b/src/stores/transaction.ts index 16e9dcf5..75c29686 100644 --- a/src/stores/transaction.ts +++ b/src/stores/transaction.ts @@ -1103,6 +1103,51 @@ export const useTransactionsStore = defineStore('transactions', () => { }); } + function moveAllTransactionsBetweenAccounts({ fromAccountId, toAccountId }: { fromAccountId: string, toAccountId: string }): Promise { + return new Promise((resolve, reject) => { + services.moveAllTransactionsBetweenAccounts({ fromAccountId, toAccountId }).then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to move transactions' }); + return; + } + + if (!transactionListStateInvalid.value) { + updateTransactionListInvalidState(true); + } + + if (!transactionReconciliationStatementStateInvalid.value) { + updateTransactionReconciliationStatementInvalidState(true); + } + + if (!accountsStore.accountListStateInvalid) { + accountsStore.updateAccountListInvalidState(true); + } + + if (!overviewStore.transactionOverviewStateInvalid) { + overviewStore.updateTransactionOverviewInvalidState(true); + } + + if (!statisticsStore.transactionStatisticsStateInvalid) { + statisticsStore.updateTransactionStatisticsInvalidState(true); + } + + resolve(data.result); + }).catch(error => { + logger.error('failed to move transactions', error); + + if (error.response && error.response.data && error.response.data.errorMessage) { + reject({ error: error.response.data }); + } else if (!error.processed) { + reject({ message: 'Unable to move transactions' }); + } else { + reject(error); + } + }); + }); + } + function deleteTransaction({ transaction, defaultCurrency, beforeResolve }: { transaction: TransactionInfoResponse, defaultCurrency: string, beforeResolve?: BeforeResolveFunction }): Promise { return new Promise((resolve, reject) => { services.deleteTransaction({ @@ -1405,6 +1450,7 @@ export const useTransactionsStore = defineStore('transactions', () => { getReconciliationStatements, getTransaction, saveTransaction, + moveAllTransactionsBetweenAccounts, deleteTransaction, recognizeReceiptImage, cancelRecognizeReceiptImage, diff --git a/src/views/base/accounts/MoveAllTransactionsPageBase.ts b/src/views/base/accounts/MoveAllTransactionsPageBase.ts new file mode 100644 index 00000000..ed55ce63 --- /dev/null +++ b/src/views/base/accounts/MoveAllTransactionsPageBase.ts @@ -0,0 +1,61 @@ +import { ref, computed } from 'vue'; + +import { useI18n } from '@/locales/helpers.ts'; + +import { useSettingsStore } from '@/stores/setting.ts'; +import { useAccountsStore } from '@/stores/account.ts'; + +import { Account, type CategorizedAccountWithDisplayBalance } from '@/models/account.ts'; + +export function useMoveAllTransactionsPageBase() { + const { tt, getCategorizedAccountsWithDisplayBalance } = useI18n(); + + const settingsStore = useSettingsStore(); + const accountsStore = useAccountsStore(); + + const moving = ref(false); + const fromAccount = ref(undefined); + const toAccountId = ref(''); + const toAccountName = ref(''); + + const showAccountBalance = computed(() => settingsStore.appSettings.showAccountBalance); + const allAccounts = computed(() => accountsStore.allPlainAccounts); + const allVisibleAccounts = computed(() => accountsStore.allVisiblePlainAccounts); + const allVisibleCategorizedAccounts = computed(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value)); + + const displayToAccountName = computed(() => { + if (!toAccountId.value) { + return tt('Target Account'); + } + + return Account.findAccountNameById(allAccounts.value, toAccountId.value, tt('Target Account')) || tt('Target Account'); + }); + + const isToAccountNameValid = computed(() => { + if (!toAccountId.value || !toAccountName.value) { + return false; + } + + const expectedAccountName = Account.findAccountNameById(allAccounts.value, toAccountId.value); + + if (!expectedAccountName) { + return false; + } + + return expectedAccountName === toAccountName.value; + }); + + return { + // states + moving, + fromAccount, + toAccountId, + toAccountName, + // computed states + allAccounts, + allVisibleAccounts, + allVisibleCategorizedAccounts, + displayToAccountName, + isToAccountNameValid + }; +} diff --git a/src/views/desktop/accounts/ListPage.vue b/src/views/desktop/accounts/ListPage.vue index 254ef13c..6fa5baa3 100644 --- a/src/views/desktop/accounts/ListPage.vue +++ b/src/views/desktop/accounts/ListPage.vue @@ -250,10 +250,21 @@ - {{ tt('Clear All Transactions') }} + :disabled="loading" :prepend-icon="mdiDotsHorizontalCircleOutline" + v-if="element.type === AccountType.SingleAccount.type || element.getSubAccount(activeSubAccount[element.id])"> + {{ tt('More') }} + + + + + + + ; type SnackBarType = InstanceType; type EditDialogType = InstanceType; type ReconciliationStatementDialogType = InstanceType; +type MoveAllTransactionsDialogType = InstanceType; type ClearAllTransactionsDialogType = InstanceType; const display = useDisplay(); @@ -372,6 +388,7 @@ const confirmDialog = useTemplateRef('confirmDialog'); const snackbar = useTemplateRef('snackbar'); const editDialog = useTemplateRef('editDialog'); const reconciliationStatementDialog = useTemplateRef('reconciliationStatementDialog'); +const moveAllTransactionsDialog = useTemplateRef('moveAllTransactionsDialog'); const clearAllTransactionsDialog = useTemplateRef('clearAllTransactionsDialog'); const activeAccountCategoryType = ref(AccountCategory.Default.type); @@ -525,6 +542,16 @@ function showReconciliationStatementCustomDateRangeDialog(account: Account, date }); } +function moveAllTransactions(account: Account): void { + moveAllTransactionsDialog.value?.open(account).then(() => { + snackbar.value?.showMessage('All transactions in this account has been moved.'); + + if (accountsStore.accountListStateInvalid && !loading.value) { + reload(false); + } + }); +} + function clearAllTransactions(account: Account): void { clearAllTransactionsDialog.value?.open(account).then(() => { snackbar.value?.showMessage('All transactions in this account has been cleared'); diff --git a/src/views/desktop/accounts/list/dialogs/MoveAllTransactionsDialog.vue b/src/views/desktop/accounts/list/dialogs/MoveAllTransactionsDialog.vue new file mode 100644 index 00000000..1538a698 --- /dev/null +++ b/src/views/desktop/accounts/list/dialogs/MoveAllTransactionsDialog.vue @@ -0,0 +1,160 @@ + + + diff --git a/src/views/mobile/accounts/ListPage.vue b/src/views/mobile/accounts/ListPage.vue index 9b632288..c36c459a 100644 --- a/src/views/mobile/accounts/ListPage.vue +++ b/src/views/mobile/accounts/ListPage.vue @@ -163,6 +163,7 @@ {{ tt('Reconciliation Statement') }} + {{ tt('Move All Transactions') }} {{ tt('Clear All Transactions') }} @@ -363,6 +365,17 @@ function showReconciliationStatement(account: Account | null): void { accountForMoreActionSheet.value = null; } +function moveAllTransactions(account: Account | null): void { + if (!account) { + showAlert('An error occurred'); + return; + } + + props.f7router.navigate('/account/move_all_transactions?fromAccountId=' + account.id); + showAccountMoreActionSheet.value = false; + accountForMoreActionSheet.value = null; +} + function showPasswordSheetForClearAllTransaction(account: Account | null): void { if (!account) { showAlert('An error occurred'); diff --git a/src/views/mobile/accounts/MoveAllTransactionsPage.vue b/src/views/mobile/accounts/MoveAllTransactionsPage.vue new file mode 100644 index 00000000..668c7e9e --- /dev/null +++ b/src/views/mobile/accounts/MoveAllTransactionsPage.vue @@ -0,0 +1,165 @@ + + +