Files
sale/backend/internal/api/handlers/shipping_template.go
T
nuyue bdefa34d41
Build and Push Docker Image / build-and-push (push) Successful in 1m0s
Build and Push Docker Image / deploy (push) Successful in 7s
feat: remove shipping settings, use shipping templates for cart calculation
2026-07-12 20:01:03 +08:00

278 lines
7.4 KiB
Go

package handlers
import (
"encoding/json"
"math"
"net/http"
"strconv"
"sale/internal/models"
"sale/internal/schemas"
"sale/internal/utils"
"github.com/gin-gonic/gin"
)
type ShippingTemplateHandler struct{}
func NewShippingTemplateHandler() *ShippingTemplateHandler {
return &ShippingTemplateHandler{}
}
// List 获取运费模板列表
func (h *ShippingTemplateHandler) List(c *gin.Context) {
var templates []models.ShippingTemplate
utils.DB.Order("sort_order ASC, id ASC").Find(&templates)
// 解析 provinces JSON 为数组
type TemplateResponse struct {
models.ShippingTemplate
ProvincesList []string `json:"provinces_list"`
}
var response []TemplateResponse
for _, t := range templates {
var provinces []string
if t.Provinces != "" {
json.Unmarshal([]byte(t.Provinces), &provinces)
}
response = append(response, TemplateResponse{
ShippingTemplate: t,
ProvincesList: provinces,
})
}
c.JSON(http.StatusOK, gin.H{"data": response})
}
// GetByID 获取单个运费模板
func (h *ShippingTemplateHandler) GetByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var template models.ShippingTemplate
if err := utils.DB.First(&template, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"})
return
}
var provinces []string
if template.Provinces != "" {
json.Unmarshal([]byte(template.Provinces), &provinces)
}
c.JSON(http.StatusOK, gin.H{
"data": gin.H{
"ShippingTemplate": template,
"provinces_list": provinces,
},
})
}
// Create 创建运费模板
func (h *ShippingTemplateHandler) Create(c *gin.Context) {
var req schemas.CreateShippingTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 如果设为默认,先取消其他默认模板
if req.IsDefault {
utils.DB.Model(&models.ShippingTemplate{}).Where("is_default = ?", true).Update("is_default", false)
}
// 序列化省份列表
var provincesJSON string
if len(req.Provinces) > 0 {
data, _ := json.Marshal(req.Provinces)
provincesJSON = string(data)
}
template := models.ShippingTemplate{
Name: req.Name,
CalcType: req.CalcType,
FirstUnit: req.FirstUnit,
FirstFee: req.FirstFee,
AdditionalUnit: req.AdditionalUnit,
AdditionalFee: req.AdditionalFee,
FreeAmount: req.FreeAmount,
Provinces: provincesJSON,
IsDefault: req.IsDefault,
SortOrder: req.SortOrder,
}
if err := utils.DB.Create(&template).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建运费模板失败"})
return
}
c.JSON(http.StatusCreated, gin.H{"data": template})
}
// Update 更新运费模板
func (h *ShippingTemplateHandler) Update(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var template models.ShippingTemplate
if err := utils.DB.First(&template, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"})
return
}
var req schemas.UpdateShippingTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
updates := make(map[string]interface{})
if req.Name != nil {
updates["name"] = *req.Name
}
if req.CalcType != nil {
updates["calc_type"] = *req.CalcType
}
if req.FirstUnit != nil {
updates["first_unit"] = *req.FirstUnit
}
if req.FirstFee != nil {
updates["first_fee"] = *req.FirstFee
}
if req.AdditionalUnit != nil {
updates["additional_unit"] = *req.AdditionalUnit
}
if req.AdditionalFee != nil {
updates["additional_fee"] = *req.AdditionalFee
}
if req.FreeAmount != nil {
updates["free_amount"] = *req.FreeAmount
}
if req.Provinces != nil {
if len(req.Provinces) > 0 {
data, _ := json.Marshal(req.Provinces)
updates["provinces"] = string(data)
} else {
updates["provinces"] = ""
}
}
if req.IsDefault != nil {
if *req.IsDefault {
utils.DB.Model(&models.ShippingTemplate{}).Where("is_default = ? AND id != ?", true, id).Update("is_default", false)
}
updates["is_default"] = *req.IsDefault
}
if req.SortOrder != nil {
updates["sort_order"] = *req.SortOrder
}
utils.DB.Model(&template).Updates(updates)
utils.DB.First(&template, id)
c.JSON(http.StatusOK, gin.H{"data": template})
}
// Delete 删除运费模板
func (h *ShippingTemplateHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var template models.ShippingTemplate
if err := utils.DB.First(&template, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "运费模板不存在"})
return
}
// 检查是否有关联的商品
var count int64
utils.DB.Model(&models.Product{}).Where("shipping_template_id = ?", id).Count(&count)
if count > 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "该运费模板已被商品关联,无法删除"})
return
}
if err := utils.DB.Delete(&template).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除运费模板失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
// CalculateShippingFee 计算运费(供内部调用)
func CalculateShippingFee(templateID *uint, weight float64, quantity int, subtotal float64, province string) (float64, error) {
var template models.ShippingTemplate
// 如果没有指定模板,使用默认模板
if templateID == nil || *templateID == 0 {
if err := utils.DB.Where("is_default = ?", true).First(&template).Error; err != nil {
// 没有默认模板,运费为0
return 0, nil
}
} else {
if err := utils.DB.First(&template, *templateID).Error; err != nil {
// 模板不存在,使用默认模板
if err := utils.DB.Where("is_default = ?", true).First(&template).Error; err != nil {
return 0, nil
}
}
}
// 检查适用地区
if template.Provinces != "" && province != "" {
var provinces []string
json.Unmarshal([]byte(template.Provinces), &provinces)
matched := false
for _, p := range provinces {
if p == province {
matched = true
break
}
}
if !matched {
// 地址不在适用范围内,使用默认模板
var defaultTemplate models.ShippingTemplate
if err := utils.DB.Where("is_default = ?", true).First(&defaultTemplate).Error; err == nil {
template = defaultTemplate
}
}
}
// 检查是否满足包邮条件
if template.FreeAmount > 0 && subtotal >= template.FreeAmount {
return 0, nil
}
// 根据计费方式计算运费
var totalUnit float64
if template.CalcType == models.CalcTypeWeight {
totalUnit = weight // 重量(克)
} else {
totalUnit = float64(quantity) // 件数
}
// 计算运费
if totalUnit <= template.FirstUnit {
return template.FirstFee, nil
}
remainingUnit := totalUnit - template.FirstUnit
additionalCount := math.Ceil(remainingUnit / template.AdditionalUnit)
return template.FirstFee + additionalCount*template.AdditionalFee, nil
}
// CalculateForUser 用户端计算运费接口
func (h *ShippingTemplateHandler) CalculateForUser(c *gin.Context) {
var req schemas.CalculateShippingRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fee, err := CalculateShippingFee(req.TemplateID, req.Weight, req.Quantity, req.Subtotal, req.Province)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "计算运费失败"})
return
}
c.JSON(http.StatusOK, gin.H{"data": gin.H{"shipping_fee": fee}})
}