Skip to content

Commit 78ba434

Browse files
committed
support adding / deleting sub account after account created (#77)
1 parent e7e2cc8 commit 78ba434

File tree

20 files changed

+520
-76
lines changed

20 files changed

+520
-76
lines changed

pkg/api/accounts.go

+229-19
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,27 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
311311
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
312312
}
313313

314+
if accountModifyReq.Id <= 0 {
315+
return nil, errs.ErrAccountIdInvalid
316+
}
317+
318+
utcOffset, err := c.GetClientTimezoneOffset()
319+
320+
if err != nil {
321+
log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error())
322+
return nil, errs.ErrClientTimezoneOffsetInvalid
323+
}
324+
314325
if accountModifyReq.Category < models.ACCOUNT_CATEGORY_CASH || accountModifyReq.Category > models.ACCOUNT_CATEGORY_CERTIFICATE_OF_DEPOSIT {
315326
log.Warnf(c, "[accounts.AccountModifyHandler] account category invalid, category is %d", accountModifyReq.Category)
316327
return nil, errs.ErrAccountCategoryInvalid
317328
}
318329

330+
if accountModifyReq.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && accountModifyReq.CreditCardStatementDate != 0 {
331+
log.Warnf(c, "[accounts.AccountModifyHandler] cannot set statement date with category \"%d\"", accountModifyReq.Category)
332+
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
333+
}
334+
319335
uid := c.GetCurrentUid()
320336
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountModifyReq.Id)
321337

@@ -331,20 +347,81 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
331347
return nil, errs.ErrAccountNotFound
332348
}
333349

334-
if len(accountModifyReq.SubAccounts)+1 != len(accountAndSubAccounts) {
335-
return nil, errs.ErrCannotAddOrDeleteSubAccountsWhenModify
350+
if accountModifyReq.Currency != nil && mainAccount.Currency != *accountModifyReq.Currency {
351+
return nil, errs.ErrNotSupportedChangeCurrency
336352
}
337353

338-
if accountModifyReq.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && accountModifyReq.CreditCardStatementDate != 0 {
339-
log.Warnf(c, "[accounts.AccountModifyHandler] cannot set statement date with category \"%d\"", accountModifyReq.Category)
340-
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
354+
if accountModifyReq.Balance != nil {
355+
return nil, errs.ErrNotSupportedChangeBalance
356+
}
357+
358+
if accountModifyReq.BalanceTime != nil {
359+
return nil, errs.ErrNotSupportedChangeBalanceTime
341360
}
342361

343-
if mainAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
362+
if mainAccount.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
363+
if len(accountModifyReq.SubAccounts) > 0 {
364+
log.Warnf(c, "[accounts.AccountModifyHandler] account cannot have any sub-accounts")
365+
return nil, errs.ErrAccountCannotHaveSubAccounts
366+
}
367+
} else if mainAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
368+
if len(accountModifyReq.SubAccounts) < 1 {
369+
log.Warnf(c, "[accounts.AccountModifyHandler] account does not have any sub-accounts")
370+
return nil, errs.ErrAccountHaveNoSubAccount
371+
}
372+
344373
for i := 0; i < len(accountModifyReq.SubAccounts); i++ {
345-
subAccount := accountModifyReq.SubAccounts[i]
374+
subAccountReq := accountModifyReq.SubAccounts[i]
346375

347-
if subAccount.CreditCardStatementDate != 0 {
376+
if subAccountReq.Category != accountModifyReq.Category {
377+
log.Warnf(c, "[accounts.AccountModifyHandler] category of sub-account#%d not equals to parent", i)
378+
return nil, errs.ErrSubAccountCategoryNotEqualsToParent
379+
}
380+
381+
if subAccountReq.Id == 0 { // create new sub-account
382+
if subAccountReq.Currency == nil {
383+
log.Warnf(c, "[accounts.AccountModifyHandler] sub-account#%d not set currency", i)
384+
return nil, errs.ErrAccountCurrencyInvalid
385+
} else if subAccountReq.Currency != nil && *subAccountReq.Currency == validators.ParentAccountCurrencyPlaceholder {
386+
log.Warnf(c, "[accounts.AccountModifyHandler] sub-account#%d cannot set currency placeholder", i)
387+
return nil, errs.ErrAccountCurrencyInvalid
388+
}
389+
390+
if subAccountReq.Balance == nil {
391+
defaultBalance := int64(0)
392+
subAccountReq.Balance = &defaultBalance
393+
}
394+
395+
if *subAccountReq.Balance == 0 {
396+
defaultBalanceTime := int64(0)
397+
subAccountReq.BalanceTime = &defaultBalanceTime
398+
}
399+
400+
if *subAccountReq.Balance != 0 && (subAccountReq.BalanceTime == nil || *subAccountReq.BalanceTime <= 0) {
401+
log.Warnf(c, "[accounts.AccountModifyHandler] sub-account#%d balance time is not set", i)
402+
return nil, errs.ErrAccountBalanceTimeNotSet
403+
}
404+
} else { // modify existed sub-account
405+
subAccount, exists := accountMap[subAccountReq.Id]
406+
407+
if !exists {
408+
return nil, errs.ErrAccountNotFound
409+
}
410+
411+
if subAccountReq.Currency != nil && subAccount.Currency != *subAccountReq.Currency {
412+
return nil, errs.ErrNotSupportedChangeCurrency
413+
}
414+
415+
if subAccountReq.Balance != nil {
416+
return nil, errs.ErrNotSupportedChangeBalance
417+
}
418+
419+
if subAccountReq.BalanceTime != nil {
420+
return nil, errs.ErrNotSupportedChangeBalanceTime
421+
}
422+
}
423+
424+
if subAccountReq.CreditCardStatementDate != 0 {
348425
log.Warnf(c, "[accounts.AccountModifyHandler] sub-account#%d cannot set statement date", i)
349426
return nil, errs.ErrCannotSetStatementDateForSubAccount
350427
}
@@ -353,6 +430,9 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
353430

354431
anythingUpdate := false
355432
var toUpdateAccounts []*models.Account
433+
var toAddAccounts []*models.Account
434+
var toAddAccountBalanceTimes []int64
435+
var toDeleteAccountIds []int64
356436

357437
toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, mainAccount, false)
358438

@@ -361,26 +441,87 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
361441
toUpdateAccounts = append(toUpdateAccounts, toUpdateAccount)
362442
}
363443

364-
for i := 0; i < len(accountModifyReq.SubAccounts); i++ {
365-
subAccountReq := accountModifyReq.SubAccounts[i]
444+
toDeleteAccountIds = a.getToDeleteSubAccountIds(&accountModifyReq, mainAccount, accountAndSubAccounts)
366445

367-
if _, exists := accountMap[subAccountReq.Id]; !exists {
368-
return nil, errs.ErrAccountNotFound
446+
if len(toDeleteAccountIds) > 0 {
447+
anythingUpdate = true
448+
}
449+
450+
maxOrderId := int32(0)
451+
452+
for i := 0; i < len(accountAndSubAccounts); i++ {
453+
account := accountAndSubAccounts[i]
454+
455+
if account.AccountId != mainAccount.AccountId && account.DisplayOrder > maxOrderId {
456+
maxOrderId = account.DisplayOrder
369457
}
458+
}
370459

371-
toUpdateSubAccount := a.getToUpdateAccount(uid, subAccountReq, accountMap[subAccountReq.Id], true)
460+
for i := 0; i < len(accountModifyReq.SubAccounts); i++ {
461+
subAccountReq := accountModifyReq.SubAccounts[i]
372462

373-
if toUpdateSubAccount != nil {
463+
if _, exists := accountMap[subAccountReq.Id]; !exists {
374464
anythingUpdate = true
375-
toUpdateAccounts = append(toUpdateAccounts, toUpdateSubAccount)
465+
maxOrderId = maxOrderId + 1
466+
newSubAccount := a.createNewSubAccountModelForModify(uid, mainAccount.Type, subAccountReq, maxOrderId)
467+
toAddAccounts = append(toAddAccounts, newSubAccount)
468+
469+
if subAccountReq.BalanceTime != nil {
470+
toAddAccountBalanceTimes = append(toAddAccountBalanceTimes, *subAccountReq.BalanceTime)
471+
} else {
472+
toAddAccountBalanceTimes = append(toAddAccountBalanceTimes, 0)
473+
}
474+
} else {
475+
toUpdateSubAccount := a.getToUpdateAccount(uid, subAccountReq, accountMap[subAccountReq.Id], true)
476+
477+
if toUpdateSubAccount != nil {
478+
anythingUpdate = true
479+
toUpdateAccounts = append(toUpdateAccounts, toUpdateSubAccount)
480+
}
376481
}
377482
}
378483

379484
if !anythingUpdate {
380485
return nil, errs.ErrNothingWillBeUpdated
381486
}
382487

383-
err = a.accounts.ModifyAccounts(c, uid, toUpdateAccounts)
488+
if len(toAddAccounts) > 0 && a.CurrentConfig().EnableDuplicateSubmissionsCheck && accountModifyReq.ClientSessionId != "" {
489+
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_SUBACCOUNT, uid, accountModifyReq.ClientSessionId)
490+
491+
if found {
492+
log.Infof(c, "[accounts.AccountModifyHandler] another account \"id:%s\" modification has been created for user \"uid:%d\"", remark, uid)
493+
accountId, err := utils.StringToInt64(remark)
494+
495+
if err == nil {
496+
accountAndSubAccounts, err := a.accounts.GetAccountAndSubAccountsByAccountId(c, uid, accountId)
497+
498+
if err != nil {
499+
log.Errorf(c, "[accounts.AccountModifyHandler] failed to get existed account \"id:%d\" for user \"uid:%d\", because %s", accountId, uid, err.Error())
500+
return nil, errs.Or(err, errs.ErrOperationFailed)
501+
}
502+
503+
accountMap := a.accounts.GetAccountMapByList(accountAndSubAccounts)
504+
mainAccount, exists := accountMap[accountId]
505+
506+
if !exists {
507+
return nil, errs.ErrOperationFailed
508+
}
509+
510+
accountInfoResp := mainAccount.ToAccountInfoResponse()
511+
512+
for i := 0; i < len(accountAndSubAccounts); i++ {
513+
if accountAndSubAccounts[i].ParentAccountId == mainAccount.AccountId {
514+
subAccountResp := accountAndSubAccounts[i].ToAccountInfoResponse()
515+
accountInfoResp.SubAccounts = append(accountInfoResp.SubAccounts, subAccountResp)
516+
}
517+
}
518+
519+
return accountInfoResp, nil
520+
}
521+
}
522+
}
523+
524+
err = a.accounts.ModifyAccounts(c, mainAccount, toUpdateAccounts, toAddAccounts, toAddAccountBalanceTimes, toDeleteAccountIds, utcOffset)
384525

385526
if err != nil {
386527
log.Errorf(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
@@ -389,6 +530,10 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
389530

390531
log.Infof(c, "[accounts.AccountModifyHandler] user \"uid:%d\" has updated account \"id:%d\" successfully", uid, accountModifyReq.Id)
391532

533+
if len(toAddAccounts) > 0 {
534+
a.SetSubmissionRemarkIfEnable(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_SUBACCOUNT, uid, accountModifyReq.ClientSessionId, utils.Int64ToString(mainAccount.AccountId))
535+
}
536+
392537
accountRespMap := make(map[int64]*models.AccountInfoResponse)
393538

394539
for i := 0; i < len(toUpdateAccounts); i++ {
@@ -405,11 +550,23 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
405550
accountRespMap[accountResp.Id] = accountResp
406551
}
407552

553+
for i := 0; i < len(toAddAccounts); i++ {
554+
account := toAddAccounts[i]
555+
accountResp := account.ToAccountInfoResponse()
556+
accountRespMap[accountResp.Id] = accountResp
557+
}
558+
559+
deletedAccountIds := make(map[int64]bool)
560+
561+
for i := 0; i < len(toDeleteAccountIds); i++ {
562+
deletedAccountIds[toDeleteAccountIds[i]] = true
563+
}
564+
408565
for i := 0; i < len(accountAndSubAccounts); i++ {
409566
oldAccount := accountAndSubAccounts[i]
410567
_, exists := accountRespMap[oldAccount.AccountId]
411568

412-
if !exists {
569+
if !exists && !deletedAccountIds[oldAccount.AccountId] {
413570
oldAccountResp := oldAccount.ToAccountInfoResponse()
414571
accountRespMap[oldAccountResp.Id] = oldAccountResp
415572
}
@@ -418,8 +575,19 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
418575
accountResp := accountRespMap[accountModifyReq.Id]
419576

420577
for i := 0; i < len(accountAndSubAccounts); i++ {
421-
if accountAndSubAccounts[i].ParentAccountId == accountResp.Id {
422-
subAccountResp := accountRespMap[accountAndSubAccounts[i].AccountId]
578+
account := accountAndSubAccounts[i]
579+
580+
if account.ParentAccountId == accountResp.Id && !deletedAccountIds[account.AccountId] {
581+
subAccountResp := accountRespMap[account.AccountId]
582+
accountResp.SubAccounts = append(accountResp.SubAccounts, subAccountResp)
583+
}
584+
}
585+
586+
for i := 0; i < len(toAddAccounts); i++ {
587+
account := toAddAccounts[i]
588+
589+
if account.ParentAccountId == accountResp.Id {
590+
subAccountResp := accountRespMap[account.AccountId]
423591
accountResp.SubAccounts = append(accountResp.SubAccounts, subAccountResp)
424592
}
425593
}
@@ -552,6 +720,24 @@ func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models.
552720
}
553721
}
554722

723+
func (a *AccountsApi) createNewSubAccountModelForModify(uid int64, accountType models.AccountType, accountModifyReq *models.AccountModifyRequest, order int32) *models.Account {
724+
accountExtend := &models.AccountExtend{}
725+
726+
return &models.Account{
727+
Uid: uid,
728+
Name: accountModifyReq.Name,
729+
DisplayOrder: order,
730+
Category: accountModifyReq.Category,
731+
Type: accountType,
732+
Icon: accountModifyReq.Icon,
733+
Color: accountModifyReq.Color,
734+
Currency: *accountModifyReq.Currency,
735+
Balance: *accountModifyReq.Balance,
736+
Comment: accountModifyReq.Comment,
737+
Extend: accountExtend,
738+
}
739+
}
740+
555741
func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models.AccountCreateRequest) ([]*models.Account, []int64) {
556742
if len(accountCreateReq.SubAccounts) <= 0 {
557743
return nil, nil
@@ -609,3 +795,27 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
609795

610796
return nil
611797
}
798+
799+
func (a *AccountsApi) getToDeleteSubAccountIds(accountModifyReq *models.AccountModifyRequest, mainAccount *models.Account, accountAndSubAccounts []*models.Account) []int64 {
800+
newSubAccountIds := make(map[int64]bool, len(accountModifyReq.SubAccounts))
801+
802+
for i := 0; i < len(accountModifyReq.SubAccounts); i++ {
803+
newSubAccountIds[accountModifyReq.SubAccounts[i].Id] = true
804+
}
805+
806+
toDeleteAccountIds := make([]int64, 0)
807+
808+
for i := 0; i < len(accountAndSubAccounts); i++ {
809+
subAccount := accountAndSubAccounts[i]
810+
811+
if subAccount.AccountId == mainAccount.AccountId {
812+
continue
813+
}
814+
815+
if _, exists := newSubAccountIds[subAccount.AccountId]; !exists {
816+
toDeleteAccountIds = append(toDeleteAccountIds, subAccount.AccountId)
817+
}
818+
}
819+
820+
return toDeleteAccountIds
821+
}

pkg/duplicatechecker/duplicate_checker_type.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ type DuplicateCheckerType uint8
77
const (
88
DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB DuplicateCheckerType = 0
99
DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT DuplicateCheckerType = 1
10-
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 2
11-
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 3
12-
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
13-
DUPLICATE_CHECKER_TYPE_NEW_PICTURE DuplicateCheckerType = 5
14-
DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS DuplicateCheckerType = 6
10+
DUPLICATE_CHECKER_TYPE_NEW_SUBACCOUNT DuplicateCheckerType = 2
11+
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 3
12+
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 4
13+
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 5
14+
DUPLICATE_CHECKER_TYPE_NEW_PICTURE DuplicateCheckerType = 6
15+
DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS DuplicateCheckerType = 7
1516
DUPLICATE_CHECKER_TYPE_FAILURE_CHECK DuplicateCheckerType = 255
1617
)

pkg/errs/account.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ var (
1414
ErrParentAccountCannotSetBalance = NewNormalError(NormalSubcategoryAccount, 7, http.StatusBadRequest, "parent account cannot set balance")
1515
ErrSubAccountCategoryNotEqualsToParent = NewNormalError(NormalSubcategoryAccount, 8, http.StatusBadRequest, "sub-account category not equals to parent")
1616
ErrSubAccountTypeInvalid = NewNormalError(NormalSubcategoryAccount, 9, http.StatusBadRequest, "sub-account type invalid")
17-
ErrCannotAddOrDeleteSubAccountsWhenModify = NewNormalError(NormalSubcategoryAccount, 10, http.StatusBadRequest, "cannot add or delete sub-accounts when modify account")
1817
ErrSourceAccountNotFound = NewNormalError(NormalSubcategoryAccount, 11, http.StatusBadRequest, "source account not found")
1918
ErrDestinationAccountNotFound = NewNormalError(NormalSubcategoryAccount, 12, http.StatusBadRequest, "destination account not found")
2019
ErrAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 13, http.StatusBadRequest, "account is in use and cannot be deleted")
@@ -24,4 +23,7 @@ var (
2423
ErrCannotSetStatementDateForSubAccount = NewNormalError(NormalSubcategoryAccount, 17, http.StatusBadRequest, "cannot set statement date for sub account")
2524
ErrSubAccountNotFound = NewNormalError(NormalSubcategoryAccount, 18, http.StatusBadRequest, "sub-account not found")
2625
ErrSubAccountInUseCannotBeDeleted = NewNormalError(NormalSubcategoryAccount, 19, http.StatusBadRequest, "sub-account is in use and cannot be deleted")
26+
ErrNotSupportedChangeCurrency = NewNormalError(NormalSubcategoryAccount, 20, http.StatusBadRequest, "not supported to modify account currency")
27+
ErrNotSupportedChangeBalance = NewNormalError(NormalSubcategoryAccount, 21, http.StatusBadRequest, "not supported to modify account balance")
28+
ErrNotSupportedChangeBalanceTime = NewNormalError(NormalSubcategoryAccount, 22, http.StatusBadRequest, "not supported to modify account balance time")
2729
)

pkg/models/account.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,19 @@ type AccountCreateRequest struct {
101101

102102
// AccountModifyRequest represents all parameters of account modification request
103103
type AccountModifyRequest struct {
104-
Id int64 `json:"id,string" binding:"required,min=1"`
104+
Id int64 `json:"id,string" binding:"required,min=0"`
105105
Name string `json:"name" binding:"required,notBlank,max=64"`
106106
Category AccountCategory `json:"category" binding:"required"`
107107
Icon int64 `json:"icon,string" binding:"min=1"`
108108
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
109+
Currency *string `json:"currency" binding:"omitempty,len=3,validCurrency"`
110+
Balance *int64 `json:"balance" binding:"omitempty"`
111+
BalanceTime *int64 `json:"balanceTime" binding:"omitempty"`
109112
Comment string `json:"comment" binding:"max=255"`
110113
CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"`
111114
Hidden bool `json:"hidden"`
112115
SubAccounts []*AccountModifyRequest `json:"subAccounts" binding:"omitempty"`
116+
ClientSessionId string `json:"clientSessionId"`
113117
}
114118

115119
// AccountListRequest represents all parameters of account listing request

0 commit comments

Comments
 (0)