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: {