package handlers import ( "math" "net/http" "strconv" "sale/internal/models" "sale/internal/schemas" "sale/internal/utils" "github.com/gin-gonic/gin" ) type CategoryHandler struct{} func NewCategoryHandler() *CategoryHandler { return &CategoryHandler{} } func (h *CategoryHandler) List(c *gin.Context) { var categories []models.Category utils.DB.Where("parent_id IS NULL").Order("sort_order ASC, id ASC").Preload("PurchaseGroup").Find(&categories) for i := range categories { utils.DB.Where("parent_id = ?", categories[i].ID).Order("sort_order ASC, id ASC").Preload("PurchaseGroup").Find(&categories[i].Children) var count int64 utils.DB.Table("product_categories").Where("category_id = ?", categories[i].ID).Count(&count) categories[i].ProductCount = count for j := range categories[i].Children { var childCount int64 utils.DB.Table("product_categories").Where("category_id = ?", categories[i].Children[j].ID).Count(&childCount) categories[i].Children[j].ProductCount = childCount } } c.JSON(http.StatusOK, gin.H{"data": categories}) } func (h *CategoryHandler) Create(c *gin.Context) { var req schemas.CreateCategoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var existing models.Category if err := utils.DB.Where("name = ?", req.Name).First(&existing).Error; err == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "分类名称已存在"}) return } category := models.Category{ Name: req.Name, Description: req.Description, ParentID: req.ParentID, PurchaseGroupID: req.PurchaseGroupID, MinAmount: req.MinAmount, MaxAmount: req.MaxAmount, MinQuantity: req.MinQuantity, MaxQuantity: req.MaxQuantity, MinWeight: req.MinWeight, MaxWeight: req.MaxWeight, SortOrder: req.SortOrder, } if err := utils.DB.Create(&category).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create category"}) return } c.JSON(http.StatusCreated, gin.H{"data": category}) } func (h *CategoryHandler) Update(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var category models.Category if err := utils.DB.First(&category, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"}) return } var req schemas.UpdateCategoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Name != nil && *req.Name != category.Name { var existing models.Category if err := utils.DB.Where("name = ? AND id != ?", *req.Name, id).First(&existing).Error; err == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "分类名称已存在"}) return } } updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if req.ParentID != nil { updates["parent_id"] = *req.ParentID } if req.MinAmount != nil { updates["min_amount"] = *req.MinAmount } if req.MaxAmount != nil { updates["max_amount"] = *req.MaxAmount } if req.MinQuantity != nil { updates["min_quantity"] = *req.MinQuantity } if req.MaxQuantity != nil { updates["max_quantity"] = *req.MaxQuantity } if req.MinWeight != nil { updates["min_weight"] = *req.MinWeight } if req.MaxWeight != nil { updates["max_weight"] = *req.MaxWeight } if req.SortOrder != nil { updates["sort_order"] = *req.SortOrder } if req.PurchaseGroupID != nil { updates["purchase_group_id"] = *req.PurchaseGroupID } utils.DB.Model(&category).Updates(updates) c.JSON(http.StatusOK, gin.H{"data": category}) } func (h *CategoryHandler) Delete(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var childCount int64 utils.DB.Model(&models.Category{}).Where("parent_id = ?", id).Count(&childCount) if childCount > 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "该分类下有子分类,无法删除"}) return } var productCount int64 utils.DB.Table("product_categories").Where("category_id = ?", id).Count(&productCount) if productCount > 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "该分类下有商品,无法删除"}) return } if err := utils.DB.Delete(&models.Category{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete category"}) return } c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"}) } type ProductHandler struct{} func NewProductHandler() *ProductHandler { return &ProductHandler{} } func (h *ProductHandler) List(c *gin.Context) { var req schemas.ProductListRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } query := utils.DB.Model(&models.Product{}).Where("is_active = ?", true) if req.CategoryID != nil { query = query.Joins("JOIN product_categories ON product_categories.product_id = products.id"). Where("product_categories.category_id = ?", *req.CategoryID) } if req.BrandID != nil { query = query.Where("brand_id = ?", *req.BrandID) } if req.Keyword != "" { query = query.Where("name ILIKE ?", "%"+req.Keyword+"%") } if req.MinPrice != nil { query = query.Where("price >= ?", *req.MinPrice) } if req.MaxPrice != nil { query = query.Where("price <= ?", *req.MaxPrice) } var total int64 query.Count(&total) var products []models.Product offset := (req.Page - 1) * req.PageSize query.Preload("Categories").Preload("CustomFields").Preload("Brand"). Order("created_at DESC"). Offset(offset).Limit(req.PageSize). Find(&products) totalPages := int(math.Ceil(float64(total) / float64(req.PageSize))) c.JSON(http.StatusOK, gin.H{ "data": products, "pagination": gin.H{ "page": req.Page, "page_size": req.PageSize, "total": total, "total_pages": totalPages, }, }) } func (h *ProductHandler) GetByID(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var product models.Product if err := utils.DB.Preload("Categories").Preload("CustomFields").First(&product, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) return } c.JSON(http.StatusOK, gin.H{"data": product}) } func (h *ProductHandler) AdminList(c *gin.Context) { var products []models.Product utils.DB.Preload("Categories").Preload("Brand").Preload("ShippingTemplate").Order("created_at DESC").Find(&products) c.JSON(http.StatusOK, gin.H{"data": products}) } func (h *ProductHandler) Create(c *gin.Context) { var req schemas.CreateProductRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } product := models.Product{ Name: req.Name, Description: req.Description, Price: req.Price, Weight: req.Weight, MinPurchase: req.MinPurchase, MaxPurchase: req.MaxPurchase, MinWeight: req.MinWeight, MaxWeight: req.MaxWeight, MinAmount: req.MinAmount, MaxAmount: req.MaxAmount, RequireCredit: req.RequireCredit, CreditCost: req.CreditCost, CreditReward: req.CreditReward, Images: req.Images, BrandID: req.BrandID, ShippingTemplateID: req.ShippingTemplateID, IsActive: true, } if err := utils.DB.Create(&product).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"}) return } if len(req.CategoryIDs) > 0 { var categories []models.Category utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories) utils.DB.Model(&product).Association("Categories").Replace(categories) } if len(req.CustomFields) > 0 { for _, cf := range req.CustomFields { utils.DB.Create(&models.ProductCustomField{ ProductID: product.ID, FieldName: cf.FieldName, FieldValue: cf.FieldValue, }) } } utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").Preload("ShippingTemplate").First(&product, product.ID) c.JSON(http.StatusCreated, gin.H{"data": product}) } func (h *ProductHandler) Update(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var product models.Product if err := utils.DB.First(&product, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) return } var req schemas.UpdateProductRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if req.Price != nil { updates["price"] = *req.Price } if req.Weight != nil { updates["weight"] = *req.Weight } if req.MinPurchase != nil { updates["min_purchase"] = *req.MinPurchase } if req.MaxPurchase != nil { updates["max_purchase"] = *req.MaxPurchase } if req.MinWeight != nil { updates["min_weight"] = *req.MinWeight } if req.MaxWeight != nil { updates["max_weight"] = *req.MaxWeight } if req.MinAmount != nil { updates["min_amount"] = *req.MinAmount } if req.MaxAmount != nil { updates["max_amount"] = *req.MaxAmount } if req.RequireCredit != nil { updates["require_credit"] = *req.RequireCredit } if req.CreditCost != nil { updates["credit_cost"] = *req.CreditCost } if req.CreditReward != nil { updates["credit_reward"] = *req.CreditReward } if req.Images != nil { updates["images"] = *req.Images } if req.IsActive != nil { updates["is_active"] = *req.IsActive } if req.BrandID != nil { updates["brand_id"] = *req.BrandID } if req.ShippingTemplateID != nil { updates["shipping_template_id"] = *req.ShippingTemplateID } utils.DB.Model(&product).Updates(updates) if req.CategoryIDs != nil { var categories []models.Category utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories) utils.DB.Model(&product).Association("Categories").Replace(categories) } if req.CustomFields != nil { utils.DB.Where("product_id = ?", product.ID).Delete(&models.ProductCustomField{}) for _, cf := range req.CustomFields { utils.DB.Create(&models.ProductCustomField{ ProductID: product.ID, FieldName: cf.FieldName, FieldValue: cf.FieldValue, }) } } utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").Preload("ShippingTemplate").First(&product, product.ID) c.JSON(http.StatusOK, gin.H{"data": product}) } func (h *ProductHandler) Delete(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := utils.DB.Delete(&models.Product{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete product"}) return } c.JSON(http.StatusOK, gin.H{"message": "Product deleted successfully"}) } func (h *ProductHandler) AddCustomField(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var product models.Product if err := utils.DB.First(&product, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) return } var req schemas.CustomFieldRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } field := models.ProductCustomField{ ProductID: product.ID, FieldName: req.FieldName, FieldValue: req.FieldValue, } utils.DB.Create(&field) c.JSON(http.StatusCreated, gin.H{"data": field}) }