add f
This commit is contained in:
@@ -2,17 +2,18 @@ package commands
|
|||||||
|
|
||||||
// CreateProductCommand 创建产品命令
|
// CreateProductCommand 创建产品命令
|
||||||
type CreateProductCommand struct {
|
type CreateProductCommand struct {
|
||||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
// UI组件相关字段
|
// UI组件相关字段
|
||||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
@@ -26,18 +27,19 @@ type CreateProductCommand struct {
|
|||||||
|
|
||||||
// UpdateProductCommand 更新产品命令
|
// UpdateProductCommand 更新产品命令
|
||||||
type UpdateProductCommand struct {
|
type UpdateProductCommand struct {
|
||||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
// UI组件相关字段
|
// UI组件相关字段
|
||||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
// CreateSubCategoryCommand 创建二级分类命令
|
||||||
|
type CreateSubCategoryCommand struct {
|
||||||
|
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
|
||||||
|
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
|
||||||
|
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
|
||||||
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||||
|
Sort int `json:"sort" binding:"min=0" comment:"排序"`
|
||||||
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubCategoryCommand 更新二级分类命令
|
||||||
|
type UpdateSubCategoryCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
|
||||||
|
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
|
||||||
|
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
|
||||||
|
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
|
||||||
|
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||||
|
Sort int `json:"sort" binding:"min=0" comment:"排序"`
|
||||||
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubCategoryCommand 删除二级分类命令
|
||||||
|
type DeleteSubCategoryCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
// GetSubCategoryQuery 获取二级分类查询
|
||||||
|
type GetSubCategoryQuery struct {
|
||||||
|
ID string `json:"id" form:"id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubCategoriesQuery 获取二级分类列表查询
|
||||||
|
type ListSubCategoriesQuery struct {
|
||||||
|
Page int `json:"page" form:"page" binding:"min=1" comment:"页码"`
|
||||||
|
PageSize int `json:"page_size" form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||||
|
CategoryID string `json:"category_id" form:"category_id" binding:"omitempty,uuid" comment:"一级分类ID"`
|
||||||
|
IsEnabled *bool `json:"is_enabled" form:"is_enabled" comment:"是否启用"`
|
||||||
|
IsVisible *bool `json:"is_visible" form:"is_visible" comment:"是否展示"`
|
||||||
|
SortBy string `json:"sort_by" form:"sort_by" comment:"排序字段"`
|
||||||
|
SortOrder string `json:"sort_order" form:"sort_order" comment:"排序方向"`
|
||||||
|
}
|
||||||
@@ -4,23 +4,23 @@ import "time"
|
|||||||
|
|
||||||
// CategoryInfoResponse 分类详情响应
|
// CategoryInfoResponse 分类详情响应
|
||||||
type CategoryInfoResponse struct {
|
type CategoryInfoResponse struct {
|
||||||
ID string `json:"id" comment:"分类ID"`
|
ID string `json:"id" comment:"分类ID"`
|
||||||
Name string `json:"name" comment:"分类名称"`
|
Name string `json:"name" comment:"分类名称"`
|
||||||
Code string `json:"code" comment:"分类编号"`
|
Code string `json:"code" comment:"分类编号"`
|
||||||
Description string `json:"description" comment:"分类描述"`
|
Description string `json:"description" comment:"分类描述"`
|
||||||
Sort int `json:"sort" comment:"排序"`
|
Sort int `json:"sort" comment:"排序"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
|
||||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CategoryListResponse 分类列表响应
|
// CategoryListResponse 分类列表响应
|
||||||
type CategoryListResponse struct {
|
type CategoryListResponse struct {
|
||||||
Total int64 `json:"total" comment:"总数"`
|
Total int64 `json:"total" comment:"总数"`
|
||||||
Page int `json:"page" comment:"页码"`
|
Page int `json:"page" comment:"页码"`
|
||||||
Size int `json:"size" comment:"每页数量"`
|
Size int `json:"size" comment:"每页数量"`
|
||||||
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
|
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,4 +29,38 @@ type CategorySimpleResponse struct {
|
|||||||
ID string `json:"id" comment:"分类ID"`
|
ID string `json:"id" comment:"分类ID"`
|
||||||
Name string `json:"name" comment:"分类名称"`
|
Name string `json:"name" comment:"分类名称"`
|
||||||
Code string `json:"code" comment:"分类编号"`
|
Code string `json:"code" comment:"分类编号"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubCategoryInfoResponse 二级分类详情响应
|
||||||
|
type SubCategoryInfoResponse struct {
|
||||||
|
ID string `json:"id" comment:"二级分类ID"`
|
||||||
|
Name string `json:"name" comment:"二级分类名称"`
|
||||||
|
Code string `json:"code" comment:"二级分类编号"`
|
||||||
|
Description string `json:"description" comment:"二级分类描述"`
|
||||||
|
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||||
|
Sort int `json:"sort" comment:"排序"`
|
||||||
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
|
|
||||||
|
// 关联信息
|
||||||
|
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubCategoryListResponse 二级分类列表响应
|
||||||
|
type SubCategoryListResponse struct {
|
||||||
|
Total int64 `json:"total" comment:"总数"`
|
||||||
|
Page int `json:"page" comment:"页码"`
|
||||||
|
Size int `json:"size" comment:"每页数量"`
|
||||||
|
Items []SubCategoryInfoResponse `json:"items" comment:"二级分类列表"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubCategorySimpleResponse 二级分类简单信息响应
|
||||||
|
type SubCategorySimpleResponse struct {
|
||||||
|
ID string `json:"id" comment:"二级分类ID"`
|
||||||
|
Name string `json:"name" comment:"二级分类名称"`
|
||||||
|
Code string `json:"code" comment:"二级分类编号"`
|
||||||
|
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,17 +15,18 @@ type PackageItemResponse struct {
|
|||||||
|
|
||||||
// ProductInfoResponse 产品详情响应
|
// ProductInfoResponse 产品详情响应
|
||||||
type ProductInfoResponse struct {
|
type ProductInfoResponse struct {
|
||||||
ID string `json:"id" comment:"产品ID"`
|
ID string `json:"id" comment:"产品ID"`
|
||||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||||
Name string `json:"name" comment:"产品名称"`
|
Name string `json:"name" comment:"产品名称"`
|
||||||
Code string `json:"code" comment:"产品编号"`
|
Code string `json:"code" comment:"产品编号"`
|
||||||
Description string `json:"description" comment:"产品简介"`
|
Description string `json:"description" comment:"产品简介"`
|
||||||
Content string `json:"content" comment:"产品内容"`
|
Content string `json:"content" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||||
Price float64 `json:"price" comment:"产品价格"`
|
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
Price float64 `json:"price" comment:"产品价格"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||||
// UI组件相关字段
|
// UI组件相关字段
|
||||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||||
@@ -36,7 +37,8 @@ type ProductInfoResponse struct {
|
|||||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||||
|
|
||||||
// 关联信息
|
// 关联信息
|
||||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||||
|
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
|
||||||
|
|
||||||
// 组合包信息
|
// 组合包信息
|
||||||
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
||||||
@@ -91,19 +93,20 @@ type ProductStatsResponse struct {
|
|||||||
|
|
||||||
// ProductAdminInfoResponse 管理员产品详情响应
|
// ProductAdminInfoResponse 管理员产品详情响应
|
||||||
type ProductAdminInfoResponse struct {
|
type ProductAdminInfoResponse struct {
|
||||||
ID string `json:"id" comment:"产品ID"`
|
ID string `json:"id" comment:"产品ID"`
|
||||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||||
Name string `json:"name" comment:"产品名称"`
|
Name string `json:"name" comment:"产品名称"`
|
||||||
Code string `json:"code" comment:"产品编号"`
|
Code string `json:"code" comment:"产品编号"`
|
||||||
Description string `json:"description" comment:"产品简介"`
|
Description string `json:"description" comment:"产品简介"`
|
||||||
Content string `json:"content" comment:"产品内容"`
|
Content string `json:"content" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||||
Price float64 `json:"price" comment:"产品价格"`
|
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
|
||||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
Price float64 `json:"price" comment:"产品价格"`
|
||||||
Remark string `json:"remark" comment:"备注"`
|
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
Remark string `json:"remark" comment:"备注"`
|
||||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||||
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
// UI组件相关字段
|
// UI组件相关字段
|
||||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
@@ -115,7 +118,8 @@ type ProductAdminInfoResponse struct {
|
|||||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||||
|
|
||||||
// 关联信息
|
// 关联信息
|
||||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||||
|
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
|
||||||
|
|
||||||
// 组合包信息
|
// 组合包信息
|
||||||
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func NewProductApplicationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateProduct 创建产品
|
// CreateProduct 创建产品
|
||||||
// 业务流程<EFBFBD>?. 构建产品实体 2. 创建产品
|
// 业务流程:1. 构建产品实体 2. 创建产品
|
||||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
||||||
// 1. 构建产品实体
|
// 1. 构建产品实体
|
||||||
product := &entities.Product{
|
product := &entities.Product{
|
||||||
@@ -59,6 +59,7 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
|||||||
Description: cmd.Description,
|
Description: cmd.Description,
|
||||||
Content: cmd.Content,
|
Content: cmd.Content,
|
||||||
CategoryID: cmd.CategoryID,
|
CategoryID: cmd.CategoryID,
|
||||||
|
SubCategoryID: cmd.SubCategoryID,
|
||||||
Price: decimal.NewFromFloat(cmd.Price),
|
Price: decimal.NewFromFloat(cmd.Price),
|
||||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||||
Remark: cmd.Remark,
|
Remark: cmd.Remark,
|
||||||
@@ -97,6 +98,7 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
|||||||
existingProduct.Description = cmd.Description
|
existingProduct.Description = cmd.Description
|
||||||
existingProduct.Content = cmd.Content
|
existingProduct.Content = cmd.Content
|
||||||
existingProduct.CategoryID = cmd.CategoryID
|
existingProduct.CategoryID = cmd.CategoryID
|
||||||
|
existingProduct.SubCategoryID = cmd.SubCategoryID
|
||||||
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
||||||
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
||||||
existingProduct.Remark = cmd.Remark
|
existingProduct.Remark = cmd.Remark
|
||||||
@@ -490,30 +492,36 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
|||||||
// convertToProductInfoResponse 转换为产品信息响应
|
// convertToProductInfoResponse 转换为产品信息响应
|
||||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||||
response := &responses.ProductInfoResponse{
|
response := &responses.ProductInfoResponse{
|
||||||
ID: product.ID,
|
ID: product.ID,
|
||||||
OldID: product.OldID,
|
OldID: product.OldID,
|
||||||
Name: product.Name,
|
Name: product.Name,
|
||||||
Code: product.Code,
|
Code: product.Code,
|
||||||
Description: product.Description,
|
Description: product.Description,
|
||||||
Content: product.Content,
|
Content: product.Content,
|
||||||
CategoryID: product.CategoryID,
|
CategoryID: product.CategoryID,
|
||||||
Price: product.Price.InexactFloat64(),
|
SubCategoryID: product.SubCategoryID,
|
||||||
IsEnabled: product.IsEnabled,
|
Price: product.Price.InexactFloat64(),
|
||||||
IsPackage: product.IsPackage,
|
IsEnabled: product.IsEnabled,
|
||||||
SellUIComponent: product.SellUIComponent,
|
IsPackage: product.IsPackage,
|
||||||
|
SellUIComponent: product.SellUIComponent,
|
||||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||||
SEOTitle: product.SEOTitle,
|
SEOTitle: product.SEOTitle,
|
||||||
SEODescription: product.SEODescription,
|
SEODescription: product.SEODescription,
|
||||||
SEOKeywords: product.SEOKeywords,
|
SEOKeywords: product.SEOKeywords,
|
||||||
CreatedAt: product.CreatedAt,
|
CreatedAt: product.CreatedAt,
|
||||||
UpdatedAt: product.UpdatedAt,
|
UpdatedAt: product.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加分类信息
|
// 添加一级分类信息
|
||||||
if product.Category != nil {
|
if product.Category != nil {
|
||||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加二级分类信息
|
||||||
|
if product.SubCategory != nil {
|
||||||
|
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
|
||||||
|
}
|
||||||
|
|
||||||
// 转换组合包项目信息
|
// 转换组合包项目信息
|
||||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||||
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
||||||
@@ -536,33 +544,39 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
|||||||
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
||||||
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
||||||
response := &responses.ProductAdminInfoResponse{
|
response := &responses.ProductAdminInfoResponse{
|
||||||
ID: product.ID,
|
ID: product.ID,
|
||||||
OldID: product.OldID,
|
OldID: product.OldID,
|
||||||
Name: product.Name,
|
Name: product.Name,
|
||||||
Code: product.Code,
|
Code: product.Code,
|
||||||
Description: product.Description,
|
Description: product.Description,
|
||||||
Content: product.Content,
|
Content: product.Content,
|
||||||
CategoryID: product.CategoryID,
|
CategoryID: product.CategoryID,
|
||||||
Price: product.Price.InexactFloat64(),
|
SubCategoryID: product.SubCategoryID,
|
||||||
CostPrice: product.CostPrice.InexactFloat64(),
|
Price: product.Price.InexactFloat64(),
|
||||||
Remark: product.Remark,
|
CostPrice: product.CostPrice.InexactFloat64(),
|
||||||
IsEnabled: product.IsEnabled,
|
Remark: product.Remark,
|
||||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
IsEnabled: product.IsEnabled,
|
||||||
IsPackage: product.IsPackage,
|
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||||
SellUIComponent: product.SellUIComponent,
|
IsPackage: product.IsPackage,
|
||||||
|
SellUIComponent: product.SellUIComponent,
|
||||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||||
SEOTitle: product.SEOTitle,
|
SEOTitle: product.SEOTitle,
|
||||||
SEODescription: product.SEODescription,
|
SEODescription: product.SEODescription,
|
||||||
SEOKeywords: product.SEOKeywords,
|
SEOKeywords: product.SEOKeywords,
|
||||||
CreatedAt: product.CreatedAt,
|
CreatedAt: product.CreatedAt,
|
||||||
UpdatedAt: product.UpdatedAt,
|
UpdatedAt: product.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加分类信息
|
// 添加一级分类信息
|
||||||
if product.Category != nil {
|
if product.Category != nil {
|
||||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加二级分类信息
|
||||||
|
if product.SubCategory != nil {
|
||||||
|
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
|
||||||
|
}
|
||||||
|
|
||||||
// 转换组合包项目信息
|
// 转换组合包项目信息
|
||||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||||
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
||||||
@@ -594,6 +608,28 @@ func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ProductApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
|
||||||
|
response := &responses.SubCategoryInfoResponse{
|
||||||
|
ID: subCategory.ID,
|
||||||
|
Name: subCategory.Name,
|
||||||
|
Code: subCategory.Code,
|
||||||
|
Description: subCategory.Description,
|
||||||
|
CategoryID: subCategory.CategoryID,
|
||||||
|
Sort: subCategory.Sort,
|
||||||
|
IsEnabled: subCategory.IsEnabled,
|
||||||
|
IsVisible: subCategory.IsVisible,
|
||||||
|
CreatedAt: subCategory.CreatedAt,
|
||||||
|
UpdatedAt: subCategory.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一级分类信息
|
||||||
|
if subCategory.Category != nil {
|
||||||
|
response.Category = s.convertToCategoryInfoResponse(subCategory.Category)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
// GetProductApiConfig 获取产品API配置
|
// GetProductApiConfig 获取产品API配置
|
||||||
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
||||||
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
|
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package product
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tyapi-server/internal/application/product/dto/commands"
|
||||||
|
"tyapi-server/internal/application/product/dto/queries"
|
||||||
|
"tyapi-server/internal/application/product/dto/responses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubCategoryApplicationService 二级分类应用服务接口
|
||||||
|
type SubCategoryApplicationService interface {
|
||||||
|
// 二级分类管理
|
||||||
|
CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error
|
||||||
|
UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error
|
||||||
|
DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error
|
||||||
|
|
||||||
|
GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error)
|
||||||
|
ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error)
|
||||||
|
ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
package product
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"tyapi-server/internal/application/product/dto/commands"
|
||||||
|
"tyapi-server/internal/application/product/dto/queries"
|
||||||
|
"tyapi-server/internal/application/product/dto/responses"
|
||||||
|
"tyapi-server/internal/domains/product/entities"
|
||||||
|
"tyapi-server/internal/domains/product/repositories"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubCategoryApplicationServiceImpl 二级分类应用服务实现
|
||||||
|
type SubCategoryApplicationServiceImpl struct {
|
||||||
|
categoryRepo repositories.ProductCategoryRepository
|
||||||
|
subCategoryRepo repositories.ProductSubCategoryRepository
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubCategoryApplicationService 创建二级分类应用服务
|
||||||
|
func NewSubCategoryApplicationService(
|
||||||
|
categoryRepo repositories.ProductCategoryRepository,
|
||||||
|
subCategoryRepo repositories.ProductSubCategoryRepository,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) SubCategoryApplicationService {
|
||||||
|
return &SubCategoryApplicationServiceImpl{
|
||||||
|
categoryRepo: categoryRepo,
|
||||||
|
subCategoryRepo: subCategoryRepo,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSubCategory 创建二级分类
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error {
|
||||||
|
// 1. 参数验证
|
||||||
|
if err := s.validateCreateSubCategory(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证一级分类是否存在
|
||||||
|
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("一级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
if !category.IsValid() {
|
||||||
|
return errors.New("一级分类已禁用或删除")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证二级分类编号唯一性
|
||||||
|
if err := s.validateSubCategoryCode(cmd.Code, "", cmd.CategoryID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建二级分类实体
|
||||||
|
subCategory := &entities.ProductSubCategory{
|
||||||
|
Name: cmd.Name,
|
||||||
|
Code: cmd.Code,
|
||||||
|
Description: cmd.Description,
|
||||||
|
CategoryID: cmd.CategoryID,
|
||||||
|
Sort: cmd.Sort,
|
||||||
|
IsEnabled: cmd.IsEnabled,
|
||||||
|
IsVisible: cmd.IsVisible,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 保存到仓储
|
||||||
|
createdSubCategory, err := s.subCategoryRepo.Create(ctx, *subCategory)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("创建二级分类失败", zap.Error(err), zap.String("code", cmd.Code))
|
||||||
|
return fmt.Errorf("创建二级分类失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("创建二级分类成功", zap.String("id", createdSubCategory.ID), zap.String("code", cmd.Code))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubCategory 更新二级分类
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error {
|
||||||
|
// 1. 参数验证
|
||||||
|
if err := s.validateUpdateSubCategory(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取现有二级分类
|
||||||
|
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("二级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证一级分类是否存在
|
||||||
|
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("一级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
if !category.IsValid() {
|
||||||
|
return errors.New("一级分类已禁用或删除")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证二级分类编号唯一性(排除当前分类)
|
||||||
|
if err := s.validateSubCategoryCode(cmd.Code, cmd.ID, cmd.CategoryID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 更新二级分类信息
|
||||||
|
existingSubCategory.Name = cmd.Name
|
||||||
|
existingSubCategory.Code = cmd.Code
|
||||||
|
existingSubCategory.Description = cmd.Description
|
||||||
|
existingSubCategory.CategoryID = cmd.CategoryID
|
||||||
|
existingSubCategory.Sort = cmd.Sort
|
||||||
|
existingSubCategory.IsEnabled = cmd.IsEnabled
|
||||||
|
existingSubCategory.IsVisible = cmd.IsVisible
|
||||||
|
|
||||||
|
// 6. 保存到仓储
|
||||||
|
if err := s.subCategoryRepo.Update(ctx, *existingSubCategory); err != nil {
|
||||||
|
s.logger.Error("更新二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||||
|
return fmt.Errorf("更新二级分类失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("更新二级分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubCategory 删除二级分类
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error {
|
||||||
|
// 1. 检查二级分类是否存在
|
||||||
|
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("二级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 删除二级分类
|
||||||
|
if err := s.subCategoryRepo.Delete(ctx, cmd.ID); err != nil {
|
||||||
|
s.logger.Error("删除二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||||
|
return fmt.Errorf("删除二级分类失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("删除二级分类成功", zap.String("id", cmd.ID), zap.String("code", existingSubCategory.Code))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubCategoryByID 根据ID获取二级分类
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error) {
|
||||||
|
subCategory, err := s.subCategoryRepo.GetByID(ctx, query.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("二级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载一级分类信息
|
||||||
|
if subCategory.CategoryID != "" {
|
||||||
|
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
|
||||||
|
if err == nil {
|
||||||
|
subCategory.Category = &category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为响应对象
|
||||||
|
response := s.convertToSubCategoryInfoResponse(subCategory)
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubCategories 获取二级分类列表
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error) {
|
||||||
|
// 构建查询条件
|
||||||
|
categoryID := query.CategoryID
|
||||||
|
isEnabled := query.IsEnabled
|
||||||
|
isVisible := query.IsVisible
|
||||||
|
|
||||||
|
var subCategories []*entities.ProductSubCategory
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// 根据条件查询
|
||||||
|
if categoryID != "" {
|
||||||
|
// 按一级分类查询
|
||||||
|
subCategories, err = s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
|
||||||
|
} else {
|
||||||
|
// 查询所有二级分类
|
||||||
|
subCategories, err = s.subCategoryRepo.List(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤状态
|
||||||
|
filteredSubCategories := make([]*entities.ProductSubCategory, 0)
|
||||||
|
for _, subCategory := range subCategories {
|
||||||
|
if isEnabled != nil && *isEnabled != subCategory.IsEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isVisible != nil && *isVisible != subCategory.IsVisible {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredSubCategories = append(filteredSubCategories, subCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载一级分类信息
|
||||||
|
for _, subCategory := range filteredSubCategories {
|
||||||
|
if subCategory.CategoryID != "" {
|
||||||
|
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
|
||||||
|
if err == nil {
|
||||||
|
subCategory.Category = &category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为响应对象
|
||||||
|
items := make([]responses.SubCategoryInfoResponse, len(filteredSubCategories))
|
||||||
|
for i, subCategory := range filteredSubCategories {
|
||||||
|
items[i] = *s.convertToSubCategoryInfoResponse(subCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &responses.SubCategoryListResponse{
|
||||||
|
Total: int64(len(items)),
|
||||||
|
Page: query.Page,
|
||||||
|
Size: query.PageSize,
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubCategoriesByCategoryID 根据一级分类ID获取二级分类列表
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error) {
|
||||||
|
subCategories, err := s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为响应对象
|
||||||
|
items := make([]*responses.SubCategorySimpleResponse, len(subCategories))
|
||||||
|
for i, subCategory := range subCategories {
|
||||||
|
items[i] = &responses.SubCategorySimpleResponse{
|
||||||
|
ID: subCategory.ID,
|
||||||
|
Name: subCategory.Name,
|
||||||
|
Code: subCategory.Code,
|
||||||
|
CategoryID: subCategory.CategoryID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToSubCategoryInfoResponse 转换为二级分类信息响应
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
|
||||||
|
response := &responses.SubCategoryInfoResponse{
|
||||||
|
ID: subCategory.ID,
|
||||||
|
Name: subCategory.Name,
|
||||||
|
Code: subCategory.Code,
|
||||||
|
Description: subCategory.Description,
|
||||||
|
CategoryID: subCategory.CategoryID,
|
||||||
|
Sort: subCategory.Sort,
|
||||||
|
IsEnabled: subCategory.IsEnabled,
|
||||||
|
IsVisible: subCategory.IsVisible,
|
||||||
|
CreatedAt: subCategory.CreatedAt,
|
||||||
|
UpdatedAt: subCategory.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一级分类信息
|
||||||
|
if subCategory.Category != nil {
|
||||||
|
response.Category = &responses.CategoryInfoResponse{
|
||||||
|
ID: subCategory.Category.ID,
|
||||||
|
Name: subCategory.Category.Name,
|
||||||
|
Description: subCategory.Category.Description,
|
||||||
|
Sort: subCategory.Category.Sort,
|
||||||
|
IsEnabled: subCategory.Category.IsEnabled,
|
||||||
|
IsVisible: subCategory.Category.IsVisible,
|
||||||
|
CreatedAt: subCategory.Category.CreatedAt,
|
||||||
|
UpdatedAt: subCategory.Category.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCreateSubCategory 验证创建二级分类参数
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) validateCreateSubCategory(cmd *commands.CreateSubCategoryCommand) error {
|
||||||
|
if cmd.Name == "" {
|
||||||
|
return errors.New("二级分类名称不能为空")
|
||||||
|
}
|
||||||
|
if cmd.Code == "" {
|
||||||
|
return errors.New("二级分类编号不能为空")
|
||||||
|
}
|
||||||
|
if cmd.CategoryID == "" {
|
||||||
|
return errors.New("一级分类ID不能为空")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateUpdateSubCategory 验证更新二级分类参数
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) validateUpdateSubCategory(cmd *commands.UpdateSubCategoryCommand) error {
|
||||||
|
if cmd.ID == "" {
|
||||||
|
return errors.New("二级分类ID不能为空")
|
||||||
|
}
|
||||||
|
if cmd.Name == "" {
|
||||||
|
return errors.New("二级分类名称不能为空")
|
||||||
|
}
|
||||||
|
if cmd.Code == "" {
|
||||||
|
return errors.New("二级分类编号不能为空")
|
||||||
|
}
|
||||||
|
if cmd.CategoryID == "" {
|
||||||
|
return errors.New("一级分类ID不能为空")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSubCategoryCode 验证二级分类编号唯一性
|
||||||
|
func (s *SubCategoryApplicationServiceImpl) validateSubCategoryCode(code, excludeID, categoryID string) error {
|
||||||
|
if code == "" {
|
||||||
|
return errors.New("二级分类编号不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSubCategory, err := s.subCategoryRepo.FindByCode(context.Background(), code)
|
||||||
|
if err == nil && existingSubCategory != nil && existingSubCategory.ID != excludeID {
|
||||||
|
// 如果指定了分类ID,检查是否在同一分类下
|
||||||
|
if categoryID == "" || existingSubCategory.CategoryID == categoryID {
|
||||||
|
return errors.New("二级分类编号已存在")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -557,6 +557,11 @@ func NewContainer() *Container {
|
|||||||
product_repo.NewGormProductCategoryRepository,
|
product_repo.NewGormProductCategoryRepository,
|
||||||
fx.As(new(domain_product_repo.ProductCategoryRepository)),
|
fx.As(new(domain_product_repo.ProductCategoryRepository)),
|
||||||
),
|
),
|
||||||
|
// 产品二级分类仓储 - 同时注册具体类型和接口类型
|
||||||
|
fx.Annotate(
|
||||||
|
product_repo.NewGormProductSubCategoryRepository,
|
||||||
|
fx.As(new(domain_product_repo.ProductSubCategoryRepository)),
|
||||||
|
),
|
||||||
// 订阅仓储 - 同时注册具体类型和接口类型
|
// 订阅仓储 - 同时注册具体类型和接口类型
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
product_repo.NewGormSubscriptionRepository,
|
product_repo.NewGormSubscriptionRepository,
|
||||||
@@ -1002,6 +1007,11 @@ func NewContainer() *Container {
|
|||||||
product.NewCategoryApplicationService,
|
product.NewCategoryApplicationService,
|
||||||
fx.As(new(product.CategoryApplicationService)),
|
fx.As(new(product.CategoryApplicationService)),
|
||||||
),
|
),
|
||||||
|
// 二级分类应用服务 - 绑定到接口
|
||||||
|
fx.Annotate(
|
||||||
|
product.NewSubCategoryApplicationService,
|
||||||
|
fx.As(new(product.SubCategoryApplicationService)),
|
||||||
|
),
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
product.NewDocumentationApplicationService,
|
product.NewDocumentationApplicationService,
|
||||||
fx.As(new(product.DocumentationApplicationServiceInterface)),
|
fx.As(new(product.DocumentationApplicationServiceInterface)),
|
||||||
@@ -1198,6 +1208,8 @@ func NewContainer() *Container {
|
|||||||
handlers.NewProductHandler,
|
handlers.NewProductHandler,
|
||||||
// 产品管理员HTTP处理器
|
// 产品管理员HTTP处理器
|
||||||
handlers.NewProductAdminHandler,
|
handlers.NewProductAdminHandler,
|
||||||
|
// 二级分类HTTP处理器
|
||||||
|
handlers.NewSubCategoryHandler,
|
||||||
// API Handler
|
// API Handler
|
||||||
handlers.NewApiHandler,
|
handlers.NewApiHandler,
|
||||||
// 统计HTTP处理器
|
// 统计HTTP处理器
|
||||||
@@ -1266,6 +1278,8 @@ func NewContainer() *Container {
|
|||||||
routes.NewProductRoutes,
|
routes.NewProductRoutes,
|
||||||
// 产品管理员路由
|
// 产品管理员路由
|
||||||
routes.NewProductAdminRoutes,
|
routes.NewProductAdminRoutes,
|
||||||
|
// 二级分类路由
|
||||||
|
routes.NewSubCategoryRoutes,
|
||||||
// 组件报告订单路由
|
// 组件报告订单路由
|
||||||
routes.NewComponentReportOrderRoutes,
|
routes.NewComponentReportOrderRoutes,
|
||||||
// UI组件路由
|
// UI组件路由
|
||||||
@@ -1384,6 +1398,7 @@ func RegisterRoutes(
|
|||||||
financeRoutes *routes.FinanceRoutes,
|
financeRoutes *routes.FinanceRoutes,
|
||||||
productRoutes *routes.ProductRoutes,
|
productRoutes *routes.ProductRoutes,
|
||||||
productAdminRoutes *routes.ProductAdminRoutes,
|
productAdminRoutes *routes.ProductAdminRoutes,
|
||||||
|
subCategoryRoutes *routes.SubCategoryRoutes,
|
||||||
componentReportOrderRoutes *routes.ComponentReportOrderRoutes,
|
componentReportOrderRoutes *routes.ComponentReportOrderRoutes,
|
||||||
uiComponentRoutes *routes.UIComponentRoutes,
|
uiComponentRoutes *routes.UIComponentRoutes,
|
||||||
articleRoutes *routes.ArticleRoutes,
|
articleRoutes *routes.ArticleRoutes,
|
||||||
@@ -1406,6 +1421,7 @@ func RegisterRoutes(
|
|||||||
financeRoutes.Register(router)
|
financeRoutes.Register(router)
|
||||||
productRoutes.Register(router)
|
productRoutes.Register(router)
|
||||||
productAdminRoutes.Register(router)
|
productAdminRoutes.Register(router)
|
||||||
|
subCategoryRoutes.Register(router)
|
||||||
componentReportOrderRoutes.Register(router)
|
componentReportOrderRoutes.Register(router)
|
||||||
uiComponentRoutes.Register(router)
|
uiComponentRoutes.Register(router)
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,20 @@ import (
|
|||||||
|
|
||||||
// Product 产品实体
|
// Product 产品实体
|
||||||
type Product struct {
|
type Product struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
|
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
|
||||||
OldID *string `gorm:"type:varchar(36);index" comment:"旧产品ID,用于兼容"`
|
OldID *string `gorm:"type:varchar(36);index" comment:"旧产品ID,用于兼容"`
|
||||||
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
|
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
|
||||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
|
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
|
||||||
Description string `gorm:"type:text" comment:"产品简介"`
|
Description string `gorm:"type:text" comment:"产品简介"`
|
||||||
Content string `gorm:"type:text" comment:"产品内容"`
|
Content string `gorm:"type:text" comment:"产品内容"`
|
||||||
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
|
CategoryID string `gorm:"type:varchar(36);not null" comment:"一级分类ID"`
|
||||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
SubCategoryID *string `gorm:"type:varchar(36);index" comment:"二级分类ID"`
|
||||||
CostPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"成本价"`
|
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
||||||
Remark string `gorm:"type:text" comment:"备注"`
|
CostPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"成本价"`
|
||||||
IsEnabled bool `gorm:"default:false" comment:"是否启用"`
|
Remark string `gorm:"type:text" comment:"备注"`
|
||||||
IsVisible bool `gorm:"default:false" comment:"是否展示"`
|
IsEnabled bool `gorm:"default:false" comment:"是否启用"`
|
||||||
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
IsVisible bool `gorm:"default:false" comment:"是否展示"`
|
||||||
|
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
||||||
// 组合包相关关联
|
// 组合包相关关联
|
||||||
PackageItems []*ProductPackageItem `gorm:"foreignKey:PackageID" comment:"组合包项目列表"`
|
PackageItems []*ProductPackageItem `gorm:"foreignKey:PackageID" comment:"组合包项目列表"`
|
||||||
// UI组件相关字段
|
// UI组件相关字段
|
||||||
@@ -34,7 +35,8 @@ type Product struct {
|
|||||||
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
|
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
|
||||||
|
|
||||||
// 关联关系
|
// 关联关系
|
||||||
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"产品分类"`
|
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
|
||||||
|
SubCategory *ProductSubCategory `gorm:"foreignKey:SubCategoryID" comment:"二级分类"`
|
||||||
Documentation *ProductDocumentation `gorm:"foreignKey:ProductID" comment:"产品文档"`
|
Documentation *ProductDocumentation `gorm:"foreignKey:ProductID" comment:"产品文档"`
|
||||||
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||||
@@ -118,3 +120,34 @@ func (p *Product) GetOldID() string {
|
|||||||
func (p *Product) HasOldID() bool {
|
func (p *Product) HasOldID() bool {
|
||||||
return p.OldID != nil && *p.OldID != ""
|
return p.OldID != nil && *p.OldID != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasSubCategory 检查是否有二级分类
|
||||||
|
func (p *Product) HasSubCategory() bool {
|
||||||
|
return p.SubCategoryID != nil && *p.SubCategoryID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullCategoryPath 获取完整分类路径(一级分类/二级分类)
|
||||||
|
func (p *Product) GetFullCategoryPath() string {
|
||||||
|
if p.Category == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.SubCategory != nil {
|
||||||
|
return p.Category.Name + " / " + p.SubCategory.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Category.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullCategoryCode 获取完整分类编号(一级分类编号.二级分类编号)
|
||||||
|
func (p *Product) GetFullCategoryCode() string {
|
||||||
|
if p.Category == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.SubCategory != nil {
|
||||||
|
return p.Category.Code + "." + p.SubCategory.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Category.Code
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ type ProductCategory struct {
|
|||||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||||
|
|
||||||
// 关联关系
|
// 关联关系
|
||||||
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
||||||
|
SubCategories []ProductSubCategory `gorm:"foreignKey:CategoryID" comment:"二级分类列表"`
|
||||||
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||||
|
|||||||
82
internal/domains/product/entities/product_sub_category.go
Normal file
82
internal/domains/product/entities/product_sub_category.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductSubCategory 产品二级分类实体
|
||||||
|
type ProductSubCategory struct {
|
||||||
|
ID string `gorm:"primaryKey;type:varchar(36)" comment:"二级分类ID"`
|
||||||
|
Name string `gorm:"type:varchar(100);not null" comment:"二级分类名称"`
|
||||||
|
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"二级分类编号"`
|
||||||
|
Description string `gorm:"type:text" comment:"二级分类描述"`
|
||||||
|
CategoryID string `gorm:"type:varchar(36);not null;index" comment:"一级分类ID"`
|
||||||
|
Sort int `gorm:"default:0" comment:"排序"`
|
||||||
|
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||||
|
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||||
|
|
||||||
|
// 关联关系
|
||||||
|
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
|
||||||
|
Products []Product `gorm:"foreignKey:SubCategoryID" comment:"产品列表"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||||
|
func (psc *ProductSubCategory) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if psc.ID == "" {
|
||||||
|
psc.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid 检查二级分类是否有效
|
||||||
|
func (psc *ProductSubCategory) IsValid() bool {
|
||||||
|
return psc.DeletedAt.Time.IsZero() && psc.IsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisibleToUser 检查二级分类是否对用户可见
|
||||||
|
func (psc *ProductSubCategory) IsVisibleToUser() bool {
|
||||||
|
return psc.IsValid() && psc.IsVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable 启用二级分类
|
||||||
|
func (psc *ProductSubCategory) Enable() {
|
||||||
|
psc.IsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable 禁用二级分类
|
||||||
|
func (psc *ProductSubCategory) Disable() {
|
||||||
|
psc.IsEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show 显示二级分类
|
||||||
|
func (psc *ProductSubCategory) Show() {
|
||||||
|
psc.IsVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide 隐藏二级分类
|
||||||
|
func (psc *ProductSubCategory) Hide() {
|
||||||
|
psc.IsVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullPath 获取完整路径(一级分类/二级分类)
|
||||||
|
func (psc *ProductSubCategory) GetFullPath() string {
|
||||||
|
if psc.Category != nil {
|
||||||
|
return psc.Category.Name + " / " + psc.Name
|
||||||
|
}
|
||||||
|
return psc.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullCode 获取完整编号(一级分类编号.二级分类编号)
|
||||||
|
func (psc *ProductSubCategory) GetFullCode() string {
|
||||||
|
if psc.Category != nil {
|
||||||
|
return psc.Category.Code + "." + psc.Code
|
||||||
|
}
|
||||||
|
return psc.Code
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tyapi-server/internal/domains/product/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductSubCategoryRepository 产品二级分类仓储接口
|
||||||
|
type ProductSubCategoryRepository interface {
|
||||||
|
// 基础CRUD方法
|
||||||
|
GetByID(ctx context.Context, id string) (*entities.ProductSubCategory, error)
|
||||||
|
Create(ctx context.Context, category entities.ProductSubCategory) (*entities.ProductSubCategory, error)
|
||||||
|
Update(ctx context.Context, category entities.ProductSubCategory) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
List(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||||
|
|
||||||
|
// 查询方法
|
||||||
|
FindByCode(ctx context.Context, code string) (*entities.ProductSubCategory, error)
|
||||||
|
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.ProductSubCategory, error)
|
||||||
|
FindVisible(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||||
|
FindEnabled(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||||
|
}
|
||||||
@@ -18,21 +18,24 @@ import (
|
|||||||
// ProductManagementService 产品管理领域服务
|
// ProductManagementService 产品管理领域服务
|
||||||
// 负责产品的基本管理操作,包括创建、查询、更新等
|
// 负责产品的基本管理操作,包括创建、查询、更新等
|
||||||
type ProductManagementService struct {
|
type ProductManagementService struct {
|
||||||
productRepo repositories.ProductRepository
|
productRepo repositories.ProductRepository
|
||||||
categoryRepo repositories.ProductCategoryRepository
|
categoryRepo repositories.ProductCategoryRepository
|
||||||
logger *zap.Logger
|
subCategoryRepo repositories.ProductSubCategoryRepository
|
||||||
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProductManagementService 创建产品管理领域服务
|
// NewProductManagementService 创建产品管理领域服务
|
||||||
func NewProductManagementService(
|
func NewProductManagementService(
|
||||||
productRepo repositories.ProductRepository,
|
productRepo repositories.ProductRepository,
|
||||||
categoryRepo repositories.ProductCategoryRepository,
|
categoryRepo repositories.ProductCategoryRepository,
|
||||||
|
subCategoryRepo repositories.ProductSubCategoryRepository,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) *ProductManagementService {
|
) *ProductManagementService {
|
||||||
return &ProductManagementService{
|
return &ProductManagementService{
|
||||||
productRepo: productRepo,
|
productRepo: productRepo,
|
||||||
categoryRepo: categoryRepo,
|
categoryRepo: categoryRepo,
|
||||||
logger: logger,
|
subCategoryRepo: subCategoryRepo,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +309,21 @@ func (s *ProductManagementService) ValidateProduct(product *entities.Product) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证二级分类是否存在(如果设置了二级分类)
|
||||||
|
if product.SubCategoryID != nil && *product.SubCategoryID != "" {
|
||||||
|
subCategory, err := s.subCategoryRepo.GetByID(context.Background(), *product.SubCategoryID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("产品二级分类不存在: %w", err)
|
||||||
|
}
|
||||||
|
if !subCategory.IsValid() {
|
||||||
|
return errors.New("产品二级分类已禁用或删除")
|
||||||
|
}
|
||||||
|
// 验证二级分类是否属于指定的一级分类
|
||||||
|
if subCategory.CategoryID != product.CategoryID {
|
||||||
|
return errors.New("二级分类不属于指定的一级分类")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (r *GormComponentReportRepository) GetUserDownloadedProductCodes(ctx contex
|
|||||||
|
|
||||||
func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error) {
|
func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error) {
|
||||||
var download entities.ComponentReportDownload
|
var download entities.ComponentReportDownload
|
||||||
err := r.GetDB(ctx).Where("order_number = ?", orderID).First(&download).Error
|
err := r.GetDB(ctx).Where("order_id = ?", orderID).First(&download).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, gorm.ErrRecordNotFound
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"tyapi-server/internal/domains/product/entities"
|
||||||
|
"tyapi-server/internal/domains/product/repositories"
|
||||||
|
"tyapi-server/internal/shared/database"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProductSubCategoriesTable = "product_sub_categories"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GormProductSubCategoryRepository struct {
|
||||||
|
*database.CachedBaseRepositoryImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ repositories.ProductSubCategoryRepository = (*GormProductSubCategoryRepository)(nil)
|
||||||
|
|
||||||
|
func NewGormProductSubCategoryRepository(db *gorm.DB, logger *zap.Logger) repositories.ProductSubCategoryRepository {
|
||||||
|
return &GormProductSubCategoryRepository{
|
||||||
|
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, ProductSubCategoriesTable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) Create(ctx context.Context, category entities.ProductSubCategory) (*entities.ProductSubCategory, error) {
|
||||||
|
err := r.CreateEntity(ctx, &category)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据ID获取二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) GetByID(ctx context.Context, id string) (*entities.ProductSubCategory, error) {
|
||||||
|
var entity entities.ProductSubCategory
|
||||||
|
err := r.SmartGetByID(ctx, id, &entity)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) Update(ctx context.Context, category entities.ProductSubCategory) error {
|
||||||
|
return r.UpdateEntity(ctx, &category)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
return r.DeleteEntity(ctx, id, &entities.ProductSubCategory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取所有二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) List(ctx context.Context) ([]*entities.ProductSubCategory, error) {
|
||||||
|
var categories []entities.ProductSubCategory
|
||||||
|
err := r.GetDB(ctx).Order("sort ASC, created_at DESC").Find(&categories).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为指针切片
|
||||||
|
result := make([]*entities.ProductSubCategory, len(categories))
|
||||||
|
for i := range categories {
|
||||||
|
result[i] = &categories[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByCode 根据编号查找二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) FindByCode(ctx context.Context, code string) (*entities.ProductSubCategory, error) {
|
||||||
|
var entity entities.ProductSubCategory
|
||||||
|
err := r.GetDB(ctx).Where("code = ?", code).First(&entity).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByCategoryID 根据一级分类ID查找二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.ProductSubCategory, error) {
|
||||||
|
var categories []entities.ProductSubCategory
|
||||||
|
err := r.GetDB(ctx).Where("category_id = ?", categoryID).Order("sort ASC, created_at DESC").Find(&categories).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为指针切片
|
||||||
|
result := make([]*entities.ProductSubCategory, len(categories))
|
||||||
|
for i := range categories {
|
||||||
|
result[i] = &categories[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindVisible 查找可见的二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) FindVisible(ctx context.Context) ([]*entities.ProductSubCategory, error) {
|
||||||
|
var categories []entities.ProductSubCategory
|
||||||
|
err := r.GetDB(ctx).Where("is_visible = ? AND is_enabled = ?", true, true).Order("sort ASC, created_at DESC").Find(&categories).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为指针切片
|
||||||
|
result := make([]*entities.ProductSubCategory, len(categories))
|
||||||
|
for i := range categories {
|
||||||
|
result[i] = &categories[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEnabled 查找启用的二级分类
|
||||||
|
func (r *GormProductSubCategoryRepository) FindEnabled(ctx context.Context) ([]*entities.ProductSubCategory, error) {
|
||||||
|
var categories []entities.ProductSubCategory
|
||||||
|
err := r.GetDB(ctx).Where("is_enabled = ?", true).Order("sort ASC, created_at DESC").Find(&categories).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为指针切片
|
||||||
|
result := make([]*entities.ProductSubCategory, len(categories))
|
||||||
|
for i := range categories {
|
||||||
|
result[i] = &categories[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
229
internal/infrastructure/http/handlers/sub_category_handler.go
Normal file
229
internal/infrastructure/http/handlers/sub_category_handler.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tyapi-server/internal/application/product"
|
||||||
|
"tyapi-server/internal/application/product/dto/commands"
|
||||||
|
"tyapi-server/internal/application/product/dto/queries"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubCategoryHandler 二级分类HTTP处理器
|
||||||
|
type SubCategoryHandler struct {
|
||||||
|
subCategoryAppService product.SubCategoryApplicationService
|
||||||
|
responseBuilder interfaces.ResponseBuilder
|
||||||
|
validator interfaces.RequestValidator
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubCategoryHandler 创建二级分类HTTP处理器
|
||||||
|
func NewSubCategoryHandler(
|
||||||
|
subCategoryAppService product.SubCategoryApplicationService,
|
||||||
|
responseBuilder interfaces.ResponseBuilder,
|
||||||
|
validator interfaces.RequestValidator,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *SubCategoryHandler {
|
||||||
|
return &SubCategoryHandler{
|
||||||
|
subCategoryAppService: subCategoryAppService,
|
||||||
|
responseBuilder: responseBuilder,
|
||||||
|
validator: validator,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSubCategory 创建二级分类
|
||||||
|
// @Summary 创建二级分类
|
||||||
|
// @Description 管理员创建新二级分类
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param request body commands.CreateSubCategoryCommand true "创建二级分类请求"
|
||||||
|
// @Success 201 {object} map[string]interface{} "二级分类创建成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/sub-categories [post]
|
||||||
|
func (h *SubCategoryHandler) CreateSubCategory(c *gin.Context) {
|
||||||
|
var cmd commands.CreateSubCategoryCommand
|
||||||
|
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.subCategoryAppService.CreateSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||||
|
h.logger.Error("创建二级分类失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Created(c, nil, "二级分类创建成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubCategory 更新二级分类
|
||||||
|
// @Summary 更新二级分类
|
||||||
|
// @Description 管理员更新二级分类信息
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "二级分类ID"
|
||||||
|
// @Param request body commands.UpdateSubCategoryCommand true "更新二级分类请求"
|
||||||
|
// @Success 200 {object} map[string]interface{} "二级分类更新成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/sub-categories/{id} [put]
|
||||||
|
func (h *SubCategoryHandler) UpdateSubCategory(c *gin.Context) {
|
||||||
|
var cmd commands.UpdateSubCategoryCommand
|
||||||
|
cmd.ID = c.Param("id")
|
||||||
|
if cmd.ID == "" {
|
||||||
|
h.responseBuilder.BadRequest(c, "二级分类ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.subCategoryAppService.UpdateSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||||
|
h.logger.Error("更新二级分类失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, nil, "二级分类更新成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubCategory 删除二级分类
|
||||||
|
// @Summary 删除二级分类
|
||||||
|
// @Description 管理员删除二级分类
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "二级分类ID"
|
||||||
|
// @Success 200 {object} map[string]interface{} "二级分类删除成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/sub-categories/{id} [delete]
|
||||||
|
func (h *SubCategoryHandler) DeleteSubCategory(c *gin.Context) {
|
||||||
|
cmd := commands.DeleteSubCategoryCommand{ID: c.Param("id")}
|
||||||
|
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.subCategoryAppService.DeleteSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||||
|
h.logger.Error("删除二级分类失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, nil, "二级分类删除成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubCategory 获取二级分类详情
|
||||||
|
// @Summary 获取二级分类详情
|
||||||
|
// @Description 获取指定二级分类的详细信息
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "二级分类ID"
|
||||||
|
// @Success 200 {object} responses.SubCategoryInfoResponse "二级分类信息"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/sub-categories/{id} [get]
|
||||||
|
func (h *SubCategoryHandler) GetSubCategory(c *gin.Context) {
|
||||||
|
query := &queries.GetSubCategoryQuery{ID: c.Param("id")}
|
||||||
|
if err := h.validator.ValidateParam(c, query); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.subCategoryAppService.GetSubCategoryByID(c.Request.Context(), query)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取二级分类失败", zap.Error(err))
|
||||||
|
h.responseBuilder.NotFound(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, result, "获取二级分类成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubCategories 获取二级分类列表
|
||||||
|
// @Summary 获取二级分类列表
|
||||||
|
// @Description 获取二级分类列表,支持筛选和分页
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param category_id query string false "一级分类ID"
|
||||||
|
// @Param page query int false "页码"
|
||||||
|
// @Param page_size query int false "每页数量"
|
||||||
|
// @Param is_enabled query bool false "是否启用"
|
||||||
|
// @Param is_visible query bool false "是否展示"
|
||||||
|
// @Param sort_by query string false "排序字段"
|
||||||
|
// @Param sort_order query string false "排序方向"
|
||||||
|
// @Success 200 {object} responses.SubCategoryListResponse "二级分类列表"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/sub-categories [get]
|
||||||
|
func (h *SubCategoryHandler) ListSubCategories(c *gin.Context) {
|
||||||
|
query := &queries.ListSubCategoriesQuery{}
|
||||||
|
if err := h.validator.ValidateQuery(c, query); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认分页参数
|
||||||
|
if query.Page == 0 {
|
||||||
|
query.Page = 1
|
||||||
|
}
|
||||||
|
if query.PageSize == 0 {
|
||||||
|
query.PageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.subCategoryAppService.ListSubCategories(c.Request.Context(), query)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, result, "获取二级分类列表成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubCategoriesByCategory 根据一级分类ID获取二级分类列表(级联选择用)
|
||||||
|
// @Summary 根据一级分类获取二级分类列表
|
||||||
|
// @Description 根据一级分类ID获取二级分类简单列表,用于级联选择
|
||||||
|
// @Tags 二级分类管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "一级分类ID"
|
||||||
|
// @Success 200 {object} []responses.SubCategorySimpleResponse "二级分类简单列表"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/categories/{id}/sub-categories [get]
|
||||||
|
func (h *SubCategoryHandler) ListSubCategoriesByCategory(c *gin.Context) {
|
||||||
|
categoryID := c.Param("id")
|
||||||
|
if categoryID == "" {
|
||||||
|
h.responseBuilder.BadRequest(c, "一级分类ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.subCategoryAppService.ListSubCategoriesByCategoryID(c.Request.Context(), categoryID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, result, "获取二级分类列表成功")
|
||||||
|
}
|
||||||
52
internal/infrastructure/http/routes/sub_category_routes.go
Normal file
52
internal/infrastructure/http/routes/sub_category_routes.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tyapi-server/internal/infrastructure/http/handlers"
|
||||||
|
sharedhttp "tyapi-server/internal/shared/http"
|
||||||
|
"tyapi-server/internal/shared/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubCategoryRoutes 二级分类路由
|
||||||
|
type SubCategoryRoutes struct {
|
||||||
|
handler *handlers.SubCategoryHandler
|
||||||
|
auth *middleware.JWTAuthMiddleware
|
||||||
|
admin *middleware.AdminAuthMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubCategoryRoutes 创建二级分类路由
|
||||||
|
func NewSubCategoryRoutes(
|
||||||
|
handler *handlers.SubCategoryHandler,
|
||||||
|
auth *middleware.JWTAuthMiddleware,
|
||||||
|
admin *middleware.AdminAuthMiddleware,
|
||||||
|
) *SubCategoryRoutes {
|
||||||
|
return &SubCategoryRoutes{
|
||||||
|
handler: handler,
|
||||||
|
auth: auth,
|
||||||
|
admin: admin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 注册路由
|
||||||
|
func (r *SubCategoryRoutes) Register(router *sharedhttp.GinRouter) {
|
||||||
|
engine := router.GetEngine()
|
||||||
|
adminGroup := engine.Group("/api/v1/admin")
|
||||||
|
adminGroup.Use(r.auth.Handle())
|
||||||
|
adminGroup.Use(r.admin.Handle())
|
||||||
|
{
|
||||||
|
// 二级分类管理
|
||||||
|
subCategories := adminGroup.Group("/sub-categories")
|
||||||
|
{
|
||||||
|
subCategories.POST("", r.handler.CreateSubCategory) // 创建二级分类
|
||||||
|
subCategories.PUT("/:id", r.handler.UpdateSubCategory) // 更新二级分类
|
||||||
|
subCategories.DELETE("/:id", r.handler.DeleteSubCategory) // 删除二级分类
|
||||||
|
subCategories.GET("/:id", r.handler.GetSubCategory) // 获取二级分类详情
|
||||||
|
subCategories.GET("", r.handler.ListSubCategories) // 获取二级分类列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一级分类下的二级分类路由(级联选择)
|
||||||
|
categoryAdmin := adminGroup.Group("/product-categories")
|
||||||
|
{
|
||||||
|
categoryAdmin.GET("/:id/sub-categories", r.handler.ListSubCategoriesByCategory) // 根据一级分类获取二级分类列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user