healthapp
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

256 lines
6.9 KiB

package logic
import (
"context"
"fmt"
"time"
"healthapi/internal/model"
"healthapi/internal/svc"
"healthapi/internal/types"
"healthapi/pkg/errorx"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
)
type CreateOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateOrderLogic {
return &CreateOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateOrderLogic) CreateOrder(req *types.CreateOrderReq) (resp *types.Order, err error) {
userID, err := GetUserIDFromCtx(l.ctx)
if err != nil {
return nil, errorx.ErrUnauthorized
}
// 获取用户信息(会员等级)
var user model.User
if err := l.svcCtx.DB.First(&user, userID).Error; err != nil {
return nil, errorx.ErrUserNotFound
}
levelConfig := model.MemberLevelConfigs[user.MemberLevel]
if levelConfig.Level == "" {
levelConfig = model.MemberLevelConfigs[model.MemberLevelNormal]
}
// 验证收货地址
var address model.Address
if err := l.svcCtx.DB.Where("id = ? AND user_id = ?", req.AddressID, userID).First(&address).Error; err != nil {
return nil, errorx.NewCodeError(404, "收货地址不存在")
}
// 获取购物车项
var cartItems []model.CartItem
if err := l.svcCtx.DB.Where("id IN ? AND user_id = ?", req.CartItemIDs, userID).Find(&cartItems).Error; err != nil {
return nil, err
}
if len(cartItems) == 0 {
return nil, errorx.NewCodeError(400, "购物车为空")
}
// 计算订单金额
var totalAmount float64
orderItems := make([]model.OrderItem, 0, len(cartItems))
for _, cartItem := range cartItems {
var product model.Product
if err := l.svcCtx.DB.First(&product, cartItem.ProductID).Error; err != nil {
return nil, errorx.NewCodeError(404, fmt.Sprintf("商品 %d 不存在", cartItem.ProductID))
}
if !product.IsActive {
return nil, errorx.NewCodeError(400, fmt.Sprintf("商品 %s 已下架", product.Name))
}
price := product.Price
stock := product.Stock
skuName := ""
image := product.MainImage
if cartItem.SkuID > 0 {
var sku model.ProductSku
if err := l.svcCtx.DB.First(&sku, cartItem.SkuID).Error; err != nil {
return nil, errorx.NewCodeError(404, "商品规格不存在")
}
price = sku.Price
stock = sku.Stock
skuName = sku.Name
if sku.Image != "" {
image = sku.Image
}
}
if stock < cartItem.Quantity {
return nil, errorx.NewCodeError(400, fmt.Sprintf("商品 %s 库存不足", product.Name))
}
itemTotal := price * float64(cartItem.Quantity)
totalAmount += itemTotal
orderItems = append(orderItems, model.OrderItem{
ProductID: cartItem.ProductID,
SkuID: cartItem.SkuID,
ProductName: product.Name,
SkuName: skuName,
Image: image,
Price: price,
Quantity: cartItem.Quantity,
TotalAmount: itemTotal,
})
}
// 计算会员折扣
discountAmount := totalAmount * (1 - levelConfig.Discount)
// 计算运费
shippingFee := float64(0)
afterDiscount := totalAmount - discountAmount
if afterDiscount < levelConfig.FreeShippingMin {
shippingFee = 10 // 默认运费
}
// 计算积分抵扣(100积分=1元)
pointsDiscount := float64(0)
pointsUsed := req.PointsUsed
if pointsUsed > 0 {
if pointsUsed > user.Points {
pointsUsed = user.Points
}
maxPointsDiscount := afterDiscount * 0.2 // 最多抵扣20%
pointsDiscount = float64(pointsUsed) / 100
if pointsDiscount > maxPointsDiscount {
pointsDiscount = maxPointsDiscount
pointsUsed = int(maxPointsDiscount * 100)
}
}
// 最终支付金额
payAmount := afterDiscount + shippingFee - pointsDiscount
// 生成订单号
orderNo := fmt.Sprintf("%s%d%04d", time.Now().Format("20060102150405"), userID, time.Now().Nanosecond()%10000)
// 计算获得积分
pointsEarned := int(payAmount * levelConfig.PointsMultiplier)
// 创建订单(使用事务)
order := model.Order{
UserID: userID,
OrderNo: orderNo,
Status: model.OrderStatusPending,
TotalAmount: totalAmount,
DiscountAmount: discountAmount,
ShippingFee: shippingFee,
PayAmount: payAmount,
PointsUsed: pointsUsed,
PointsEarned: pointsEarned,
ReceiverName: address.ReceiverName,
ReceiverPhone: address.Phone,
ReceiverAddr: fmt.Sprintf("%s%s%s%s", address.Province, address.City, address.District, address.DetailAddr),
Remark: req.Remark,
}
err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
// 创建订单
if err := tx.Create(&order).Error; err != nil {
return err
}
// 创建订单项
for i := range orderItems {
orderItems[i].OrderID = uint(order.ID)
}
if err := tx.Create(&orderItems).Error; err != nil {
return err
}
// 扣减库存
for _, cartItem := range cartItems {
if cartItem.SkuID > 0 {
if err := tx.Model(&model.ProductSku{}).Where("id = ?", cartItem.SkuID).
Update("stock", gorm.Expr("stock - ?", cartItem.Quantity)).Error; err != nil {
return err
}
} else {
if err := tx.Model(&model.Product{}).Where("id = ?", cartItem.ProductID).
Update("stock", gorm.Expr("stock - ?", cartItem.Quantity)).Error; err != nil {
return err
}
}
}
// 删除购物车项
if err := tx.Where("id IN ?", req.CartItemIDs).Delete(&model.CartItem{}).Error; err != nil {
return err
}
// 扣除积分
if pointsUsed > 0 {
if err := tx.Model(&user).Update("points", gorm.Expr("points - ?", pointsUsed)).Error; err != nil {
return err
}
// 记录积分变动
tx.Create(&model.PointsRecord{
UserID: userID,
Type: model.PointsTypeSpend,
Points: -pointsUsed,
Balance: user.Points - pointsUsed,
Source: model.PointsSourceOrder,
ReferenceID: uint(order.ID),
Remark: fmt.Sprintf("订单 %s 使用积分", orderNo),
})
}
return nil
})
if err != nil {
return nil, err
}
// 构建响应
respItems := make([]types.OrderItem, 0, len(orderItems))
for _, item := range orderItems {
respItems = append(respItems, types.OrderItem{
ID: uint(item.ID),
ProductID: item.ProductID,
SkuID: item.SkuID,
ProductName: item.ProductName,
SkuName: item.SkuName,
Image: item.Image,
Price: item.Price,
Quantity: item.Quantity,
TotalAmount: item.TotalAmount,
})
}
resp = &types.Order{
ID: uint(order.ID),
OrderNo: order.OrderNo,
Status: order.Status,
TotalAmount: order.TotalAmount,
DiscountAmount: order.DiscountAmount,
ShippingFee: order.ShippingFee,
PayAmount: order.PayAmount,
PointsUsed: order.PointsUsed,
PointsEarned: order.PointsEarned,
ReceiverName: order.ReceiverName,
ReceiverPhone: order.ReceiverPhone,
ReceiverAddr: order.ReceiverAddr,
Remark: order.Remark,
Items: respItems,
CreatedAt: order.CreatedAt.Format("2006-01-02 15:04:05"),
}
return resp, nil
}