package model import ( "errors" "fmt" "sort" "time" "github.com/openaccounting/oa-server/core/model/types" "github.com/openaccounting/oa-server/core/ws" ) type AccountInterface interface { CreateAccount(account *types.Account, userId string) error UpdateAccount(account *types.Account, userId string) error DeleteAccount(id string, userId string, orgId string) error GetAccounts(orgId string, userId string, tokenId string) ([]*types.Account, error) GetAccountsWithBalances(orgId string, userId string, tokenId string, date time.Time) ([]*types.Account, error) GetAccount(orgId, accId, userId, tokenId string) (*types.Account, error) GetAccountWithBalance(orgId, accId, userId, tokenId string, date time.Time) (*types.Account, error) } type ByName []*types.Account func (a ByName) Len() int { return len(a) } func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name } func (model *Model) CreateAccount(account *types.Account, userId string) (err error) { if account.Id == "" { return errors.New("id required") } if account.OrgId == "" { return errors.New("orgId required") } if account.Name == "" { return errors.New("name required") } if account.Currency == "" { return errors.New("currency required") } userAccounts, err := model.GetAccounts(account.OrgId, userId, "") if err != nil { return } if !model.accountsContainWriteAccess(userAccounts, account.Parent) { return errors.New(fmt.Sprintf("%s %s", "user does not have permission to access account", account.Parent)) } err = model.db.InsertAccount(account) if err != nil { return } // Notify web socket subscribers // TODO only get user ids that have permission to access account userIds, err2 := model.db.GetOrgUserIds(account.OrgId) if err2 == nil { ws.PushAccount(account, userIds, "create") } return } func (model *Model) UpdateAccount(account *types.Account, userId string) (err error) { if account.Id == "" { return errors.New("id required") } if account.OrgId == "" { return errors.New("orgId required") } if account.Name == "" { return errors.New("name required") } if account.Currency == "" { return errors.New("currency required") } if account.Parent == account.Id { return errors.New("account cannot be its own parent") } // don't allow setting parent that has transactions count, err := model.db.GetSplitCountByAccountId(account.Parent) if err != nil { return } if count != 0 { return errors.New("cannot set parent to account with transactions") } userAccounts, err := model.GetAccounts(account.OrgId, userId, "") if err != nil { return } if !model.accountsContainWriteAccess(userAccounts, account.Parent) { return errors.New(fmt.Sprintf("%s %s", "user does not have permission to access account", account.Parent)) } // make sure parent is not a descendant of account accountMap := model.makeAccountMap(userAccounts) children := model.getChildren(account.Id, accountMap) for _, childAccount := range children { if childAccount.Id == account.Parent { return errors.New("cannot set parent to descendant account") } } err = model.db.UpdateAccount(account) if err != nil { return } err = model.db.AddBalance(account, time.Now()) if err != nil { return } err = model.db.AddNativeBalanceCost(account, time.Now()) if err != nil { return } // Notify web socket subscribers // TODO only get user ids that have permission to access account userIds, err2 := model.db.GetOrgUserIds(account.OrgId) if err2 == nil { ws.PushAccount(account, userIds, "update") } return } func (model *Model) DeleteAccount(id string, userId string, orgId string) (err error) { // TODO make sure user is part of org // check to make sure user has permission userAccounts, err := model.GetAccounts(orgId, userId, "") if err != nil { return } if !model.accountsContainWriteAccess(userAccounts, id) { return errors.New(fmt.Sprintf("%s %s", "user does not have permission to access account", id)) } // don't allow deleting of accounts that have transactions or child accounts count, err := model.db.GetSplitCountByAccountId(id) if err != nil { return } if count != 0 { return errors.New("Cannot delete an account that has transactions") } count, err = model.db.GetChildCountByAccountId(id) if err != nil { return } if count != 0 { return errors.New("Cannot delete an account that has children") } account, err := model.db.GetAccount(id) if err != nil { return } err = model.db.DeleteAccount(id) if err != nil { return } // Notify web socket subscribers // TODO only get user ids that have permission to access account userIds, err2 := model.db.GetOrgUserIds(account.OrgId) if err2 == nil { ws.PushAccount(account, userIds, "delete") } return } func (model *Model) getAccounts(orgId string, userId string, tokenId string, date time.Time, withBalances bool) ([]*types.Account, error) { permissionedAccounts, err := model.db.GetPermissionedAccountIds(orgId, userId, "") if err != nil { return nil, err } var allAccounts []*types.Account if withBalances == true { allAccounts, err = model.getAllAccountsWithBalances(orgId, date) } else { allAccounts, err = model.getAllAccounts(orgId) } if err != nil { return nil, err } accountMap := model.makeAccountMap(allAccounts) writeAccessMap := make(map[string]*types.Account) readAccessMap := make(map[string]*types.Account) for _, accountId := range permissionedAccounts { writeAccessMap[accountId] = accountMap[accountId].Account // parents are read only parents := model.getParents(accountId, accountMap) for _, parentAccount := range parents { readAccessMap[parentAccount.Id] = parentAccount } // top level accounts are initially read only unless user has permission topLevelAccounts := model.getTopLevelAccounts(accountMap) for _, topLevelAccount := range topLevelAccounts { readAccessMap[topLevelAccount.Id] = topLevelAccount } // Children have write access children := model.getChildren(accountId, accountMap) for _, childAccount := range children { writeAccessMap[childAccount.Id] = childAccount } } filtered := make([]*types.Account, 0) for _, account := range writeAccessMap { filtered = append(filtered, account) } for id, account := range readAccessMap { _, ok := writeAccessMap[id] if ok == false { account.ReadOnly = true filtered = append(filtered, account) } } // TODO sort by inserted sort.Sort(ByName(filtered)) return filtered, nil } func (model *Model) getAccount(orgId, accId, userId, tokenId string, date time.Time, withBalances bool) (*types.Account, error) { accounts, err := model.getAccounts(orgId, userId, tokenId, date, withBalances) if err != nil { return nil, err } for _, account := range accounts { if account.Id == accId { return account, nil } } return nil, nil } func (model *Model) GetAccounts(orgId string, userId string, tokenId string) ([]*types.Account, error) { return model.getAccounts(orgId, userId, tokenId, time.Time{}, false) } func (model *Model) GetAccountsWithBalances(orgId string, userId string, tokenId string, date time.Time) ([]*types.Account, error) { return model.getAccounts(orgId, userId, tokenId, date, true) } func (model *Model) GetAccount(orgId, accId, userId, tokenId string) (*types.Account, error) { return model.getAccount(orgId, accId, userId, tokenId, time.Time{}, false) } func (model *Model) GetAccountWithBalance(orgId, accId, userId, tokenId string, date time.Time) (*types.Account, error) { return model.getAccount(orgId, accId, userId, tokenId, date, true) } func (model *Model) getAllAccounts(orgId string) ([]*types.Account, error) { return model.db.GetAccountsByOrgId(orgId) } func (model *Model) getAllAccountsWithBalances(orgId string, date time.Time) ([]*types.Account, error) { accounts, err := model.db.GetAccountsByOrgId(orgId) if err != nil { return nil, err } err = model.db.AddBalances(accounts, date) if err != nil { return nil, err } err = model.db.AddNativeBalancesCost(accounts, date) if err != nil { return nil, err } return accounts, nil } func (model *Model) makeAccountMap(accounts []*types.Account) map[string]*types.AccountNode { m := make(map[string]*types.AccountNode) for _, account := range accounts { m[account.Id] = &types.AccountNode{ Account: account, Parent: nil, Children: nil, } } for _, account := range accounts { m[account.Id].Parent = m[account.Parent] if value, ok := m[account.Parent]; ok { value.Children = append(value.Children, m[account.Id]) value.Account.HasChildren = true } } return m } func (model *Model) getChildren(parentId string, accountMap map[string]*types.AccountNode) []*types.Account { if _, ok := accountMap[parentId]; !ok { return nil } children := make([]*types.Account, 0) for _, childAccountNode := range accountMap[parentId].Children { children = append(children, childAccountNode.Account) grandChildren := model.getChildren(childAccountNode.Account.Id, accountMap) children = append(children, grandChildren...) } return children } func (model *Model) getParents(accountId string, accountMap map[string]*types.AccountNode) []*types.Account { node, ok := accountMap[accountId] if !ok { return nil } if node.Parent == nil { return make([]*types.Account, 0) } parents := model.getParents(node.Parent.Account.Id, accountMap) return append(parents, node.Parent.Account) } func (model *Model) accountsContainWriteAccess(accounts []*types.Account, accountId string) bool { for _, account := range accounts { if account.Id == accountId && !account.ReadOnly { return true } } return false } func (model *Model) getAccountFromList(accounts []*types.Account, accountId string) *types.Account { for _, account := range accounts { if account.Id == accountId { return account } } return nil } func (model *Model) getTopLevelAccounts(accountMap map[string]*types.AccountNode) []*types.Account { accounts := make([]*types.Account, 0) for _, node := range accountMap { if node.Parent == nil { accounts = append(accounts, node.Account) for _, child := range node.Children { accounts = append(accounts, child.Account) } break } } return accounts }