Initial commit: 商品售卖网站
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
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,
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&order).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order"})
|
||||
return
|
||||
}
|
||||
|
||||
for i := range orderItems {
|
||||
orderItems[i].OrderID = order.ID
|
||||
}
|
||||
utils.DB.Create(&orderItems)
|
||||
|
||||
for _, cart := range carts {
|
||||
if cart.Product.RequireCredit {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", cart.Product.CreditCost*cart.Quantity))
|
||||
}
|
||||
if cart.Product.CreditReward > 0 {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", cart.Product.CreditReward*cart.Quantity))
|
||||
}
|
||||
}
|
||||
|
||||
utils.DB.Where("user_id = ?", userID).Delete(&models.Cart{})
|
||||
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user