support last reconciled time for account

This commit is contained in:
MaysWind
2026-05-07 01:17:00 +08:00
parent 39ee47e05a
commit de132dd7fd
43 changed files with 648 additions and 110 deletions
+1
View File
@@ -953,6 +953,7 @@ func printUserInfo(user *models.User) {
fmt.Printf("[Password] %s\n", user.Password)
fmt.Printf("[Salt] %s\n", user.Salt)
fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
fmt.Printf("[UseLastReconciledTime] %t\n", user.UseLastReconciledTime)
fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
fmt.Printf("[Language] %s\n", user.Language)
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
+9 -5
View File
@@ -766,6 +766,7 @@ func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models
func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account, isSubAccount bool) *models.Account {
newAccountExtend := &models.AccountExtend{}
newAccountExtend.LastReconciledTime = accountModifyReq.LastReconciledTime
if !isSubAccount && accountModifyReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
newAccountExtend.CreditCardStatementDate = &accountModifyReq.CreditCardStatementDate
@@ -793,14 +794,17 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
return newAccount
}
if (newAccount.Extend != nil && oldAccount.Extend == nil) ||
(newAccount.Extend == nil && oldAccount.Extend != nil) {
oldAccountExtend := oldAccount.Extend
if (newAccountExtend.LastReconciledTime != nil && (oldAccountExtend == nil || oldAccountExtend.LastReconciledTime == nil)) ||
(newAccountExtend.LastReconciledTime == nil && oldAccountExtend != nil && oldAccountExtend.LastReconciledTime != nil) ||
(newAccountExtend.LastReconciledTime != nil && oldAccountExtend != nil && oldAccountExtend.LastReconciledTime != nil && *newAccountExtend.LastReconciledTime != *oldAccountExtend.LastReconciledTime) {
return newAccount
}
oldAccountExtend := oldAccount.Extend
if newAccountExtend.CreditCardStatementDate != oldAccountExtend.CreditCardStatementDate {
if (newAccountExtend.CreditCardStatementDate != nil && (oldAccountExtend == nil || oldAccountExtend.CreditCardStatementDate == nil)) ||
(newAccountExtend.CreditCardStatementDate == nil && oldAccountExtend != nil && oldAccountExtend.CreditCardStatementDate != nil) ||
(newAccountExtend.CreditCardStatementDate != nil && oldAccountExtend != nil && oldAccountExtend.CreditCardStatementDate != nil && *newAccountExtend.CreditCardStatementDate != *oldAccountExtend.CreditCardStatementDate) {
return newAccount
}
+1 -1
View File
@@ -166,7 +166,7 @@ func (a *ForgetPasswordsApi) UserResetPasswordHandler(c *core.WebContext) (any,
Password: request.Password,
}
_, _, err = a.users.UpdateUser(c, userNew, false)
_, _, err = a.users.UpdateUser(c, userNew, false, false)
if err != nil {
log.Errorf(c, "[forget_passwords.UserResetPasswordHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
+129 -16
View File
@@ -1075,7 +1075,15 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
}
transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP())
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, []*models.Transaction{transaction})
if err != nil {
log.Errorf(c, "[transactions.TransactionCreateHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
@@ -1268,8 +1276,15 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
return nil, errs.ErrNothingWillBeUpdated
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, clientTimezone)
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, []*models.Transaction{transaction, newTransaction})
if err != nil {
log.Errorf(c, "[transactions.TransactionModifyHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, clientTimezone, allUsedAccounts[newTransaction.AccountId], allUsedAccounts[newTransaction.RelatedAccountId])
if !transactionEditable || !newTransactionEditable {
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
@@ -1402,6 +1417,13 @@ func (a *TransactionsApi) TransactionBatchUpdateCategoriesHandler(c *core.WebCon
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, transactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionBatchUpdateCategoriesHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allTransactionIds := make([]int64, 0, len(transactions))
for i := 0; i < len(transactions); i++ {
@@ -1412,7 +1434,7 @@ func (a *TransactionsApi) TransactionBatchUpdateCategoriesHandler(c *core.WebCon
return nil, errs.ErrTransactionTypeInvalid
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionBatchUpdateCategoriesHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
@@ -1548,9 +1570,19 @@ func (a *TransactionsApi) TransactionBatchUpdateAccountsHandler(c *core.WebConte
return nil, errs.ErrCannotMoveTransactionBetweenAccountsWithDifferentCurrencies
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
newSourceAccount := accountMap[transaction.AccountId]
newDestinationAccount := accountMap[transaction.RelatedAccountId]
if !transactionEditable {
if !transactionBatchUpdateReq.IsDestinationAccount && transaction.AccountId != account.AccountId {
newSourceAccount = account
} else if transactionBatchUpdateReq.IsDestinationAccount && transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT && transaction.RelatedAccountId != account.AccountId {
newDestinationAccount = account
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
newTransactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, newSourceAccount, newDestinationAccount)
if !transactionEditable || !newTransactionEditable {
log.Warnf(c, "[transactions.TransactionBatchUpdateAccountsHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
}
@@ -1651,6 +1683,13 @@ func (a *TransactionsApi) TransactionBatchAddTagsHandler(c *core.WebContext) (an
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, transactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionBatchAddTagsHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionTagIndexes, err := a.transactionTags.GetAllTagIdsOfTransactions(c, uid, transactionIds)
if err != nil {
@@ -1668,7 +1707,7 @@ func (a *TransactionsApi) TransactionBatchAddTagsHandler(c *core.WebContext) (an
return nil, errs.ErrTransactionTypeInvalid
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionBatchAddTagsHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
@@ -1769,6 +1808,13 @@ func (a *TransactionsApi) TransactionBatchRemoveTagsHandler(c *core.WebContext)
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, transactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionBatchRemoveTagsHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allTransactionIds := make([]int64, 0, len(transactions))
for i := 0; i < len(transactions); i++ {
@@ -1779,7 +1825,7 @@ func (a *TransactionsApi) TransactionBatchRemoveTagsHandler(c *core.WebContext)
return nil, errs.ErrTransactionTypeInvalid
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionBatchRemoveTagsHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
@@ -1842,6 +1888,13 @@ func (a *TransactionsApi) TransactionBatchClearTagsHandler(c *core.WebContext) (
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, transactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionBatchClearTagsHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allTransactionIds := make([]int64, 0, len(transactions))
for i := 0; i < len(transactions); i++ {
@@ -1852,7 +1905,7 @@ func (a *TransactionsApi) TransactionBatchClearTagsHandler(c *core.WebContext) (
return nil, errs.ErrTransactionTypeInvalid
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionBatchClearTagsHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
@@ -1970,7 +2023,14 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
return nil, errs.ErrTransactionTypeInvalid
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, []*models.Transaction{transaction})
if err != nil {
log.Errorf(c, "[transactions.TransactionDeleteHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime
@@ -2033,13 +2093,20 @@ func (a *TransactionsApi) TransactionBatchDeleteHandler(c *core.WebContext) (any
return nil, errs.Or(err, errs.ErrOperationFailed)
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, transactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionBatchDeleteHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
for i := 0; i < len(transactions); i++ {
transaction := transactions[i]
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionBatchUpdateCategoriesHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
log.Warnf(c, "[transactions.TransactionBatchDeleteHandler] transaction \"id:%d\" is not editable for user \"uid:%d\"", transaction.TransactionId, uid)
return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime
}
}
@@ -2470,13 +2537,24 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
for i := 0; i < len(transactionImportReq.Transactions); i++ {
transactionCreateReq := transactionImportReq.Transactions[i]
transaction := a.createNewTransactionModel(uid, transactionCreateReq, c.ClientIP())
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
newTransactions[i] = transaction
}
allUsedAccounts, err := a.getTransactionUsedAccounts(c, uid, newTransactions)
if err != nil {
log.Errorf(c, "[transactions.TransactionImportHandler] failed to get transaction used accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
for i := 0; i < len(newTransactions); i++ {
transaction := newTransactions[i]
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone, allUsedAccounts[transaction.AccountId], allUsedAccounts[transaction.RelatedAccountId])
if !transactionEditable {
log.Warnf(c, "[transactions.TransactionImportHandler] transaction \"index:%d\" is not editable for user \"uid:%d\"", i, uid)
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
}
newTransactions[i] = transaction
}
err = a.transactions.BatchCreateTransactions(c, user.Uid, newTransactions, newTransactionTagIdsMap, func(currentProcess float64) {
@@ -2708,6 +2786,41 @@ func (a *TransactionsApi) getTransactionEssentialDataByTransactionIds(c *core.We
return accountMap, categoryMap, tagMap, allTransactionTagIds, pictureInfoMap, nil
}
func (a *TransactionsApi) getTransactionUsedAccounts(c *core.WebContext, uid int64, transactions []*models.Transaction) (map[int64]*models.Account, error) {
accountIds := make([]int64, 0, len(transactions)*2)
for i := 0; i < len(transactions); i++ {
accountIds = append(accountIds, transactions[i].AccountId)
if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
accountIds = append(accountIds, transactions[i].RelatedAccountId)
}
}
accountMap, err := a.accounts.GetAccountsByAccountIds(c, uid, utils.ToUniqueInt64Slice(accountIds))
if err != nil {
log.Errorf(c, "[transactions.getTransactionUsedAccounts] failed to get accounts for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
for i := 0; i < len(transactions); i++ {
if _, exists := accountMap[transactions[i].AccountId]; !exists {
log.Warnf(c, "[transactions.getTransactionUsedAccounts] account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transactions[i].TransactionId, uid)
return nil, errs.ErrSourceAccountNotFound
}
if transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN || transactions[i].Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
if _, exists := accountMap[transactions[i].RelatedAccountId]; !exists {
log.Warnf(c, "[transactions.getTransactionUsedAccounts] related account of transaction \"id:%d\" does not exist for user \"uid:%d\"", transactions[i].TransactionId, uid)
return nil, errs.ErrDestinationAccountNotFound
}
}
}
return accountMap, nil
}
func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, allAccounts map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTransactionTagIds map[int64][]int64, pictureInfoMap map[int64][]*models.TransactionPictureInfo, clientTimezone *time.Location, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) {
result := make(models.TransactionInfoResponseSlice, len(transactions))
+12 -1
View File
@@ -256,6 +256,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
userUpdateReq.Nickname = strings.TrimSpace(userUpdateReq.Nickname)
modifyProfileBasicInfo := false
modifyUseLastReconciledTime := false
anythingUpdate := false
userNew := &models.User{
Uid: user.Uid,
@@ -317,6 +318,16 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
anythingUpdate = true
}
if userUpdateReq.UseLastReconciledTime != nil && *userUpdateReq.UseLastReconciledTime != user.UseLastReconciledTime {
user.UseLastReconciledTime = *userUpdateReq.UseLastReconciledTime
userNew.UseLastReconciledTime = *userUpdateReq.UseLastReconciledTime
modifyProfileBasicInfo = true
modifyUseLastReconciledTime = true
anythingUpdate = true
} else {
modifyUseLastReconciledTime = false
}
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
@@ -531,7 +542,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro
return nil, errs.ErrNothingWillBeUpdated
}
keyProfileUpdated, emailSetToUnverified, err := a.users.UpdateUser(c, userNew, modifyUserLanguage)
keyProfileUpdated, emailSetToUnverified, err := a.users.UpdateUser(c, userNew, modifyUserLanguage, modifyUseLastReconciledTime)
if err != nil {
log.Errorf(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
+1 -1
View File
@@ -178,7 +178,7 @@ func (h *mcpAddTransactionToolHandler) Handle(c *core.WebContext, callToolReq *M
return nil, nil, err
}
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60))
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60), sourceAccount, destinationAccount)
if !transactionEditable {
return nil, nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
+19 -1
View File
@@ -90,7 +90,8 @@ type Account struct {
// AccountExtend represents account extend data stored in database
type AccountExtend struct {
CreditCardStatementDate *int `json:"creditCardStatementDate"`
LastReconciledTime *int64 `json:"lastReconciledTime"`
CreditCardStatementDate *int `json:"creditCardStatementDate"`
}
// AccountCreateRequest represents all parameters of account creation request
@@ -119,6 +120,7 @@ type AccountModifyRequest struct {
Currency *string `json:"currency" binding:"omitempty,len=3,validCurrency"`
Balance *int64 `json:"balance" binding:"omitempty"`
BalanceTime *int64 `json:"balanceTime" binding:"omitempty"`
LastReconciledTime *int64 `json:"lastReconciledTime" binding:"omitempty"`
Comment string `json:"comment" binding:"max=255"`
CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"`
Hidden bool `json:"hidden"`
@@ -169,6 +171,7 @@ type AccountInfoResponse struct {
Color string `json:"color"`
Currency string `json:"currency"`
Balance int64 `json:"balance"`
LastReconciledTime *int64 `json:"lastReconciledTime,omitempty"`
Comment string `json:"comment"`
CreditCardStatementDate *int `json:"creditCardStatementDate,omitempty"`
DisplayOrder int32 `json:"displayOrder"`
@@ -178,10 +181,24 @@ type AccountInfoResponse struct {
SubAccounts AccountInfoResponseSlice `json:"subAccounts,omitempty"`
}
// GetLastReconciledTime returns the last reconciled time of the account
func (a *Account) GetLastReconciledTime() int64 {
if a.Extend != nil && a.Extend.LastReconciledTime != nil {
return *a.Extend.LastReconciledTime
}
return 0
}
// ToAccountInfoResponse returns a view-object according to database model
func (a *Account) ToAccountInfoResponse() *AccountInfoResponse {
var lastReconciledTime *int64
var creditCardStatementDate *int
if a.Extend != nil {
lastReconciledTime = a.Extend.LastReconciledTime
}
if a.ParentAccountId == LevelOneAccountParentId && a.Category == ACCOUNT_CATEGORY_CREDIT_CARD {
if a.Extend != nil {
creditCardStatementDate = a.Extend.CreditCardStatementDate
@@ -201,6 +218,7 @@ func (a *Account) ToAccountInfoResponse() *AccountInfoResponse {
Currency: a.Currency,
Balance: a.Balance,
Comment: a.Comment,
LastReconciledTime: lastReconciledTime,
CreditCardStatementDate: creditCardStatementDate,
DisplayOrder: a.DisplayOrder,
IsAsset: assetAccountCategory[a.Category],
+4 -4
View File
@@ -549,10 +549,6 @@ func ParseTransactionTagFilter(tagFilterStr string) ([]*TransactionTagFilter, er
// IsEditable returns whether this transaction can be edited
func (t *Transaction) IsEditable(currentUser *User, clientTimezone *time.Location, account *Account, relatedAccount *Account) bool {
if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, clientTimezone) {
return false
}
if account == nil || account.Hidden {
return false
}
@@ -563,6 +559,10 @@ func (t *Transaction) IsEditable(currentUser *User, clientTimezone *time.Locatio
}
}
if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, clientTimezone, account, relatedAccount) {
return false
}
return true
}
+38 -15
View File
@@ -13,14 +13,15 @@ type TransactionEditScope byte
// Editable Transaction Ranges
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
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_LAST_RECONCILED_TIME_OR_LATER TransactionEditScope = 7
TRANSACTION_EDIT_SCOPE_INVALID TransactionEditScope = 255
)
// String returns a textual representation of the editable transaction ranges enum
@@ -40,6 +41,8 @@ func (s TransactionEditScope) String() string {
return "ThisMonthOrLater"
case TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER:
return "ThisYearOrLater"
case TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER:
return "LastReconciledTimeOrLater"
case TRANSACTION_EDIT_SCOPE_INVALID:
return "Invalid"
default:
@@ -90,6 +93,7 @@ type User struct {
Salt string `xorm:"VARCHAR(10) NOT NULL"`
CustomAvatarType string `xorm:"VARCHAR(10)"`
DefaultAccountId int64
UseLastReconciledTime bool
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
Language string `xorm:"VARCHAR(10)"`
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
@@ -128,6 +132,7 @@ type UserBasicInfo struct {
AvatarUrl string `json:"avatar"`
AvatarProvider string `json:"avatarProvider,omitempty"`
DefaultAccountId int64 `json:"defaultAccountId,string"`
UseLastReconciledTime bool `json:"useLastReconciledTime"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
Language string `json:"language"`
DefaultCurrency string `json:"defaultCurrency"`
@@ -194,7 +199,8 @@ type UserProfileUpdateRequest struct {
Password string `json:"password" binding:"omitempty,min=6,max=128"`
OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"`
DefaultAccountId int64 `json:"defaultAccountId,string" binding:"omitempty,min=1"`
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=6"`
UseLastReconciledTime *bool `json:"useLastReconciledTime" binding:"omitempty"`
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
Language string `json:"language" binding:"omitempty,min=2,max=16"`
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
FirstDayOfWeek *core.WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
@@ -230,7 +236,7 @@ type UserProfileResponse struct {
}
// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, clientTimezone *time.Location) bool {
func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, clientTimezone *time.Location, account *Account, destinationAccount *Account) bool {
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE {
return false
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL {
@@ -242,14 +248,14 @@ func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, client
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transactionTime)
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER {
return transactionUnixTime >= now.Add(-24*time.Hour).Unix()
return transactionUnixTime > now.Add(-24*time.Hour).Unix()
}
clientNow := now.In(clientTimezone)
clientTodayStartTime := utils.GetStartOfDay(clientNow)
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER {
return transactionUnixTime >= clientTodayStartTime.Unix()
return transactionUnixTime > clientTodayStartTime.Unix()
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER {
dayOfWeek := int(now.Weekday()) - int(u.FirstDayOfWeek)
@@ -258,13 +264,29 @@ func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, client
}
clientWeekStartTime := clientTodayStartTime.AddDate(0, 0, -dayOfWeek)
return transactionUnixTime >= clientWeekStartTime.Unix()
return transactionUnixTime > clientWeekStartTime.Unix()
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER {
clientMonthStartTime := clientTodayStartTime.AddDate(0, 0, -(now.Day() - 1))
return transactionUnixTime >= clientMonthStartTime.Unix()
return transactionUnixTime > clientMonthStartTime.Unix()
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
clientYearStartTime := clientTodayStartTime.AddDate(0, 0, -(now.YearDay() - 1))
return transactionUnixTime >= clientYearStartTime.Unix()
return transactionUnixTime > clientYearStartTime.Unix()
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER && u.UseLastReconciledTime {
minAccountLastReconciledTime := int64(0)
if account != nil {
minAccountLastReconciledTime = account.GetLastReconciledTime()
}
if destinationAccount != nil {
destinationAccountLastReconciledTime := destinationAccount.GetLastReconciledTime()
if destinationAccountLastReconciledTime > minAccountLastReconciledTime {
minAccountLastReconciledTime = destinationAccountLastReconciledTime
}
}
return transactionUnixTime > minAccountLastReconciledTime
}
return false
@@ -285,6 +307,7 @@ func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, avata
AvatarUrl: avatarUrl,
AvatarProvider: string(avatarProvider),
DefaultAccountId: u.DefaultAccountId,
UseLastReconciledTime: u.UseLastReconciledTime,
TransactionEditScope: u.TransactionEditScope,
Language: u.Language,
DefaultCurrency: u.DefaultCurrency,
+85 -16
View File
@@ -16,7 +16,7 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsNone(t *testing.T) {
}
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) {
@@ -25,7 +25,7 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) {
}
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing.T) {
@@ -39,9 +39,10 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing.
yesterdayLastDatetime := todayFirstDatetime.Add(-1 * time.Second)
todayLastDatetime := yesterdayLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Add(1*time.Second).Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayLastDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *testing.T) {
@@ -53,8 +54,9 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *tes
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
twentyfourHourBeforeDatetime := now.Add(-24 * time.Hour).Add(-1 * time.Second)
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(1*time.Second).Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(1*time.Second).Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(2*time.Second).Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testing.T) {
@@ -76,9 +78,10 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testi
lastWeekLastDatetime := thisWeekFirstDatetime.Add(-1 * time.Second)
thisWeekLastDatetime := lastWeekLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Add(1*time.Second).Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekLastDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *testing.T) {
@@ -92,9 +95,10 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *test
lastMonthLastDatetime := thisMonthFirstDatetime.Add(-1 * time.Second)
thisMonthLastDatetime := lastMonthLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Add(1*time.Second).Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthLastDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testing.T) {
@@ -108,7 +112,72 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testi
lastYearLastDatetime := thisYearFirstDatetime.Add(-1 * time.Second)
thisYearLastDatetime := lastYearLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Add(1*time.Second).Unix()), timezone, nil, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearLastDatetime.Unix()), timezone, nil, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), timezone, nil, nil))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsLastReconciledTimeOrLater(t *testing.T) {
user := &User{
TransactionEditScope: TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER,
UseLastReconciledTime: true,
}
now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
sourceAccountLastReconciledTime := now.Add(-24 * time.Hour)
sourceAccountLastRecondiledUnixTime := sourceAccountLastReconciledTime.Unix()
sourceAccount := &Account{
Extend: &AccountExtend{
LastReconciledTime: &sourceAccountLastRecondiledUnixTime,
},
}
destinationAccountLastReconciledTime := now.Add(-20 * time.Hour)
destinationAccountLastReconciledUnixTime := destinationAccountLastReconciledTime.Unix()
destinationAccount := &Account{
Extend: &AccountExtend{
LastReconciledTime: &destinationAccountLastReconciledUnixTime,
},
}
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Add(-1*time.Second).Unix()), timezone, sourceAccount, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Unix()), timezone, sourceAccount, nil))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Add(1*time.Second).Unix()), timezone, sourceAccount, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Add(-1*time.Second).Unix()), timezone, sourceAccount, destinationAccount))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Unix()), timezone, sourceAccount, destinationAccount))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Add(1*time.Second).Unix()), timezone, sourceAccount, destinationAccount))
}
func TestUserCanEditTransactionByTransactionTime_ScopeIsLastReconciledTimeOrLaterButUserDoesNotUseLastReconciledTime(t *testing.T) {
user := &User{
TransactionEditScope: TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER,
UseLastReconciledTime: false,
}
now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
sourceAccountLastReconciledTime := now.Add(-24 * time.Hour)
sourceAccountLastRecondiledUnixTime := sourceAccountLastReconciledTime.Unix()
sourceAccount := &Account{
Extend: &AccountExtend{
LastReconciledTime: &sourceAccountLastRecondiledUnixTime,
},
}
destinationAccountLastReconciledTime := now.Add(-20 * time.Hour)
destinationAccountLastReconciledUnixTime := destinationAccountLastReconciledTime.Unix()
destinationAccount := &Account{
Extend: &AccountExtend{
LastReconciledTime: &destinationAccountLastReconciledUnixTime,
},
}
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Add(-1*time.Second).Unix()), timezone, sourceAccount, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Unix()), timezone, sourceAccount, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(sourceAccountLastReconciledTime.Add(1*time.Second).Unix()), timezone, sourceAccount, nil))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Add(-1*time.Second).Unix()), timezone, sourceAccount, destinationAccount))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Unix()), timezone, sourceAccount, destinationAccount))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(destinationAccountLastReconciledTime.Add(1*time.Second).Unix()), timezone, sourceAccount, destinationAccount))
}
+6 -2
View File
@@ -234,7 +234,7 @@ func (s *UserService) CreateUser(c core.Context, user *models.User, noPassword b
}
// UpdateUser saves an existed user model to database
func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLanguage bool) (keyProfileUpdated bool, emailSetToUnverified bool, err error) {
func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLanguage bool, modifyUseLastReconciledTime bool) (keyProfileUpdated bool, emailSetToUnverified bool, err error) {
if user.Uid <= 0 {
return false, false, errs.ErrUserIdInvalid
}
@@ -277,7 +277,11 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
updateCols = append(updateCols, "default_account_id")
}
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
if modifyUseLastReconciledTime {
updateCols = append(updateCols, "use_last_reconciled_time")
}
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER {
updateCols = append(updateCols, "transaction_edit_scope")
}
+11 -2
View File
@@ -3,6 +3,7 @@
persistent-placeholder
:readonly="readonly"
:disabled="disabled"
:clearable="!emptyValue ? clearable : false"
:label="label"
:menu-props="{ contentClass: 'date-time-select-menu' }"
v-model="dateTime"
@@ -107,13 +108,16 @@ import { setChildInputFocus } from '@/lib/ui/desktop.ts';
const props = defineProps<{
modelValue: number;
timezoneUtcOffset: number;
emptyValue?: boolean;
disabled?: boolean;
readonly?: boolean;
clearable?: boolean;
label?: string;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: number): void;
(e: 'clear:modelValue'): void;
(e: 'error', message: string): void;
}>();
@@ -154,7 +158,12 @@ const dateTime = computed<Date>({
get: () => {
return getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue, props.timezoneUtcOffset);
},
set: (value: Date) => {
set: (value: Date | null) => {
if (!value) {
emit('clear:modelValue');
return;
}
const unixTime = getUnixTimeFromSameDateTimeOfLocalDatetime(value, props.timezoneUtcOffset);
if (unixTime < 0) {
@@ -166,7 +175,7 @@ const dateTime = computed<Date>({
}
});
const displayTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTimeWithTimezoneOffset(props.modelValue, props.timezoneUtcOffset)));
const displayTime = computed<string>(() => props.emptyValue ? tt('None') : formatDateTimeToLongDateTime(parseDateTimeFromUnixTimeWithTimezoneOffset(props.modelValue, props.timezoneUtcOffset)));
const hourItems = computed<TimePickerValue[]>(() => generateAllHours(1, isHourTwoDigits.value));
const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isMinuteTwoDigits.value));
@@ -4,7 +4,8 @@
<f7-toolbar class="toolbar-with-swipe-handler">
<div class="swipe-handler"></div>
<div class="left">
<f7-link :text="tt('Now')" @click="setCurrentTime"></f7-link>
<f7-link :text="tt('Clear')" @click="clear" v-if="clearable"></f7-link>
<f7-link :text="tt('Now')" @click="setCurrentTime" v-if="!clearable"></f7-link>
</div>
<div class="right">
<f7-link :icon-f7="mode === 'time' ? 'calendar' : 'clock'" @click="switchMode"></f7-link>
@@ -122,11 +123,13 @@ const props = defineProps<{
modelValue: number;
timezoneUtcOffset: number;
initMode?: string;
clearable?: boolean;
show: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: number): void;
(e: 'clear:modelValue'): void;
(e: 'update:show', value: boolean): void;
}>();
@@ -221,6 +224,11 @@ function setCurrentTime(): void {
}
}
function clear(): void {
emit('clear:modelValue');
emit('update:show', false);
}
function confirm(): void {
if (!dateTime.value) {
return;
+29 -10
View File
@@ -14,27 +14,46 @@ export enum TransactionRelatedAccountType {
export class TransactionEditScopeType implements TypeAndName {
private static readonly allInstances: TransactionEditScopeType[] = [];
private static readonly allInstancesWithoutReconciledTime: TransactionEditScopeType[] = [];
private static readonly allInstancesByType: Record<number, TransactionEditScopeType> = {};
public static readonly None = new TransactionEditScopeType(0, 'None');
public static readonly All = new TransactionEditScopeType(1, 'All');
public static readonly TodayOrLater = new TransactionEditScopeType(2, 'Today or later');
public static readonly Recent24HoursOrLater = new TransactionEditScopeType(3, 'Recent 24 hours or later');
public static readonly ThisWeekOrLater = new TransactionEditScopeType(4, 'This week or later');
public static readonly ThisMonthOrLater = new TransactionEditScopeType(5, 'This month or later');
public static readonly ThisYearOrLater = new TransactionEditScopeType(6, 'This year or later');
public static readonly None = new TransactionEditScopeType(0, 'None', false);
public static readonly All = new TransactionEditScopeType(1, 'All', false);
public static readonly TodayOrLater = new TransactionEditScopeType(2, 'Today or later', false);
public static readonly Recent24HoursOrLater = new TransactionEditScopeType(3, 'Recent 24 hours or later', false);
public static readonly ThisWeekOrLater = new TransactionEditScopeType(4, 'This week or later', false);
public static readonly ThisMonthOrLater = new TransactionEditScopeType(5, 'This month or later', false);
public static readonly ThisYearOrLater = new TransactionEditScopeType(6, 'This year or later', false);
public static readonly LastReconciledTimeOrlater = new TransactionEditScopeType(7, 'Last reconciled time or later', true);
public readonly type: number;
public readonly name: string;
public readonly needLastReconciledTime: boolean;
private constructor(type: number, name: string) {
private constructor(type: number, name: string, needLastReconciledTime: boolean) {
this.type = type;
this.name = name;
this.needLastReconciledTime = needLastReconciledTime;
TransactionEditScopeType.allInstances.push(this);
if (!needLastReconciledTime) {
TransactionEditScopeType.allInstancesWithoutReconciledTime.push(this);
}
TransactionEditScopeType.allInstancesByType[type] = this;
}
public static values(): TransactionEditScopeType[] {
return TransactionEditScopeType.allInstances;
public static values(useLastReconciledTime: boolean): TransactionEditScopeType[] {
if (useLastReconciledTime) {
return TransactionEditScopeType.allInstances;
} else {
return TransactionEditScopeType.allInstancesWithoutReconciledTime;
}
}
public static valueOf(type: number): TransactionEditScopeType | undefined {
return TransactionEditScopeType.allInstancesByType[type];
}
}
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Dezimaltrennzeichen",
"Digit Grouping Symbol": "Zifferngruppierungssymbol",
"Digit Grouping": "Zifferngruppierung",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Bearbeitbarer Transaktionsbereich",
"Today or later": "Heute oder später",
"Recent 24 hours or later": "Letzte 24 Stunden oder später",
"This week or later": "Diese Woche oder später",
"This month or later": "Dieser Monat oder später",
"This year or later": "Dieses Jahr oder später",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Anmelden",
"Log in with OAuth 2.0": "Mit OAuth 2.0 anmelden",
"Log in with Connect ID": "Mit Connect ID anmelden",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Ausstehender Teilkontostand",
"Balance Time": "Saldozeit",
"Sub-account Balance Time": "Teilkontosaldozeit",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Abrechnungsdatum",
"Description": "Beschreibung",
"Your account description (optional)": "Ihre Kontobeschreibung (optional)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Decimal Separator",
"Digit Grouping Symbol": "Digit Grouping Symbol",
"Digit Grouping": "Digit Grouping",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Editable Transaction Range",
"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",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Log In",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Sub-account Outstanding Balance",
"Balance Time": "Balance Time",
"Sub-account Balance Time": "Sub-account Balance Time",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Statement Date",
"Description": "Description",
"Your account description (optional)": "Your account description (optional)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Separador Decimal",
"Digit Grouping Symbol": "Símbolo de Agrupación de Dígitos",
"Digit Grouping": "Agrupación de Dígitos",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Rango de Transacciones Editables",
"Today or later": "Hoy o más tarde",
"Recent 24 hours or later": "Últimas 24 horas o más",
"This week or later": "Esta semana o más tarde",
"This month or later": "Este mes o más tarde",
"This year or later": "Este año o más tarde",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Acceso",
"Log in with OAuth 2.0": "Iniciar sesión con OAuth 2.0",
"Log in with Connect ID": "Inicie sesión con Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Saldo pendiente de la Subcuenta",
"Balance Time": "Fecha del Saldo",
"Sub-account Balance Time": "Fecha del Saldo de la Subcuenta",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Fecha del Extracto",
"Description": "Descripción",
"Your account description (optional)": "Descripción de tu cuenta (opcional)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Séparateur décimal",
"Digit Grouping Symbol": "Symbole de regroupement des chiffres",
"Digit Grouping": "Regroupement des chiffres",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Plage de transactions modifiables",
"Today or later": "Aujourd'hui ou plus tard",
"Recent 24 hours or later": "24 dernières heures ou plus tard",
"This week or later": "Cette semaine ou plus tard",
"This month or later": "Ce mois ou plus tard",
"This year or later": "Cette année ou plus tard",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Se connecter",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Solde impayé du sous-compte",
"Balance Time": "Heure du solde",
"Sub-account Balance Time": "Heure du solde du sous-compte",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Date de relevé",
"Description": "Description",
"Your account description (optional)": "Description de votre compte (optionnel)",
+2 -1
View File
@@ -2564,7 +2564,7 @@ export function useI18n() {
getAllStatisticsSortingTypes: (useAlternativeName?: boolean) => getLocalizedDisplayNameAndType(ChartSortingType.values(), useAlternativeName),
getAllStatisticsDateAggregationTypes: (analysisType: StatisticsAnalysisType, includeBillingCycle: boolean) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, true, includeBillingCycle),
getAllStatisticsDateAggregationTypesWithShortName: (analysisType: StatisticsAnalysisType, includeBillingCycle: boolean) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, false, includeBillingCycle),
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
getAllTransactionEditScopeTypes: (useLastReconciledTime: boolean) => getLocalizedDisplayNameAndType(TransactionEditScopeType.values(useLastReconciledTime)),
getAllTransactionQuickSaveButtonStyles: () => getLocalizedDisplayNameAndType(TransactionQuickSaveButtonStyle.values()),
getAllTransactionQuickAddButtonActionTypes: () => getLocalizedDisplayNameAndType(TransactionQuickAddButtonActionType.values()),
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
@@ -2579,6 +2579,7 @@ export function useI18n() {
getAllTransactionExplorerChartTypes: (operators?: TransactionExplorerChartType[]) => getLocalizedNameValue(operators ?? TransactionExplorerChartType.values()),
// get localized info
getLanguageInfo,
getEnableDisableOption: (value: boolean) => t(value ? 'Enabled' : 'Disabled'),
getMonthShortName,
getMonthLongName,
getMonthdayOrdinal,
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Separatore decimale",
"Digit Grouping Symbol": "Simbolo di raggruppamento cifre",
"Digit Grouping": "Raggruppamento cifre",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Intervallo di modifica transazioni",
"Today or later": "Oggi o successivo",
"Recent 24 hours or later": "Ultime 24 ore o successivo",
"This week or later": "Questa settimana o successiva",
"This month or later": "Questo mese o successivo",
"This year or later": "Quest'anno o successivo",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Accedi",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Saldo residuo sotto-account",
"Balance Time": "Ora saldo",
"Sub-account Balance Time": "Ora saldo sotto-account",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Data estratto conto",
"Description": "Descrizione",
"Your account description (optional)": "La descrizione del tuo account (opzionale)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "小数点",
"Digit Grouping Symbol": "桁区切り記号",
"Digit Grouping": "桁区切り位置",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "編集可能な取引範囲",
"Today or later": "今日以降",
"Recent 24 hours or later": "直近24時間以降",
"This week or later": "今週以降",
"This month or later": "今月以降",
"This year or later": "今年以降",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "ログイン",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "サブ口座の未払い残高",
"Balance Time": "残高時間",
"Sub-account Balance Time": "サブ口座残高時間",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "明細日",
"Description": "説明",
"Your account description (optional)": "口座の説明(オプション)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "ದಶಾಂಶ ವಿಭಜಕ",
"Digit Grouping Symbol": "ಅಂಕ ವಿಂಗಡಣ ಚಿಹ್ನೆ",
"Digit Grouping": "ಅಂಕ ವಿಂಗಡಣೆ",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "ತಿದ್ದುಪಡಿ ಮಾಡಬಹುದಾದ ವಹಿವಾಟಿನ ವ್ಯಾಪ್ತಿ",
"Today or later": "ಇಂದು ಅಥವಾ ನಂತರ",
"Recent 24 hours or later": "ಕಳೆದ 24 ಗಂಟೆಗಳು ಅಥವಾ ನಂತರ",
"This week or later": "ಈ ವಾರ ಅಥವಾ ನಂತರ",
"This month or later": "ಈ ತಿಂಗಳು ಅಥವಾ ನಂತರ",
"This year or later": "ಈ ವರ್ಷ ಅಥವಾ ನಂತರ",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "ಲಾಗ್ ಇನ್",
"Log in with OAuth 2.0": "OAuth 2.0 ಮೂಲಕ ಲಾಗ್ ಇನ್ ಆಗಿ",
"Log in with Connect ID": "Connect ID ಮೂಲಕ ಲಾಗ್ ಇನ್ ಆಗಿ",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "ಉಪ-ಖಾತೆ ಬಾಕಿ ಶೇಷ",
"Balance Time": "ಶೇಷ ಸಮಯ",
"Sub-account Balance Time": "ಉಪ-ಖಾತೆ ಶೇಷ ಸಮಯ",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "ಸ್ಟೇಟ್ಮೆಂಟ್ ದಿನಾಂಕ",
"Description": "ವಿವರಣೆ",
"Your account description (optional)": "ನಿಮ್ಮ ಖಾತೆ ವಿವರಣೆ (ಐಚ್ಛಿಕ)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "소수점 구분자",
"Digit Grouping Symbol": "자리 구분 기호",
"Digit Grouping": "자리 구분",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "편집 가능한 거래 범위",
"Today or later": "오늘 이후",
"Recent 24 hours or later": "최근 24시간 이후",
"This week or later": "이번 주 이후",
"This month or later": "이번 달 이후",
"This year or later": "올해 이후",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "로그인",
"Log in with OAuth 2.0": "OAuth 2.0으로 로그인",
"Log in with Connect ID": "Connect ID로 로그인",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "하위계좌 미결제 잔액",
"Balance Time": "잔액 시간",
"Sub-account Balance Time": "하위계좌 잔액 시간",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "명세서 날짜",
"Description": "설명",
"Your account description (optional)": "계좌 설명 (선택 사항)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Decimaalteken",
"Digit Grouping Symbol": "Scheidingsteken voor duizendtallen",
"Digit Grouping": "Groeperen van cijfers",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Bewerkbare transactiebereik",
"Today or later": "Vandaag of later",
"Recent 24 hours or later": "Afgelopen 24 uur of later",
"This week or later": "Deze week of later",
"This month or later": "Deze maand of later",
"This year or later": "Dit jaar of later",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Inloggen",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Openstaand saldo subrekening",
"Balance Time": "Saldo-tijdstip",
"Sub-account Balance Time": "Saldo-tijdstip subrekening",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Afsluitdatum",
"Description": "Beschrijving",
"Your account description (optional)": "Je rekeningbeschrijving (optioneel)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Separador Decimal",
"Digit Grouping Symbol": "Símbolo de Agrupamento de Dígitos",
"Digit Grouping": "Agrupamento de Dígitos",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Período Editável de Transações",
"Today or later": "A partir de hoje",
"Recent 24 hours or later": "A partir das últimas 24 horas",
"This week or later": "A partir desta semana",
"This month or later": "A partir deste mês",
"This year or later": "A partir deste ano",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Fazer Login",
"Log in with OAuth 2.0": "Entrar com OAuth 2.0",
"Log in with Connect ID": "Entrar com Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Saldo em Aberto da Subconta",
"Balance Time": "Hora do Saldo",
"Sub-account Balance Time": "Hora do Saldo da Subconta",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Data do Extrato",
"Description": "Descrição",
"Your account description (optional)": "Descrição da sua conta (opcional)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Разделитель десятичных",
"Digit Grouping Symbol": "Символ группировки цифр",
"Digit Grouping": "Группировка цифр",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Диапазон редактируемых транзакций",
"Today or later": "Сегодня или позже",
"Recent 24 hours or later": "Последние 24 часа или позже",
"This week or later": "На этой неделе или позже",
"This month or later": "В этом месяце или позже",
"This year or later": "В этом году или позже",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Войти",
"Log in with OAuth 2.0": "Войти с OAuth 2.0",
"Log in with Connect ID": "Войти с Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Невыплаченные остаток субсчета",
"Balance Time": "Время баланса",
"Sub-account Balance Time": "Время баланса субсчета",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Дата выписки",
"Description": "Описание",
"Your account description (optional)": "Описание вашего счета (необязательно)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Decimalno ločilo",
"Digit Grouping Symbol": "Ločilo tisočic",
"Digit Grouping": "Združevanje števil",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Obseg urejanja transakcij",
"Today or later": "Danes ali pozneje",
"Recent 24 hours or later": "Zadnjih 24 ur ali pozneje",
"This week or later": "Ta teden ali pozneje",
"This month or later": "Ta mesec ali pozneje",
"This year or later": "Letos ali pozneje",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Prijava",
"Log in with OAuth 2.0": "Prijava z OAuth 2.0",
"Log in with Connect ID": "Prijava s Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Neporavnano stanje na podračunu",
"Balance Time": "Čas stanja",
"Sub-account Balance Time": "Čas stanja na podračunu",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Datum izpiska",
"Description": "Opis",
"Your account description (optional)": "Opis vašega računa (neobvezno)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "தசம பிரிப்பான்",
"Digit Grouping Symbol": "இலக்க குழு குறியீடு",
"Digit Grouping": "இலக்க குழுவாக்கம்",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "திருத்துபடி செய்யக்கூடிய பரிவர்த்தனையின் வரம்பு",
"Today or later": "இன்று அல்லது பின்பு",
"Recent 24 hours or later": "கடந்த 24 மணி நேரம் அல்லது பின்பு",
"This week or later": "இந்த வாரம் அல்லது பின்பு",
"This month or later": "இந்த மாதம் அல்லது பின்பு",
"This year or later": "இந்த வருடம் அல்லது பின்பு",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "உள்நுழை",
"Log in with OAuth 2.0": "OAuth 2.0 மூலக உள்நுழை ஆக",
"Log in with Connect ID": "Connect ID மூலக உள்நுழை ஆக",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "துணை-கணக்கு நிலுவை இருப்பு",
"Balance Time": "இருப்பு நேரம்",
"Sub-account Balance Time": "துணை-கணக்கு இருப்பு நேரம்",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "அறிக்கை தேதி",
"Description": "விளக்கம்",
"Your account description (optional)": "உங்கள் கணக்கு விளக்கம் (விருப்பமான)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "ตัวคั่นทศนิยม",
"Digit Grouping Symbol": "สัญลักษณ์จัดกลุ่มตัวเลข",
"Digit Grouping": "จัดกลุ่มตัวเลข",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "ช่วงรายการที่แก้ไขได้",
"Today or later": "วันนี้หรือหลังจากนี้",
"Recent 24 hours or later": "24 ชั่วโมงที่ผ่านมา หรือหลังจากนี้",
"This week or later": "สัปดาห์นี้หรือหลังจากนี้",
"This month or later": "เดือนนี้หรือหลังจากนี้",
"This year or later": "ปีนี้หรือหลังจากนี้",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "เข้าสู่ระบบ",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "ยอดค้างชำระบัญชีย่อย",
"Balance Time": "เวลายอดคงเหลือ",
"Sub-account Balance Time": "เวลายอดคงเหลือบัญชีย่อย",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "วันรายการ",
"Description": "รายละเอียด",
"Your account description (optional)": "รายละเอียดบัญชีของคุณ (ไม่บังคับ)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Ondalık Ayırıcı",
"Digit Grouping Symbol": "Basamak Gruplama Sembolü",
"Digit Grouping": "Basamak Gruplama",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Düzenlenebilir İşlem Aralığı",
"Today or later": "Bugün veya sonrası",
"Recent 24 hours or later": "Son 24 saat veya sonrası",
"This week or later": "Bu hafta veya sonrası",
"This month or later": "Bu ay veya sonrası",
"This year or later": "Bu yıl veya sonrası",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Giriş Yap",
"Log in with OAuth 2.0": "OAuth 2.0 ile giriş yap",
"Log in with Connect ID": "Connect ID ile giriş yap",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Alt Hesap Kalan Borç Bakiyesi",
"Balance Time": "Bakiye Zamanı",
"Sub-account Balance Time": "Alt Hesap Bakiye Zamanı",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Ekstre Tarihi",
"Description": "Açıklama",
"Your account description (optional)": "Hesap açıklamanız (isteğe bağlı)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Десятковий роздільник",
"Digit Grouping Symbol": "Символ групування цифр",
"Digit Grouping": "Групування цифр",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Діапазон редагованих транзакцій",
"Today or later": "Сьогодні або пізніше",
"Recent 24 hours or later": "Останні 24 години або пізніше",
"This week or later": "Цього тижня або пізніше",
"This month or later": "Цього місяця або пізніше",
"This year or later": "Цього року або пізніше",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Увійти",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Несплачений баланс субрахунку",
"Balance Time": "Час балансу",
"Sub-account Balance Time": "Час балансу субрахунку",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Дата виписки",
"Description": "Опис",
"Your account description (optional)": "Опис вашого рахунку (необов'язково)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "Dấu phân cách thập phân",
"Digit Grouping Symbol": "Ký hiệu nhóm chữ số",
"Digit Grouping": "Nhóm chữ số",
"Use Last Reconciled Time": "Use Last Reconciled Time",
"Editable Transaction Range": "Phạm vi giao dịch có thể chỉnh sửa",
"Today or later": "Hôm nay trở đi",
"Recent 24 hours or later": "24 giờ gần đây trở đi",
"This week or later": "Tuần này trở đi",
"This month or later": "Tháng này trở đi",
"This year or later": "Năm nay trở đi",
"Last reconciled time or later": "Last reconciled time or later",
"Log In": "Đăng nhập",
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
"Log in with Connect ID": "Log in with Connect ID",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "Sub-account Outstanding Balance",
"Balance Time": "Thời gian số dư",
"Sub-account Balance Time": "Thời gian số dư tài khoản phụ",
"Last Reconciled Time": "Last Reconciled Time",
"Sub-account Last Reconciled Time": "Sub-account Last Reconciled Time",
"Statement Date": "Statement Date",
"Description": "Mô tả",
"Your account description (optional)": "Mô tả tài khoản của bạn (tùy chọn)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "小数点",
"Digit Grouping Symbol": "数字分组符号",
"Digit Grouping": "数字分组",
"Use Last Reconciled Time": "使用最后对账时间",
"Editable Transaction Range": "可编辑交易范围",
"Today or later": "今天或更晚",
"Recent 24 hours or later": "最近24小时或更晚",
"This week or later": "本周或更晚",
"This month or later": "本月或更晚",
"This year or later": "今年或更晚",
"Last reconciled time or later": "最后对账时间或更晚",
"Log In": "登录",
"Log in with OAuth 2.0": "使用 OAuth 2.0 登录",
"Log in with Connect ID": "使用 Connect ID 登录",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "子账户未清余额",
"Balance Time": "余额时间",
"Sub-account Balance Time": "子账户余额时间",
"Last Reconciled Time": "最后对账时间",
"Sub-account Last Reconciled Time": "子账户最后对账时间",
"Statement Date": "账单日",
"Description": "描述",
"Your account description (optional)": "你的账户描述 (可选)",
+4
View File
@@ -1683,12 +1683,14 @@
"Decimal Separator": "小數點",
"Digit Grouping Symbol": "數字分組符號",
"Digit Grouping": "數字分組",
"Use Last Reconciled Time": "使用最後對帳時間",
"Editable Transaction Range": "可編輯交易範圍",
"Today or later": "今天或更晚",
"Recent 24 hours or later": "最近24小時或更晚",
"This week or later": "本週或更晚",
"This month or later": "本月或更晚",
"This year or later": "今年或更晚",
"Last reconciled time or later": "最後對帳時間或更晚",
"Log In": "登入",
"Log in with OAuth 2.0": "使用 OAuth 2.0 登入",
"Log in with Connect ID": "使用 Connect ID 登入",
@@ -1922,6 +1924,8 @@
"Sub-account Outstanding Balance": "子帳戶未清餘額",
"Balance Time": "餘額時間",
"Sub-account Balance Time": "子帳戶餘額時間",
"Last Reconciled Time": "最後對帳時間",
"Sub-account Last Reconciled Time": "子帳戶最後對帳時間",
"Statement Date": "帳單日",
"Description": "描述",
"Your account description (optional)": "您的帳戶描述 (選填)",
+14 -1
View File
@@ -15,6 +15,7 @@ export class Account implements AccountInfoResponse {
public currency: string;
public balance: number;
public balanceTime?: number;
public lastReconciledTime?: number;
public comment: string;
public creditCardStatementDate?: number;
public displayOrder: number;
@@ -24,7 +25,7 @@ export class Account implements AccountInfoResponse {
private readonly _isAsset?: boolean;
private readonly _isLiability?: boolean;
protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, subAccounts?: Account[]) {
protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, lastReconciledTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, subAccounts?: Account[]) {
this.id = id;
this.name = name;
this.parentId = parentId;
@@ -35,6 +36,7 @@ export class Account implements AccountInfoResponse {
this.currency = currency;
this.balance = balance;
this.balanceTime = balanceTime;
this.lastReconciledTime = lastReconciledTime;
this.comment = comment;
this.displayOrder = displayOrder;
this.visible = visible;
@@ -92,6 +94,7 @@ export class Account implements AccountInfoResponse {
this.currency === other.currency &&
this.balance === other.balance &&
this.balanceTime === other.balanceTime &&
this.lastReconciledTime === other.lastReconciledTime &&
this.comment === other.comment &&
this.displayOrder === other.displayOrder &&
this.visible === other.visible &&
@@ -128,6 +131,7 @@ export class Account implements AccountInfoResponse {
this.currency = other.currency;
this.balance = other.balance;
this.balanceTime = other.balanceTime;
this.lastReconciledTime = other.lastReconciledTime;
this.comment = other.comment;
this.creditCardStatementDate = other.creditCardStatementDate;
this.visible = other.visible;
@@ -212,6 +216,7 @@ export class Account implements AccountInfoResponse {
currency: parentAccount && (!this.id || this.id === '0') ? this.currency : undefined,
balance: parentAccount && (!this.id || this.id === '0') ? this.balance : undefined,
balanceTime: parentAccount && (!this.id || this.id === '0') ? this.balanceTime : undefined,
lastReconciledTime: this.lastReconciledTime,
comment: this.comment,
creditCardStatementDate: !parentAccount && this.category === AccountCategory.CreditCard.type ? this.creditCardStatementDate : undefined,
hidden: !this.visible,
@@ -363,6 +368,7 @@ export class Account implements AccountInfoResponse {
this.displayOrder,
this.visible,
this.balanceTime,
this.lastReconciledTime,
this.creditCardStatementDate,
this.isAsset,
this.isLiability
@@ -384,6 +390,7 @@ export class Account implements AccountInfoResponse {
this.displayOrder,
this.visible,
this.balanceTime,
this.lastReconciledTime,
this.creditCardStatementDate,
this.isAsset,
this.isLiability,
@@ -405,6 +412,7 @@ export class Account implements AccountInfoResponse {
0, // displayOrder
true, // visible
balanceTime, // balanceTime
undefined, // lastReconciledTime
0 // creditCardStatementDate
);
}
@@ -424,6 +432,7 @@ export class Account implements AccountInfoResponse {
0, // displayOrder
true, // visible
balanceTime, // balanceTime
undefined, // lastReconciledTime
0 // creditCardStatementDate
);
}
@@ -443,6 +452,7 @@ export class Account implements AccountInfoResponse {
accountResponse.displayOrder,
!accountResponse.hidden,
undefined,
accountResponse.lastReconciledTime,
accountResponse.creditCardStatementDate,
accountResponse.isAsset,
accountResponse.isLiability,
@@ -557,6 +567,7 @@ export class AccountWithDisplayBalance extends Account {
account.displayOrder,
account.visible,
account.balanceTime,
account.lastReconciledTime,
account.creditCardStatementDate,
account.isAsset,
account.isLiability,
@@ -595,6 +606,7 @@ export interface AccountModifyRequest {
readonly currency?: string;
readonly balance?: number;
readonly balanceTime?: number;
readonly lastReconciledTime?: number;
readonly comment: string;
readonly creditCardStatementDate?: number;
readonly hidden: boolean;
@@ -612,6 +624,7 @@ export interface AccountInfoResponse {
readonly color: string;
readonly currency: string;
readonly balance: number;
readonly lastReconciledTime?: number;
readonly comment: string;
readonly creditCardStatementDate?: number;
readonly displayOrder: number;
+7
View File
@@ -19,6 +19,7 @@ export class User {
public firstDayOfWeek: number;
public defaultAccountId: string = EMPTY_USER_BASIC_INFO.defaultAccountId;
public useLastReconciledTime: boolean = EMPTY_USER_BASIC_INFO.useLastReconciledTime;
public transactionEditScope: number = EMPTY_USER_BASIC_INFO.transactionEditScope;
public fiscalYearStart: number = EMPTY_USER_BASIC_INFO.fiscalYearStart;
public calendarDisplayType: number = EMPTY_USER_BASIC_INFO.calendarDisplayType;
@@ -48,6 +49,7 @@ export class User {
this.email = user.email;
this.nickname = user.nickname;
this.defaultAccountId = user.defaultAccountId;
this.useLastReconciledTime = user.useLastReconciledTime;
this.transactionEditScope = user.transactionEditScope;
this.language = user.language;
this.defaultCurrency = user.defaultCurrency;
@@ -90,6 +92,7 @@ export class User {
password: this.password,
oldPassword: currentPassword,
defaultAccountId: this.defaultAccountId,
useLastReconciledTime: this.useLastReconciledTime,
transactionEditScope: this.transactionEditScope,
language: this.language,
defaultCurrency: this.defaultCurrency,
@@ -116,6 +119,7 @@ export class User {
public static of(userInfo: UserBasicInfo): User {
const user = new User(userInfo.language, userInfo.defaultCurrency, userInfo.firstDayOfWeek);
user.defaultAccountId = userInfo.defaultAccountId;
user.useLastReconciledTime = userInfo.useLastReconciledTime;
user.transactionEditScope = userInfo.transactionEditScope;
user.fiscalYearStart = userInfo.fiscalYearStart;
user.calendarDisplayType = userInfo.calendarDisplayType;
@@ -149,6 +153,7 @@ export interface UserBasicInfo {
readonly avatar: string;
readonly avatarProvider?: string;
readonly defaultAccountId: string;
readonly useLastReconciledTime: boolean;
readonly transactionEditScope: number;
readonly language: string;
readonly defaultCurrency: string;
@@ -205,6 +210,7 @@ export interface UserProfileUpdateRequest {
readonly password?: string;
readonly oldPassword?: string;
readonly defaultAccountId?: string;
readonly useLastReconciledTime?: boolean;
readonly transactionEditScope?: number;
readonly language?: string;
readonly defaultCurrency?: string;
@@ -244,6 +250,7 @@ export const EMPTY_USER_BASIC_INFO: UserBasicInfo = {
avatar: '',
avatarProvider: undefined,
defaultAccountId: '',
useLastReconciledTime: false,
transactionEditScope: TransactionEditScopeType.All.type,
language: '',
defaultCurrency: '',
+6
View File
@@ -66,6 +66,11 @@ export const useUserStore = defineStore('user', () => {
return userInfo.defaultCurrency || settingsStore.localeDefaultSettings.currency;
});
const currentUserUseLastReconciledTime = computed<boolean>(() => {
const userInfo = currentUserBasicInfo.value || EMPTY_USER_BASIC_INFO;
return userInfo.useLastReconciledTime ?? false;
});
const currentUserFirstDayOfWeek = computed<WeekDayValue>(() => {
const userInfo = currentUserBasicInfo.value || EMPTY_USER_BASIC_INFO;
return isNumber(userInfo.firstDayOfWeek) && WeekDay.valueOf(userInfo.firstDayOfWeek) ? userInfo.firstDayOfWeek as WeekDayValue : settingsStore.localeDefaultSettings.firstDayOfWeek;
@@ -443,6 +448,7 @@ export const useUserStore = defineStore('user', () => {
currentUserDefaultAccountId,
currentUserLanguage,
currentUserDefaultCurrency,
currentUserUseLastReconciledTime,
currentUserFirstDayOfWeek,
currentUserFiscalYearStart,
currentUserCalendarDisplayType,
+25 -4
View File
@@ -38,6 +38,8 @@ export function useAccountEditPageBase() {
const account = ref<Account>(Account.createNewAccount(defaultAccountCategory, userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount()));
const subAccounts = ref<Account[]>([]);
const useLastReconciledTime = computed(() => userStore.currentUserUseLastReconciledTime);
const title = computed<string>(() => {
if (!editAccountId.value) {
return 'Add Account';
@@ -97,12 +99,12 @@ export function useAccountEditPageBase() {
return getSameDateTimeWithCurrentTimezone(parseDateTimeFromUnixTimeWithBrowserTimezone(getCurrentUnixTime())).getUnixTime();
}
function getDefaultTimezoneOffsetMinutes(account: Account): number {
if (!account.balanceTime) {
return 0;
function getDefaultTimezoneOffsetMinutes(unixTime?: number): number {
if (!unixTime) {
return getTimezoneOffsetMinutes(getCurrentUnixTime());
}
return getTimezoneOffsetMinutes(account.balanceTime);
return getTimezoneOffsetMinutes(unixTime);
}
function getAccountCreditCardStatementDate(statementDate?: number): string | null {
@@ -132,6 +134,23 @@ export function useAccountEditPageBase() {
account.balanceTime = balanceTime - (newUtcOffset - oldUtcOffset) * 60;
}
function updateAccountLastReconciledTime(account: Account, lastReconciledTime: number): void {
if (!isDefined(account.lastReconciledTime)) {
account.lastReconciledTime = lastReconciledTime;
return;
}
const oldUtcOffset = getTimezoneOffsetMinutes(account.lastReconciledTime);
const newUtcOffset = getTimezoneOffsetMinutes(lastReconciledTime);
if (oldUtcOffset === newUtcOffset) {
account.lastReconciledTime = lastReconciledTime;
return;
}
account.lastReconciledTime = lastReconciledTime - (newUtcOffset - oldUtcOffset) * 60;
}
function getInputEmptyProblemMessage(account: Account, isSubAccount: boolean): string | null {
if (!isSubAccount && !account.category) {
return 'Account category cannot be blank';
@@ -189,6 +208,7 @@ export function useAccountEditPageBase() {
account,
subAccounts,
// computed states
useLastReconciledTime,
title,
saveButtonTitle,
inputEmptyProblemMessage,
@@ -202,6 +222,7 @@ export function useAccountEditPageBase() {
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate,
updateAccountBalanceTime,
updateAccountLastReconciledTime,
isNewAccount,
addSubAccount,
setAccount
+7 -2
View File
@@ -6,10 +6,11 @@ import { useSettingsStore } from '@/stores/setting.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useOverviewStore } from '@/stores/overview.ts';
import type { TypeAndDisplayName } from '@/core/base.ts';
import type { TypeAndDisplayName, LocalizedSwitchOption } from '@/core/base.ts';
import { DateDisplayType } from '@/core/calendar.ts';
import { WeekDay } from '@/core/datetime.ts';
import { type LocalizedDigitGroupingType, NumeralSystem, DecimalSeparator, DigitGroupingSymbol } from '@/core/numeral.ts';
import { TransactionEditScopeType } from '@/core/transaction.ts';
import { type UserBasicInfo, User } from '@/models/user.ts';
import { type CategorizedAccount, Account} from '@/models/account.ts';
@@ -22,6 +23,7 @@ export function useUserProfilePageBase() {
tt,
getDefaultCurrency,
getDefaultFirstDayOfWeek,
getAllEnableDisableOptions,
getAllWeekDays,
getAllCalendarDisplayTypes,
getAllDateDisplayTypes,
@@ -85,7 +87,8 @@ export function useUserProfilePageBase() {
const allCoordinateDisplayTypes = computed<TypeAndDisplayName[]>(() => getAllCoordinateDisplayTypes());
const allExpenseAmountColorTypes = computed<TypeAndDisplayName[]>(() => getAllExpenseAmountColors());
const allIncomeAmountColorTypes = computed<TypeAndDisplayName[]>(() => getAllIncomeAmountColors());
const allTransactionEditScopeTypes = computed<TypeAndDisplayName[]>(() => getAllTransactionEditScopeTypes());
const allTransactionEditScopeTypes = computed<TypeAndDisplayName[]>(() => getAllTransactionEditScopeTypes(newProfile.value.useLastReconciledTime || (TransactionEditScopeType.valueOf(newProfile.value.transactionEditScope)?.needLastReconciledTime ?? false)));
const enableDisableOptions = computed<LocalizedSwitchOption[]>(() => getAllEnableDisableOptions());
const languageTitle = computed<string>(() => {
const languageInCurrentLanguage = tt('Language');
@@ -114,6 +117,7 @@ export function useUserProfilePageBase() {
newProfile.value.email === oldProfile.value.email &&
newProfile.value.nickname === oldProfile.value.nickname &&
newProfile.value.defaultAccountId === oldProfile.value.defaultAccountId &&
newProfile.value.useLastReconciledTime === oldProfile.value.useLastReconciledTime &&
newProfile.value.transactionEditScope === oldProfile.value.transactionEditScope &&
newProfile.value.language === oldProfile.value.language &&
newProfile.value.defaultCurrency === oldProfile.value.defaultCurrency &&
@@ -229,6 +233,7 @@ export function useUserProfilePageBase() {
allExpenseAmountColorTypes,
allIncomeAmountColorTypes,
allTransactionEditScopeTypes,
enableDisableOptions,
languageTitle,
supportDigitGroupingSymbol,
inputIsNotChangedProblemMessage,
@@ -129,7 +129,7 @@
v-model="account.creditCardStatementDate"
></v-autocomplete>
</v-col>
<v-col cols="12" :md="(!editAccountId || isNewAccount(selectedAccount)) && selectedAccount.balance ? 6 : 12"
<v-col cols="12" :md="((canShowBalanceTime && selectedAccount.balance) || canShowLastReconciledTime) ? 6 : 12"
v-if="account.type === AccountType.SingleAccount.type || currentAccountIndex >= 0">
<amount-input :disabled="loading || submitting || (!!editAccountId && !isNewAccount(selectedAccount))"
:persistent-placeholder="true"
@@ -140,16 +140,27 @@
:placeholder="accountAmountTitle"
v-model="selectedAccount.balance"/>
</v-col>
<v-col cols="12" md="6" v-show="selectedAccount.balance"
v-if="(!editAccountId || isNewAccount(selectedAccount)) && (account.type === AccountType.SingleAccount.type || currentAccountIndex >= 0)">
<v-col cols="12" md="6" v-show="selectedAccount.balance" v-if="canShowBalanceTime">
<date-time-select
:disabled="loading || submitting"
:label="tt('Balance Time')"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(selectedAccount)"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(selectedAccount.balanceTime)"
:model-value="selectedAccount.balanceTime"
@update:model-value="updateAccountBalanceTime(selectedAccount, $event)"
@error="onShowDateTimeError" />
</v-col>
<v-col cols="12" md="6" v-if="canShowLastReconciledTime">
<date-time-select
:disabled="loading || submitting"
:clearable="true"
:label="tt('Last Reconciled Time')"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(selectedAccount.lastReconciledTime)"
:model-value="selectedAccount.lastReconciledTime ?? getCurrentUnixTime()"
:empty-value="!selectedAccount.lastReconciledTime"
@update:model-value="updateAccountLastReconciledTime(selectedAccount, $event)"
@clear:model-value="selectedAccount.lastReconciledTime = undefined"
@error="onShowDateTimeError" />
</v-col>
<v-col cols="12" md="12">
<v-textarea
type="text"
@@ -212,6 +223,7 @@ import { ALL_ACCOUNT_COLORS } from '@/consts/color.ts';
import { Account } from '@/models/account.ts';
import { isNumber } from '@/lib/common.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts';
import { generateRandomUUID } from '@/lib/misc.ts';
import {
@@ -236,6 +248,7 @@ const {
submitting,
account,
subAccounts,
useLastReconciledTime,
title,
saveButtonTitle,
inputEmptyProblemMessage,
@@ -247,6 +260,7 @@ const {
getCurrentUnixTimeForNewAccount,
getDefaultTimezoneOffsetMinutes,
updateAccountBalanceTime,
updateAccountLastReconciledTime,
isNewAccount,
addSubAccount,
setAccount
@@ -262,6 +276,9 @@ const showState = ref<boolean>(false);
const activeTab = ref<string>('account');
const currentAccountIndex = ref<number>(-1);
const canShowBalanceTime = computed<boolean>(() => (!editAccountId.value || isNewAccount(selectedAccount.value)) && (account.value.type === AccountType.SingleAccount.type || currentAccountIndex.value >= 0));
const canShowLastReconciledTime = computed<boolean>(() => useLastReconciledTime.value && !!editAccountId.value && !isNewAccount(selectedAccount.value) && (account.value.type === AccountType.SingleAccount.type || currentAccountIndex.value >= 0));
const selectedAccount = computed<Account>(() => {
if (currentAccountIndex.value < 0) {
return account.value;
@@ -96,6 +96,27 @@
</two-column-select>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
item-value="value"
persistent-placeholder
:disabled="loading || saving"
:label="tt('Use Last Reconciled Time')"
:placeholder="tt('Use Last Reconciled Time')"
:items="enableDisableOptions"
v-model="newProfile.useLastReconciledTime"
>
<template #item="{ props, item }">
<v-list-item :disabled="!item.raw.value && (TransactionEditScopeType.valueOf(newProfile.transactionEditScope)?.needLastReconciledTime ?? false)" v-bind="props">
<template #title>
<div class="text-truncate">{{ item.raw.displayName }}</div>
</template>
</v-list-item>
</template>
</v-select>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
@@ -408,6 +429,7 @@ import { useRootStore } from '@/stores/index.ts';
import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { TransactionEditScopeType } from '@/core/transaction.ts';
import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts';
import type { UserProfileResponse } from '@/models/user.ts';
import { Account } from '@/models/account.ts';
@@ -452,6 +474,7 @@ const {
allExpenseAmountColorTypes,
allIncomeAmountColorTypes,
allTransactionEditScopeTypes,
enableDisableOptions,
languageTitle,
supportDigitGroupingSymbol,
inputIsNotChangedProblemMessage,
+86 -23
View File
@@ -219,27 +219,53 @@
</f7-list-item>
<f7-list-item
class="account-edit-balancetime list-item-with-header-and-title"
class="account-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
v-show="account.balance"
v-if="!editAccountId"
>
<template #header>
<div class="account-edit-balancetime-header" @click="showDateTimeDialog(accountContext, 'time')">{{ tt('Balance Time') }}</div>
<div class="account-edit-datetime-header" @click="showBalanceDateTimeDialog(accountContext, 'time')">{{ tt('Balance Time') }}</div>
</template>
<template #title>
<div class="account-edit-balancetime-title">
<div @click="showDateTimeDialog(accountContext, 'date')">{{ formatAccountBalanceDate(account) }}</div>&nbsp;<div class="account-edit-balancetime-time" @click="showDateTimeDialog(accountContext, 'time')">{{ formatAccountBalanceTime(account) }}</div>
<div class="account-edit-datetime-title">
<div @click="showBalanceDateTimeDialog(accountContext, 'date')">{{ formatDate(account.balanceTime) }}</div>&nbsp;<div class="account-edit-datetime-time" @click="showBalanceDateTimeDialog(accountContext, 'time')">{{ formatTime(account.balanceTime) }}</div>
</div>
</template>
<date-time-selection-sheet :init-mode="accountContext.balanceDateTimeSheetMode"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(account)"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(account.balanceTime)"
:model-value="account.balanceTime"
v-model:show="accountContext.showBalanceDateTimeSheet"
@update:model-value="updateAccountBalanceTime(account, $event)">
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item
class="account-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
v-if="editAccountId && useLastReconciledTime"
>
<template #header>
<div class="account-edit-datetime-header" @click="showLastReconciledDateTimeDialog(accountContext, 'time')">{{ tt('Last Reconciled Time') }}</div>
</template>
<template #title>
<div class="account-edit-datetime-title" v-if="account.lastReconciledTime">
<div @click="showLastReconciledDateTimeDialog(accountContext, 'date')">{{ formatDate(account.lastReconciledTime) }}</div>&nbsp;<div class="account-edit-datetime-time" @click="showLastReconciledDateTimeDialog(accountContext, 'time')">{{ formatTime(account.lastReconciledTime) }}</div>
</div>
<div class="account-edit-datetime-title" v-else>
<div class="account-edit-datetime-time" @click="showLastReconciledDateTimeDialog(accountContext, 'date')">{{ tt('None') }}</div>
</div>
</template>
<date-time-selection-sheet :init-mode="accountContext.lastReconciledDateTimeSheetMode"
:clearable="true"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(account.lastReconciledTime)"
:model-value="account.lastReconciledTime ?? getCurrentUnixTime()"
v-model:show="accountContext.showLastReconciledTimeSheet"
@update:model-value="updateAccountLastReconciledTime(account, $event)"
@clear:model-value="account.lastReconciledTime = undefined">
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item :title="tt('Visible')" v-if="editAccountId">
<f7-toggle :checked="account.visible" @toggle:change="account.visible = $event"></f7-toggle>
</f7-list-item>
@@ -463,27 +489,53 @@
</f7-list-item>
<f7-list-item
class="account-edit-balancetime list-item-with-header-and-title"
class="account-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
v-show="subAccount.balance"
v-if="!editAccountId || isNewAccount(subAccount)"
>
<template #header>
<div class="account-edit-balancetime-header" @click="showDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ tt('Sub-account Balance Time') }}</div>
<div class="account-edit-datetime-header" @click="showBalanceDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ tt('Sub-account Balance Time') }}</div>
</template>
<template #title>
<div class="account-edit-balancetime-title">
<div @click="showDateTimeDialog(subAccountContexts[idx] as AccountContext, 'date')">{{ formatAccountBalanceDate(subAccount) }}</div>&nbsp;<div class="account-edit-balancetime-time" @click="showDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ formatAccountBalanceTime(subAccount) }}</div>
<div class="account-edit-datetime-title">
<div @click="showBalanceDateTimeDialog(subAccountContexts[idx] as AccountContext, 'date')">{{ formatDate(subAccount.balanceTime) }}</div>&nbsp;<div class="account-edit-datetime-time" @click="showBalanceDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ formatTime(subAccount.balanceTime) }}</div>
</div>
</template>
<date-time-selection-sheet :init-mode="subAccountContexts[idx]!.balanceDateTimeSheetMode"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(subAccount)"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(subAccount.balanceTime)"
:model-value="subAccount.balanceTime"
v-model:show="subAccountContexts[idx]!.showBalanceDateTimeSheet"
@update:model-value="updateAccountBalanceTime(subAccount, $event)">
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item
class="account-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
v-if="editAccountId && !isNewAccount(subAccount) && useLastReconciledTime"
>
<template #header>
<div class="account-edit-datetime-header" @click="showLastReconciledDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ tt('Sub-account Last Reconciled Time') }}</div>
</template>
<template #title>
<div class="account-edit-datetime-title" v-if="subAccount.lastReconciledTime">
<div @click="showLastReconciledDateTimeDialog(subAccountContexts[idx] as AccountContext, 'date')">{{ formatDate(subAccount.lastReconciledTime) }}</div>&nbsp;<div class="account-edit-datetime-time" @click="showLastReconciledDateTimeDialog(subAccountContexts[idx] as AccountContext, 'time')">{{ formatTime(subAccount.lastReconciledTime) }}</div>
</div>
<div class="account-edit-datetime-title" v-else>
<div class="account-edit-datetime-time" @click="showLastReconciledDateTimeDialog(subAccountContexts[idx] as AccountContext, 'date')">{{ tt('None') }}</div>
</div>
</template>
<date-time-selection-sheet :init-mode="subAccountContexts[idx]!.lastReconciledDateTimeSheetMode"
:clearable="true"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(subAccount.lastReconciledTime)"
:model-value="subAccount.lastReconciledTime ?? getCurrentUnixTime()"
v-model:show="subAccountContexts[idx]!.showLastReconciledTimeSheet"
@update:model-value="updateAccountLastReconciledTime(subAccount, $event)"
@clear:model-value="subAccount.lastReconciledTime = undefined">
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item :title="tt('Visible')" v-if="editAccountId && !isNewAccount(subAccount)">
<f7-toggle :checked="subAccount.visible" @toggle:change="subAccount.visible = $event"></f7-toggle>
</f7-list-item>
@@ -542,6 +594,7 @@ import { isDefined, findDisplayNameByType } from '@/lib/common.ts';
import { generateRandomUUID } from '@/lib/misc.ts';
import {
getTimezoneOffsetMinutes,
getCurrentUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
@@ -552,7 +605,9 @@ interface AccountContext {
showCreditCardStatementDatePopup: boolean;
showBalanceSheet: boolean;
showBalanceDateTimeSheet: boolean;
showLastReconciledTimeSheet: boolean;
balanceDateTimeSheetMode: string;
lastReconciledDateTimeSheetMode: string;
}
const props = defineProps<{
@@ -578,6 +633,7 @@ const {
submitting,
account,
subAccounts,
useLastReconciledTime,
title,
inputEmptyProblemMessage,
inputIsEmpty,
@@ -588,6 +644,7 @@ const {
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate,
updateAccountBalanceTime,
updateAccountLastReconciledTime,
isNewAccount,
addSubAccount,
setAccount
@@ -602,7 +659,9 @@ const DEFAULT_ACCOUNT_CONTEXT: AccountContext = {
showCreditCardStatementDatePopup: false,
showBalanceSheet: false,
showBalanceDateTimeSheet: false,
balanceDateTimeSheetMode: 'time'
showLastReconciledTimeSheet: false,
balanceDateTimeSheetMode: 'time',
lastReconciledDateTimeSheetMode: 'time'
};
const accountContext = ref<AccountContext>(Object.assign({}, DEFAULT_ACCOUNT_CONTEXT));
@@ -621,21 +680,21 @@ function formatAccountDisplayBalance(selectedAccount: Account): string {
return formatAmountToLocalizedNumeralsWithCurrency(balance, selectedAccount.currency);
}
function formatAccountBalanceDate(account: Account): string {
if (!isDefined(account.balanceTime)) {
function formatDate(unixTime?: number): string {
if (!isDefined(unixTime)) {
return '';
}
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(unixTime, getTimezoneOffsetMinutes(unixTime));
return formatDateTimeToLongDate(dateTime);
}
function formatAccountBalanceTime(account: Account): string {
if (!isDefined(account.balanceTime)) {
function formatTime(unixTime?: number): string {
if (!isDefined(unixTime)) {
return '';
}
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(unixTime, getTimezoneOffsetMinutes(unixTime));
return formatDateTimeToLongTime(dateTime);
}
@@ -739,11 +798,16 @@ function removeSubAccount(currentSubAccount: Account | null, confirm: boolean):
}
}
function showDateTimeDialog(accountContext: AccountContext, sheetMode: string): void {
function showBalanceDateTimeDialog(accountContext: AccountContext, sheetMode: string): void {
accountContext.balanceDateTimeSheetMode = sheetMode;
accountContext.showBalanceDateTimeSheet = true;
}
function showLastReconciledDateTimeDialog(accountContext: AccountContext, sheetMode: string): void {
accountContext.lastReconciledDateTimeSheetMode = sheetMode;
accountContext.showLastReconciledTimeSheet = true;
}
function onPageAfterIn(): void {
routeBackOnError(props.f7router, loadingError);
}
@@ -758,22 +822,21 @@ init();
</script>
<style>
.account-edit-balancetime .item-title {
.account-edit-datetime .item-title {
width: 100%;
}
.account-edit-balancetime .item-title > .item-header > .account-edit-balancetime-header {
.account-edit-datetime .item-title > .item-header > .account-edit-datetime-header {
display: block;
width: 100%;
}
.account-edit-balancetime .item-title > .account-edit-balancetime-title {
.account-edit-datetime .item-title > .account-edit-datetime-title {
display: flex;
width: 100%;
}
.account-edit-balancetime .item-title > .account-edit-balancetime-title > .account-edit-balancetime-time {
.account-edit-datetime .item-title > .account-edit-datetime-title > .account-edit-datetime-time {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
@@ -18,6 +18,7 @@
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Default Account" title="Unspecified"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Use Last Reconciled Time" title="Disabled" link="#"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Editable Transaction Range" title="All" link="#"></f7-list-item>
</f7-list>
@@ -118,6 +119,29 @@
</two-column-list-item-selection-sheet>
</f7-list-item>
<f7-list-item
link="#"
class="list-item-with-header-and-title list-item-no-item-after"
popover-open=".use-last-reconciled-time-popover-menu"
:header="tt('Use Last Reconciled Time')"
:title="getEnableDisableOption(newProfile.useLastReconciledTime)"
>
<f7-popover class="use-last-reconciled-time-popover-menu">
<f7-list dividers>
<f7-list-item link="#" no-chevron popover-close
:title="option.displayName"
:class="{ 'list-item-selected': newProfile.useLastReconciledTime === option.value, 'disabled': !option.value && (TransactionEditScopeType.valueOf(newProfile.transactionEditScope)?.needLastReconciledTime ?? false) }"
:key="option.value"
v-for="option in enableDisableOptions"
@click="newProfile.useLastReconciledTime = option.value">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="newProfile.useLastReconciledTime === option.value"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
</f7-list-item>
<f7-list-item
link="#"
class="list-item-with-header-and-title list-item-no-item-after"
@@ -572,6 +596,7 @@ import { useAccountsStore } from '@/stores/account.ts';
import { TextDirection } from '@/core/text.ts';
import { NumeralSystem } from '@/core/numeral.ts';
import type { LocalizedCurrencyInfo } from '@/core/currency.ts';
import { TransactionEditScopeType } from '@/core/transaction.ts';
import type { UserProfileResponse } from '@/models/user.ts';
import { Account } from '@/models/account.ts';
@@ -588,6 +613,7 @@ const {
getCurrentLanguageTextDirection,
getAllLanguageOptions,
getAllCurrencies,
getEnableDisableOption,
getCurrencyName,
formatFiscalYearStartToGregorianLikeLongMonth
} = useI18n();
@@ -620,6 +646,7 @@ const {
allExpenseAmountColorTypes,
allIncomeAmountColorTypes,
allTransactionEditScopeTypes,
enableDisableOptions,
languageTitle,
supportDigitGroupingSymbol,
inputIsNotChangedProblemMessage,