修复账号禁用及队列场次状态规则

问题与需求:后台禁用员工后旧会话仍可继续访问;实时队列跨天误读昨日场次;项目暂停后需禁止取号但允许叫号。

修复思路:账号权限变更时撤销会话并同步前端登录态;实时查询统一按项目时区当天场次过滤;拆分取号与叫号的状态校验,并补充前后端及 PostgreSQL 回归测试。
This commit is contained in:
2026-07-30 12:02:05 +08:00
parent 0e1ac400dc
commit 17d296263c
14 changed files with 752 additions and 54 deletions

View File

@@ -283,7 +283,9 @@ func (s *Server) updateAdminUser(w http.ResponseWriter, r *http.Request) {
return &apiError{Status: 409, Code: "LAST_ACTIVE_ADMIN", Message: "必须保留至少一个启用的管理员账号"}
}
}
updates := map[string]any{"updated_at": s.now()}
now := s.now()
authChanged := nextRole != user.Role || nextActive != user.Active || input.Password != ""
updates := map[string]any{"updated_at": now}
if input.Role != "" {
updates["role"] = input.Role
}
@@ -303,6 +305,13 @@ func (s *Server) updateAdminUser(w http.ResponseWriter, r *http.Request) {
if err := tx.Model(&user).Updates(updates).Error; err != nil {
return err
}
if authChanged {
if err := tx.Model(&model.AuthSession{}).
Where("user_id = ? AND revoked_at IS NULL", id).
Update("revoked_at", now).Error; err != nil {
return err
}
}
if err := tx.Where("user_id = ?", id).Delete(&model.UserProject{}).Error; err != nil {
return err
}
@@ -335,14 +344,21 @@ func (s *Server) adminOverview(w http.ResponseWriter, r *http.Request) {
}
var totals totalRow
if err := s.db.WithContext(r.Context()).Raw(`
WITH current_sessions AS (
SELECT session.id
FROM queue_sessions AS session
JOIN projects AS project ON project.id = session.project_id
WHERE session.status IN ('RUNNING', 'PAUSED')
AND session.business_date = (? AT TIME ZONE project.timezone)::date
)
SELECT
(SELECT count(*) FROM projects) AS projects,
(SELECT count(*) FROM queue_sessions WHERE status IN ('RUNNING','PAUSED')) AS active_sessions,
(SELECT count(*) FROM queue_tickets WHERE status = 'WAITING') AS waiting_tickets,
(SELECT COALESCE(sum(party_size), 0) FROM queue_tickets WHERE status = 'WAITING') AS waiting_people,
(SELECT count(*) FROM queue_tickets WHERE status IN ('CALLED','ARRIVED')) AS called_tickets,
(SELECT COALESCE(sum(party_size), 0) FROM queue_tickets WHERE status IN ('CALLED','ARRIVED')) AS called_people
`).Scan(&totals).Error; err != nil {
(SELECT count(*) FROM current_sessions) AS active_sessions,
(SELECT count(*) FROM queue_tickets AS ticket JOIN current_sessions AS session ON session.id = ticket.queue_session_id WHERE ticket.status = 'WAITING') AS waiting_tickets,
(SELECT COALESCE(sum(ticket.party_size), 0) FROM queue_tickets AS ticket JOIN current_sessions AS session ON session.id = ticket.queue_session_id WHERE ticket.status = 'WAITING') AS waiting_people,
(SELECT count(*) FROM queue_tickets AS ticket JOIN current_sessions AS session ON session.id = ticket.queue_session_id WHERE ticket.status IN ('CALLED','ARRIVED')) AS called_tickets,
(SELECT COALESCE(sum(ticket.party_size), 0) FROM queue_tickets AS ticket JOIN current_sessions AS session ON session.id = ticket.queue_session_id WHERE ticket.status IN ('CALLED','ARRIVED')) AS called_people
`, s.now()).Scan(&totals).Error; err != nil {
writeError(w, err)
return
}
@@ -357,9 +373,14 @@ func (s *Server) adminOverview(w http.ResponseWriter, r *http.Request) {
return
}
var activeTickets []model.QueueTicket
if err := s.db.WithContext(r.Context()).
Where("status IN ?", []string{model.TicketWaiting, model.TicketCalled, model.TicketArrived}).
Order("created_at ASC").Find(&activeTickets).Error; err != nil {
if err := s.db.WithContext(r.Context()).Model(&model.QueueTicket{}).
Select("queue_tickets.*").
Joins("JOIN queue_sessions AS current_session ON current_session.id = queue_tickets.queue_session_id AND current_session.project_id = queue_tickets.project_id").
Joins("JOIN projects AS current_project ON current_project.id = queue_tickets.project_id").
Where("queue_tickets.status IN ?", []string{model.TicketWaiting, model.TicketCalled, model.TicketArrived}).
Where("current_session.status IN ?", []string{"RUNNING", "PAUSED"}).
Where("current_session.business_date = (? AT TIME ZONE current_project.timezone)::date", s.now()).
Order("queue_tickets.created_at ASC").Find(&activeTickets).Error; err != nil {
writeError(w, err)
return
}
@@ -456,9 +477,7 @@ func (s *Server) adminProjectProjection(ctx context.Context, project model.Proje
deviceStatus = map[string]any{"mode": "DISABLED", "status": "DISABLED", "label": "设备模拟器已停用"}
}
var session model.QueueSession
sessionErr := s.db.WithContext(ctx).Where("project_id = ? AND status IN ?", project.ID, []string{"RUNNING", "PAUSED"}).
Order("business_date DESC").First(&session).Error
session, sessionErr := s.currentQueueSession(s.db.WithContext(ctx), project)
if errors.Is(sessionErr, gorm.ErrRecordNotFound) {
experiencedPeople, err := s.displayedExperiencedPeople(ctx, project, nil)
if err != nil {