diff --git a/backend/internal/api/handlers/brand.go b/backend/internal/api/handlers/brand.go index e501a1e..51986f3 100644 --- a/backend/internal/api/handlers/brand.go +++ b/backend/internal/api/handlers/brand.go @@ -27,7 +27,7 @@ func (h *BrandHandler) Create(c *gin.Context) { return } utils.DB.Create(&brand) - c.JSON(http.StatusOK, brand) + c.JSON(http.StatusOK, gin.H{"data": brand}) } func (h *BrandHandler) Update(c *gin.Context) { @@ -37,13 +37,32 @@ func (h *BrandHandler) Update(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "Brand not found"}) return } - var input models.Brand - if err := c.ShouldBindJSON(&input); err != nil { + var req struct { + Name *string `json:"name"` + Icon *string `json:"icon"` + Info *string `json:"info"` + SortOrder *int `json:"sort_order"` + } + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - utils.DB.Model(&brand).Updates(input) - c.JSON(http.StatusOK, brand) + updates := make(map[string]interface{}) + if req.Name != nil { + updates["name"] = *req.Name + } + if req.Icon != nil { + updates["icon"] = *req.Icon + } + if req.Info != nil { + updates["info"] = *req.Info + } + if req.SortOrder != nil { + updates["sort_order"] = *req.SortOrder + } + utils.DB.Model(&brand).Updates(updates) + utils.DB.First(&brand, brand.ID) + c.JSON(http.StatusOK, gin.H{"data": brand}) } func (h *BrandHandler) Delete(c *gin.Context) { diff --git a/backend/internal/api/handlers/lottery.go b/backend/internal/api/handlers/lottery.go index ad35db6..61e554b 100644 --- a/backend/internal/api/handlers/lottery.go +++ b/backend/internal/api/handlers/lottery.go @@ -52,6 +52,31 @@ func (h *LotteryHandler) Register(c *gin.Context) { return } + if !lottery.IsActive { + c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not active"}) + return + } + + if lottery.TotalQuota != nil && *lottery.TotalQuota > 0 { + var participantCount int64 + utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ?", id).Count(&participantCount) + if int(participantCount) >= *lottery.TotalQuota { + c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery quota is full"}) + return + } + } + + if lottery.DailyQuota != nil && *lottery.DailyQuota > 0 { + today := time.Now().Truncate(24 * time.Hour) + tomorrow := today.Add(24 * time.Hour) + var dailyCount int64 + utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ? AND registered_at >= ? AND registered_at < ?", id, today, tomorrow).Count(&dailyCount) + if int(dailyCount) >= *lottery.DailyQuota { + c.JSON(http.StatusBadRequest, gin.H{"error": "Daily quota is full"}) + return + } + } + var existing models.LotteryParticipant if err := utils.DB.Where("lottery_id = ? AND user_id = ?", id, userID).First(&existing).Error; err == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Already registered"}) diff --git a/backend/internal/api/handlers/order.go b/backend/internal/api/handlers/order.go index 7ec608f..dc52ac6 100644 --- a/backend/internal/api/handlers/order.go +++ b/backend/internal/api/handlers/order.go @@ -141,7 +141,11 @@ func (h *OrderHandler) Create(c *gin.Context) { } var carts []models.Cart - utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts) + cartQuery := utils.DB.Where("user_id = ?", userID).Preload("Product") + if len(req.CartItemIDs) > 0 { + cartQuery = cartQuery.Where("id IN ?", req.CartItemIDs) + } + cartQuery.Find(&carts) if len(carts) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Cart is empty"}) @@ -154,6 +158,20 @@ func (h *OrderHandler) Create(c *gin.Context) { supplierMap := make(map[uint]bool) for _, cart := range carts { + if !cart.Product.IsActive { + c.JSON(http.StatusBadRequest, gin.H{"error": "Product " + cart.Product.Name + " is no longer available"}) + return + } + + if cart.Product.MinPurchase > 0 && cart.Quantity < cart.Product.MinPurchase { + c.JSON(http.StatusBadRequest, gin.H{"error": cart.Product.Name + " minimum purchase is " + strconv.Itoa(cart.Product.MinPurchase)}) + return + } + if cart.Product.MaxPurchase != nil && *cart.Product.MaxPurchase > 0 && cart.Quantity > *cart.Product.MaxPurchase { + c.JSON(http.StatusBadRequest, gin.H{"error": cart.Product.Name + " maximum purchase is " + strconv.Itoa(*cart.Product.MaxPurchase)}) + return + } + if cart.Product.RequireCredit { var user models.User utils.DB.First(&user, userID) @@ -268,7 +286,11 @@ func (h *OrderHandler) Create(c *gin.Context) { } } - if err := tx.Where("user_id = ?", userID).Delete(&models.Cart{}).Error; err != nil { + cartIDs := make([]uint, len(carts)) + for i, cart := range carts { + cartIDs[i] = cart.ID + } + if err := tx.Where("id IN ? AND user_id = ?", cartIDs, userID).Delete(&models.Cart{}).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear cart"}) return @@ -394,7 +416,7 @@ func (h *OrderHandler) ProcessRefund(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var order models.Order - if err := utils.DB.First(&order, id).Error; err != nil { + if err := utils.DB.Preload("OrderItems").First(&order, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } @@ -413,11 +435,35 @@ func (h *OrderHandler) ProcessRefund(c *gin.Context) { } refundAmount := order.TotalAmount * (1 - feeRate/100) - utils.DB.Model(&order).Updates(map[string]interface{}{ + + tx := utils.DB.Begin() + + tx.Model(&order).Updates(map[string]interface{}{ "refund_status": models.RefundStatusCompleted, "refund_amount": refundAmount, "status": models.OrderStatusRefunded, }) + + for _, item := range order.OrderItems { + var product models.Product + if err := tx.First(&product, item.ProductID).Error; err == nil { + if product.RequireCredit { + tx.Model(&models.User{}).Where("id = ?", order.UserID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity)) + } + if product.CreditReward > 0 { + tx.Model(&models.User{}).Where("id = ?", order.UserID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity)) + } + } + + var inventory models.Inventory + if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil { + tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity) + } + } + + tx.Commit() } else { utils.DB.Model(&order).Updates(map[string]interface{}{ "refund_status": models.RefundStatusRejected, @@ -441,7 +487,7 @@ func (h *OrderHandler) CancelOrder(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var order models.Order - if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil { + if err := utils.DB.Preload("OrderItems").Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } @@ -451,7 +497,29 @@ func (h *OrderHandler) CancelOrder(c *gin.Context) { return } - utils.DB.Model(&order).Update("status", models.OrderStatusCancelled) + tx := utils.DB.Begin() + tx.Model(&order).Update("status", models.OrderStatusCancelled) + + for _, item := range order.OrderItems { + var product models.Product + if err := tx.First(&product, item.ProductID).Error; err == nil { + if product.RequireCredit { + tx.Model(&models.User{}).Where("id = ?", order.UserID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity)) + } + if product.CreditReward > 0 { + tx.Model(&models.User{}).Where("id = ?", order.UserID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity)) + } + } + + var inventory models.Inventory + if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil { + tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity) + } + } + + tx.Commit() c.JSON(http.StatusOK, gin.H{"message": "Order cancelled successfully"}) } diff --git a/backend/internal/api/handlers/product.go b/backend/internal/api/handlers/product.go index 65f2f8d..513e72a 100644 --- a/backend/internal/api/handlers/product.go +++ b/backend/internal/api/handlers/product.go @@ -23,6 +23,14 @@ func (h *CategoryHandler) List(c *gin.Context) { 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}) } @@ -121,6 +129,21 @@ func (h *CategoryHandler) Update(c *gin.Context) { 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 diff --git a/backend/internal/models/category.go b/backend/internal/models/category.go index bda08f4..62af1fa 100644 --- a/backend/internal/models/category.go +++ b/backend/internal/models/category.go @@ -21,6 +21,7 @@ type Category struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + ProductCount int64 `gorm:"-" json:"product_count"` Children []Category `gorm:"foreignKey:ParentID" json:"children,omitempty"` } diff --git a/backend/internal/schemas/order.go b/backend/internal/schemas/order.go index 7b3f5cd..e4bc499 100644 --- a/backend/internal/schemas/order.go +++ b/backend/internal/schemas/order.go @@ -10,8 +10,9 @@ type UpdateCartRequest struct { } type CreateOrderRequest struct { - ShippingAddressID uint `json:"shipping_address_id" binding:"required"` - PaymentMethod string `json:"payment_method"` + ShippingAddressID uint `json:"shipping_address_id" binding:"required"` + PaymentMethod string `json:"payment_method"` + CartItemIDs []uint `json:"cart_item_ids"` } type RefundRequest struct { diff --git a/frontend/src/layouts/SupplierLayout.vue b/frontend/src/layouts/SupplierLayout.vue index 315dc8e..bf0859d 100644 --- a/frontend/src/layouts/SupplierLayout.vue +++ b/frontend/src/layouts/SupplierLayout.vue @@ -35,6 +35,7 @@