Skip to content

Commit

Permalink
credit card account supports statement date
Browse files Browse the repository at this point in the history
  • Loading branch information
mayswind committed Dec 10, 2024
1 parent 50c774f commit 62e0919
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 66 deletions.
56 changes: 54 additions & 2 deletions pkg/api/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
log.Warnf(c, "[accounts.AccountCreateHandler] account balance time is not set")
return nil, errs.ErrAccountBalanceTimeNotSet
}

if accountCreateReq.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && accountCreateReq.CreditCardStatementDate != 0 {
log.Warnf(c, "[accounts.AccountCreateHandler] cannot set statement date with category \"%d\"", accountCreateReq.Category)
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
}
} else if accountCreateReq.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
if len(accountCreateReq.SubAccounts) < 1 {
log.Warnf(c, "[accounts.AccountCreateHandler] account does not have any sub-accounts")
Expand Down Expand Up @@ -212,6 +217,11 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
log.Warnf(c, "[accounts.AccountCreateHandler] sub-account#%d balance time is not set", i)
return nil, errs.ErrAccountBalanceTimeNotSet
}

if subAccount.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && subAccount.CreditCardStatementDate != 0 {
log.Warnf(c, "[accounts.AccountCreateHandler] sub-account#%d cannot set statement date with category \"%d\"", i, subAccount.Category)
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
}
}
} else {
log.Warnf(c, "[accounts.AccountCreateHandler] account type invalid, type is %d", accountCreateReq.Type)
Expand Down Expand Up @@ -312,19 +322,36 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
}

accountMap := a.accounts.GetAccountMapByList(accountAndSubAccounts)
mainAccount, exists := accountMap[accountModifyReq.Id]

if _, exists := accountMap[accountModifyReq.Id]; !exists {
if !exists {
return nil, errs.ErrAccountNotFound
}

if len(accountModifyReq.SubAccounts)+1 != len(accountAndSubAccounts) {
return nil, errs.ErrCannotAddOrDeleteSubAccountsWhenModify
}

if mainAccount.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
if accountModifyReq.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && accountModifyReq.CreditCardStatementDate != 0 {
log.Warnf(c, "[accounts.AccountModifyHandler] cannot set statement date with category \"%d\"", accountModifyReq.Category)
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
}
} else if mainAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
for i := 0; i < len(accountModifyReq.SubAccounts); i++ {
subAccount := accountModifyReq.SubAccounts[i]

if subAccount.Category != models.ACCOUNT_CATEGORY_CREDIT_CARD && subAccount.CreditCardStatementDate != 0 {
log.Warnf(c, "[accounts.AccountModifyHandler] sub-account#%d cannot set statement date with category \"%d\"", i, subAccount.Category)
return nil, errs.ErrCannotSetStatementDateForNonCreditCard
}
}
}

anythingUpdate := false
var toUpdateAccounts []*models.Account

toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, accountMap[accountModifyReq.Id])
toUpdateAccount := a.getToUpdateAccount(uid, &accountModifyReq, mainAccount)

if toUpdateAccount != nil {
anythingUpdate = true
Expand Down Expand Up @@ -479,6 +506,12 @@ func (a *AccountsApi) AccountDeleteHandler(c *core.WebContext) (any, *errs.Error
}

func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models.AccountCreateRequest, order int32) *models.Account {
accountExtend := &models.AccountExtend{}

if accountCreateReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
accountExtend.CreditCardStatementDate = &accountCreateReq.CreditCardStatementDate
}

return &models.Account{
Uid: uid,
Name: accountCreateReq.Name,
Expand All @@ -490,6 +523,7 @@ func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models.
Currency: accountCreateReq.Currency,
Balance: accountCreateReq.Balance,
Comment: accountCreateReq.Comment,
Extend: accountExtend,
}
}

Expand All @@ -510,6 +544,12 @@ func (a *AccountsApi) createSubAccountModels(uid int64, accountCreateReq *models
}

func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.AccountModifyRequest, oldAccount *models.Account) *models.Account {
newAccountExtend := &models.AccountExtend{}

if accountModifyReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
newAccountExtend.CreditCardStatementDate = &accountModifyReq.CreditCardStatementDate
}

newAccount := &models.Account{
AccountId: oldAccount.AccountId,
Uid: uid,
Expand All @@ -518,6 +558,7 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
Icon: accountModifyReq.Icon,
Color: accountModifyReq.Color,
Comment: accountModifyReq.Comment,
Extend: newAccountExtend,
Hidden: accountModifyReq.Hidden,
}

Expand All @@ -530,5 +571,16 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc
return newAccount
}

if (newAccount.Extend != nil && oldAccount.Extend == nil) ||
(newAccount.Extend == nil && oldAccount.Extend != nil) {
return newAccount
}

oldAccountExtend := oldAccount.Extend

if newAccountExtend.CreditCardStatementDate != oldAccountExtend.CreditCardStatementDate {
return newAccount
}

return nil
}
1 change: 1 addition & 0 deletions pkg/errs/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ var (
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")
)
130 changes: 82 additions & 48 deletions pkg/models/account.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package models

import "encoding/json"

// LevelOneAccountParentId represents the parent id of level-one account
const LevelOneAccountParentId = 0

Expand Down Expand Up @@ -52,6 +54,8 @@ const (
ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS AccountType = 2
)

var defaultCreditCardAccountStatementDate = 0

// Account represents account data stored in database
type Account struct {
AccountId int64 `xorm:"PK"`
Expand All @@ -67,37 +71,45 @@ type Account struct {
Currency string `xorm:"VARCHAR(3) NOT NULL"`
Balance int64 `xorm:"NOT NULL"`
Comment string `xorm:"VARCHAR(255) NOT NULL"`
Extend *AccountExtend `xorm:"BLOB"`
Hidden bool `xorm:"NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
}

// AccountExtend represents account extend data stored in database
type AccountExtend struct {
CreditCardStatementDate *int `json:"creditCardStatementDate"`
}

// AccountCreateRequest represents all parameters of account creation request
type AccountCreateRequest struct {
Name string `json:"name" binding:"required,notBlank,max=32"`
Category AccountCategory `json:"category" binding:"required"`
Type AccountType `json:"type" binding:"required"`
Icon int64 `json:"icon,string" binding:"required,min=1"`
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
Currency string `json:"currency" binding:"required,len=3,validCurrency"`
Balance int64 `json:"balance"`
BalanceTime int64 `json:"balanceTime"`
Comment string `json:"comment" binding:"max=255"`
SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"`
ClientSessionId string `json:"clientSessionId"`
Name string `json:"name" binding:"required,notBlank,max=32"`
Category AccountCategory `json:"category" binding:"required"`
Type AccountType `json:"type" binding:"required"`
Icon int64 `json:"icon,string" binding:"required,min=1"`
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
Currency string `json:"currency" binding:"required,len=3,validCurrency"`
Balance int64 `json:"balance"`
BalanceTime int64 `json:"balanceTime"`
Comment string `json:"comment" binding:"max=255"`
CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"`
SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"`
ClientSessionId string `json:"clientSessionId"`
}

// AccountModifyRequest represents all parameters of account modification request
type AccountModifyRequest struct {
Id int64 `json:"id,string" binding:"required,min=1"`
Name string `json:"name" binding:"required,notBlank,max=32"`
Category AccountCategory `json:"category" binding:"required"`
Icon int64 `json:"icon,string" binding:"min=1"`
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
Comment string `json:"comment" binding:"max=255"`
Hidden bool `json:"hidden"`
SubAccounts []*AccountModifyRequest `json:"subAccounts" binding:"omitempty"`
Id int64 `json:"id,string" binding:"required,min=1"`
Name string `json:"name" binding:"required,notBlank,max=32"`
Category AccountCategory `json:"category" binding:"required"`
Icon int64 `json:"icon,string" binding:"min=1"`
Color string `json:"color" binding:"required,len=6,validHexRGBColor"`
Comment string `json:"comment" binding:"max=255"`
CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"`
Hidden bool `json:"hidden"`
SubAccounts []*AccountModifyRequest `json:"subAccounts" binding:"omitempty"`
}

// AccountListRequest represents all parameters of account listing request
Expand Down Expand Up @@ -134,43 +146,65 @@ type AccountDeleteRequest struct {

// AccountInfoResponse represents a view-object of account
type AccountInfoResponse struct {
Id int64 `json:"id,string"`
Name string `json:"name"`
ParentId int64 `json:"parentId,string"`
Category AccountCategory `json:"category"`
Type AccountType `json:"type"`
Icon int64 `json:"icon,string"`
Color string `json:"color"`
Currency string `json:"currency"`
Balance int64 `json:"balance"`
Comment string `json:"comment"`
DisplayOrder int32 `json:"displayOrder"`
IsAsset bool `json:"isAsset,omitempty"`
IsLiability bool `json:"isLiability,omitempty"`
Hidden bool `json:"hidden"`
SubAccounts AccountInfoResponseSlice `json:"subAccounts,omitempty"`
Id int64 `json:"id,string"`
Name string `json:"name"`
ParentId int64 `json:"parentId,string"`
Category AccountCategory `json:"category"`
Type AccountType `json:"type"`
Icon int64 `json:"icon,string"`
Color string `json:"color"`
Currency string `json:"currency"`
Balance int64 `json:"balance"`
Comment string `json:"comment"`
CreditCardStatementDate *int `json:"creditCardStatementDate,omitempty"`
DisplayOrder int32 `json:"displayOrder"`
IsAsset bool `json:"isAsset,omitempty"`
IsLiability bool `json:"isLiability,omitempty"`
Hidden bool `json:"hidden"`
SubAccounts AccountInfoResponseSlice `json:"subAccounts,omitempty"`
}

// ToAccountInfoResponse returns a view-object according to database model
func (a *Account) ToAccountInfoResponse() *AccountInfoResponse {
var creditCardStatementDate *int

if a.Category == ACCOUNT_CATEGORY_CREDIT_CARD {
if a.Extend != nil {
creditCardStatementDate = a.Extend.CreditCardStatementDate
} else {
creditCardStatementDate = &defaultCreditCardAccountStatementDate
}
}

return &AccountInfoResponse{
Id: a.AccountId,
Name: a.Name,
ParentId: a.ParentAccountId,
Category: a.Category,
Type: a.Type,
Icon: a.Icon,
Color: a.Color,
Currency: a.Currency,
Balance: a.Balance,
Comment: a.Comment,
DisplayOrder: a.DisplayOrder,
IsAsset: assetAccountCategory[a.Category],
IsLiability: liabilityAccountCategory[a.Category],
Hidden: a.Hidden,
Id: a.AccountId,
Name: a.Name,
ParentId: a.ParentAccountId,
Category: a.Category,
Type: a.Type,
Icon: a.Icon,
Color: a.Color,
Currency: a.Currency,
Balance: a.Balance,
Comment: a.Comment,
CreditCardStatementDate: creditCardStatementDate,
DisplayOrder: a.DisplayOrder,
IsAsset: assetAccountCategory[a.Category],
IsLiability: liabilityAccountCategory[a.Category],
Hidden: a.Hidden,
}
}

// FromDB fills the fields from the data stored in database
func (a *AccountExtend) FromDB(data []byte) error {
return json.Unmarshal(data, a)
}

// ToDB returns the actual stored data in database
func (a *AccountExtend) ToDB() ([]byte, error) {
return json.Marshal(a)
}

// AccountInfoResponseSlice represents the slice data structure of AccountInfoResponse
type AccountInfoResponseSlice []*AccountInfoResponse

Expand Down
2 changes: 1 addition & 1 deletion pkg/services/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func (s *AccountService) ModifyAccounts(c core.Context, uid int64, accounts []*m
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
for i := 0; i < len(accounts); i++ {
account := accounts[i]
updatedRows, err := sess.ID(account.AccountId).Cols("name", "category", "icon", "color", "comment", "hidden", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(account)
updatedRows, err := sess.ID(account.AccountId).Cols("name", "category", "icon", "color", "comment", "extend", "hidden", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(account)

if err != nil {
return err
Expand Down
36 changes: 25 additions & 11 deletions src/consts/account.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,61 @@
const allAccountCategories = [
{
const allAccountCategories = {
Cash: {
id: 1,
name: 'Cash',
defaultAccountIconId: '1'
},
{
CheckingAccount: {
id: 2,
name: 'Checking Account',
defaultAccountIconId: '100'
},
{
SavingsAccount: {
id: 8,
name: 'Savings Account',
defaultAccountIconId: '100'
},
{
CreditCard: {
id: 3,
name: 'Credit Card',
defaultAccountIconId: '100'
},
{
VirtualAccount: {
id: 4,
name: 'Virtual Account',
defaultAccountIconId: '500'
},
{
DebtAccount: {
id: 5,
name: 'Debt Account',
defaultAccountIconId: '600'
},
{
Receivables: {
id: 6,
name: 'Receivables',
defaultAccountIconId: '700'
},
{
CertificatePfDeposit: {
id: 9,
name: 'Certificate of Deposit',
defaultAccountIconId: '110'
},
{
InvestmentAccount: {
id: 7,
name: 'Investment Account',
defaultAccountIconId: '800'
}
};

const allAccountCategoriesArray = [
allAccountCategories.Cash,
allAccountCategories.CheckingAccount,
allAccountCategories.SavingsAccount,
allAccountCategories.CreditCard,
allAccountCategories.VirtualAccount,
allAccountCategories.DebtAccount,
allAccountCategories.Receivables,
allAccountCategories.CertificatePfDeposit,
allAccountCategories.InvestmentAccount
];
const allAccountTypes = {
SingleAccount: 1,
Expand All @@ -60,7 +72,9 @@ const allAccountTypesArray = [
];

export default {
allCategories: allAccountCategories,
cashCategoryType: allAccountCategories.Cash.id,
creditCardCategoryType: allAccountCategories.CreditCard.id,
allCategories: allAccountCategoriesArray,
allAccountTypes: allAccountTypes,
allAccountTypesArray: allAccountTypesArray,
};
Loading

0 comments on commit 62e0919

Please sign in to comment.