| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package service
- import (
- "crypto/rand"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "gorm.io/gorm"
- "time"
- "wails-app/internal/dao"
- "wails-app/internal/global"
- "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) {
- return s.CreateTx(global.GVA_DB, plateNumber, triggerMode, recordID)
- }
- func (s *TicketService) CreateTx(db *gorm.DB, 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.CreateTx(db, ticket); err != nil {
- return nil, err
- }
- return ticket, nil
- }
- // Transition 状态转换(校验合法性,原子更新)
- func (s *TicketService) Transition(id uint, toState string, extra map[string]interface{}) error {
- return s.TransitionTx(global.GVA_DB, id, toState, extra)
- }
- func (s *TicketService) TransitionTx(db *gorm.DB, id uint, toState string, extra map[string]interface{}) error {
- ticket, err := s.repo.GetByIDTx(db, 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
- }
- // 只允许表中存在的字段写入数据库,其余仅记录到事件日志
- dbFields := map[string]bool{"payment_entry": true, "payment_method": true, "payment_order_no": true, "fee": true}
- for k, v := range extra {
- if dbFields[k] {
- 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.UpdateStateTx(db, id, toState, updates)
- }
- // MarkPaidAndExitedTx updates the ticket lifecycle in the caller's
- // transaction. Payment entry and method are stored as payment metadata only;
- // they do not define the ticket itself.
- func (s *TicketService) MarkPaidAndExitedTx(db *gorm.DB, recordID uint, paymentEntry, paymentMethod, paymentOrderNo string, paidAmount, fee float64) error {
- ticket, err := s.repo.GetByVehicleRecordIDTx(db, recordID)
- if err != nil {
- return errors.New("车辆对应的数字票不存在")
- }
- if ticket.State == StatePendingPayment {
- extra := map[string]interface{}{
- "payment_entry": paymentEntry,
- "payment_method": paymentMethod,
- "payment_order_no": paymentOrderNo,
- "paid_amount": paidAmount,
- "fee": fee,
- }
- if err := s.TransitionTx(db, ticket.ID, StatePaid, extra); err != nil {
- return err
- }
- ticket.State = StatePaid
- }
- if ticket.State == StatePaid {
- return s.TransitionTx(db, ticket.ID, StateExited, nil)
- }
- if ticket.State == StateExited {
- return nil
- }
- return fmt.Errorf("数字票状态不允许出场: %s", ticket.State)
- }
- 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)
- }
|