diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index ffcbfc7a..0fe4ad7f 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -36,7 +36,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, } uid := c.GetCurrentUid() - transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, transactionListReq.Count+1) + transactions, err := a.transactions.GetTransactionsByMaxTime(uid, transactionListReq.MaxTime, nil, 0, 0, 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()) @@ -49,6 +49,12 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, finalCount = len(transactions) } + hasMore := false + + if finalCount < len(transactions) { + hasMore = true + } + transactionIds := make([]int64, finalCount) for i := 0; i < finalCount; i++ { @@ -72,7 +78,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, sort.Sort(transactionResps.Items) - if finalCount < len(transactions) { + if hasMore { transactionResps.NextTimeSequenceId = &transactions[finalCount].TransactionTime } @@ -90,7 +96,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac } uid := c.GetCurrentUid() - transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Page, transactionListReq.Count) + transactions, err := a.transactions.GetTransactionsInMonthByPage(uid, transactionListReq.Year, transactionListReq.Month, nil, 0, 0, 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()) @@ -138,6 +144,10 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * return nil, errs.ErrOperationFailed } + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) + } + allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId}) if err != nil { @@ -178,17 +188,17 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{} return nil, errs.ErrBalanceModificationTransactionCannotSetCategory } - if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.SourceAccountId != transactionCreateReq.DestinationAccountId { - log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] non-transfer transaction source account is not destination account") - return nil, errs.ErrTransactionSourceAndDestinationIdNotEqual + if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.DestinationAccountId != 0 { + log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] non-transfer transaction destination account cannot be set") + return nil, errs.ErrTransactionDestinationAccountCannotBeSet } else if transactionCreateReq.Type == models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.SourceAccountId == transactionCreateReq.DestinationAccountId { log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] transfer transaction source account must not be destination account") return nil, errs.ErrTransactionSourceAndDestinationIdCannotBeEqual } - if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.SourceAmount != transactionCreateReq.DestinationAmount { - log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] non-transfer transaction source amount is not destination amount") - return nil, errs.ErrTransactionSourceAndDestinationAmountNotEqual + if transactionCreateReq.Type != models.TRANSACTION_TYPE_TRANSFER && transactionCreateReq.DestinationAmount != 0 { + log.WarnfWithRequestId(c, "[transactions.TransactionCreateHandler] non-transfer transaction destination amount cannot be set") + return nil, errs.ErrTransactionDestinationAmountCannotBeSet } uid := c.GetCurrentUid() @@ -233,6 +243,11 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} return nil, errs.Or(err, errs.ErrOperationFailed) } + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] cannot modify transaction \"id:%d\" for user \"uid:%d\", because transaction type is transfer in", transactionModifyReq.Id, uid) + return nil, errs.ErrTransactionTypeInvalid + } + allTransactionTagIds, err := a.transactionTags.GetAllTagIdsOfTransactions(uid, []int64{transaction.TransactionId}) if err != nil { @@ -249,19 +264,22 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} Uid: uid, CategoryId: transactionModifyReq.CategoryId, TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionModifyReq.Time), - SourceAccountId: transactionModifyReq.SourceAccountId, - DestinationAccountId: transactionModifyReq.DestinationAccountId, - SourceAmount: transactionModifyReq.SourceAmount, - DestinationAmount: transactionModifyReq.DestinationAmount, + AccountId: transactionModifyReq.SourceAccountId, + Amount: transactionModifyReq.SourceAmount, Comment: transactionModifyReq.Comment, } + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + newTransaction.RelatedAccountId = transactionModifyReq.DestinationAccountId + newTransaction.RelatedAccountAmount = transactionModifyReq.DestinationAmount + } + if newTransaction.CategoryId == transaction.CategoryId && utils.GetUnixTimeFromTransactionTime(newTransaction.TransactionTime) == utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) && - newTransaction.SourceAccountId == transaction.SourceAccountId && - newTransaction.DestinationAccountId == transaction.DestinationAccountId && - newTransaction.SourceAmount == transaction.SourceAmount && - newTransaction.DestinationAmount == transaction.DestinationAmount && + newTransaction.AccountId == transaction.AccountId && + newTransaction.Amount == transaction.Amount && + (transaction.Type != models.TRANSACTION_DB_TYPE_TRANSFER_OUT || newTransaction.RelatedAccountId == transaction.RelatedAccountId) && + (transaction.Type != models.TRANSACTION_DB_TYPE_TRANSFER_OUT || newTransaction.RelatedAccountAmount == transaction.RelatedAccountAmount) && newTransaction.Comment == transaction.Comment && len(addTransactionTagIds) < 1 && len(removeTransactionTagIds) < 1 { @@ -303,15 +321,32 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.Context) (interface{} } func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreateReq *models.TransactionCreateRequest) *models.Transaction { - return &models.Transaction{ - Uid: uid, - Type: transactionCreateReq.Type, - CategoryId: transactionCreateReq.CategoryId, - TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionCreateReq.Time), - SourceAccountId: transactionCreateReq.SourceAccountId, - DestinationAccountId: transactionCreateReq.DestinationAccountId, - SourceAmount: transactionCreateReq.SourceAmount, - DestinationAmount: transactionCreateReq.DestinationAmount, - Comment: transactionCreateReq.Comment, + var transactionDbType models.TransactionDbType + + if transactionCreateReq.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { + transactionDbType = models.TRANSACTION_DB_TYPE_MODIFY_BALANCE + } else if transactionCreateReq.Type == models.TRANSACTION_TYPE_EXPENSE { + transactionDbType = models.TRANSACTION_DB_TYPE_EXPENSE + } else if transactionCreateReq.Type == models.TRANSACTION_TYPE_INCOME { + transactionDbType = models.TRANSACTION_DB_TYPE_INCOME + } else if transactionCreateReq.Type == models.TRANSACTION_TYPE_TRANSFER { + transactionDbType = models.TRANSACTION_DB_TYPE_TRANSFER_OUT } + + transaction := &models.Transaction{ + Uid: uid, + Type: transactionDbType, + CategoryId: transactionCreateReq.CategoryId, + TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionCreateReq.Time), + AccountId: transactionCreateReq.SourceAccountId, + Amount: transactionCreateReq.SourceAmount, + Comment: transactionCreateReq.Comment, + } + + if transactionCreateReq.Type == models.TRANSACTION_TYPE_TRANSFER { + transaction.RelatedAccountId = transactionCreateReq.DestinationAccountId + transaction.RelatedAccountAmount = transactionCreateReq.DestinationAmount + } + + return transaction } diff --git a/pkg/errs/transaction.go b/pkg/errs/transaction.go index 8592efe7..dada690e 100644 --- a/pkg/errs/transaction.go +++ b/pkg/errs/transaction.go @@ -7,14 +7,15 @@ 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") - ErrTransactionSourceAndDestinationIdNotEqual = NewNormalError(NormalSubcategoryTransaction, 3, http.StatusBadRequest, "transaction source and destination account id not equal") - ErrTransactionSourceAndDestinationIdCannotBeEqual = NewNormalError(NormalSubcategoryTransaction, 4, http.StatusBadRequest, "transaction source and destination account id cannot be equal") - ErrTransactionSourceAndDestinationAmountNotEqual = NewNormalError(NormalSubcategoryTransaction, 5, http.StatusBadRequest, "transaction source and destination amount not equal") - ErrTooMuchTransactionInOneSecond = NewNormalError(NormalSubcategoryTransaction, 6, http.StatusBadRequest, "too much transaction in one second") - ErrBalanceModificationTransactionCannotSetCategory = NewNormalError(NormalSubcategoryTransaction, 7, http.StatusBadRequest, "balance modification transaction cannot set category") - ErrBalanceModificationTransactionCannotChangeAccountId = NewNormalError(NormalSubcategoryTransaction, 8, http.StatusBadRequest, "balance modification transaction cannot change account id") - ErrBalanceModificationTransactionCannotAddWhenNotEmpty = NewNormalError(NormalSubcategoryTransaction, 9, http.StatusBadRequest, "balance modification transaction cannot add when other transaction exists") - ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 10, http.StatusBadRequest, "cannot add transaction to hidden account") - ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot modify transaction of hidden account") - ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot delete transaction in hidden account") + 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") ) diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index 09357dad..94cc98ff 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -13,19 +13,32 @@ const ( TRANSACTION_TYPE_TRANSFER TransactionType = 4 ) +// TransactionDbType represents transaction type in database +type TransactionDbType byte + +// Transaction db types +const ( + TRANSACTION_DB_TYPE_MODIFY_BALANCE TransactionDbType = 1 + TRANSACTION_DB_TYPE_INCOME TransactionDbType = 2 + TRANSACTION_DB_TYPE_EXPENSE TransactionDbType = 3 + TRANSACTION_DB_TYPE_TRANSFER_OUT TransactionDbType = 4 + TRANSACTION_DB_TYPE_TRANSFER_IN TransactionDbType = 5 +) + // Transaction represents transaction data stored in database type Transaction struct { - TransactionId int64 `xorm:"PK"` - Uid int64 `xorm:"UNIQUE(UQE_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(UQE_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"` + TransactionId int64 `xorm:"PK"` + Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` + Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` + Type TransactionDbType `xorm:"INDEX(IDX_transaction_uid_deleted_type_time) NOT NULL"` + CategoryId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"` + AccountId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` + TransactionTime int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` + Amount int64 `xorm:"NOT NULL"` + RelatedId int64 `xorm:"NOT NULL"` + RelatedAccountId int64 `xorm:"NOT NULL"` + RelatedAccountAmount int64 `xorm:"NOT NULL"` + Comment string `xorm:"VARCHAR(255) NOT NULL"` CreatedUnixTime int64 UpdatedUnixTime int64 DeletedUnixTime int64 @@ -37,7 +50,7 @@ type TransactionCreateRequest struct { 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"` + DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"` SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` TagIds []string `json:"tagIds"` @@ -50,7 +63,7 @@ type TransactionModifyRequest struct { 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"` + DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"` SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` TagIds []string `json:"tagIds"` @@ -89,9 +102,9 @@ type TransactionInfoResponse struct { CategoryId int64 `json:"categoryId,string"` Time int64 `json:"time"` SourceAccountId int64 `json:"sourceAccountId,string"` - DestinationAccountId int64 `json:"destinationAccountId,string"` + DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"` SourceAmount int64 `json:"sourceAmount"` - DestinationAmount int64 `json:"destinationAmount"` + DestinationAmount int64 `json:"destinationAmount,omitempty"` TagIds []string `json:"tagIds"` Comment string `json:"comment"` } @@ -104,16 +117,49 @@ type TransactionInfoPageWrapperResponse struct { // ToTransactionInfoResponse returns a view-object according to database model func (c *Transaction) ToTransactionInfoResponse(tagIds []int64) *TransactionInfoResponse { + var transactionType TransactionType + + if c.Type == TRANSACTION_DB_TYPE_MODIFY_BALANCE { + transactionType = TRANSACTION_TYPE_MODIFY_BALANCE + } else if c.Type == TRANSACTION_DB_TYPE_EXPENSE { + transactionType = TRANSACTION_TYPE_EXPENSE + } else if c.Type == TRANSACTION_DB_TYPE_INCOME { + transactionType = TRANSACTION_TYPE_INCOME + } else if c.Type == TRANSACTION_DB_TYPE_TRANSFER_OUT { + transactionType = TRANSACTION_TYPE_TRANSFER + } else if c.Type == TRANSACTION_DB_TYPE_TRANSFER_IN { + transactionType = TRANSACTION_TYPE_TRANSFER + } else { + return nil + } + + sourceAccountId := c.AccountId + sourceAmount := c.Amount + + destinationAccountId := int64(0) + destinationAmount := int64(0) + + if c.Type == TRANSACTION_DB_TYPE_TRANSFER_OUT { + destinationAccountId = c.RelatedAccountId + destinationAmount = c.RelatedAccountAmount + } else if c.Type == TRANSACTION_DB_TYPE_TRANSFER_IN { + sourceAccountId = c.RelatedAccountId + sourceAmount = c.RelatedAccountAmount + + destinationAccountId = c.AccountId + destinationAmount = c.Amount + } + return &TransactionInfoResponse{ Id: c.TransactionId, TimeSequenceId: c.TransactionTime, - Type: c.Type, + Type: transactionType, CategoryId: c.CategoryId, Time: utils.GetUnixTimeFromTransactionTime(c.TransactionTime), - SourceAccountId: c.SourceAccountId, - DestinationAccountId: c.DestinationAccountId, - SourceAmount: c.SourceAmount, - DestinationAmount: c.DestinationAmount, + SourceAccountId: sourceAccountId, + DestinationAccountId: destinationAccountId, + SourceAmount: sourceAmount, + DestinationAmount: destinationAmount, TagIds: utils.Int64ArrayToStringArray(tagIds), Comment: c.Comment, } diff --git a/pkg/services/accounts.go b/pkg/services/accounts.go index 6bd5b991..1b8f7981 100644 --- a/pkg/services/accounts.go +++ b/pkg/services/accounts.go @@ -140,12 +140,12 @@ func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAcc TransactionId: s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION), Uid: allAccounts[i].Uid, Deleted: false, - Type: models.TRANSACTION_TYPE_MODIFY_BALANCE, + Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, TransactionTime: transactionTime, - SourceAccountId: allAccounts[i].AccountId, - DestinationAccountId: allAccounts[i].AccountId, - SourceAmount: allAccounts[i].Balance, - DestinationAmount: allAccounts[i].Balance, + AccountId: allAccounts[i].AccountId, + Amount: allAccounts[i].Balance, + RelatedAccountId: allAccounts[i].AccountId, + RelatedAccountAmount: allAccounts[i].Balance, CreatedUnixTime: now, UpdatedUnixTime: now, } @@ -287,49 +287,26 @@ func (s *AccountService) DeleteAccount(uid int64, accountId int64) error { accountAndSubAccountIds[i] = accountAndSubAccounts[i].AccountId } - var relatedTransactionsBySourceAccount []*models.Transaction - err = sess.Cols("uid", "deleted", "source_account_id", "type").Where("uid=? AND deleted=?", uid, false).In("source_account_id", accountAndSubAccountIds).Limit(len(accountAndSubAccounts) + 1).Find(&relatedTransactionsBySourceAccount) + var relatedTransactionsByAccount []*models.Transaction + err = sess.Cols("uid", "deleted", "account_id", "type").Where("uid=? AND deleted=?", uid, false).In("account_id", accountAndSubAccountIds).Limit(len(accountAndSubAccounts) + 1).Find(&relatedTransactionsByAccount) if err != nil { return err - } else if len(relatedTransactionsBySourceAccount) > len(accountAndSubAccountIds) { + } else if len(relatedTransactionsByAccount) > len(accountAndSubAccountIds) { return errs.ErrAccountInUseCannotBeDeleted - } else if len(relatedTransactionsBySourceAccount) > 0 { + } else if len(relatedTransactionsByAccount) > 0 { accountTransactionExists := make(map[int64]bool) - for i := 0; i < len(relatedTransactionsBySourceAccount); i++ { - transaction := relatedTransactionsBySourceAccount[i] + for i := 0; i < len(relatedTransactionsByAccount); i++ { + transaction := relatedTransactionsByAccount[i] - if transaction.Type != models.TRANSACTION_TYPE_MODIFY_BALANCE { + if transaction.Type != models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { return errs.ErrAccountInUseCannotBeDeleted - } else if _, exists := accountTransactionExists[transaction.SourceAccountId]; exists { + } else if _, exists := accountTransactionExists[transaction.AccountId]; exists { return errs.ErrAccountInUseCannotBeDeleted } - accountTransactionExists[transaction.SourceAccountId] = true - } - } - - var relatedTransactionsByDestinationAccount []*models.Transaction - err = sess.Cols("uid", "deleted", "destination_account_id", "type").Where("uid=? AND deleted=?", uid, false).In("destination_account_id", accountAndSubAccountIds).Limit(len(accountAndSubAccounts) + 1).Find(&relatedTransactionsByDestinationAccount) - - if err != nil { - return err - } else if len(relatedTransactionsByDestinationAccount) > len(accountAndSubAccountIds) { - return errs.ErrAccountInUseCannotBeDeleted - } else if len(relatedTransactionsByDestinationAccount) > 0 { - accountTransactionExists := make(map[int64]bool) - - for i := 0; i < len(relatedTransactionsByDestinationAccount); i++ { - transaction := relatedTransactionsByDestinationAccount[i] - - if transaction.Type != models.TRANSACTION_TYPE_MODIFY_BALANCE { - return errs.ErrAccountInUseCannotBeDeleted - } else if _, exists := accountTransactionExists[transaction.DestinationAccountId]; exists { - return errs.ErrAccountInUseCannotBeDeleted - } - - accountTransactionExists[transaction.DestinationAccountId] = true + accountTransactionExists[transaction.AccountId] = true } } diff --git a/pkg/services/transactions.go b/pkg/services/transactions.go index 539e0444..40b30db1 100644 --- a/pkg/services/transactions.go +++ b/pkg/services/transactions.go @@ -32,7 +32,7 @@ var ( ) // GetTransactionsByMaxTime returns transactions before given time -func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTime int64, count int) ([]*models.Transaction, error) { +func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTime int64, transactionType *models.TransactionDbType, categoryId int64, accountId int64, count int) ([]*models.Transaction, error) { if uid <= 0 { return nil, errs.ErrUserIdInvalid } @@ -44,17 +44,44 @@ func (s *TransactionService) GetTransactionsByMaxTime(uid int64, maxTime int64, var transactions []*models.Transaction var err error - if maxTime > 0 { - err = s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time<=?", uid, false, maxTime).Limit(count, 0).OrderBy("transaction_time desc").Find(&transactions) - } else { - err = s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).Limit(count, 0).OrderBy("transaction_time desc").Find(&transactions) + condition := "uid=? AND deleted=?" + conditionParams := make([]interface{}, 0, 10) + conditionParams = append(conditionParams, uid) + conditionParams = append(conditionParams, false) + + if transactionType != nil { + condition = condition + " AND type=?" + conditionParams = append(conditionParams, transactionType) + } else if accountId == 0 { + condition = condition + " AND (type=? OR type=? OR type=? OR type=?)" + conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE) + conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_INCOME) + conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_EXPENSE) + conditionParams = append(conditionParams, models.TRANSACTION_DB_TYPE_TRANSFER_OUT) } + if categoryId > 0 { + condition = condition + " AND category_id=?" + conditionParams = append(conditionParams, categoryId) + } + + if accountId > 0 { + condition = condition + " AND account_id=?" + conditionParams = append(conditionParams, accountId) + } + + if maxTime > 0 { + condition = condition + " AND transaction_time<=?" + conditionParams = append(conditionParams, maxTime) + } + + err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(count, 0).OrderBy("transaction_time desc").Find(&transactions) + return transactions, err } // GetTransactionsInMonthByPage returns transactions in given year and month -func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, month int, page int, count int) ([]*models.Transaction, error) { +func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, month int, transactionType *models.TransactionDbType, categoryId int64, accountId int64, page int, count int) ([]*models.Transaction, error) { if uid <= 0 { return nil, errs.ErrUserIdInvalid } @@ -79,7 +106,36 @@ func (s *TransactionService) GetTransactionsInMonthByPage(uid int64, year int, m endUnixTime := endTime.Unix() var transactions []*models.Transaction - err = s.UserDataDB(uid).Where("uid=? AND deleted=? AND transaction_time>=? AND transaction_time=? AND transaction_time 0 { + condition = condition + " AND category_id=?" + conditionParams = append(conditionParams, categoryId) + } + + if accountId > 0 { + condition = condition + " AND account_id=?" + conditionParams = append(conditionParams, accountId) + } + + err = s.UserDataDB(uid).Where(condition, conditionParams...).Limit(count, count*(page-1)).OrderBy("transaction_time desc").Find(&transactions) return transactions, err } @@ -177,11 +233,12 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, return err } - if sourceAccount.Hidden || destinationAccount.Hidden { + if sourceAccount.Hidden || (destinationAccount != nil && destinationAccount.Hidden) { return errs.ErrCannotAddTransactionToHiddenAccount } - if sourceAccount.Currency == destinationAccount.Currency && transaction.SourceAmount != transaction.DestinationAmount { + if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) && + sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount { return errs.ErrTransactionSourceAndDestinationAmountNotEqual } @@ -200,8 +257,8 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, } // Verify balance modification transaction and calculate real amount - if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - otherTransactionExists, err := sess.Cols("uid", "deleted", "destination_account_id").Where("uid=? AND deleted=? AND destination_account_id=?", transaction.Uid, false, destinationAccount.AccountId).Limit(1).Exist(&models.Transaction{}) + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + otherTransactionExists, err := sess.Cols("uid", "deleted", "account_id").Where("uid=? AND deleted=? AND account_id=?", transaction.Uid, false, sourceAccount.AccountId).Limit(1).Exist(&models.Transaction{}) if err != nil { return err @@ -209,10 +266,18 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, return errs.ErrBalanceModificationTransactionCannotAddWhenNotEmpty } - transaction.DestinationAmount = transaction.SourceAmount - destinationAccount.Balance + transaction.RelatedAccountId = transaction.TransactionId + transaction.RelatedAccountAmount = transaction.Amount - sourceAccount.Balance } // Insert transaction row + var relatedTransaction *models.Transaction + + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + relatedTransaction = s.GetRelatedTransferTransaction(transaction, s.GenerateUuid(uuid.UUID_TYPE_TRANSACTION)) + transaction.RelatedId = relatedTransaction.TransactionId + } + createdRows, err := sess.Insert(transaction) if err != nil || createdRows < 1 { // maybe another transaction has same time @@ -240,6 +305,22 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, } } + if relatedTransaction != nil { + relatedTransaction.TransactionTime = transaction.TransactionTime + 1 + + if utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) != utils.GetUnixTimeFromTransactionTime(relatedTransaction.TransactionTime) { + return errs.ErrTooMuchTransactionInOneSecond + } + + createdRows, err := sess.Insert(relatedTransaction) + + if err != nil { + return err + } else if createdRows < 1 { + return errs.ErrDatabaseOperationFailed + } + } + err = nil // Insert transaction tag index @@ -255,36 +336,36 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, } // Update account table - if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if transaction.Type == models.TRANSACTION_TYPE_INCOME { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if transaction.Type == models.TRANSACTION_TYPE_EXPENSE { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if transaction.Type == models.TRANSACTION_TYPE_TRANSFER { + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { 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) + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.RelatedAccountAmount)).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_DB_TYPE_INCOME { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.Amount)).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_DB_TYPE_EXPENSE { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", transaction.Amount)).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_DB_TYPE_TRANSFER_OUT { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedSourceRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", transaction.Amount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount) if err != nil { return err @@ -293,13 +374,15 @@ func (s *TransactionService) CreateTransaction(transaction *models.Transaction, } 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) + updatedDestinationRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", transaction.RelatedAccountAmount)).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 } + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + return errs.ErrTransactionTypeInvalid } return err @@ -348,6 +431,10 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, transaction.Type = oldTransaction.Type + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + transaction.RelatedId = oldTransaction.RelatedId + } + // Check whether account id is valid err = s.isAccountIdValid(transaction) @@ -362,11 +449,12 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return err } - if sourceAccount.Hidden || destinationAccount.Hidden { + if sourceAccount.Hidden || (destinationAccount != nil && destinationAccount.Hidden) { return errs.ErrCannotModifyTransactionInHiddenAccount } - if sourceAccount.Currency == destinationAccount.Currency && transaction.SourceAmount != transaction.DestinationAmount { + if (transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) && + sourceAccount.Currency == destinationAccount.Currency && transaction.Amount != transaction.RelatedAccountAmount { return errs.ErrTransactionSourceAndDestinationAmountNotEqual } @@ -376,7 +464,7 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return err } - if oldSourceAccount.Hidden || oldDestinationAccount.Hidden { + if oldSourceAccount.Hidden || (oldDestinationAccount != nil && oldDestinationAccount.Hidden) { return errs.ErrCannotAddTransactionToHiddenAccount } @@ -412,25 +500,28 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, updateCols = append(updateCols, "transaction_time") } - if transaction.SourceAccountId != oldTransaction.SourceAccountId { - updateCols = append(updateCols, "source_account_id") + if transaction.AccountId != oldTransaction.AccountId { + updateCols = append(updateCols, "account_id") } - if transaction.DestinationAccountId != oldTransaction.DestinationAccountId { - updateCols = append(updateCols, "destination_account_id") - } - - if transaction.SourceAmount != oldTransaction.SourceAmount { - if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - originalBalance := sourceAccount.Balance - oldTransaction.DestinationAmount - transaction.DestinationAmount = transaction.SourceAmount - originalBalance + if transaction.Amount != oldTransaction.Amount { + if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + originalBalance := sourceAccount.Balance - oldTransaction.RelatedAccountAmount + transaction.RelatedAccountAmount = transaction.Amount - originalBalance + updateCols = append(updateCols, "related_account_amount") } - updateCols = append(updateCols, "source_amount") + updateCols = append(updateCols, "amount") } - if transaction.DestinationAmount != oldTransaction.DestinationAmount { - updateCols = append(updateCols, "destination_amount") + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + if transaction.RelatedAccountId != oldTransaction.RelatedAccountId { + updateCols = append(updateCols, "related_account_id") + } + + if transaction.RelatedAccountAmount != oldTransaction.RelatedAccountAmount { + updateCols = append(updateCols, "related_account_amount") + } } if transaction.Comment != oldTransaction.Comment { @@ -453,6 +544,22 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return errs.ErrTransactionNotFound } + if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + relatedTransaction := s.GetRelatedTransferTransaction(transaction, transaction.RelatedId) + + if utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) != utils.GetUnixTimeFromTransactionTime(relatedTransaction.TransactionTime) { + return errs.ErrTooMuchTransactionInOneSecond + } + + updatedRows, err := sess.ID(relatedTransaction.TransactionId).Cols(updateCols...).Where("uid=? AND deleted=?", relatedTransaction.Uid, false).Update(relatedTransaction) + + if err != nil { + return err + } else if updatedRows < 1 { + return errs.ErrDatabaseOperationFailed + } + } + // Update transaction tag index if len(removeTagIds) > 0 { deletedRows, err := sess.Where("uid=?", transaction.Uid).In("tag_id", removeTagIds).Delete(&models.TransactionTagIndex{}) @@ -476,14 +583,14 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, } // Update account table - if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - if transaction.DestinationAccountId != oldTransaction.DestinationAccountId { + if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + if transaction.AccountId != oldTransaction.AccountId { return errs.ErrBalanceModificationTransactionCannotChangeAccountId } - if transaction.DestinationAmount != oldTransaction.DestinationAmount { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.DestinationAmount, transaction.DestinationAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", destinationAccount.Uid, false).Update(destinationAccount) + if transaction.RelatedAccountAmount != oldTransaction.RelatedAccountAmount { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.RelatedAccountAmount, transaction.RelatedAccountAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount) if err != nil { return err @@ -491,19 +598,19 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return errs.ErrDatabaseOperationFailed } } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_INCOME { + } else if oldTransaction.Type == models.TRANSACTION_DB_TYPE_INCOME { var oldAccountNewAmount int64 = 0 var newAccountNewAmount int64 = 0 - if transaction.DestinationAccountId == oldTransaction.DestinationAccountId { - oldAccountNewAmount = transaction.DestinationAmount - } else if transaction.DestinationAccountId != oldTransaction.DestinationAccountId { - newAccountNewAmount = transaction.DestinationAmount + if transaction.AccountId == oldTransaction.AccountId { + oldAccountNewAmount = transaction.Amount + } else if transaction.AccountId != oldTransaction.AccountId { + newAccountNewAmount = transaction.Amount } - if oldAccountNewAmount != oldTransaction.DestinationAmount { - oldDestinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(oldDestinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.DestinationAmount, oldAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldDestinationAccount.Uid, false).Update(oldDestinationAccount) + if oldAccountNewAmount != oldTransaction.Amount { + oldSourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(oldSourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.Amount, oldAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldSourceAccount.Uid, false).Update(oldSourceAccount) if err != nil { return err @@ -513,8 +620,8 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, } if newAccountNewAmount != 0 { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", newAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", destinationAccount.Uid, false).Update(destinationAccount) + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", newAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount) if err != nil { return err @@ -522,19 +629,19 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return errs.ErrDatabaseOperationFailed } } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE { + } else if oldTransaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE { var oldAccountNewAmount int64 = 0 var newAccountNewAmount int64 = 0 - if transaction.DestinationAccountId == oldTransaction.DestinationAccountId { - oldAccountNewAmount = transaction.DestinationAmount - } else if transaction.DestinationAccountId != oldTransaction.DestinationAccountId { - newAccountNewAmount = transaction.DestinationAmount + if transaction.AccountId == oldTransaction.AccountId { + oldAccountNewAmount = transaction.Amount + } else if transaction.AccountId != oldTransaction.AccountId { + newAccountNewAmount = transaction.Amount } - if oldAccountNewAmount != oldTransaction.DestinationAmount { - oldDestinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(oldDestinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)-(%d)", oldTransaction.DestinationAmount, oldAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldDestinationAccount.Uid, false).Update(oldDestinationAccount) + if oldAccountNewAmount != oldTransaction.Amount { + oldSourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(oldSourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)-(%d)", oldTransaction.Amount, oldAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldSourceAccount.Uid, false).Update(oldSourceAccount) if err != nil { return err @@ -544,8 +651,8 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, } if newAccountNewAmount != 0 { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", newAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", destinationAccount.Uid, false).Update(destinationAccount) + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", newAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount) if err != nil { return err @@ -553,19 +660,19 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return errs.ErrDatabaseOperationFailed } } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER { + } else if oldTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { var oldSourceAccountNewAmount int64 = 0 var newSourceAccountNewAmount int64 = 0 - if transaction.SourceAccountId == oldTransaction.SourceAccountId { - oldSourceAccountNewAmount = transaction.SourceAmount - } else if transaction.SourceAccountId != oldTransaction.SourceAccountId { - newSourceAccountNewAmount = transaction.SourceAmount + if transaction.AccountId == oldTransaction.AccountId { + oldSourceAccountNewAmount = transaction.Amount + } else if transaction.AccountId != oldTransaction.AccountId { + newSourceAccountNewAmount = transaction.Amount } - if oldSourceAccountNewAmount != oldTransaction.SourceAmount { + if oldSourceAccountNewAmount != oldTransaction.Amount { oldSourceAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(oldSourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)-(%d)", oldTransaction.SourceAmount, oldSourceAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldSourceAccount.Uid, false).Update(oldSourceAccount) + updatedRows, err := sess.ID(oldSourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)-(%d)", oldTransaction.Amount, oldSourceAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldSourceAccount.Uid, false).Update(oldSourceAccount) if err != nil { return err @@ -588,15 +695,15 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, var oldDestinationAccountNewAmount int64 = 0 var newDestinationAccountNewAmount int64 = 0 - if transaction.DestinationAccountId == oldTransaction.DestinationAccountId { - oldDestinationAccountNewAmount = transaction.DestinationAmount - } else if transaction.DestinationAccountId != oldTransaction.DestinationAccountId { - newDestinationAccountNewAmount = transaction.DestinationAmount + if transaction.RelatedAccountId == oldTransaction.RelatedAccountId { + oldDestinationAccountNewAmount = transaction.RelatedAccountAmount + } else if transaction.RelatedAccountId != oldTransaction.RelatedAccountId { + newDestinationAccountNewAmount = transaction.RelatedAccountAmount } - if oldDestinationAccountNewAmount != oldTransaction.DestinationAmount { + if oldDestinationAccountNewAmount != oldTransaction.RelatedAccountAmount { oldDestinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, err := sess.ID(oldDestinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.DestinationAmount, oldDestinationAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldDestinationAccount.Uid, false).Update(oldDestinationAccount) + updatedRows, err := sess.ID(oldDestinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)+(%d)", oldTransaction.RelatedAccountAmount, oldDestinationAccountNewAmount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", oldDestinationAccount.Uid, false).Update(oldDestinationAccount) if err != nil { return err @@ -615,6 +722,8 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction, return errs.ErrDatabaseOperationFailed } } + } else if oldTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + return errs.ErrTransactionTypeInvalid } return nil @@ -658,12 +767,12 @@ func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) e return err } - if sourceAccount.Hidden || destinationAccount.Hidden { + if sourceAccount.Hidden || (destinationAccount != nil && destinationAccount.Hidden) { return errs.ErrCannotDeleteTransactionInHiddenAccount } // Update transaction row to deleted - deletedRows, err := sess.ID(transactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel) + deletedRows, err := sess.ID(oldTransaction.TransactionId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel) if err != nil { return err @@ -671,37 +780,47 @@ func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) e return errs.ErrTransactionNotFound } + if oldTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || oldTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + deletedRows, err = sess.ID(oldTransaction.RelatedId).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 + } + } + // Update account table - if oldTransaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_INCOME { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_EXPENSE { - destinationAccount.UpdatedUnixTime = time.Now().Unix() - updatedRows, 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 updatedRows < 1 { - return errs.ErrDatabaseOperationFailed - } - } else if oldTransaction.Type == models.TRANSACTION_TYPE_TRANSFER { + if oldTransaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { 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) + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.RelatedAccountAmount)).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_DB_TYPE_INCOME { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.Amount)).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_DB_TYPE_EXPENSE { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", oldTransaction.Amount)).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_DB_TYPE_TRANSFER_OUT { + sourceAccount.UpdatedUnixTime = time.Now().Unix() + updatedSourceRows, err := sess.ID(sourceAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance+(%d)", oldTransaction.Amount)).Cols("updated_unix_time").Where("uid=? AND deleted=?", sourceAccount.Uid, false).Update(sourceAccount) if err != nil { return err @@ -710,32 +829,74 @@ func (s *TransactionService) DeleteTransaction(uid int64, transactionId int64) e } 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) + updatedDestinationRows, err := sess.ID(destinationAccount.AccountId).SetExpr("balance", fmt.Sprintf("balance-(%d)", oldTransaction.RelatedAccountAmount)).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 } + } else if oldTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + return errs.ErrTransactionTypeInvalid } return err }) } +func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *models.Transaction, relatedTransactionId int64) *models.Transaction { + var relatedType models.TransactionDbType + var relatedTransactionTime int64 + + if originalTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + relatedType = models.TRANSACTION_DB_TYPE_TRANSFER_IN + relatedTransactionTime = originalTransaction.TransactionTime + 1 + } else if originalTransaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + relatedType = models.TRANSACTION_DB_TYPE_TRANSFER_OUT + relatedTransactionTime = originalTransaction.TransactionTime - 1 + } else { + return nil + } + + relatedTransaction := &models.Transaction{ + TransactionId: relatedTransactionId, + Uid: originalTransaction.Uid, + Deleted: originalTransaction.Deleted, + Type: relatedType, + CategoryId: originalTransaction.CategoryId, + TransactionTime: relatedTransactionTime, + AccountId: originalTransaction.RelatedAccountId, + Amount: originalTransaction.RelatedAccountAmount, + RelatedId: originalTransaction.TransactionId, + RelatedAccountId: originalTransaction.AccountId, + RelatedAccountAmount: originalTransaction.Amount, + Comment: originalTransaction.Comment, + CreatedUnixTime: originalTransaction.CreatedUnixTime, + UpdatedUnixTime: originalTransaction.UpdatedUnixTime, + DeletedUnixTime: originalTransaction.DeletedUnixTime, + } + + return relatedTransaction +} + func (s *TransactionService) isAccountIdValid(transaction *models.Transaction) error { - 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 + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId { + return errs.ErrTransactionDestinationAccountCannotBeSet } - } else if transaction.Type == models.TRANSACTION_TYPE_TRANSFER { - if transaction.SourceAccountId == transaction.DestinationAccountId { + } else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME || + transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE { + if transaction.RelatedAccountId != 0 { + return errs.ErrTransactionDestinationAccountCannotBeSet + } else if transaction.RelatedAccountAmount != 0 { + return errs.ErrTransactionDestinationAmountCannotBeSet + } + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + if transaction.AccountId == transaction.RelatedAccountId { return errs.ErrTransactionSourceAndDestinationIdCannotBeEqual } + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + return errs.ErrTransactionTypeInvalid } else { return errs.ErrTransactionTypeInvalid } @@ -747,7 +908,7 @@ func (s *TransactionService) getAccountModels(sess *xorm.Session, transaction *m sourceAccount = &models.Account{} destinationAccount = &models.Account{} - has, err := sess.ID(transaction.SourceAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(sourceAccount) + has, err := sess.ID(transaction.AccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(sourceAccount) if err != nil { return nil, nil, err @@ -755,17 +916,32 @@ func (s *TransactionService) getAccountModels(sess *xorm.Session, transaction *m return nil, nil, errs.ErrSourceAccountNotFound } - if transaction.DestinationAccountId == transaction.SourceAccountId { - destinationAccount = sourceAccount - } else { - has, err = sess.ID(transaction.DestinationAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(destinationAccount) + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId { + return nil, nil, errs.ErrAccountIdInvalid + } else { + destinationAccount = sourceAccount + } + } else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME || transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE { + if transaction.RelatedAccountId != 0 { + return nil, nil, errs.ErrAccountIdInvalid + } - if err != nil { - return nil, nil, err - } else if !has { - return nil, nil, errs.ErrDestinationAccountNotFound + destinationAccount = nil + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + if transaction.RelatedAccountId <= 0 { + return nil, nil, errs.ErrAccountIdInvalid + } else { + has, err = sess.ID(transaction.RelatedAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(destinationAccount) + + if err != nil { + return nil, nil, err + } else if !has { + return nil, nil, errs.ErrDestinationAccountNotFound + } } } + return sourceAccount, destinationAccount, nil } @@ -773,10 +949,10 @@ func (s *TransactionService) getOldAccountModels(sess *xorm.Session, transaction oldSourceAccount = &models.Account{} oldDestinationAccount = &models.Account{} - if transaction.SourceAccountId == oldTransaction.SourceAccountId { + if transaction.AccountId == oldTransaction.AccountId { oldSourceAccount = sourceAccount } else { - has, err := sess.ID(oldTransaction.SourceAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldSourceAccount) + has, err := sess.ID(oldTransaction.AccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldSourceAccount) if err != nil { return nil, nil, err @@ -785,10 +961,10 @@ func (s *TransactionService) getOldAccountModels(sess *xorm.Session, transaction } } - if transaction.DestinationAccountId == oldTransaction.DestinationAccountId { + if transaction.RelatedAccountId == oldTransaction.RelatedAccountId { oldDestinationAccount = destinationAccount } else { - has, err := sess.ID(oldTransaction.DestinationAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldDestinationAccount) + has, err := sess.ID(oldTransaction.RelatedAccountId).Where("uid=? AND deleted=?", transaction.Uid, false).Get(oldDestinationAccount) if err != nil { return nil, nil, err @@ -800,8 +976,8 @@ func (s *TransactionService) getOldAccountModels(sess *xorm.Session, transaction } func (s *TransactionService) isCategoryValid(sess *xorm.Session, transaction *models.Transaction) error { - if transaction.Type == models.TRANSACTION_TYPE_MODIFY_BALANCE { - if transaction.CategoryId > 0 { + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + if transaction.CategoryId != 0 { return errs.ErrBalanceModificationTransactionCannotSetCategory } } else { @@ -818,9 +994,9 @@ func (s *TransactionService) isCategoryValid(sess *xorm.Session, transaction *mo return errs.ErrCannotUsePrimaryCategoryForTransaction } - if (transaction.Type == models.TRANSACTION_TYPE_INCOME && category.Type != models.CATEGORY_TYPE_INCOME) || - (transaction.Type == models.TRANSACTION_TYPE_EXPENSE && category.Type != models.CATEGORY_TYPE_EXPENSE) || - (transaction.Type == models.TRANSACTION_TYPE_TRANSFER && category.Type != models.CATEGORY_TYPE_TRANSFER) { + if (transaction.Type == models.TRANSACTION_DB_TYPE_INCOME && category.Type != models.CATEGORY_TYPE_INCOME) || + (transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE && category.Type != models.CATEGORY_TYPE_EXPENSE) || + ((transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN) && category.Type != models.CATEGORY_TYPE_TRANSFER) { return errs.ErrTransactionCategoryTypeInvalid } } diff --git a/src/locales/en.js b/src/locales/en.js index acca3e38..83f9adb8 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -337,9 +337,10 @@ export default { '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', + 'transaction destination account cannot be set': 'Cannot set destination account', + 'transaction destination amount cannot be set': 'Cannot set destination amount', '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', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index bf1e1820..a59a004e 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -335,11 +335,12 @@ export default { 'destination account not found': '目标账户不存在', 'account is in use and cannot be deleted': '账户正在被使用,无法删除', 'transaction id is invalid': '交易ID无效', - 'transaction not found': '交易不存', + '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': '源金额和目标金额不一致', + 'transaction destination account cannot be set': '不能设置目标账户', + 'transaction destination amount cannot be set': '不能设置目标金额', 'too much transaction in one second': '一秒钟内交易太多,请选择其他时间', 'balance modification transaction cannot set category': '您无法对修改余额的交易设置分类', 'balance modification transaction cannot change account id': '您无法对修改余额的交易修改账户ID', diff --git a/src/views/mobile/transactions/Edit.vue b/src/views/mobile/transactions/Edit.vue index d35335c4..ca0b39c1 100644 --- a/src/views/mobile/transactions/Edit.vue +++ b/src/views/mobile/transactions/Edit.vue @@ -532,9 +532,9 @@ export default { type: self.transaction.type, time: self.transaction.unixTime, sourceAccountId: self.transaction.sourceAccountId, - destinationAccountId: self.transaction.sourceAccountId, sourceAmount: self.transaction.sourceAmount, - destinationAmount: self.transaction.sourceAmount, + destinationAccountId: '0', + destinationAmount: 0, tagIds: self.transaction.tagIds, comment: self.transaction.comment };