package server import ( "encoding/json" "errors" "github.com/gin-gonic/gin" "github.com/google/uuid" "perler-beads-server/internal/domain" "time" ) func (s *Server) requestList(c *gin.Context) { if !need(c, "") { return } raw, e := s.list(c, "request") if e != nil { fail(c, e) return } out := []domain.Request{} u := user(c) for _, b := range raw { var v domain.Request if json.Unmarshal(b, &v) != nil { continue } if u.Role == "admin" || u.Role == "staff" { if v.StoreID != u.StoreID { continue } } else if v.UserID != u.ID { continue } out = append(out, v) } ok(c, out) } func (s *Server) requestCreate(c *gin.Context) { if !need(c, "") { return } var in struct { Kind string `json:"kind"` Note string `json:"note"` PatternID string `json:"patternId"` RequestID string `json:"requestId"` } if !bind(c, &in) { return } if _, e := uuid.Parse(in.RequestID); e != nil { fail(c, errors.New("requestId 必须为 UUID")) return } if in.Kind != "help" && in.Kind != "refill" && in.Kind != "ironing" && in.Kind != "print" || len(in.Note) > 2000 { fail(c, errors.New("服务类型或备注无效")) return } u := user(c) fingerprint := domain.Fingerprint(in.Kind, in.Note, in.PatternID) tx, e := s.DB.Begin(c) if e != nil { fail(c, e) return } defer rollback(tx) // Serialize same user's keys and visit changes while checking persistent idempotency. if _, e = tx.Exec(c, `SELECT pg_advisory_xact_lock(hashtextextended($1,0))`, u.ID); e != nil { fail(c, e) return } var existing []byte var oldHash string e = tx.QueryRow(c, `SELECT fingerprint,request_body FROM request_keys WHERE user_id=$1 AND request_id=$2`, u.ID, in.RequestID).Scan(&oldHash, &existing) if e == nil { if oldHash != fingerprint { reply(c, 409, "重复请求标识对应不同内容", nil) return } ok(c, json.RawMessage(existing)) return } var visitBytes []byte if e = tx.QueryRow(c, `SELECT body FROM visits WHERE user_id=$1 FOR UPDATE`, u.ID).Scan(&visitBytes); e != nil { fail(c, errors.New("请先扫码到店")) return } var v domain.Visit if json.Unmarshal(visitBytes, &v) != nil || v.Status != "active" { fail(c, errors.New("请先扫码到店")) return } var tables []domain.Table if getTx(c, tx, "tables", v.StoreID, &tables) != nil { fail(c, errors.New("门店桌位不可用")) return } enabled := false for _, t := range tables { if t.Code == v.TableCode && t.Enabled { enabled = true } } if !enabled { fail(c, errors.New("桌位已停用,请重新扫码")) return } if in.Kind == "print" && in.PatternID == "" { fail(c, errors.New("打印需要图纸")) return } if in.PatternID != "" { var p domain.Pattern if getTx(c, tx, "pattern", in.PatternID, &p) != nil || !domain.CanRead(p, u) || p.StoreID != v.StoreID { reply(c, 401, "图纸无权限或不属于当前门店", nil) return } } now := time.Now().UTC().Format(time.RFC3339Nano) r := domain.Request{ID: uuid.NewString(), StoreID: v.StoreID, UserID: u.ID, TableCode: v.TableCode, Kind: in.Kind, Note: in.Note, PatternID: in.PatternID, Status: "pending", CreatedAt: now, UpdatedAt: now} b, _ := json.Marshal(r) if _, e = tx.Exec(c, `INSERT INTO documents(kind,id,store_id,owner_id,body) VALUES('request',$1,$2,$3,$4)`, r.ID, r.StoreID, r.UserID, b); e == nil { _, e = tx.Exec(c, `INSERT INTO request_keys VALUES($1,$2,$3,$4)`, u.ID, in.RequestID, fingerprint, b) } if e != nil { fail(c, e) return } if e = tx.Commit(c); e != nil { fail(c, e) return } s.notifyTasks(r.StoreID) ok(c, r) } func (s *Server) requestPatch(c *gin.Context) { if !need(c, "") { return } var in struct { Status string `json:"status"` } if !bind(c, &in) { return } tx, e := s.DB.Begin(c) if e != nil { fail(c, e) return } defer rollback(tx) var b []byte if e = tx.QueryRow(c, `SELECT body FROM documents WHERE kind='request' AND id=$1 FOR UPDATE`, c.Param("id")).Scan(&b); e != nil { reply(c, 404, "任务不存在", nil) return } var r domain.Request if json.Unmarshal(b, &r) != nil { fail(c, errors.New("任务数据无效")) return } u := user(c) staff := u.Role == "admin" || u.Role == "staff" if staff && r.StoreID != u.StoreID || !domain.Transition(u.Role, r.Status, in.Status, r.UserID == u.ID) { reply(c, 401, "无权进行此状态变更", nil) return } r.Status = in.Status r.UpdatedAt = time.Now().UTC().Format(time.RFC3339Nano) b, _ = json.Marshal(r) if _, e = tx.Exec(c, `UPDATE documents SET body=$1 WHERE kind='request' AND id=$2`, b, r.ID); e != nil { fail(c, e) return } if e = tx.Commit(c); e != nil { fail(c, e) return } s.notifyTasks(r.StoreID) ok(c, r) }