182 lines
4.6 KiB
Go
182 lines
4.6 KiB
Go
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)
|
|
}
|