Files
sale/backend/internal/api/handlers/product.go
T
admin b26fea9621
Build and Push Docker Image / build-and-push (push) Successful in 1m0s
Build and Push Docker Image / deploy (push) Successful in 8s
Docker Build / Build and Push Docker Image (push) Has been cancelled
Docker Build / Deploy to Server (push) Has been cancelled
feat: 分类管理添加是否可跨分类购物选项
2026-07-05 20:18:27 +08:00

402 lines
11 KiB
Go

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)
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,
MinAmount: req.MinAmount,
MaxAmount: req.MaxAmount,
MinQuantity: req.MinQuantity,
MaxQuantity: req.MaxQuantity,
MinWeight: req.MinWeight,
MaxWeight: req.MaxWeight,
AllowCrossCategory: req.AllowCrossCategory != nil && *req.AllowCrossCategory,
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.AllowCrossCategory != nil {
updates["allow_cross_category"] = *req.AllowCrossCategory
}
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").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,
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.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
}
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})
}