diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index ff726366..c19c89b9 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -17,6 +17,7 @@ type TransactionsApi struct { transactionCategories *services.TransactionCategoryService transactionTags *services.TransactionTagService accounts *services.AccountService + users *services.UserService } // Initialize a transaction api singleton instance @@ -26,6 +27,7 @@ var ( transactionCategories: services.TransactionCategories, transactionTags: services.TransactionTags, accounts: services.Accounts, + users: services.Users, } ) @@ -40,6 +42,16 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, } uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get user, because %s", err.Error()) + } + + return nil, errs.ErrUserNotFound + } + var allCategoryIds []int64 if transactionListReq.CategoryId > 0 { @@ -119,7 +131,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{}, transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) } - transactionEditable := transaction.IsEditable(allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) + transactionEditable := transaction.IsEditable(user, transactionListReq.UtcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResps.Items[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) } @@ -144,6 +156,16 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac } uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get user, because %s", err.Error()) + } + + return nil, errs.ErrUserNotFound + } + var allCategoryIds []int64 if transactionListReq.CategoryId > 0 { @@ -213,7 +235,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId) } - transactionEditable := transaction.IsEditable(allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) + transactionEditable := transaction.IsEditable(user, transactionListReq.UtcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResps[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) } @@ -232,6 +254,16 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * } uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get user, because %s", err.Error()) + } + + return nil, errs.ErrUserNotFound + } + transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionGetReq.Id) if err != nil { @@ -272,7 +304,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, * return nil, errs.ErrOperationFailed } - transactionEditable := transaction.IsEditable(accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId]) + transactionEditable := transaction.IsEditable(user, transactionGetReq.UtcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId]) transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) @@ -320,7 +352,22 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{} } uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to get user, because %s", err.Error()) + } + + return nil, errs.ErrUserNotFound + } + transaction := a.createNewTransactionModel(uid, &transactionCreateReq) + transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset) + + if !transactionEditable { + return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime + } err = a.transactions.CreateTransaction(transaction, tagIds) @@ -331,7 +378,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{} log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId) - transactionResp := transaction.ToTransactionInfoResponse(nil, true) + transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable) return transactionResp, nil } @@ -354,6 +401,16 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} } uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get user, because %s", err.Error()) + } + + return nil, errs.ErrUserNotFound + } + transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionModifyReq.Id) if err != nil { @@ -409,6 +466,12 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} addTransactionTagIds = tagIds } + transactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset) + + if !transactionEditable { + return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime + } + err = a.transactions.ModifyTransaction(newTransaction, addTransactionTagIds, removeTransactionTagIds) if err != nil { @@ -419,7 +482,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{} log.InfofWithRequestId(c, "[transactions.TransactionModifyHandler] user \"uid:%d\" has updated transaction \"id:%d\" successfully", uid, transactionModifyReq.Id) newTransaction.Type = transaction.Type - newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, true) + newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, transactionEditable) return newTransactionResp, nil } diff --git a/pkg/api/users.go b/pkg/api/users.go index 645d334a..0f60a8e9 100644 --- a/pkg/api/users.go +++ b/pkg/api/users.go @@ -45,12 +45,13 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro userRegisterReq.Nickname = strings.TrimSpace(userRegisterReq.Nickname) user := &models.User{ - Username: userRegisterReq.Username, - Email: userRegisterReq.Email, - Nickname: userRegisterReq.Nickname, - Password: userRegisterReq.Password, - DefaultCurrency: userRegisterReq.DefaultCurrency, - FirstDayOfWeek: userRegisterReq.FirstDayOfWeek, + Username: userRegisterReq.Username, + Email: userRegisterReq.Email, + Nickname: userRegisterReq.Nickname, + Password: userRegisterReq.Password, + DefaultCurrency: userRegisterReq.DefaultCurrency, + FirstDayOfWeek: userRegisterReq.FirstDayOfWeek, + TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL, } err = a.users.CreateUser(user) @@ -167,6 +168,14 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs userNew.FirstDayOfWeek = models.WEEKDAY_INVALID } + if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope { + user.TransactionEditScope = *userUpdateReq.TransactionEditScope + userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope + anythingUpdate = true + } else { + userNew.TransactionEditScope = models.TRANSACTION_EDIT_SCOPE_INVALID + } + if !anythingUpdate { return nil, errs.ErrNothingWillBeUpdated } diff --git a/pkg/errs/transaction.go b/pkg/errs/transaction.go index dada690e..a924deb1 100644 --- a/pkg/errs/transaction.go +++ b/pkg/errs/transaction.go @@ -18,4 +18,6 @@ var ( 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") + ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction with this transaction time") + ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot edit transaction with this transaction time") ) diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index d8820bcc..46d5c464 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -62,6 +62,7 @@ type TransactionCreateRequest struct { DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` TagIds []string `json:"tagIds"` Comment string `json:"comment" binding:"max=255"` + UtcOffset int `json:"utcOffset" binding:"required,min=-720,max=840"` } // TransactionModifyRequest represents all parameters of transaction modification request @@ -75,6 +76,7 @@ type TransactionModifyRequest struct { DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` TagIds []string `json:"tagIds"` Comment string `json:"comment" binding:"max=255"` + UtcOffset int `json:"utcOffset" binding:"required,min=-720,max=840"` } // TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request @@ -86,6 +88,7 @@ type TransactionListByMaxTimeRequest struct { MaxTime int64 `form:"max_time" binding:"min=0"` MinTime int64 `form:"min_time" binding:"min=0"` Count int `form:"count" binding:"required,min=1,max=50"` + UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"` } // TransactionListInMonthByPageRequest represents all parameters of transaction listing by month request @@ -98,11 +101,13 @@ type TransactionListInMonthByPageRequest struct { Keyword string `form:"keyword"` Page int `form:"page" binding:"required,min=1"` Count int `form:"count" binding:"required,min=1,max=50"` + UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"` } // TransactionGetRequest represents all parameters of transaction getting request type TransactionGetRequest struct { - Id int64 `form:"id,string" binding:"required,min=1"` + Id int64 `form:"id,string" binding:"required,min=1"` + UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"` } // TransactionDeleteRequest represents all parameters of transaction deleting request @@ -133,7 +138,11 @@ type TransactionInfoPageWrapperResponse struct { } // IsEditable returns whether this transaction can be edited -func (t *Transaction) IsEditable(account *Account, relatedAccount *Account) bool { +func (t *Transaction) IsEditable(currentUser *User, utcOffset int, account *Account, relatedAccount *Account) bool { + if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) { + return false + } + if account == nil || account.Hidden { return false } diff --git a/pkg/models/user.go b/pkg/models/user.go index c9fb8fcf..914f42bf 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -1,5 +1,11 @@ package models +import ( + "time" + + "github.com/mayswind/lab/pkg/utils" +) + // UserType represents user type type UserType byte @@ -25,34 +31,51 @@ const ( WEEKDAY_INVALID WeekDay = 255 ) +// TransactionEditScope represents the scope which transaction can be edited +type TransactionEditScope byte + +// Historical Transaction Edit Scopes +const ( + TRANSACTION_EDIT_SCOPE_NONE TransactionEditScope = 0 + TRANSACTION_EDIT_SCOPE_ALL TransactionEditScope = 1 + TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER TransactionEditScope = 2 + TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER TransactionEditScope = 3 + TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER TransactionEditScope = 4 + TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER TransactionEditScope = 5 + TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER TransactionEditScope = 6 + TRANSACTION_EDIT_SCOPE_INVALID TransactionEditScope = 255 +) + // User represents user data stored in database type User struct { - Uid int64 `xorm:"PK"` - Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"` - Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"` - Nickname string `xorm:"VARCHAR(64) NOT NULL"` - Password string `xorm:"VARCHAR(64) NOT NULL"` - Salt string `xorm:"VARCHAR(10) NOT NULL"` - Rands string `xorm:"VARCHAR(10) NOT NULL"` - Type UserType `xorm:"TINYINT NOT NULL"` - DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"` - FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"` - IsAdmin bool `xorm:"NOT NULL"` - Deleted bool `xorm:"NOT NULL"` - EmailVerified bool `xorm:"NOT NULL"` - CreatedUnixTime int64 - UpdatedUnixTime int64 - DeletedUnixTime int64 - LastLoginUnixTime int64 + Uid int64 `xorm:"PK"` + Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"` + Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"` + Nickname string `xorm:"VARCHAR(64) NOT NULL"` + Password string `xorm:"VARCHAR(64) NOT NULL"` + Salt string `xorm:"VARCHAR(10) NOT NULL"` + Rands string `xorm:"VARCHAR(10) NOT NULL"` + Type UserType `xorm:"TINYINT NOT NULL"` + DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"` + FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"` + TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"` + IsAdmin bool `xorm:"NOT NULL"` + Deleted bool `xorm:"NOT NULL"` + EmailVerified bool `xorm:"NOT NULL"` + CreatedUnixTime int64 + UpdatedUnixTime int64 + DeletedUnixTime int64 + LastLoginUnixTime int64 } // UserBasicInfo represents a view-object of user basic info type UserBasicInfo struct { - Username string `json:"username"` - Email string `json:"email"` - Nickname string `json:"nickname"` - DefaultCurrency string `json:"defaultCurrency"` - FirstDayOfWeek WeekDay `json:"firstDayOfWeek"` + Username string `json:"username"` + Email string `json:"email"` + Nickname string `json:"nickname"` + DefaultCurrency string `json:"defaultCurrency"` + FirstDayOfWeek WeekDay `json:"firstDayOfWeek"` + TransactionEditScope TransactionEditScope `json:"transactionEditScope"` } // UserLoginRequest represents all parameters of user login request @@ -73,12 +96,13 @@ type UserRegisterRequest struct { // UserProfileUpdateRequest represents all parameters of user updating profile request type UserProfileUpdateRequest struct { - Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"` - Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"` - Password string `json:"password" binding:"omitempty,min=6,max=128"` - OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"` - DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"` - FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"` + Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"` + Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"` + Password string `json:"password" binding:"omitempty,min=6,max=128"` + OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"` + DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"` + FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"` + TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"` } // UserProfileUpdateResponse represents the data returns to frontend after updating profile @@ -89,35 +113,80 @@ type UserProfileUpdateResponse struct { // UserProfileResponse represents a view-object of user profile type UserProfileResponse struct { - Username string `json:"username"` - Email string `json:"email"` - Nickname string `json:"nickname"` - Type UserType `json:"type"` - DefaultCurrency string `json:"defaultCurrency"` - FirstDayOfWeek WeekDay `json:"firstDayOfWeek"` - LastLoginAt int64 `json:"lastLoginAt"` + Username string `json:"username"` + Email string `json:"email"` + Nickname string `json:"nickname"` + Type UserType `json:"type"` + DefaultCurrency string `json:"defaultCurrency"` + FirstDayOfWeek WeekDay `json:"firstDayOfWeek"` + TransactionEditScope TransactionEditScope `json:"transactionEditScope"` + LastLoginAt int64 `json:"lastLoginAt"` +} + +// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time +func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, utcOffset int) bool { + if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE { + return false + } else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL { + return true + } + + now := time.Now() + + transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transactionTime) + + if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER { + return transactionUnixTime >= now.Unix()-24*60*60 + } + + _, serverUtcOffset := now.Zone() + serverTodayFirstUnixTime := now.Unix() - int64(now.Hour()*60*60+now.Minute()*60+now.Second()) + clientTodayFirstUnixTime := serverTodayFirstUnixTime + int64(utcOffset*60-serverUtcOffset) + + if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER { + return transactionUnixTime >= clientTodayFirstUnixTime + } else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER { + dayOfWeek := int(now.Weekday()) - int(u.FirstDayOfWeek) + + if dayOfWeek < 0 { + dayOfWeek += 7 + } + + clientWeekFirstUnixTime := clientTodayFirstUnixTime - int64(dayOfWeek*24*60*60) + return transactionUnixTime >= clientWeekFirstUnixTime + } else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER { + clientMonthFirstUnixTime := clientTodayFirstUnixTime - int64((now.Day()-1)*24*60*60) + return transactionUnixTime >= clientMonthFirstUnixTime + } else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER { + clientYearFirstUnixTime := clientTodayFirstUnixTime - int64((now.YearDay()-1)*24*60*60) + return transactionUnixTime >= clientYearFirstUnixTime + } + + return false } // ToUserBasicInfo returns a user basic view-object according to database model func (u *User) ToUserBasicInfo() *UserBasicInfo { return &UserBasicInfo{ - Username: u.Username, - Email: u.Email, - Nickname: u.Nickname, - DefaultCurrency: u.DefaultCurrency, - FirstDayOfWeek: u.FirstDayOfWeek, + Username: u.Username, + Email: u.Email, + Nickname: u.Nickname, + DefaultCurrency: u.DefaultCurrency, + FirstDayOfWeek: u.FirstDayOfWeek, + TransactionEditScope: u.TransactionEditScope, } } // ToUserProfileResponse returns a user profile view-object according to database model func (u *User) ToUserProfileResponse() *UserProfileResponse { return &UserProfileResponse{ - Username: u.Username, - Email: u.Email, - Nickname: u.Nickname, - Type: u.Type, - DefaultCurrency: u.DefaultCurrency, - FirstDayOfWeek: u.FirstDayOfWeek, - LastLoginAt: u.LastLoginUnixTime, + Username: u.Username, + Email: u.Email, + Nickname: u.Nickname, + Type: u.Type, + DefaultCurrency: u.DefaultCurrency, + FirstDayOfWeek: u.FirstDayOfWeek, + TransactionEditScope: u.TransactionEditScope, + LastLoginAt: u.LastLoginUnixTime, } } diff --git a/pkg/services/users.go b/pkg/services/users.go index 6ebbb044..73db01ca 100644 --- a/pkg/services/users.go +++ b/pkg/services/users.go @@ -198,6 +198,10 @@ func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err updateCols = append(updateCols, "first_day_of_week") } + if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER { + updateCols = append(updateCols, "transaction_edit_scope") + } + user.UpdatedUnixTime = now updateCols = append(updateCols, "updated_unix_time") diff --git a/src/lib/services.js b/src/lib/services.js index f970c80f..d09f3689 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -2,6 +2,7 @@ import axios from 'axios'; import api from "../consts/api.js"; import userState from "./userstate.js"; +import utils from "./utils.js"; let needBlockRequest = false; let blockedRequests = []; @@ -130,14 +131,15 @@ export default { getProfile: () => { return axios.get('v1/users/profile/get.json'); }, - updateProfile: ({ email, nickname, password, oldPassword, defaultCurrency, firstDayOfWeek }) => { + updateProfile: ({ email, nickname, password, oldPassword, defaultCurrency, firstDayOfWeek, transactionEditScope }) => { return axios.post('v1/users/profile/update.json', { email, nickname, password, oldPassword, defaultCurrency, - firstDayOfWeek + firstDayOfWeek, + transactionEditScope }); }, get2FAStatus: () => { @@ -249,12 +251,14 @@ export default { }); }, getTransactions: ({ maxTime, minTime, type, categoryId, accountId, keyword }) => { - return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&count=50`); + const utcOffset = utils.getTimezoneOffsetMinutes(); + return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&count=50&utc_offset=${utcOffset}`); }, getTransaction: ({ id }) => { - return axios.get('v1/transactions/get.json?id=' + id); + const utcOffset = utils.getTimezoneOffsetMinutes(); + return axios.get(`v1/transactions/get.json?id=${id}&utc_offset=${utcOffset}`); }, - addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, tagIds, comment }) => { + addTransaction: ({ type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, tagIds, comment, utcOffset }) => { return axios.post('v1/transactions/add.json', { type, categoryId, @@ -264,10 +268,11 @@ export default { sourceAmount, destinationAmount, tagIds, - comment + comment, + utcOffset }); }, - modifyTransaction: ({ id, type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, tagIds, comment }) => { + modifyTransaction: ({ id, type, categoryId, time, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, tagIds, comment, utcOffset }) => { return axios.post('v1/transactions/modify.json', { id, type, @@ -278,7 +283,8 @@ export default { sourceAmount, destinationAmount, tagIds, - comment + comment, + utcOffset }); }, deleteTransaction: ({ id }) => { diff --git a/src/lib/utils.js b/src/lib/utils.js index b7c7e992..a40e93d5 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -42,6 +42,22 @@ function getTimezoneOffset(timezone) { } } +function getTimezoneOffsetMinutes(timezone) { + const offset = getTimezoneOffset(timezone); + + if (!offset) { + return 0; + } + + const parts = offset.split(':'); + + if (parts.length !== 2) { + return 0; + } + + return parseInt(parts[0]) * 60 + parseInt(parts[1]); +} + function getCurrentUnixTime() { return moment().unix(); } @@ -571,6 +587,7 @@ export default { isNumber, isBoolean, getTimezoneOffset, + getTimezoneOffsetMinutes, getCurrentUnixTime, parseDateFromUnixTime, formatUnixTime, diff --git a/src/locales/en.js b/src/locales/en.js index 6c724431..7c6550d2 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -546,6 +546,8 @@ export default { 'cannot add transaction to hidden account': 'You cannot add transaction to an hidden account', 'cannot modify transaction of hidden account': 'You cannot modify transaction of an hidden account', 'cannot delete transaction in hidden account': 'You cannot delete transaction in an hidden account', + 'cannot add transaction with this transaction time': 'You cannot add transaction with this transaction time', + 'cannot modify transaction with this transaction time ': 'You cannot modify this transaction with this transaction time', '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', @@ -571,6 +573,7 @@ export default { 'oldPassword': 'Current Password', 'defaultCurrency': 'Default Currency', 'firstDayOfWeek': 'First Day of Week', + 'transactionEditScope': 'Transaction Edit Scope', 'name': 'Name', 'category': 'Category', 'type': 'Type', @@ -678,6 +681,12 @@ export default { 'Your nickname': 'Your nickname', 'Default Currency': 'Default Currency', 'First Day of Week': 'First Day of Week', + 'Transaction Edit Scope': 'Transaction Edit Scope', + 'Today or later': 'Today or later', + 'Recent 24 hours or later': 'Recent 24 hours or later', + 'This week or later': 'This week or later', + 'This month or later': 'This month or later', + 'This year or later': 'This year or later', 'Log In': 'Log In', 'Don\'t have an account?': 'Don\'t have an account?', 'Create an account': 'Create an account', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 82153b11..035854cf 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -546,6 +546,8 @@ export default { 'cannot add transaction to hidden account': '您不能在隐藏账户中添加交易', 'cannot modify transaction of hidden account': '您不能修改隐藏账户中的交易', 'cannot delete transaction in hidden account': '您不能删除隐藏账户中的交易', + 'cannot add transaction with this transaction time': '您不能添加有该交易时间的交易', + 'cannot modify transaction with this transaction time ': '您不能修改有该交易时间的交易', 'transaction category id is invalid': '交易分类ID无效', 'transaction category not found': '交易分类不存在', 'transaction category type is invalid': '交易分类类型无效', @@ -571,6 +573,7 @@ export default { 'oldPassword': '当前密码', 'defaultCurrency': '默认货币', 'firstDayOfWeek': '每周第一天', + 'transactionEditScope': '交易编辑范围', 'name': '名称', 'category': '分类', 'type': '类型', @@ -678,6 +681,12 @@ export default { 'Your nickname': '你的昵称', 'Default Currency': '默认货币', 'First Day of Week': '每周第一天', + 'Transaction Edit Scope': '交易编辑范围', + 'Today or later': '今天或更晚', + 'Recent 24 hours or later': '最近24小时或更晚', + 'This week or later': '本周或更晚', + 'This month or later': '本月或更晚', + 'This year or later': '今年或更晚', 'Log In': '登录', 'Don\'t have an account?': '还没有账号?', 'Create an account': '创建新账号', diff --git a/src/store/user.js b/src/store/user.js index 4618c53a..8e3f0425 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -236,7 +236,8 @@ export function updateUserProfile(context, { profile, currentPassword }) { email: profile.email, nickname: profile.nickname, defaultCurrency: profile.defaultCurrency, - firstDayOfWeek: profile.firstDayOfWeek + firstDayOfWeek: profile.firstDayOfWeek, + transactionEditScope: profile.transactionEditScope }).then(response => { const data = response.data; diff --git a/src/views/mobile/transactions/Edit.vue b/src/views/mobile/transactions/Edit.vue index 77701543..f8ffbad2 100644 --- a/src/views/mobile/transactions/Edit.vue +++ b/src/views/mobile/transactions/Edit.vue @@ -662,7 +662,8 @@ export default { destinationAccountId: '0', destinationAmount: 0, tagIds: self.transaction.tagIds, - comment: self.transaction.comment + comment: self.transaction.comment, + utcOffset: self.$utilities.getTimezoneOffsetMinutes() }; if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Expense) { diff --git a/src/views/mobile/users/UserProfile.vue b/src/views/mobile/users/UserProfile.vue index de14d35b..42090b0e 100644 --- a/src/views/mobile/users/UserProfile.vue +++ b/src/views/mobile/users/UserProfile.vue @@ -86,6 +86,21 @@ + + + + @@ -112,13 +127,15 @@ export default { email: '', nickname: '', defaultCurrency: '', - firstDayOfWeek: 0 + firstDayOfWeek: 0, + transactionEditScope: 1 }, oldProfile: { email: '', nickname: '', defaultCurrency: '', - firstDayOfWeek: 0 + firstDayOfWeek: 0, + transactionEditScope: 1 }, currentPassword: '', loading: true, @@ -147,7 +164,8 @@ export default { this.newProfile.email === this.oldProfile.email && this.newProfile.nickname === this.oldProfile.nickname && this.newProfile.defaultCurrency === this.oldProfile.defaultCurrency && - this.newProfile.firstDayOfWeek === this.oldProfile.firstDayOfWeek) { + this.newProfile.firstDayOfWeek === this.oldProfile.firstDayOfWeek && + this.newProfile.transactionEditScope === this.oldProfile.transactionEditScope) { return 'Nothing has been modified'; } else if (!this.newProfile.password && this.newProfile.confirmPassword) { return 'Password cannot be empty'; @@ -181,11 +199,13 @@ export default { self.oldProfile.nickname = profile.nickname; self.oldProfile.defaultCurrency = profile.defaultCurrency; self.oldProfile.firstDayOfWeek = profile.firstDayOfWeek; + self.oldProfile.transactionEditScope = profile.transactionEditScope; self.newProfile.email = self.oldProfile.email self.newProfile.nickname = self.oldProfile.nickname; self.newProfile.defaultCurrency = self.oldProfile.defaultCurrency; self.newProfile.firstDayOfWeek = self.oldProfile.firstDayOfWeek; + self.newProfile.transactionEditScope = self.oldProfile.transactionEditScope; self.loading = false; }).catch(error => {