Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 40s
feat(pay): 添加支付API基础结构 feat(miniapp): 创建支付测试小程序页面与配置 feat(wechatpay): 配置微信支付参数与证书 fix(guild): 修复成员列表查询条件 docs: 更新代码规范文档与需求文档 style: 统一前后端枚举显示与注释格式 refactor(admin): 重构用户奖励发放接口参数处理 test(title): 添加称号效果参数验证测试
302 lines
13 KiB
Go
302 lines
13 KiB
Go
package admin
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"bindbox-game/internal/code"
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/validation"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
)
|
||
|
||
type listShippingStatsRequest struct {
|
||
Page int `form:"page"`
|
||
PageSize int `form:"page_size"`
|
||
ProductID *int64 `form:"product_id"`
|
||
ProductName string `form:"product_name"`
|
||
UserID *int64 `form:"user_id"`
|
||
UserName string `form:"user_name"`
|
||
ExpressCode string `form:"express_code"`
|
||
ExpressNo string `form:"express_no"`
|
||
OrderID *int64 `form:"order_id"`
|
||
OrderNo string `form:"order_no"`
|
||
OrderSourceType *int32 `form:"order_source_type"`
|
||
Payer string `form:"payer"`
|
||
Keyword string `form:"keyword"`
|
||
}
|
||
|
||
type listShippingStatsResponse struct {
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
Total int64 `json:"total"`
|
||
List []*model.OpsShippingStats `json:"list"`
|
||
}
|
||
|
||
// ListShippingStats 发货统计列表
|
||
func (h *handler) ListShippingStats() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(listShippingStatsRequest)
|
||
rsp := new(listShippingStatsResponse)
|
||
if err := ctx.ShouldBindForm(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
if req.Page <= 0 { req.Page = 1 }
|
||
if req.PageSize <= 0 { req.PageSize = 20 }
|
||
if req.PageSize > 100 { req.PageSize = 100 }
|
||
|
||
q := h.readDB.OpsShippingStats.WithContext(ctx.RequestContext()).ReadDB()
|
||
|
||
// 精确匹配
|
||
if req.ProductID != nil { q = q.Where(h.readDB.OpsShippingStats.ProductID.Eq(*req.ProductID)) }
|
||
if req.UserID != nil { q = q.Where(h.readDB.OpsShippingStats.UserID.Eq(*req.UserID)) }
|
||
if req.OrderID != nil { q = q.Where(h.readDB.OpsShippingStats.OrderID.Eq(*req.OrderID)) }
|
||
if req.OrderSourceType != nil { q = q.Where(h.readDB.OpsShippingStats.OrderSourceType.Eq(*req.OrderSourceType)) }
|
||
if req.Payer != "" { q = q.Where(h.readDB.OpsShippingStats.Payer.Eq(req.Payer)) }
|
||
// 字符串匹配
|
||
if req.ProductName != "" { q = q.Where(h.readDB.OpsShippingStats.ProductName.Like("%" + req.ProductName + "%")) }
|
||
if req.UserName != "" { q = q.Where(h.readDB.OpsShippingStats.UserName.Like("%" + req.UserName + "%")) }
|
||
if req.ExpressCode != "" { q = q.Where(h.readDB.OpsShippingStats.ExpressCode.Eq(req.ExpressCode)) }
|
||
if req.ExpressNo != "" { q = q.Where(h.readDB.OpsShippingStats.ExpressNo.Like("%" + req.ExpressNo + "%")) }
|
||
if req.OrderNo != "" { q = q.Where(h.readDB.OpsShippingStats.OrderNo.Like("%" + req.OrderNo + "%")) }
|
||
if req.Keyword != "" {
|
||
like := "%" + req.Keyword + "%"
|
||
q = q.Where(h.readDB.OpsShippingStats.ProductName.Like(like)).
|
||
Or(h.readDB.OpsShippingStats.UserName.Like(like)).
|
||
Or(h.readDB.OpsShippingStats.OrderNo.Like(like)).
|
||
Or(h.readDB.OpsShippingStats.ExpressNo.Like(like))
|
||
}
|
||
|
||
total, err := q.Count()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 40101, err.Error()))
|
||
return
|
||
}
|
||
rows, err := q.Order(h.readDB.OpsShippingStats.CreatedAt.Desc(), h.readDB.OpsShippingStats.ID.Desc()).
|
||
Offset((req.Page-1)*req.PageSize).Limit(req.PageSize).Find()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 40102, err.Error()))
|
||
return
|
||
}
|
||
rsp.Page = req.Page
|
||
rsp.PageSize = req.PageSize
|
||
rsp.Total = total
|
||
rsp.List = rows
|
||
ctx.Payload(rsp)
|
||
}
|
||
}
|
||
|
||
type createShippingStatRequest struct {
|
||
ProductID int64 `json:"product_id" binding:"required"`
|
||
ProductName string `json:"product_name" binding:"required"`
|
||
ProductPriceCents int64 `json:"product_price_cents"`
|
||
ShippedQty int64 `json:"shipped_qty" binding:"required"`
|
||
UserID int64 `json:"user_id" binding:"required"`
|
||
UserName string `json:"user_name" binding:"required"`
|
||
UserAddressText string `json:"user_address_text"`
|
||
ExpressCode string `json:"express_code"`
|
||
ExpressNo string `json:"express_no"`
|
||
OrderID int64 `json:"order_id"`
|
||
OrderNo string `json:"order_no"`
|
||
OrderQty int64 `json:"order_qty"`
|
||
OrderAmountCents int64 `json:"order_amount_cents"`
|
||
OrderSourceType int32 `json:"order_source_type"`
|
||
Payer string `json:"payer"`
|
||
}
|
||
|
||
type simpleMessageResponseShipping struct {
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// CreateShippingStat 新增发货统计记录
|
||
func (h *handler) CreateShippingStat() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
var req createShippingStatRequest
|
||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
shippedAt := time.Now()
|
||
// 若传入了订单ID但未给金额/数量,尝试回填
|
||
var orderAmount int64 = req.OrderAmountCents
|
||
var orderQty int64 = req.OrderQty
|
||
var orderNo string = req.OrderNo
|
||
if req.OrderID > 0 && (orderAmount == 0 || orderQty == 0 || orderNo == "") {
|
||
if ord, e := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(req.OrderID)).First(); e == nil && ord != nil {
|
||
if orderAmount == 0 { orderAmount = ord.ActualAmount }
|
||
if orderNo == "" { orderNo = ord.OrderNo }
|
||
}
|
||
if orderQty == 0 {
|
||
_ = h.readDB.OrderItems.WithContext(ctx.RequestContext()).Where(h.readDB.OrderItems.OrderID.Eq(req.OrderID)).Select(h.readDB.OrderItems.Quantity.Sum()).Scan(&orderQty)
|
||
}
|
||
}
|
||
|
||
// 自动计算盈亏:行价×发货数量 − (订单实付金额/下单总量)×发货数量(订单可空)
|
||
var perUnitPaid int64
|
||
if orderQty > 0 {
|
||
perUnitPaid = orderAmount / orderQty
|
||
}
|
||
autoProfit := req.ProductPriceCents*req.ShippedQty - perUnitPaid*req.ShippedQty
|
||
|
||
it := &model.OpsShippingStats{
|
||
ShippedAt: shippedAt,
|
||
ProductID: req.ProductID,
|
||
ProductName: req.ProductName,
|
||
ProductPriceCents: req.ProductPriceCents,
|
||
ShippedQty: req.ShippedQty,
|
||
UserID: req.UserID,
|
||
UserName: req.UserName,
|
||
UserAddressText: req.UserAddressText,
|
||
ExpressCode: req.ExpressCode,
|
||
ExpressNo: req.ExpressNo,
|
||
OrderID: req.OrderID,
|
||
OrderNo: orderNo,
|
||
OrderQty: orderQty,
|
||
OrderAmountCents: orderAmount,
|
||
ProfitLossCents: autoProfit,
|
||
OrderSourceType: req.OrderSourceType,
|
||
OrderSourceText: sourceTextFromType(req.OrderSourceType),
|
||
Payer: req.Payer,
|
||
}
|
||
if err := h.writeDB.OpsShippingStats.WithContext(ctx.RequestContext()).Create(it); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusInternalServerError, 40106, "创建发货统计失败"))
|
||
return
|
||
}
|
||
ctx.Payload(&simpleMessageResponseShipping{Message: "创建成功"})
|
||
}
|
||
}
|
||
|
||
type modifyShippingStatRequest struct {
|
||
ProductID *int64 `json:"product_id"`
|
||
ProductName string `json:"product_name"`
|
||
ProductPriceCents *int64 `json:"product_price_cents"`
|
||
ShippedQty *int64 `json:"shipped_qty"`
|
||
UserID *int64 `json:"user_id"`
|
||
UserName string `json:"user_name"`
|
||
UserAddressText string `json:"user_address_text"`
|
||
ExpressCode string `json:"express_code"`
|
||
ExpressNo string `json:"express_no"`
|
||
OrderID *int64 `json:"order_id"`
|
||
OrderNo string `json:"order_no"`
|
||
OrderQty *int64 `json:"order_qty"`
|
||
OrderAmountCents *int64 `json:"order_amount_cents"`
|
||
OrderSourceType *int32 `json:"order_source_type"`
|
||
Payer string `json:"payer"`
|
||
}
|
||
|
||
// ModifyShippingStat 修改发货统计记录
|
||
func (h *handler) ModifyShippingStat() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
id, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
||
if err != nil || id <= 0 {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递ID"))
|
||
return
|
||
}
|
||
var req modifyShippingStatRequest
|
||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
row, err := h.readDB.OpsShippingStats.WithContext(ctx.RequestContext()).Where(h.readDB.OpsShippingStats.ID.Eq(id)).First()
|
||
if err != nil || row == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 40107, "记录不存在"))
|
||
return
|
||
}
|
||
// shipped_at 字段从统计维度中移除,不再更新
|
||
if req.ProductID != nil { row.ProductID = *req.ProductID }
|
||
if req.ProductName != "" { row.ProductName = req.ProductName }
|
||
if req.ProductPriceCents != nil { row.ProductPriceCents = *req.ProductPriceCents }
|
||
if req.ShippedQty != nil { row.ShippedQty = *req.ShippedQty }
|
||
if req.UserID != nil { row.UserID = *req.UserID }
|
||
if req.UserName != "" { row.UserName = req.UserName }
|
||
if req.UserAddressText != "" { row.UserAddressText = req.UserAddressText }
|
||
if req.ExpressCode != "" { row.ExpressCode = req.ExpressCode }
|
||
if req.ExpressNo != "" { row.ExpressNo = req.ExpressNo }
|
||
if req.OrderID != nil {
|
||
row.OrderID = *req.OrderID
|
||
// 回填订单相关字段(若空)
|
||
if row.OrderID > 0 {
|
||
if ord, e := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(row.OrderID)).First(); e == nil && ord != nil {
|
||
if row.OrderNo == "" { row.OrderNo = ord.OrderNo }
|
||
if row.OrderAmountCents == 0 { row.OrderAmountCents = ord.ActualAmount }
|
||
}
|
||
if row.OrderQty == 0 {
|
||
var totalQty int64
|
||
if err := h.readDB.OrderItems.WithContext(ctx.RequestContext()).Where(h.readDB.OrderItems.OrderID.Eq(row.OrderID)).Select(h.readDB.OrderItems.Quantity.Sum()).Scan(&totalQty); err == nil {
|
||
row.OrderQty = totalQty
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if req.OrderNo != "" { row.OrderNo = req.OrderNo }
|
||
if req.OrderQty != nil { row.OrderQty = *req.OrderQty }
|
||
if req.OrderAmountCents != nil { row.OrderAmountCents = *req.OrderAmountCents }
|
||
if req.OrderSourceType != nil {
|
||
row.OrderSourceType = *req.OrderSourceType
|
||
row.OrderSourceText = sourceTextFromType(*req.OrderSourceType)
|
||
}
|
||
if req.Payer != "" { row.Payer = req.Payer }
|
||
// 自动计算盈亏(订单金额按单位分摊)
|
||
var perUnitPaidUpd int64
|
||
if row.OrderQty > 0 {
|
||
perUnitPaidUpd = row.OrderAmountCents / row.OrderQty
|
||
}
|
||
row.ProfitLossCents = row.ProductPriceCents*row.ShippedQty - perUnitPaidUpd*row.ShippedQty
|
||
if err := h.writeDB.OpsShippingStats.Save(row); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusInternalServerError, 40108, "修改失败"))
|
||
return
|
||
}
|
||
ctx.Payload(&simpleMessageResponseShipping{Message: "修改成功"})
|
||
}
|
||
}
|
||
|
||
// DeleteShippingStat 删除发货统计记录
|
||
func (h *handler) DeleteShippingStat() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
id, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
||
if err != nil || id <= 0 {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递ID"))
|
||
return
|
||
}
|
||
del := &model.OpsShippingStats{ID: id}
|
||
if _, err := h.writeDB.OpsShippingStats.Delete(del); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusInternalServerError, 40109, "删除失败"))
|
||
return
|
||
}
|
||
ctx.Payload(&simpleMessageResponseShipping{Message: "删除成功"})
|
||
}
|
||
}
|
||
|
||
// GetShippingStat 获取单条发货统计详情
|
||
func (h *handler) GetShippingStat() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
id, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
||
if err != nil || id <= 0 {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递ID"))
|
||
return
|
||
}
|
||
row, err := h.readDB.OpsShippingStats.WithContext(ctx.RequestContext()).Where(h.readDB.OpsShippingStats.ID.Eq(id)).First()
|
||
if err != nil || row == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 40103, "记录不存在"))
|
||
return
|
||
}
|
||
ctx.Payload(row)
|
||
}
|
||
}
|
||
|
||
func sourceTextFromType(t int32) string {
|
||
switch t {
|
||
case 1:
|
||
return "淘宝"
|
||
case 2:
|
||
return "拼多多"
|
||
case 3:
|
||
return "京东"
|
||
case 4:
|
||
return "线下"
|
||
default:
|
||
return "未知"
|
||
}
|
||
} |