mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 00:12:11 +08:00
add account list page and account add page
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/mayswind/lab/pkg/core"
|
||||
"github.com/mayswind/lab/pkg/errs"
|
||||
"github.com/mayswind/lab/pkg/log"
|
||||
"github.com/mayswind/lab/pkg/models"
|
||||
"github.com/mayswind/lab/pkg/services"
|
||||
)
|
||||
|
||||
type AccountsApi struct {
|
||||
accounts *services.AccountService
|
||||
}
|
||||
|
||||
var (
|
||||
Accounts = &AccountsApi{
|
||||
accounts: services.Accounts,
|
||||
}
|
||||
)
|
||||
|
||||
func (a *AccountsApi) AccountListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountListHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
userAllAccountResps := make([]*models.AccountInfoResponse, len(accounts))
|
||||
userAllAccountRespMap := make(map[int64]*models.AccountInfoResponse)
|
||||
|
||||
for i := 0; i < len(accounts); i++ {
|
||||
userAllAccountResps[i] = accounts[i].ToAccountInfoResponse()
|
||||
userAllAccountRespMap[userAllAccountResps[i].Id] = userAllAccountResps[i]
|
||||
}
|
||||
|
||||
for i := 0; i < len(userAllAccountResps); i++ {
|
||||
userAccountResp := userAllAccountResps[i]
|
||||
|
||||
if userAccountResp.ParentId <= models.ACCOUNT_PARENT_ID_LEVEL_ONE {
|
||||
continue
|
||||
}
|
||||
|
||||
parentAccount, parentExists := userAllAccountRespMap[userAccountResp.ParentId]
|
||||
|
||||
if !parentExists || parentAccount == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
parentAccount.SubAccounts = append(parentAccount.SubAccounts, userAccountResp)
|
||||
}
|
||||
|
||||
userFinalAccountResps := make(models.AccountInfoResponseSlice, 0)
|
||||
|
||||
for i := 0; i < len(userAllAccountResps); i++ {
|
||||
if userAllAccountResps[i].ParentId == models.ACCOUNT_PARENT_ID_LEVEL_ONE {
|
||||
sort.Sort(userAllAccountResps[i].SubAccounts)
|
||||
userFinalAccountResps = append(userFinalAccountResps, userAllAccountResps[i])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(userFinalAccountResps)
|
||||
|
||||
return userFinalAccountResps, nil
|
||||
}
|
||||
|
||||
func (a *AccountsApi) AccountCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
var accountCreateReq models.AccountCreateRequest
|
||||
err := c.ShouldBindJSON(&accountCreateReq)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
if accountCreateReq.Type == models.ACCOUNT_TYPE_SINGLE_ACCOUNT {
|
||||
if len(accountCreateReq.SubAccounts) > 0 {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account cannot have any sub accounts")
|
||||
return nil, errs.ErrAccountCannotHaveSubAccounts
|
||||
}
|
||||
} else if accountCreateReq.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
||||
if len(accountCreateReq.SubAccounts) < 1 {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account does not have any sub accounts")
|
||||
return nil, errs.ErrAccountHaveNoSubAccount
|
||||
}
|
||||
} else {
|
||||
log.WarnfWithRequestId(c, "[accounts.AccountCreateHandler] account type invalid, type is %d", accountCreateReq.Type)
|
||||
return nil, errs.ErrAccountTypeInvalid
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
maxOrderId, err := a.accounts.GetMaxDisplayOrder(uid)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrOperationFailed
|
||||
}
|
||||
|
||||
mainAccount := a.createNewAccount(uid, &accountCreateReq, maxOrderId+1)
|
||||
childrenAccounts := a.createSubAccounts(uid, &accountCreateReq)
|
||||
|
||||
err = a.accounts.CreateAccounts(mainAccount, childrenAccounts)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[accounts.AccountCreateHandler] user \"uid:%d\" has created a new account \"id:%d\" successfully", uid, mainAccount.AccountId)
|
||||
|
||||
accountInfoResp := mainAccount.ToAccountInfoResponse()
|
||||
|
||||
if len(childrenAccounts) > 0 {
|
||||
accountInfoResp.SubAccounts = make([]*models.AccountInfoResponse, len(childrenAccounts))
|
||||
|
||||
for i := 0; i < len(childrenAccounts); i++ {
|
||||
accountInfoResp.SubAccounts[i] = childrenAccounts[i].ToAccountInfoResponse()
|
||||
}
|
||||
}
|
||||
|
||||
return accountInfoResp, nil
|
||||
}
|
||||
|
||||
func (a *AccountsApi) createNewAccount(uid int64, accountCreateReq *models.AccountCreateRequest, order int) *models.Account {
|
||||
return &models.Account{
|
||||
Uid: uid,
|
||||
Name: accountCreateReq.Name,
|
||||
DisplayOrder: order,
|
||||
Category: accountCreateReq.Category,
|
||||
Icon: accountCreateReq.Icon,
|
||||
Currency: accountCreateReq.Currency,
|
||||
Comment: accountCreateReq.Comment,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccountsApi) createSubAccounts(uid int64, accountCreateReq *models.AccountCreateRequest) []*models.Account {
|
||||
if len(accountCreateReq.SubAccounts) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
childrenAccounts := make([]*models.Account, len(accountCreateReq.SubAccounts))
|
||||
|
||||
for i := 0; i < len(accountCreateReq.SubAccounts); i++ {
|
||||
childrenAccounts[i] = a.createNewAccount(uid, accountCreateReq.SubAccounts[i], i+1)
|
||||
}
|
||||
|
||||
return childrenAccounts
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
var (
|
||||
ErrAccountIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 0, http.StatusBadRequest, "account id is invalid")
|
||||
ErrAccountNotFound = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 1, http.StatusBadRequest, "account not found")
|
||||
ErrAccountTypeInvalid = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 2, http.StatusBadRequest, "account type is invalid")
|
||||
ErrAccountHaveNoSubAccount = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 3, http.StatusBadRequest, "account must have at least one sub account")
|
||||
ErrAccountCannotHaveSubAccounts = NewNormalError(NORMAL_SUBCATEGORY_ACCOUNT, 4, http.StatusBadRequest, "account cannot have sub accounts")
|
||||
)
|
||||
@@ -0,0 +1,97 @@
|
||||
package models
|
||||
|
||||
// Level-One Account
|
||||
const ACCOUNT_PARENT_ID_LEVEL_ONE = 0
|
||||
|
||||
type AccountCategory byte
|
||||
|
||||
const (
|
||||
ACCOUNT_CATEGORY_CASH AccountCategory = 1
|
||||
ACCOUNT_CATEGORY_DEBIT_CARD AccountCategory = 2
|
||||
ACCOUNT_CATEGORY_CREDIT_CARD AccountCategory = 3
|
||||
ACCOUNT_CATEGORY_VIRTUAL AccountCategory = 4
|
||||
ACCOUNT_CATEGORY_DEBT AccountCategory = 5
|
||||
ACCOUNT_CATEGORY_RECEIVABLES AccountCategory = 6
|
||||
ACCOUNT_CATEGORY_INVESTMENT AccountCategory = 7
|
||||
)
|
||||
|
||||
type AccountType byte
|
||||
|
||||
const (
|
||||
ACCOUNT_TYPE_SINGLE_ACCOUNT AccountType = 1
|
||||
ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS AccountType = 2
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
AccountId int64 `xorm:"PK"`
|
||||
Uid int64 `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
|
||||
Deleted bool `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
|
||||
Category AccountCategory `xorm:"NOT NULL"`
|
||||
Type AccountType `xorm:"NOT NULL"`
|
||||
ParentAccountId int64 `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
|
||||
Name string `xorm:"VARCHAR(32) NOT NULL"`
|
||||
DisplayOrder int `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"`
|
||||
Icon int64 `xorm:"NOT NULL"`
|
||||
Currency string `xorm:"VARCHAR(3) NOT NULL"`
|
||||
Balance int64 `xorm:"NOT NULL"`
|
||||
Comment string `xorm:"VARCHAR(255) NOT NULL"`
|
||||
Hidden bool `xorm:"NOT NULL"`
|
||||
CreatedUnixTime int64
|
||||
UpdatedUnixTime int64
|
||||
DeletedUnixTime int64
|
||||
}
|
||||
|
||||
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"`
|
||||
Currency string `json:"currency" binding:"required,len=3,validCurrency"`
|
||||
Comment string `json:"comment" binding:"max=255"`
|
||||
SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
Currency string `json:"currency"`
|
||||
Balance int64 `json:"balance"`
|
||||
Comment string `json:"comment"`
|
||||
DisplayOrder int `json:"displayOrder"`
|
||||
Hidden bool `json:"hidden"`
|
||||
SubAccounts AccountInfoResponseSlice `json:"subAccounts,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Account) ToAccountInfoResponse() *AccountInfoResponse {
|
||||
return &AccountInfoResponse{
|
||||
Id: a.AccountId,
|
||||
Name: a.Name,
|
||||
ParentId: a.ParentAccountId,
|
||||
Category: a.Category,
|
||||
Type: a.Type,
|
||||
Icon: a.Icon,
|
||||
Currency: a.Currency,
|
||||
Balance: a.Balance,
|
||||
Comment: a.Comment,
|
||||
DisplayOrder: a.DisplayOrder,
|
||||
Hidden: a.Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
type AccountInfoResponseSlice []*AccountInfoResponse
|
||||
|
||||
func (a AccountInfoResponseSlice) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a AccountInfoResponseSlice) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
func (a AccountInfoResponseSlice) Less(i, j int) bool {
|
||||
return a[i].DisplayOrder < a[j].DisplayOrder
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/lab/pkg/datastore"
|
||||
"github.com/mayswind/lab/pkg/errs"
|
||||
"github.com/mayswind/lab/pkg/models"
|
||||
"github.com/mayswind/lab/pkg/uuid"
|
||||
)
|
||||
|
||||
type AccountService struct {
|
||||
ServiceUsingDB
|
||||
ServiceUsingUuid
|
||||
}
|
||||
|
||||
var (
|
||||
Accounts = &AccountService{
|
||||
ServiceUsingDB: ServiceUsingDB{
|
||||
container: datastore.Container,
|
||||
},
|
||||
ServiceUsingUuid: ServiceUsingUuid{
|
||||
container: uuid.Container,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (s *AccountService) GetAllAccountsByUid(uid int64) ([]*models.Account, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var accounts []*models.Account
|
||||
err := s.UserDataDB(uid).Where("uid=? AND deleted=?", uid, false).OrderBy("parent_account_id asc, display_order asc").Find(&accounts)
|
||||
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
func (s *AccountService) GetMaxDisplayOrder(uid int64) (int, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
account := &models.Account{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=?", uid, false, models.ACCOUNT_PARENT_ID_LEVEL_ONE).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if has {
|
||||
return account.DisplayOrder, nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AccountService) GetMaxSubAccountDisplayOrder(uid int64, parentAccountId int64) (int, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if parentAccountId <= 0 {
|
||||
return 0, errs.ErrAccountIdInvalid
|
||||
}
|
||||
|
||||
account := &models.Account{}
|
||||
has, err := s.UserDataDB(uid).Cols("uid", "deleted", "parent_account_id", "display_order").Where("uid=? AND deleted=? AND parent_account_id=?", uid, false, parentAccountId).OrderBy("display_order desc").Limit(1).Get(account)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if has {
|
||||
return account.DisplayOrder, nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AccountService) CreateAccounts(mainAccount *models.Account, childrenAccounts []*models.Account) error {
|
||||
if mainAccount.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
allAccounts := make([]*models.Account, len(childrenAccounts)+1)
|
||||
|
||||
mainAccount.AccountId = s.GenerateUuid(uuid.UUID_TYPE_ACCOUNT)
|
||||
allAccounts[0] = mainAccount
|
||||
|
||||
if mainAccount.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS {
|
||||
for i := 0; i < len(childrenAccounts); i++ {
|
||||
childAccount := childrenAccounts[i]
|
||||
childAccount.AccountId = s.GenerateUuid(uuid.UUID_TYPE_ACCOUNT)
|
||||
childAccount.ParentAccountId = mainAccount.AccountId
|
||||
childAccount.Uid = mainAccount.Uid
|
||||
childAccount.Type = models.ACCOUNT_TYPE_SINGLE_ACCOUNT
|
||||
|
||||
allAccounts[i+1] = childrenAccounts[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(allAccounts); i++ {
|
||||
allAccounts[i].Deleted = false
|
||||
allAccounts[i].CreatedUnixTime = time.Now().Unix()
|
||||
allAccounts[i].UpdatedUnixTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return s.UserDataDB(mainAccount.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(allAccounts); i++ {
|
||||
account := allAccounts[i]
|
||||
_, err := sess.Insert(account)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user