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").Find(&categories) for i := range categories { utils.DB.Where("parent_id = ?", categories[i].ID).Order("sort_order ASC, id ASC").Find(&categories[i].Children) } 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 } category := models.Category{ Name: req.Name, Description: req.Description, ParentID: req.ParentID, 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 } 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 } 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")) 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").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, 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, 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").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.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 } 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").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}) }