|
|
@@ -0,0 +1,110 @@
|
|
|
+package service
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/rand"
|
|
|
+ "encoding/hex"
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "time"
|
|
|
+ "wails-app/internal/dao"
|
|
|
+ "wails-app/internal/modules/digital-ticket/repository"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ StateCreated = "created"
|
|
|
+ StatePendingPayment = "pending_payment"
|
|
|
+ StatePaid = "paid"
|
|
|
+ StateExited = "exited"
|
|
|
+ StateExpired = "expired"
|
|
|
+)
|
|
|
+
|
|
|
+type TicketService struct {
|
|
|
+ repo *repository.TicketRepository
|
|
|
+}
|
|
|
+
|
|
|
+func NewTicketService() *TicketService {
|
|
|
+ return &TicketService{repo: &repository.TicketRepository{}}
|
|
|
+}
|
|
|
+
|
|
|
+func genTicketNo() string {
|
|
|
+ b := make([]byte, 16)
|
|
|
+ rand.Read(b)
|
|
|
+ return hex.EncodeToString(b)
|
|
|
+}
|
|
|
+
|
|
|
+func appendEvent(log string, event map[string]interface{}) string {
|
|
|
+ var events []map[string]interface{}
|
|
|
+ json.Unmarshal([]byte(log), &events)
|
|
|
+ event["at"] = time.Now().Format(time.RFC3339)
|
|
|
+ events = append(events, event)
|
|
|
+ data, _ := json.Marshal(events)
|
|
|
+ return string(data)
|
|
|
+}
|
|
|
+
|
|
|
+// Create 创建数字票(所有进场方式统一入口)
|
|
|
+func (s *TicketService) Create(plateNumber, triggerMode string, recordID uint) (*dao.DigitalTicket, error) {
|
|
|
+ log := appendEvent("[]", map[string]interface{}{"event": "created", "trigger": triggerMode})
|
|
|
+ ticket := &dao.DigitalTicket{
|
|
|
+ TicketNo: genTicketNo(), PlateNumber: plateNumber, TriggerMode: triggerMode,
|
|
|
+ State: StatePendingPayment, VehicleRecordID: recordID, EntryTime: time.Now(), EventLog: log,
|
|
|
+ }
|
|
|
+ if err := s.repo.Create(ticket); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return ticket, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Transition 状态转换(校验合法性,原子更新)
|
|
|
+func (s *TicketService) Transition(id uint, toState string, extra map[string]interface{}) error {
|
|
|
+ ticket, err := s.repo.GetByID(id)
|
|
|
+ if err != nil {
|
|
|
+ return errors.New("票不存在")
|
|
|
+ }
|
|
|
+
|
|
|
+ validTransitions := map[string][]string{
|
|
|
+ StateCreated: {StatePendingPayment},
|
|
|
+ StatePendingPayment: {StatePaid, StateExpired},
|
|
|
+ StatePaid: {StateExited, StateExpired},
|
|
|
+ }
|
|
|
+
|
|
|
+ allowed := false
|
|
|
+ for _, s := range validTransitions[ticket.State] {
|
|
|
+ if s == toState {
|
|
|
+ allowed = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !allowed {
|
|
|
+ return fmt.Errorf("状态转换不合法: %s → %s", ticket.State, toState)
|
|
|
+ }
|
|
|
+
|
|
|
+ updates := map[string]interface{}{}
|
|
|
+ if toState == StatePaid {
|
|
|
+ now := time.Now()
|
|
|
+ updates["paid_at"] = now
|
|
|
+ }
|
|
|
+ if toState == StateExited {
|
|
|
+ now := time.Now()
|
|
|
+ updates["exit_time"] = now
|
|
|
+ }
|
|
|
+ for k, v := range extra {
|
|
|
+ updates[k] = v
|
|
|
+ }
|
|
|
+
|
|
|
+ event := map[string]interface{}{"event": toState}
|
|
|
+ for k, v := range extra {
|
|
|
+ event[k] = v
|
|
|
+ }
|
|
|
+ updates["event_log"] = appendEvent(ticket.EventLog, event)
|
|
|
+
|
|
|
+ return s.repo.UpdateState(id, toState, updates)
|
|
|
+}
|
|
|
+
|
|
|
+func (s *TicketService) GetByTicketNo(no string) (*dao.DigitalTicket, error) {
|
|
|
+ return s.repo.GetByTicketNo(no)
|
|
|
+}
|
|
|
+
|
|
|
+func (s *TicketService) List(q repository.TicketListQuery) ([]dao.DigitalTicket, int64, error) {
|
|
|
+ return s.repo.List(q)
|
|
|
+}
|