diff --git a/internal/api/admin/shipping_orders_admin.go b/internal/api/admin/shipping_orders_admin.go index 512cc56..5ce931b 100755 --- a/internal/api/admin/shipping_orders_admin.go +++ b/internal/api/admin/shipping_orders_admin.go @@ -1,6 +1,7 @@ package admin import ( + "fmt" "net/http" "strconv" "time" @@ -8,6 +9,7 @@ import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" + "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" ) @@ -429,3 +431,91 @@ func (h *handler) GetShippingOrderDetail() core.HandlerFunc { 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}) + } +} diff --git a/internal/router/router.go b/internal/router/router.go index a737272..3a5de3d 100755 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -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/:id", intc.RequireAdminAction("shipping:view"), adminHandler.GetShippingOrderDetail()) 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.GET("/pay/refunds", intc.RequireAdminAction("refund:view"), adminHandler.ListRefunds())