Files
sale/backend/internal/api/handlers/order.go
T
admin 3a898e8aa0 fix: 全面修复项目问题
后端修复:
- 验证码存储与校验机制
- 订单创建事务+库存扣减
- GetStats字段名错误
- VerifyEmail改为POST
- 供应商更新字段白名单
- 文件删除安全检查
- 抽奖安全随机数
- 用户管理CRUD
- 订单取消/确认收货
- 工单回复
- Toggle返回新数据
- 移除死代码

前端修复:
- 404兜底路由
- 401软跳转
- API层统一
- 面包屑补充banners
- 国际化完善
- 购物车并行删除
- 退出清理购物车
- 供应商Dashboard数据
- 工单详情页
- 订单取消/确认收货
2026-05-06 09:55:01 +08:00

476 lines
14 KiB
Go

package handlers
import (
"math"
"net/http"
"strconv"
"sale/internal/models"
"sale/internal/schemas"
"sale/internal/utils"
"github.com/gin-gonic/gin"
)
type CartHandler struct{}
func NewCartHandler() *CartHandler {
return &CartHandler{}
}
func (h *CartHandler) List(c *gin.Context) {
userID := c.GetUint("user_id")
var carts []models.Cart
utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts)
c.JSON(http.StatusOK, gin.H{"data": carts})
}
func (h *CartHandler) Add(c *gin.Context) {
userID := c.GetUint("user_id")
var req schemas.AddToCartRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var cart models.Cart
result := utils.DB.Where("user_id = ? AND product_id = ?", userID, req.ProductID).First(&cart)
if result.Error == nil {
cart.Quantity += req.Quantity
utils.DB.Save(&cart)
} else {
cart = models.Cart{
UserID: userID,
ProductID: req.ProductID,
Quantity: req.Quantity,
}
utils.DB.Create(&cart)
}
utils.DB.Preload("Product").First(&cart, cart.ID)
c.JSON(http.StatusOK, gin.H{"data": cart})
}
func (h *CartHandler) Update(c *gin.Context) {
userID := c.GetUint("user_id")
id, _ := strconv.Atoi(c.Param("id"))
var cart models.Cart
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&cart).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Cart item not found"})
return
}
var req schemas.UpdateCartRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
cart.Quantity = req.Quantity
utils.DB.Save(&cart)
utils.DB.Preload("Product").First(&cart, cart.ID)
c.JSON(http.StatusOK, gin.H{"data": cart})
}
func (h *CartHandler) Delete(c *gin.Context) {
userID := c.GetUint("user_id")
id, _ := strconv.Atoi(c.Param("id"))
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Cart{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete cart item"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Cart item deleted successfully"})
}
type OrderHandler struct{}
func NewOrderHandler() *OrderHandler {
return &OrderHandler{}
}
func (h *OrderHandler) List(c *gin.Context) {
userID := c.GetUint("user_id")
role, _ := c.Get("role")
var orders []models.Order
query := utils.DB.Model(&models.Order{})
if role == "user" {
query = query.Where("user_id = ?", userID)
} else if role == "supplier" {
query = query.Where("supplier_id = ?", userID)
}
query.Preload("OrderItems.Product").Preload("ShippingAddress").
Order("created_at DESC").Find(&orders)
c.JSON(http.StatusOK, gin.H{"data": orders})
}
func (h *OrderHandler) GetByID(c *gin.Context) {
userID := c.GetUint("user_id")
role, _ := c.Get("role")
id, _ := strconv.Atoi(c.Param("id"))
var order models.Order
query := utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User")
if role == "user" {
query = query.Where("user_id = ?", userID)
} else if role == "supplier" {
query = query.Where("supplier_id = ?", userID)
}
if err := query.First(&order, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": order})
}
func (h *OrderHandler) Create(c *gin.Context) {
userID := c.GetUint("user_id")
var req schemas.CreateOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var carts []models.Cart
utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts)
if len(carts) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Cart is empty"})
return
}
var subtotal float64
var totalQuantity int
var orderItems []models.OrderItem
supplierMap := make(map[uint]bool)
for _, cart := range carts {
if cart.Product.RequireCredit {
var user models.User
utils.DB.First(&user, userID)
if user.PurchaseCredits < cart.Product.CreditCost*cart.Quantity {
c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient purchase credits for " + cart.Product.Name})
return
}
}
var inventory models.Inventory
if err := utils.DB.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil {
if inventory.Quantity < cart.Quantity {
c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for " + cart.Product.Name})
return
}
}
subtotal += cart.Product.Price * float64(cart.Quantity)
totalQuantity += cart.Quantity
orderItems = append(orderItems, models.OrderItem{
ProductID: cart.ProductID,
Quantity: cart.Quantity,
Price: cart.Product.Price,
})
if inventory.SupplierID != 0 {
supplierMap[inventory.SupplierID] = true
}
}
var supplierID *uint
for sid := range supplierMap {
sid := sid
supplierID = &sid
break
}
var shippingFeeFirstWeight, shippingFeePerGram, serviceFeeRate, taxRate float64
var setting models.SystemSetting
if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil {
shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64)
}
if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil {
shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64)
}
if err := utils.DB.Where("`key` = ?", "service_fee_rate").First(&setting).Error; err == nil {
serviceFeeRate, _ = strconv.ParseFloat(setting.Value, 64)
}
if err := utils.DB.Where("`key` = ?", "tax_rate").First(&setting).Error; err == nil {
taxRate, _ = strconv.ParseFloat(setting.Value, 64)
}
shippingFee := shippingFeeFirstWeight
if subtotal >= 99 || shippingFeeFirstWeight == 0 {
shippingFee = 0
} else if totalQuantity > 500 {
shippingFee += shippingFeePerGram * float64(totalQuantity-500)
}
serviceFee := subtotal * serviceFeeRate / 100
tax := subtotal * taxRate / 100
totalAmount := subtotal + shippingFee + serviceFee + tax
order := models.Order{
UserID: userID,
SupplierID: supplierID,
Subtotal: subtotal,
ShippingFee: shippingFee,
ServiceFee: serviceFee,
Tax: tax,
TotalAmount: totalAmount,
Status: models.OrderStatusPendingPayment,
ShippingAddressID: &req.ShippingAddressID,
PaymentMethod: req.PaymentMethod,
}
tx := utils.DB.Begin()
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order"})
return
}
for i := range orderItems {
orderItems[i].OrderID = order.ID
}
if err := tx.Create(&orderItems).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order items"})
return
}
for _, cart := range carts {
if cart.Product.RequireCredit {
if err := tx.Model(&models.User{}).Where("id = ?", userID).
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", cart.Product.CreditCost*cart.Quantity)).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to deduct credits"})
return
}
}
if cart.Product.CreditReward > 0 {
tx.Model(&models.User{}).Where("id = ?", userID).
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", cart.Product.CreditReward*cart.Quantity))
}
var inventory models.Inventory
if err := tx.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil {
if inventory.Quantity >= cart.Quantity {
tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity-cart.Quantity)
}
}
}
if err := tx.Where("user_id = ?", userID).Delete(&models.Cart{}).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear cart"})
return
}
tx.Commit()
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").First(&order, order.ID)
c.JSON(http.StatusCreated, gin.H{"data": order})
}
func (h *OrderHandler) Refund(c *gin.Context) {
userID := c.GetUint("user_id")
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
if order.Status == models.OrderStatusShipped || order.Status == models.OrderStatusCompleted {
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot refund a shipped or completed order"})
return
}
if order.RefundStatus == models.RefundStatusPending {
c.JSON(http.StatusBadRequest, gin.H{"error": "Refund already requested"})
return
}
var req schemas.RefundRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
utils.DB.Model(&order).Updates(map[string]interface{}{
"refund_status": models.RefundStatusPending,
"refund_reason": req.Reason,
"status": models.OrderStatusRefunding,
})
c.JSON(http.StatusOK, gin.H{"message": "Refund request submitted successfully"})
}
func (h *OrderHandler) ConfirmOrder(c *gin.Context) {
userID := c.GetUint("user_id")
id, _ := strconv.Atoi(c.Param("id"))
var order models.Order
if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
if order.Status != models.OrderStatusPendingConfirm {
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"})
return
}
utils.DB.Model(&order).Update("status", models.OrderStatusPendingShip)
c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"})
}
func (h *OrderHandler) ShipOrder(c *gin.Context) {
userID := c.GetUint("user_id")
id, _ := strconv.Atoi(c.Param("id"))
var order models.Order
if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
if order.Status != models.OrderStatusPendingShip {
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be shipped"})
return
}
var req schemas.ShipOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
utils.DB.Model(&order).Updates(map[string]interface{}{
"status": models.OrderStatusShipped,
"tracking_number": req.TrackingNumber,
"shipping_photo": req.ShippingPhoto,
"express_photo": req.ExpressPhoto,
"customs_photo": req.CustomsPhoto,
})
c.JSON(http.StatusOK, gin.H{"message": "Order shipped successfully"})
}
func (h *OrderHandler) AdminList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
var total int64
utils.DB.Model(&models.Order{}).Count(&total)
var orders []models.Order
offset := (page - 1) * pageSize
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders)
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
c.JSON(http.StatusOK, gin.H{
"data": orders,
"pagination": gin.H{
"page": page,
"page_size": pageSize,
"total": total,
"total_pages": totalPages,
},
})
}
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
var req schemas.ProcessRefundRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Status == "approved" {
var setting models.SystemSetting
feeRate := 0.0
if err := utils.DB.Where("`key` = ?", "payment_channel_fee_rate").First(&setting).Error; err == nil {
feeRate, _ = strconv.ParseFloat(setting.Value, 64)
}
refundAmount := order.TotalAmount * (1 - feeRate/100)
utils.DB.Model(&order).Updates(map[string]interface{}{
"refund_status": models.RefundStatusCompleted,
"refund_amount": refundAmount,
"status": models.OrderStatusRefunded,
})
} else {
utils.DB.Model(&order).Updates(map[string]interface{}{
"refund_status": models.RefundStatusRejected,
"status": models.OrderStatusPendingConfirm,
})
}
c.JSON(http.StatusOK, gin.H{"message": "Refund processed successfully"})
}
func (h *OrderHandler) Export(c *gin.Context) {
var orders []models.Order
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
Order("created_at DESC").Find(&orders)
c.JSON(http.StatusOK, gin.H{"data": orders})
}
func (h *OrderHandler) CancelOrder(c *gin.Context) {
userID := c.GetUint("user_id")
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
if order.Status != models.OrderStatusPendingPayment && order.Status != models.OrderStatusPendingConfirm {
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be cancelled"})
return
}
utils.DB.Model(&order).Update("status", models.OrderStatusCancelled)
c.JSON(http.StatusOK, gin.H{"message": "Order cancelled successfully"})
}
func (h *OrderHandler) ConfirmReceipt(c *gin.Context) {
userID := c.GetUint("user_id")
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 {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
if order.Status != models.OrderStatusShipped {
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"})
return
}
utils.DB.Model(&order).Update("status", models.OrderStatusCompleted)
c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"})
}