587 lines
14 KiB
Go
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)
|
|
}
|