bindbox-game/internal/api/admin/shipping_stats_admin.go
邹方成 6ee627139c
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 40s
feat: 新增支付测试小程序与微信支付集成
feat(pay): 添加支付API基础结构
feat(miniapp): 创建支付测试小程序页面与配置
feat(wechatpay): 配置微信支付参数与证书
fix(guild): 修复成员列表查询条件
docs: 更新代码规范文档与需求文档
style: 统一前后端枚举显示与注释格式
refactor(admin): 重构用户奖励发放接口参数处理
test(title): 添加称号效果参数验证测试
2025-11-17 00:42:08 +08:00

302 lines
13 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 "未知"
}
}