Files
ai-agent/internal/services/ticket_no_service.go
T
mlogclub efe801b8bf Init
2026-04-09 10:01:23 +08:00

85 lines
2.2 KiB
Go

package services
import (
"cs-agent/internal/models"
"cs-agent/internal/repositories"
"fmt"
"strings"
"time"
"gorm.io/gorm"
)
var TicketNoService = newTicketNoService()
func newTicketNoService() *ticketNoService {
return &ticketNoService{}
}
type ticketNoService struct{}
func (s *ticketNoService) Next(tx *gorm.DB, now time.Time) (string, error) {
if tx == nil {
return "", fmt.Errorf("ticket number transaction is required")
}
dateKey := now.Format("20060102")
for attempt := 0; attempt < 20; attempt++ {
current := repositories.TicketNoSequenceRepository.GetByDateKey(tx, dateKey)
if current == nil {
item := &models.TicketNoSequence{
DateKey: dateKey,
NextSeq: 2,
CreatedAt: now,
UpdatedAt: now,
}
err := repositories.TicketNoSequenceRepository.Create(tx, item)
if err == nil {
return formatTicketNo(dateKey, 1), nil
}
if !isRetriableTicketNoError(err) {
return "", err
}
time.Sleep(time.Duration(attempt+1) * 10 * time.Millisecond)
continue
}
allocated := current.NextSeq
ok, err := repositories.TicketNoSequenceRepository.UpdateNextSeq(tx, current.ID, current.NextSeq, current.NextSeq+1, now)
if err != nil {
if isRetriableTicketNoError(err) {
time.Sleep(time.Duration(attempt+1) * 10 * time.Millisecond)
continue
}
return "", err
}
if ok {
return formatTicketNo(dateKey, allocated), nil
}
time.Sleep(time.Duration(attempt+1) * 10 * time.Millisecond)
}
return "", fmt.Errorf("failed to allocate ticket number")
}
func formatTicketNo(dateKey string, seq int64) string {
return fmt.Sprintf("TK%s%05d", dateKey, seq)
}
func isDuplicateKeyError(err error) bool {
if err == nil {
return false
}
message := strings.ToLower(err.Error())
return strings.Contains(message, "duplicate") || strings.Contains(message, "unique") || strings.Contains(message, "constraint failed")
}
func isRetriableTicketNoError(err error) bool {
return isDuplicateKeyError(err) || isDatabaseLockedError(err)
}
func isDatabaseLockedError(err error) bool {
if err == nil {
return false
}
message := strings.ToLower(err.Error())
return strings.Contains(message, "database is locked") || strings.Contains(message, "database table is locked")
}