diff --git a/cmd/webserver.go b/cmd/webserver.go index d5a38388..f9ead4ea 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -189,6 +189,7 @@ func startWebServer(c *cli.Context) error { apiV1Route.GET("/transaction/categories/list.json", bindApi(api.TransactionCategories.CategoryListHandler)) apiV1Route.GET("/transaction/categories/get.json", bindApi(api.TransactionCategories.CategoryGetHandler)) apiV1Route.POST("/transaction/categories/add.json", bindApi(api.TransactionCategories.CategoryCreateHandler)) + apiV1Route.POST("/transaction/categories/add_batch.json", bindApi(api.TransactionCategories.CategoryCreateBatchHandler)) apiV1Route.POST("/transaction/categories/modify.json", bindApi(api.TransactionCategories.CategoryModifyHandler)) apiV1Route.POST("/transaction/categories/hide.json", bindApi(api.TransactionCategories.CategoryHideHandler)) apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler)) diff --git a/pkg/api/transaction_categories.go b/pkg/api/transaction_categories.go index 8fed700e..026bb768 100644 --- a/pkg/api/transaction_categories.go +++ b/pkg/api/transaction_categories.go @@ -173,6 +173,112 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter return categoryResp, nil } +func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (interface{}, *errs.Error) { + var categoryCreateBatchReq models.TransactionCategoryCreateBatchRequest + err := c.ShouldBindJSON(&categoryCreateBatchReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + + categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int) + categoriesMap := make(map[*models.TransactionCategory][]*models.TransactionCategory) + categoriesMap[nil] = make([]*models.TransactionCategory, len(categoryCreateBatchReq.Categories)) + totalCount := 0 + + for i := 0; i < len(categoryCreateBatchReq.Categories); i++ { + categoryCreateReq := categoryCreateBatchReq.Categories[i] + var maxOrderId, exists = categoryTypeMaxOrderMap[categoryCreateReq.Type] + + if !exists { + maxOrderId, err = a.categories.GetMaxDisplayOrder(uid, categoryCreateReq.Type) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + } + + category := a.createNewCategory(uid, &models.TransactionCategoryCreateRequest{ + Name: categoryCreateReq.Name, + Type: categoryCreateReq.Type, + Icon: categoryCreateReq.Icon, + Color: categoryCreateReq.Color, + }, maxOrderId+1) + + categoriesMap[category] = make([]*models.TransactionCategory, len(categoryCreateReq.SubCategories)) + + for j := 0; j < len(categoryCreateReq.SubCategories); j++ { + subCategory := a.createNewCategory(uid, categoryCreateReq.SubCategories[j], j+1) + categoriesMap[category][j] = subCategory + totalCount++ + } + + categoriesMap[nil][i] = category + categoryTypeMaxOrderMap[categoryCreateReq.Type] = maxOrderId+1 + totalCount++ + } + + categories, err := a.categories.CreateCategories(uid, totalCount, categoriesMap) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to create categories for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] user \"uid:%d\" has created categoroies successfully", uid) + + categoryResps := make([]*models.TransactionCategoryInfoResponse, len(categories)) + categoryRespMap := make(map[int64]*models.TransactionCategoryInfoResponse) + + for i := 0; i < len(categories); i++ { + categoryResps[i] = categories[i].ToTransactionCategoryInfoResponse() + categoryRespMap[categoryResps[i].Id] = categoryResps[i] + } + + for i := 0; i < len(categoryResps); i++ { + categoryResp := categoryResps[i] + + if categoryResp.ParentId <= models.TRANSACTION_PARENT_ID_LEVEL_ONE { + continue + } + + parentCategory, parentExists := categoryRespMap[categoryResp.ParentId] + + if !parentExists || parentCategory == nil { + continue + } + + parentCategory.SubCategories = append(parentCategory.SubCategories, categoryResp) + } + + finalCategoryResps := make(models.TransactionCategoryInfoResponseSlice, 0) + + for i := 0; i < len(categoryResps); i++ { + if categoryResps[i].ParentId == models.TRANSACTION_PARENT_ID_LEVEL_ONE { + sort.Sort(categoryResps[i].SubCategories) + finalCategoryResps = append(finalCategoryResps, categoryResps[i]) + } + } + + sort.Sort(finalCategoryResps) + + typeCategoryMapResponse := make(map[models.TransactionCategoryType]models.TransactionCategoryInfoResponseSlice) + + for i := 0; i < len(finalCategoryResps); i++ { + category := finalCategoryResps[i] + categoryList, _ := typeCategoryMapResponse[category.Type] + + categoryList = append(categoryList, category) + typeCategoryMapResponse[category.Type] = categoryList + } + + return typeCategoryMapResponse, nil +} + func (a *TransactionCategoriesApi) CategoryModifyHandler(c *core.Context) (interface{}, *errs.Error) { var categoryModifyReq models.TransactionCategoryModifyRequest err := c.ShouldBindJSON(&categoryModifyReq) diff --git a/pkg/models/transaction_category.go b/pkg/models/transaction_category.go index 3a1ca61b..2f65eab6 100644 --- a/pkg/models/transaction_category.go +++ b/pkg/models/transaction_category.go @@ -37,6 +37,19 @@ type TransactionCategoryCreateRequest struct { Comment string `json:"comment" binding:"max=255"` } +type TransactionCategoryCreateBatchRequest struct { + Categories []*TransactionCategoryCreateWithSubCategories `json:"categories" binding:"required"` +} + +type TransactionCategoryCreateWithSubCategories struct { + Name string `json:"name" binding:"required,notBlank,max=32"` + Type TransactionCategoryType `json:"type" 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"` + SubCategories []*TransactionCategoryCreateRequest `json:"subCategories" binding:"required"` +} + type TransactionCategoryListRequest struct { Type TransactionCategoryType `form:"type" binding:"min=0"` ParentId int64 `form:"parent_id,string,default=-1" binding:"min=-1"` diff --git a/pkg/services/transaction_categories.go b/pkg/services/transaction_categories.go index d9a533d2..85e29e3a 100644 --- a/pkg/services/transaction_categories.go +++ b/pkg/services/transaction_categories.go @@ -150,6 +150,59 @@ func (s *TransactionCategoryService) CreateCategory(category *models.Transaction }) } +func (s *TransactionCategoryService) CreateCategories(uid int64, totalCount int, categories map[*models.TransactionCategory][]*models.TransactionCategory) ([]*models.TransactionCategory, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + var allCategories []*models.TransactionCategory + primaryCategories := categories[nil] + + for i := 0; i < len(primaryCategories); i++ { + primaryCategory := primaryCategories[i] + primaryCategory.CategoryId = s.GenerateUuid(uuid.UUID_TYPE_CATEGORY) + + primaryCategory.Deleted = false + primaryCategory.CreatedUnixTime = time.Now().Unix() + primaryCategory.UpdatedUnixTime = time.Now().Unix() + + allCategories = append(allCategories, primaryCategory) + + secondaryCategories := categories[primaryCategory] + + for j := 0; j < len(secondaryCategories); j++ { + secondaryCategory := secondaryCategories[j] + secondaryCategory.CategoryId = s.GenerateUuid(uuid.UUID_TYPE_CATEGORY) + secondaryCategory.ParentCategoryId = primaryCategory.CategoryId + + secondaryCategory.Deleted = false + secondaryCategory.CreatedUnixTime = time.Now().Unix() + secondaryCategory.UpdatedUnixTime = time.Now().Unix() + + allCategories = append(allCategories, secondaryCategory) + } + } + + err := s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { + for i := 0; i < len(allCategories); i++ { + category := allCategories[i] + _, err := sess.Insert(category) + + if err != nil { + return err + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + return allCategories, nil +} + func (s *TransactionCategoryService) ModifyCategory(category *models.TransactionCategory) error { if category.Uid <= 0 { return errs.ErrUserIdInvalid diff --git a/src/lib/services.js b/src/lib/services.js index 26a710d0..c3cdc279 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -228,6 +228,11 @@ export default { comment }); }, + addTransactionCategoryBatch: ({ categories }) => { + return axios.post('v1/transaction/categories/add_batch.json', { + categories + }); + }, modifyTransactionCategory: ({ id, name, icon, color, comment, hidden }) => { return axios.post('v1/transaction/categories/modify.json', { id, diff --git a/src/locales/en.js b/src/locales/en.js index fac6c582..fc11424e 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -516,6 +516,7 @@ export default { 'Unable to add category': 'Unable to add category', 'Unable to save category': 'Unable to save category', 'You have added a new category': 'You have added a new category', + 'You have added default categories': 'You have added default categories', 'You have saved this category': 'You have saved this category', 'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?', 'Unable to logout from this session': 'Unable to logout from this session', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 0fc4e03a..6ada3741 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -516,6 +516,7 @@ export default { 'Unable to add category': '无法添加分类', 'Unable to save category': '无法保存分类', 'You have added a new category': '您已经添加新分类', + 'You have added default categories': '您已经添加默认分类', 'You have saved this category': '您已经保存该分类', 'Are you sure you want to logout from this session?': '您确定要退出该会话?', 'Unable to logout from this session': '无法退出该会话', diff --git a/src/views/mobile/categories/CategoryDefault.vue b/src/views/mobile/categories/CategoryDefault.vue index 222e103b..a860ae7d 100644 --- a/src/views/mobile/categories/CategoryDefault.vue +++ b/src/views/mobile/categories/CategoryDefault.vue @@ -4,7 +4,7 @@ - + @@ -50,7 +50,7 @@ export default { return { categoryType: '', allCategories: [], - saving: false + submitting: false }; }, created() { @@ -94,7 +94,67 @@ export default { } }, save() { + const self = this; + const router = self.$f7router; + self.submitting = true; + self.$showLoading(() => self.submitting); + + const categories = []; + + for (let i = 0; i < self.allCategories.length; i++) { + const categoryInfo = self.allCategories[i]; + + for (let j = 0; j < categoryInfo.categories.length; j++) { + const category = categoryInfo.categories[j]; + const submitCategory = { + name: self.$t('category.' + category.name), + type: parseInt(categoryInfo.type), + icon: category.categoryIconId, + color: category.color, + subCategories: [] + } + + for (let k = 0; k < category.subCategories.length; k++) { + const subCategory = category.subCategories[k]; + submitCategory.subCategories.push({ + name: self.$t('category.' + subCategory.name), + type: parseInt(categoryInfo.type), + icon: subCategory.categoryIconId, + color: subCategory.color + }); + } + + categories.push(submitCategory); + } + } + + self.$services.addTransactionCategoryBatch({ + categories: categories + }).then(response => { + self.submitting = false; + self.$hideLoading(); + const data = response.data; + + if (!data || !data.success || !data.result) { + self.$toast('Unable to add category'); + return; + } + + self.$toast('You have added default categories'); + router.back(); + }).catch(error => { + self.$logger.error('failed to save default categories', error); + + self.submitting = false; + self.$hideLoading(); + + if (error.response && error.response.data && error.response.data.errorMessage) { + self.$toast({ error: error.response.data }); + } else if (!error.processed) { + self.$toast('Unable to add category'); + } + }); } }, filters: {