Files
goauto/server/app/goauto/purchase/handler.go
T

587 lines
14 KiB
Go

package purchase
import (
"context"
"encoding/json"
"errors"
"go-admin/common/clientprincipal"
"io"
"net/http"
"strconv"
"strings"
"go-admin/app/goauto/device"
"go-admin/app/goauto/models"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"gorm.io/gorm"
)
type Handler struct{ DB *gorm.DB }
func (h Handler) AdminList(c *gin.Context) {
if !allowedOperator(c) {
return
}
page, err := positiveAdminQuery(c.Query("page"), 1)
if err != nil {
writeError(c, fail(CodeInvalidRequest, "page 无效"))
return
}
pageSize, err := positiveAdminQuery(c.Query("pageSize"), 20)
if err != nil {
writeError(c, fail(CodeInvalidRequest, "pageSize 无效"))
return
}
taskID, err := optionalAdminUint(c.Query("taskId"))
if err != nil {
writeError(c, fail(CodeInvalidRequest, "taskId 无效"))
return
}
sybProductID, err := optionalAdminUint(c.Query("sybProductId"))
if err != nil {
writeError(c, fail(CodeInvalidRequest, "sybProductId 无效"))
return
}
service, ok := h.service(c)
if !ok {
return
}
response, err := service.AdminList(c.Request.Context(), AdminListRequest{
Page: page, PageSize: pageSize, TaskID: taskID, SYBProductID: sybProductID,
Status: strings.TrimSpace(c.Query("status")), ExecutionMode: strings.TrimSpace(c.Query("executionMode")),
TaskType: strings.TrimSpace(c.Query("taskType")),
ShopeeOrderNo: strings.TrimSpace(c.Query("shopeeOrderNo")),
PDDOrderNo: strings.TrimSpace(c.Query("pddOrderNo")),
})
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) AdminCreateStock(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req StockCreateRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
record, replayed, err := service.CreateStock(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminReplay(c, record, replayed)
}
func (h Handler) AdminDetail(c *gin.Context) {
if !allowedOperator(c) {
return
}
id, ok := pathID(c)
if !ok {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
response, err := service.AdminDetail(c.Request.Context(), id)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) AdminCreate(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req CreateRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
record, replayed, err := service.Create(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminReplay(c, record, replayed)
}
func (h Handler) AdminBatchPreview(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req BatchPreviewRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
response, err := service.BatchPreview(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) AdminBatchSpecMatch(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req BatchSpecMatchRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
response, err := service.BatchSpecMatch(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) AdminBatchCreate(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req BatchCreateRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
response, err := service.BatchCreate(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) AdminBatchRetry(c *gin.Context) {
if !allowedOperator(c) {
return
}
var req BatchRetryRequest
if !decode(c, &req) {
return
}
service, ok := h.service(c)
if !ok {
return
}
response, err := service.BatchRetry(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, response)
}
func (h Handler) Next(c *gin.Context) {
service, ok := h.service(c)
if !ok {
return
}
p, err := service.Next(c.Request.Context(), bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
if p == nil {
c.Status(http.StatusNoContent)
return
}
c.JSON(http.StatusOK, gin.H{"data": p})
}
func (h Handler) AgentHistory(c *gin.Context) {
page, err := positiveAdminQuery(c.Query("page"), 1)
if err != nil {
writeError(c, fail(CodeInvalidRequest, "page 无效"))
return
}
pageSize, err := positiveAdminQuery(c.Query("pageSize"), 20)
if err != nil {
writeError(c, fail(CodeInvalidRequest, "pageSize 无效"))
return
}
days, err := positiveAdminQuery(c.Query("days"), agentPurchaseHistoryDays)
if err != nil || days > agentPurchaseHistoryDays {
writeError(c, fail(CodeInvalidRequest, "days 必须在 1 到 30 之间"))
return
}
service, ok := h.service(c)
if !ok {
return
}
result, err := service.AgentHistory(c.Request.Context(), AgentHistoryRequest{
Page: page, PageSize: pageSize, Days: days, Status: strings.TrimSpace(c.Query("status")), TaskNo: c.Query("taskNo"),
}, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (h Handler) AgentHistoryDetail(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
result, err := service.AgentHistoryDetail(c.Request.Context(), id, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (h Handler) AgentRetry(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req AgentRetryRequest
if !decode(c, &req) {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
result, err := service.AgentRetry(c.Request.Context(), id, req, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (h Handler) AgentReset(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req PurchaseResetRequest
if !decode(c, &req) {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
result, err := service.ResetForDevice(c.Request.Context(), id, req, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (h Handler) Claim(c *gin.Context) { h.action(c, (*Service).Claim) }
func (h Handler) Start(c *gin.Context) { h.action(c, (*Service).Start) }
func (h Handler) OrderSubmitStarted(c *gin.Context) { h.action(c, (*Service).MarkOrderSubmitStarted) }
func (h Handler) Result(c *gin.Context) {
id, ok := pathID(c)
if !ok {
return
}
var req ResultRequest
if !decode(c, &req) {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
p, err := service.SubmitResult(c.Request.Context(), id, req, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"data": p})
}
func (h Handler) SpecDecision(c *gin.Context) {
if !allowedOperator(c) {
return
}
id, ok := pathID(c)
if !ok {
return
}
var req SpecDecisionRequest
if !decode(c, &req) {
return
}
req.OperatorID = operatorID(c)
service, serviceOK := h.service(c)
if !serviceOK {
return
}
p, replayed, err := service.ApplySpecDecision(c.Request.Context(), id, req)
if err != nil {
writeError(c, err)
return
}
writeAdminReplay(c, p, replayed)
}
func (h Handler) MatchingDetail(c *gin.Context) {
if !allowedOperator(c) {
return
}
id, ok := pathID(c)
if !ok {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
view, err := service.MatchingDetail(c.Request.Context(), id)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, view)
}
func (h Handler) RequeueMatching(c *gin.Context) { h.matchingAction(c, (*Service).RequeueMatching) }
func (h Handler) ManualMatching(c *gin.Context) { h.matchingAction(c, (*Service).ManualMatching) }
func (h Handler) matchingAction(c *gin.Context, fn func(*Service, context.Context, uint64, MatchingActionRequest) (MatchingView, error)) {
if !allowedOperator(c) {
return
}
id, ok := pathID(c)
if !ok {
return
}
var req MatchingActionRequest
if !decode(c, &req) {
return
}
req.OperatorID = operatorID(c)
if req.OperatorID == 0 {
writeError(c, fail(CodeInvalidRequest, "无法识别当前操作人"))
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
view, err := fn(service, c.Request.Context(), id, req)
if err != nil {
writeError(c, err)
return
}
writeAdminData(c, view)
}
func (h Handler) AuthorizeRePurchase(c *gin.Context) { h.manual(c, (*Service).AuthorizeRePurchase) }
func (h Handler) ReviewPayment(c *gin.Context) { h.manual(c, (*Service).ReviewPayment) }
func (h Handler) SelectWriteback(c *gin.Context) { h.manual(c, (*Service).SelectWriteback) }
func (h Handler) Cancel(c *gin.Context) { h.manual(c, (*Service).Cancel) }
func (h Handler) ResolveUnknown(c *gin.Context) { h.manual(c, (*Service).ResolveUnknown) }
func (h Handler) action(c *gin.Context, fn func(*Service, context.Context, uint64, ActionRequest, string) (TaskPayload, error)) {
id, ok := pathID(c)
if !ok {
return
}
var req ActionRequest
if !decode(c, &req) {
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
p, err := fn(service, c.Request.Context(), id, req, bearer(c.GetHeader("Authorization")))
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"data": p})
}
func (h Handler) manual(c *gin.Context, fn func(*Service, context.Context, uint64, ManualRequest) (models.PurchaseTask, bool, error)) {
if !allowedOperator(c) {
return
}
id, ok := pathID(c)
if !ok {
return
}
var req ManualRequest
if !decode(c, &req) {
return
}
req.OperatorID = operatorID(c)
if req.OperatorID == 0 {
writeError(c, fail(CodeInvalidRequest, "无法识别当前操作人"))
return
}
service, serviceOK := h.service(c)
if !serviceOK {
return
}
p, replayed, err := fn(service, c.Request.Context(), id, req)
if err != nil {
writeError(c, err)
return
}
writeAdminReplay(c, p, replayed)
}
func writeAdminData(c *gin.Context, data any) {
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": data})
}
func writeAdminReplay(c *gin.Context, data any, replayed bool) {
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": data, "replayed": replayed})
}
func allowedOperator(c *gin.Context) bool {
if _, ok := clientprincipal.Get(c); ok {
return true
}
role, _ := jwt.ExtractClaims(c)["rolekey"].(string)
if role == "admin" || role == "purchaser" || role == "after_sales" {
return true
}
c.JSON(http.StatusForbidden, gin.H{"code": "FORBIDDEN", "message": "只有管理员、采购员或售后可以操作采购任务"})
c.Abort()
return false
}
func (h Handler) service(c *gin.Context) (*Service, bool) {
if h.DB != nil {
return NewService(h.DB), true
}
db, err := pkg.GetOrm(c)
if err != nil {
writeError(c, internal(err))
return nil, false
}
return NewService(db), true
}
func decode(c *gin.Context, v any) bool {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20)
d := json.NewDecoder(c.Request.Body)
d.DisallowUnknownFields()
if err := d.Decode(v); err != nil {
writeError(c, fail(CodeInvalidRequest, "请求 JSON 无效"))
return false
}
if err := d.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
writeError(c, fail(CodeInvalidRequest, "请求只能包含一个 JSON 对象"))
return false
}
return true
}
func pathID(c *gin.Context) (uint64, bool) {
id, err := strconv.ParseUint(c.Param("taskId"), 10, 64)
if err != nil || id == 0 {
writeError(c, fail(CodeInvalidRequest, "taskId 无效"))
return 0, false
}
return id, true
}
func bearer(v string) string {
p := strings.Fields(v)
if len(p) == 2 && strings.EqualFold(p[0], "Bearer") {
return p[1]
}
return ""
}
func writeError(c *gin.Context, err error) {
code, msg, retryable, status := CodeInternal, "服务端处理失败", true, http.StatusInternalServerError
var se *ServiceError
var de *device.ServiceError
if errors.As(err, &se) {
code, msg, retryable = se.Code, se.Message, se.Retryable
} else if errors.As(err, &de) {
code, msg, retryable = de.Code, de.Message, de.Retryable
}
switch code {
case CodeInvalidRequest:
status = http.StatusUnprocessableEntity
case device.CodeTokenInvalid:
status = http.StatusUnauthorized
case device.CodeDeviceDisabled:
status = http.StatusForbidden
case CodeTaskNotFound:
status = http.StatusNotFound
case CodeStateConflict, CodeCapabilityMismatch, CodeDeviceBusy, CodeTaskClaimed, CodeLeaseExpired, CodeMappingRequired, CodeResultConflict, CodeRePurchaseRequired, CodeOrderNumberUsed:
status = http.StatusConflict
}
c.JSON(status, gin.H{"code": code, "message": msg, "retryable": retryable})
}
func operatorID(c *gin.Context) uint64 {
if id, ok := clientprincipal.Get(c); ok {
return id.AuthorizedBy
}
claims := jwt.ExtractClaims(c)
switch v := claims["identity"].(type) {
case float64:
return uint64(v)
case int:
return uint64(v)
case json.Number:
n, _ := strconv.ParseUint(string(v), 10, 64)
return n
case string:
n, _ := strconv.ParseUint(v, 10, 64)
return n
}
return 0
}
func positiveAdminQuery(raw string, fallback int) (int, error) {
if strings.TrimSpace(raw) == "" {
return fallback, nil
}
value, err := strconv.Atoi(raw)
if err != nil || value < 1 {
return 0, errors.New("invalid positive integer")
}
return value, nil
}
func optionalAdminUint(raw string) (uint64, error) {
if strings.TrimSpace(raw) == "" {
return 0, nil
}
return strconv.ParseUint(raw, 10, 64)
}