feat(shipping): 新增管理端撤销发货功能
- 新增 AdminCancelShipping handler,支持批量撤销待发货记录(status=1→5) - 事务内同步恢复 user_inventory.status=1 并清空 shipping_no - 在 remark 记录操作人 adminID,保证审计可追溯 - 注册路由 POST /api/admin/shipping/orders/cancel
This commit is contained in:
parent
b9a40df5c5
commit
0722e515c4
@ -1,6 +1,7 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@ -8,6 +9,7 @@ import (
|
|||||||
"bindbox-game/internal/code"
|
"bindbox-game/internal/code"
|
||||||
"bindbox-game/internal/pkg/core"
|
"bindbox-game/internal/pkg/core"
|
||||||
"bindbox-game/internal/pkg/validation"
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
"bindbox-game/internal/repository/mysql/dao"
|
||||||
"bindbox-game/internal/repository/mysql/model"
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -429,3 +431,91 @@ func (h *handler) GetShippingOrderDetail() core.HandlerFunc {
|
|||||||
ctx.Payload(rsp)
|
ctx.Payload(rsp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cancelShippingRequest struct {
|
||||||
|
RecordIDs []int64 `json:"record_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cancelShippingResponse struct {
|
||||||
|
CancelledCount int64 `json:"cancelled_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminCancelShipping 管理端撤销发货申请
|
||||||
|
// @Summary 管理端撤销发货申请
|
||||||
|
// @Description 将待发货(status=1)的记录撤销为已取消(status=5),并恢复对应库存状态
|
||||||
|
// @Tags 管理端.发货管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security LoginVerifyToken
|
||||||
|
// @Param RequestBody body cancelShippingRequest true "请求参数"
|
||||||
|
// @Success 200 {object} cancelShippingResponse
|
||||||
|
// @Failure 400 {object} code.Failure
|
||||||
|
// @Router /api/admin/shipping/orders/cancel [post]
|
||||||
|
func (h *handler) AdminCancelShipping() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
req := new(cancelShippingRequest)
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.RecordIDs) == 0 {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "record_ids不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.RecordIDs) > 100 {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "单次最多处理100条记录"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adminID := ctx.SessionUserInfo().Id
|
||||||
|
var cancelledCount int64
|
||||||
|
|
||||||
|
err := h.writeDB.Transaction(func(tx *dao.Query) error {
|
||||||
|
records, err := tx.ShippingRecords.WithContext(ctx.RequestContext()).
|
||||||
|
Select(tx.ShippingRecords.ID, tx.ShippingRecords.InventoryID, tx.ShippingRecords.UserID).
|
||||||
|
Where(tx.ShippingRecords.ID.In(req.RecordIDs...)).
|
||||||
|
Where(tx.ShippingRecords.Status.Eq(1)).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("query shipping records failed: %w", err)
|
||||||
|
}
|
||||||
|
if len(records) == 0 {
|
||||||
|
return fmt.Errorf("没有找到待发货记录,可能已被处理")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range records {
|
||||||
|
res, err := tx.ShippingRecords.WithContext(ctx.RequestContext()).
|
||||||
|
Where(tx.ShippingRecords.ID.Eq(rec.ID)).
|
||||||
|
Where(tx.ShippingRecords.Status.Eq(1)).
|
||||||
|
Update(tx.ShippingRecords.Status, 5)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update shipping record failed: %w", err)
|
||||||
|
}
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remark := fmt.Sprintf("|shipping_cancelled_by_admin:%d", adminID)
|
||||||
|
dbResult := tx.UserInventory.WithContext(ctx.RequestContext()).UnderlyingDB().Exec(
|
||||||
|
"UPDATE user_inventory SET status=1, shipping_no='', remark=CONCAT(IFNULL(remark,''), ?) WHERE id=? AND user_id=?",
|
||||||
|
remark, rec.InventoryID, rec.UserID,
|
||||||
|
)
|
||||||
|
if dbResult.Error != nil {
|
||||||
|
return fmt.Errorf("restore inventory failed: %w", dbResult.Error)
|
||||||
|
}
|
||||||
|
if dbResult.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("restore inventory failed: inventory id=%d not matched", rec.InventoryID)
|
||||||
|
}
|
||||||
|
cancelledCount++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&cancelShippingResponse{CancelledCount: cancelledCount})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -364,6 +364,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er
|
|||||||
adminAuthApiRouter.GET("/shipping/orders", intc.RequireAdminAction("shipping:view"), adminHandler.ListShippingOrders())
|
adminAuthApiRouter.GET("/shipping/orders", intc.RequireAdminAction("shipping:view"), adminHandler.ListShippingOrders())
|
||||||
adminAuthApiRouter.GET("/shipping/orders/:id", intc.RequireAdminAction("shipping:view"), adminHandler.GetShippingOrderDetail())
|
adminAuthApiRouter.GET("/shipping/orders/:id", intc.RequireAdminAction("shipping:view"), adminHandler.GetShippingOrderDetail())
|
||||||
adminAuthApiRouter.PUT("/shipping/orders/batch", intc.RequireAdminAction("shipping:modify"), adminHandler.UpdateShippingBatch())
|
adminAuthApiRouter.PUT("/shipping/orders/batch", intc.RequireAdminAction("shipping:modify"), adminHandler.UpdateShippingBatch())
|
||||||
|
adminAuthApiRouter.POST("/shipping/orders/cancel", intc.RequireAdminAction("shipping:modify"), adminHandler.AdminCancelShipping())
|
||||||
|
|
||||||
adminAuthApiRouter.POST("/pay/refunds", intc.RequireAdminAction("refund:create"), adminHandler.CreateRefund())
|
adminAuthApiRouter.POST("/pay/refunds", intc.RequireAdminAction("refund:create"), adminHandler.CreateRefund())
|
||||||
adminAuthApiRouter.GET("/pay/refunds", intc.RequireAdminAction("refund:view"), adminHandler.ListRefunds())
|
adminAuthApiRouter.GET("/pay/refunds", intc.RequireAdminAction("refund:view"), adminHandler.ListRefunds())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user