402 lines
11 KiB
Go
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})
|
|
}
|