support displaying transactions since the last reconciled time

This commit is contained in:
MaysWind
2026-05-08 00:58:04 +08:00
parent de132dd7fd
commit 75d801f775
45 changed files with 608 additions and 107 deletions
+95 -8
View File
@@ -19,6 +19,7 @@ type AccountsApi struct {
ApiUsingConfig
ApiUsingDuplicateChecker
accounts *services.AccountService
users *services.UserService
}
// Initialize an account api singleton instance
@@ -34,6 +35,7 @@ var (
container: duplicatechecker.Container,
},
accounts: services.Accounts,
users: services.Users,
}
)
@@ -333,6 +335,16 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(c, uid)
if err != nil {
if !errs.IsCustomError(err) {
log.Errorf(c, "[accounts.AccountModifyHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
if err != nil {
@@ -434,7 +446,11 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
var toAddAccountBalanceTimes []int64
var toDeleteAccountIds []int64
toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, mainAccount, false)
toUpdateAccount, err := a.getToUpdateAccount(user, &accountModifyReq, mainAccount, false)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if toUpdateAccount != nil {
if toUpdateAccount.Category != mainAccount.Category {
@@ -483,7 +499,11 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
toAddAccountBalanceTimes = append(toAddAccountBalanceTimes, 0)
}
} else {
toUpdateSubAccount := a.getToUpdateAccount(uid, subAccountReq, accountMap[subAccountReq.Id], true)
toUpdateSubAccount, err := a.getToUpdateAccount(user, subAccountReq, accountMap[subAccountReq.Id], true)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if toUpdateSubAccount != nil {
anythingUpdate = true
@@ -607,6 +627,69 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
return accountResp, nil
}
// AccountUpdateLastReconciledTimeHandler updates last reconciled time of an existed account by request parameters for current user
func (a *AccountsApi) AccountUpdateLastReconciledTimeHandler(c *core.WebContext) (any, *errs.Error) {
var accountUpdateReq models.AccountUpdateLastReconciledTimeRequest
err := c.ShouldBindJSON(&accountUpdateReq)
if err != nil {
log.Warnf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
if accountUpdateReq.Id <= 0 {
return nil, errs.ErrAccountIdInvalid
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(c, uid)
if err != nil {
if !errs.IsCustomError(err) {
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
if !user.UseLastReconciledTime {
return nil, errs.ErrLastReconciledTimeIsNotEnabled
}
account, err := a.accounts.GetAccountByAccountId(c, uid, accountUpdateReq.Id)
if err != nil {
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to get account \"id:%d\" for user \"uid:%d\", because %s", accountUpdateReq.Id, uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if account.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
return nil, errs.ErrParentAccountCannotSetLastReconciledTime
}
if account.Extend == nil {
account.Extend = &models.AccountExtend{}
}
if account.Extend.LastReconciledTime != nil && accountUpdateReq.LastReconciledTime < *account.Extend.LastReconciledTime {
return nil, errs.ErrCannotSetLastReconciledTimeBeforeCurrent
} else if account.Extend.LastReconciledTime != nil && accountUpdateReq.LastReconciledTime == *account.Extend.LastReconciledTime {
return nil, errs.ErrNothingWillBeUpdated
}
account.Extend.LastReconciledTime = &accountUpdateReq.LastReconciledTime
err = a.accounts.UpdateAccountExtend(c, uid, account)
if err != nil {
log.Errorf(c, "[accounts.AccountUpdateLastReconciledTimeHandler] failed to update last reconciled time for account \"id:%d\" of user \"uid:%d\", because %s", account.AccountId, uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.Infof(c, "[accounts.AccountUpdateLastReconciledTimeHandler] user \"uid:%d\" has updated last reconciled time \"%d\" for account \"id:%d\"", uid, account.Extend.LastReconciledTime, account.AccountId)
return true, nil
}
// AccountHideHandler hides an existed account by request parameters for current user
func (a *AccountsApi) AccountHideHandler(c *core.WebContext) (any, *errs.Error) {
var accountHideReq models.AccountHideRequest
@@ -764,7 +847,7 @@ func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models
return childrenAccounts, childrenAccountBalanceTimes
}
func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account, isSubAccount bool) *models.Account {
func (a *AccountsApi) getToUpdateAccount(user *models.User, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account, isSubAccount bool) (*models.Account, error) {
newAccountExtend := &models.AccountExtend{}
newAccountExtend.LastReconciledTime = accountModifyReq.LastReconciledTime
@@ -774,7 +857,7 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
newAccount := &models.Account{
AccountId: oldAccount.AccountId,
Uid: uid,
Uid: user.Uid,
Name: accountModifyReq.Name,
DisplayOrder: oldAccount.DisplayOrder,
Category: accountModifyReq.Category,
@@ -791,7 +874,7 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
newAccount.Color != oldAccount.Color ||
newAccount.Comment != oldAccount.Comment ||
newAccount.Hidden != oldAccount.Hidden {
return newAccount
return newAccount, nil
}
oldAccountExtend := oldAccount.Extend
@@ -799,16 +882,20 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
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
if !user.UseLastReconciledTime {
return nil, errs.ErrLastReconciledTimeIsNotEnabled
}
return newAccount, nil
}
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
return newAccount, nil
}
return nil
return nil, nil
}
func (a *AccountsApi) getToDeleteSubAccountIds(accountModifyReq *models.AccountModifyRequest, mainAccount *models.Account, accountAndSubAccounts []*models.Account) []int64 {
+24 -22
View File
@@ -4,26 +4,28 @@ import "net/http"
// Error codes related to accounts
var (
ErrAccountIdInvalid = NewNormalError(NormalSubcategoryAccount, 0, http.StatusBadRequest, "account id is invalid")
ErrAccountNotFound = NewNormalError(NormalSubcategoryAccount, 1, http.StatusBadRequest, "account not found")
ErrAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 2, http.StatusBadRequest, "account type is invalid")
ErrAccountCurrencyInvalid = NewNormalError(NormalSubcategoryAccount, 3, http.StatusBadRequest, "account currency is invalid")
ErrAccountHaveNoSubAccount = NewNormalError(NormalSubcategoryAccount, 4, http.StatusBadRequest, "account must have at least one sub-account")
ErrAccountCannotHaveSubAccounts = NewNormalError(NormalSubcategoryAccount, 5, http.StatusBadRequest, "account cannot have sub-accounts")
ErrParentAccountCannotSetCurrency = NewNormalError(NormalSubcategoryAccount, 6, http.StatusBadRequest, "parent account cannot set currency")
ErrParentAccountCannotSetBalance = NewNormalError(NormalSubcategoryAccount, 7, http.StatusBadRequest, "parent account cannot set balance")
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NormalSubcategoryAccount, 8, http.StatusBadRequest, "sub-account category not equals to parent")
ErrSubAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 9, http.StatusBadRequest, "sub-account type invalid")
ErrSourceAccountNotFound = NewNormalError(NormalSubcategoryAccount, 11, http.StatusBadRequest, "source account not found")
ErrDestinationAccountNotFound = NewNormalError(NormalSubcategoryAccount, 12, http.StatusBadRequest, "destination account not found")
ErrAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 13, http.StatusBadRequest, "account is in use and cannot be deleted")
ErrAccountCategoryInvalid = NewNormalError(NormalSubcategoryAccount, 14, http.StatusBadRequest, "account category is invalid")
ErrAccountBalanceTimeNotSet = NewNormalError(NormalSubcategoryAccount, 15, http.StatusBadRequest, "account balance time is not set")
ErrCannotSetStatementDateForNonCreditCard = NewNormalError(NormalSubcategoryAccount, 16, http.StatusBadRequest, "cannot set statement date for non credit card account")
ErrCannotSetStatementDateForSubAccount = NewNormalError(NormalSubcategoryAccount, 17, http.StatusBadRequest, "cannot set statement date for sub account")
ErrSubAccountNotFound = NewNormalError(NormalSubcategoryAccount, 18, http.StatusBadRequest, "sub-account not found")
ErrSubAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 19, http.StatusBadRequest, "sub-account is in use and cannot be deleted")
ErrNotSupportedChangeCurrency = NewNormalError(NormalSubcategoryAccount, 20, http.StatusBadRequest, "not supported to modify account currency")
ErrNotSupportedChangeBalance = NewNormalError(NormalSubcategoryAccount, 21, http.StatusBadRequest, "not supported to modify account balance")
ErrNotSupportedChangeBalanceTime = NewNormalError(NormalSubcategoryAccount, 22, http.StatusBadRequest, "not supported to modify account balance time")
ErrAccountIdInvalid = NewNormalError(NormalSubcategoryAccount, 0, http.StatusBadRequest, "account id is invalid")
ErrAccountNotFound = NewNormalError(NormalSubcategoryAccount, 1, http.StatusBadRequest, "account not found")
ErrAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 2, http.StatusBadRequest, "account type is invalid")
ErrAccountCurrencyInvalid = NewNormalError(NormalSubcategoryAccount, 3, http.StatusBadRequest, "account currency is invalid")
ErrAccountHaveNoSubAccount = NewNormalError(NormalSubcategoryAccount, 4, http.StatusBadRequest, "account must have at least one sub-account")
ErrAccountCannotHaveSubAccounts = NewNormalError(NormalSubcategoryAccount, 5, http.StatusBadRequest, "account cannot have sub-accounts")
ErrParentAccountCannotSetCurrency = NewNormalError(NormalSubcategoryAccount, 6, http.StatusBadRequest, "parent account cannot set currency")
ErrParentAccountCannotSetBalance = NewNormalError(NormalSubcategoryAccount, 7, http.StatusBadRequest, "parent account cannot set balance")
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NormalSubcategoryAccount, 8, http.StatusBadRequest, "sub-account category not equals to parent")
ErrSubAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 9, http.StatusBadRequest, "sub-account type invalid")
ErrSourceAccountNotFound = NewNormalError(NormalSubcategoryAccount, 11, http.StatusBadRequest, "source account not found")
ErrDestinationAccountNotFound = NewNormalError(NormalSubcategoryAccount, 12, http.StatusBadRequest, "destination account not found")
ErrAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 13, http.StatusBadRequest, "account is in use and cannot be deleted")
ErrAccountCategoryInvalid = NewNormalError(NormalSubcategoryAccount, 14, http.StatusBadRequest, "account category is invalid")
ErrAccountBalanceTimeNotSet = NewNormalError(NormalSubcategoryAccount, 15, http.StatusBadRequest, "account balance time is not set")
ErrCannotSetStatementDateForNonCreditCard = NewNormalError(NormalSubcategoryAccount, 16, http.StatusBadRequest, "cannot set statement date for non credit card account")
ErrCannotSetStatementDateForSubAccount = NewNormalError(NormalSubcategoryAccount, 17, http.StatusBadRequest, "cannot set statement date for sub account")
ErrSubAccountNotFound = NewNormalError(NormalSubcategoryAccount, 18, http.StatusBadRequest, "sub-account not found")
ErrSubAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 19, http.StatusBadRequest, "sub-account is in use and cannot be deleted")
ErrNotSupportedChangeCurrency = NewNormalError(NormalSubcategoryAccount, 20, http.StatusBadRequest, "not supported to modify account currency")
ErrNotSupportedChangeBalance = NewNormalError(NormalSubcategoryAccount, 21, http.StatusBadRequest, "not supported to modify account balance")
ErrNotSupportedChangeBalanceTime = NewNormalError(NormalSubcategoryAccount, 22, http.StatusBadRequest, "not supported to modify account balance time")
ErrParentAccountCannotSetLastReconciledTime = NewNormalError(NormalSubcategoryAccount, 23, http.StatusBadRequest, "parent account cannot set last reconciled time")
ErrCannotSetLastReconciledTimeBeforeCurrent = NewNormalError(NormalSubcategoryAccount, 24, http.StatusBadRequest, "cannot set last reconciled time before current value")
)
+1
View File
@@ -41,4 +41,5 @@ var (
ErrCannotLoginByPassword = NewNormalError(NormalSubcategoryUser, 32, http.StatusBadRequest, "cannot login by password")
ErrUserNameIsInvalid = NewNormalError(NormalSubcategoryUser, 33, http.StatusBadRequest, "user name is invalid")
ErrNickNameIsInvalid = NewNormalError(NormalSubcategoryUser, 34, http.StatusBadRequest, "nick name is invalid")
ErrLastReconciledTimeIsNotEnabled = NewNormalError(NormalSubcategoryUser, 35, http.StatusBadRequest, "last reconciled time is not enabled")
)
+6
View File
@@ -128,6 +128,12 @@ type AccountModifyRequest struct {
ClientSessionId string `json:"clientSessionId"`
}
// AccountUpdateLastReconciledTimeRequest represents all parameters of account updating last reconciled time request
type AccountUpdateLastReconciledTimeRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
LastReconciledTime int64 `json:"lastReconciledTime" binding:"required"`
}
// AccountListRequest represents all parameters of account listing request
type AccountListRequest struct {
VisibleOnly bool `form:"visible_only"`
+21
View File
@@ -592,6 +592,27 @@ func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Acco
})
}
// UpdateAccountExtend updates extend field of given account
func (s *AccountService) UpdateAccountExtend(c core.Context, uid int64, account *models.Account) error {
if uid <= 0 {
return errs.ErrUserIdInvalid
}
account.UpdatedUnixTime = time.Now().Unix()
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
updatedRows, err := sess.ID(account.AccountId).Cols("extend", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(account)
if err != nil {
return err
} else if updatedRows < 1 {
return errs.ErrAccountNotFound
}
return nil
})
}
// HideAccount updates hidden field of given accounts
func (s *AccountService) HideAccount(c core.Context, uid int64, ids []int64, hidden bool) error {
if uid <= 0 {