package handlers import ( "fmt" "math" "net/http" "strconv" "time" "sale/internal/models" "sale/internal/schemas" "sale/internal/utils" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" ) 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 product models.Product if err := utils.DB.First(&product, req.ProductID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) return } // 检查商品是否上架 if !product.IsActive { c.JSON(http.StatusBadRequest, gin.H{"error": "Product is not available"}) return } // 检查购物资格(需要资格的商品,每个订单消耗1个资格) if product.RequireCredit { var user models.User utils.DB.First(&user, userID) if user.PurchaseCredits < 1 { c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient purchase credits. This product requires 1 credit per order."}) return } } // 检查库存 var inventory models.Inventory if err := utils.DB.Where("product_id = ?", req.ProductID).First(&inventory).Error; err == nil { if inventory.Quantity < req.Quantity { c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock. Available: " + strconv.Itoa(inventory.Quantity)}) return } } // 检查购物车中是否已存在该商品 var existingCart models.Cart var totalQuantity int result := utils.DB.Where("user_id = ? AND product_id = ?", userID, req.ProductID).First(&existingCart) if result.Error == nil { totalQuantity = existingCart.Quantity + req.Quantity } else { totalQuantity = req.Quantity } // 检查最大购买数量 if product.MaxPurchase != nil && *product.MaxPurchase > 0 && totalQuantity > *product.MaxPurchase { c.JSON(http.StatusBadRequest, gin.H{"error": "Maximum purchase quantity is " + strconv.Itoa(*product.MaxPurchase)}) return } // 添加或更新购物车 var cart models.Cart if result.Error == nil { existingCart.Quantity = totalQuantity utils.DB.Save(&existingCart) cart = existingCart } 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 checkOrderExpired(order *models.Order) { if order.Status != models.OrderStatusPendingPayment || order.PaymentExpiresAt == nil { return } if time.Now().After(*order.PaymentExpiresAt) { tx := utils.DB.Begin() tx.Model(order).Update("status", models.OrderStatusCancelled) for _, item := range order.OrderItems { 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) } 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 + 1")) } if product.CreditReward > 0 { tx.Model(&models.User{}).Where("id = ?", order.UserID). UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity)) } } } tx.Commit() order.Status = models.OrderStatusCancelled } } func checkOrdersExpired(orders []models.Order) { for i := range orders { checkOrderExpired(&orders[i]) } } // getPaymentExpiryTime 获取支付过期时间(当前时间 + 30分钟) func getPaymentExpiryTime() *time.Time { t := time.Now().Add(30 * time.Minute) return &t } 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) checkOrdersExpired(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 } checkOrderExpired(&order) 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 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"}) return } var subtotal float64 var totalQuantity int var totalWeight float64 var orderItems []models.OrderItem 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) if user.PurchaseCredits < 1 { 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 { c.JSON(http.StatusBadRequest, gin.H{"error": "No inventory record for " + cart.Product.Name}) return } if inventory.Quantity < cart.Quantity { c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for " + cart.Product.Name + ". Available: " + strconv.Itoa(inventory.Quantity)}) return } subtotal += cart.Product.Price * float64(cart.Quantity) totalQuantity += cart.Quantity totalWeight += cart.Product.Weight * float64(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 shippingAddress models.Address var province string if err := utils.DB.First(&shippingAddress, req.ShippingAddressID).Error; err == nil { province = shippingAddress.Province } // 使用运费模板计算运费 shippingFee := 0.0 // 按运费模板分组计算 type TemplateGroup struct { TemplateID *uint Weight float64 Quantity int Subtotal float64 } templateGroups := make(map[uint]*TemplateGroup) noTemplateGroup := &TemplateGroup{Subtotal: 0, Weight: 0, Quantity: 0} for _, cart := range carts { var templateID uint if cart.Product.ShippingTemplateID != nil { templateID = *cart.Product.ShippingTemplateID } if templateID == 0 { // 没有运费模板,累加到无模板组 noTemplateGroup.Weight += cart.Product.Weight * float64(cart.Quantity) noTemplateGroup.Quantity += cart.Quantity noTemplateGroup.Subtotal += cart.Product.Price * float64(cart.Quantity) } else { if _, ok := templateGroups[templateID]; !ok { templateGroups[templateID] = &TemplateGroup{ TemplateID: &templateID, Weight: 0, Quantity: 0, Subtotal: 0, } } templateGroups[templateID].Weight += cart.Product.Weight * float64(cart.Quantity) templateGroups[templateID].Quantity += cart.Quantity templateGroups[templateID].Subtotal += cart.Product.Price * float64(cart.Quantity) } } // 计算有模板的运费 for _, group := range templateGroups { fee, _ := CalculateShippingFee(group.TemplateID, group.Weight, group.Quantity, group.Subtotal, province) shippingFee += fee } // 计算无模板的运费(使用旧的系统设置) if noTemplateGroup.Subtotal > 0 { var shippingFeeFirstWeight, shippingFeePerGram 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 noTemplateGroup.Subtotal < 99 && shippingFeeFirstWeight > 0 { fee := shippingFeeFirstWeight if noTemplateGroup.Weight > 500 { fee += shippingFeePerGram * (noTemplateGroup.Weight - 500) } shippingFee += fee } } // 获取其他费率 var serviceFeeRate, channelFeeRate, packagingFee, internationalFeeRate float64 var setting models.SystemSetting 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` = ?", "payment_channel_fee_rate").First(&setting).Error; err == nil { channelFeeRate, _ = strconv.ParseFloat(setting.Value, 64) } if err := utils.DB.Where("`key` = ?", "packaging_fee").First(&setting).Error; err == nil { packagingFee, _ = strconv.ParseFloat(setting.Value, 64) } if err := utils.DB.Where("`key` = ?", "international_fee_rate").First(&setting).Error; err == nil { internationalFeeRate, _ = strconv.ParseFloat(setting.Value, 64) } serviceFee := subtotal * serviceFeeRate / 100 channelFee := subtotal * channelFeeRate / 100 internationalFee := subtotal * internationalFeeRate / 100 totalAmount := subtotal + shippingFee + serviceFee + channelFee + packagingFee + internationalFee order := models.Order{ UserID: userID, SupplierID: supplierID, Subtotal: subtotal, ShippingFee: shippingFee, ServiceFee: serviceFee, ChannelFee: channelFee, PackagingFee: packagingFee, InternationalFee: internationalFee, TotalAmount: totalAmount, Status: models.OrderStatusPendingPayment, ShippingAddressID: &req.ShippingAddressID, PaymentMethod: req.PaymentMethod, PaymentExpiresAt: getPaymentExpiryTime(), } 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 - 1")).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) } } } 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 } 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) checkOrdersExpired(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) AdminGetByID(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var order models.Order if err := utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").First(&order, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } checkOrderExpired(&order) c.JSON(http.StatusOK, gin.H{"data": order}) } func (h *OrderHandler) ProcessRefund(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var order models.Order if err := utils.DB.Preload("OrderItems").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) 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 + 1")) } 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, "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) // 创建 Excel 文件 f := excelize.NewFile() defer f.Close() sheetName := "订单列表" f.SetSheetName("Sheet1", sheetName) // 设置表头 headers := []string{"订单ID", "用户名", "订单状态", "商品信息", "商品金额", "运费", "服务费", "通道费", "包装费", "国际计算手续费", "订单总额", "收货人", "联系电话", "收货地址", "快递单号", "创建时间"} for i, header := range headers { cell, _ := excelize.CoordinatesToCellName(i+1, 1) f.SetCellValue(sheetName, cell, header) } // 设置表头样式 headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{Bold: true}, Fill: excelize.Fill{Type: "pattern", Color: []string{"#4472C4"}, Pattern: 1}, Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"}, }) f.SetRowStyle(sheetName, 1, 1, headerStyle) // 状态映射 statusMap := map[string]string{ "pending_payment": "待支付", "pending_confirm": "待确认", "pending_ship": "待发货", "shipped": "已发货", "completed": "已完成", "refunding": "退款中", "refunded": "已退款", "cancelled": "已取消", } // 填充数据 for i, order := range orders { row := i + 2 // 商品信息 var productsStr string for _, item := range order.OrderItems { if productsStr != "" { productsStr += "; " } productsStr += fmt.Sprintf("%s x%d", item.Product.Name, item.Quantity) } // 收货地址 var addressStr string if order.ShippingAddress != nil { addressStr = order.ShippingAddress.Province + order.ShippingAddress.City + order.ShippingAddress.District + order.ShippingAddress.Address } // 填充单元格 f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), order.ID) f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), order.User.Username) f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), statusMap[order.Status]) f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), productsStr) f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), order.Subtotal) f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), order.ShippingFee) f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), order.ServiceFee) f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), order.ChannelFee) f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), order.PackagingFee) f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), order.InternationalFee) f.SetCellValue(sheetName, fmt.Sprintf("K%d", row), order.TotalAmount) if order.ShippingAddress != nil { f.SetCellValue(sheetName, fmt.Sprintf("L%d", row), order.ShippingAddress.Name) f.SetCellValue(sheetName, fmt.Sprintf("M%d", row), order.ShippingAddress.Phone) f.SetCellValue(sheetName, fmt.Sprintf("N%d", row), addressStr) } f.SetCellValue(sheetName, fmt.Sprintf("O%d", row), order.TrackingNumber) f.SetCellValue(sheetName, fmt.Sprintf("P%d", row), order.CreatedAt.Format("2006-01-02 15:04:05")) } // 设置列宽 f.SetColWidth(sheetName, "A", "A", 10) f.SetColWidth(sheetName, "B", "B", 15) f.SetColWidth(sheetName, "C", "C", 10) f.SetColWidth(sheetName, "D", "D", 40) f.SetColWidth(sheetName, "E", "K", 12) f.SetColWidth(sheetName, "L", "M", 15) f.SetColWidth(sheetName, "N", "N", 40) f.SetColWidth(sheetName, "O", "O", 20) f.SetColWidth(sheetName, "P", "P", 20) // 设置响应头 filename := fmt.Sprintf("orders_%s.xlsx", time.Now().Format("20060102150405")) c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) c.Header("Content-Transfer-Encoding", "binary") // 写入响应 buf, err := f.WriteToBuffer() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate Excel file"}) return } c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buf.Bytes()) } 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.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 } if order.Status != models.OrderStatusPendingPayment && order.Status != models.OrderStatusPendingConfirm { c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be cancelled"}) return } 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 + 1")) } 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"}) } 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"}) } func (h *OrderHandler) AdminUpdateStatus(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 struct { Status string `json:"status" binding:"required"` TrackingNumber string `json:"tracking_number"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } validStatuses := []string{ models.OrderStatusPendingPayment, models.OrderStatusPendingConfirm, models.OrderStatusPendingShip, models.OrderStatusShipped, models.OrderStatusCompleted, models.OrderStatusRefunding, models.OrderStatusRefunded, models.OrderStatusCancelled, } valid := false for _, s := range validStatuses { if s == req.Status { valid = true break } } if !valid { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid status"}) return } updates := map[string]interface{}{"status": req.Status} if req.TrackingNumber != "" { updates["tracking_number"] = req.TrackingNumber } utils.DB.Model(&order).Updates(updates) c.JSON(http.StatusOK, gin.H{"message": "Order status updated successfully"}) }