1010
1111namespace Ice . Services . TicketService ;
1212
13- public class TicketService ( IceDbContext iceDbContext , INotificationService notificationService ) : ITicketService
13+ public class TicketService ( IceDbContext iceDbContext , INotificationService notificationService ) : ITicketService
1414{
1515 public async Task < IReadOnlyList < Tickets > > GetAllTicketsAsync ( CancellationToken cancellationToken )
1616 {
@@ -31,7 +31,8 @@ public async Task<IReadOnlyList<Tickets>> GetAllTicketsAsync(CancellationToken c
3131 . FirstOrDefaultAsync ( t => t . Id == ticketId , cancellationToken ) ;
3232 }
3333
34- public async Task < IReadOnlyList < Tickets > > GetTicketsByStudentGroupIdAsync ( long studentGroupId , CancellationToken cancellationToken )
34+ public async Task < IReadOnlyList < Tickets > > GetTicketsByStudentGroupIdAsync ( long studentGroupId ,
35+ CancellationToken cancellationToken )
3536 {
3637 return await iceDbContext . Tickets
3738 . Where ( t => t . StudentGroupId == studentGroupId )
@@ -40,7 +41,7 @@ public async Task<IReadOnlyList<Tickets>> GetTicketsByStudentGroupIdAsync(long s
4041 . OrderByDescending ( t => t . CreatedAt )
4142 . ToListAsync ( cancellationToken ) ;
4243 }
43-
44+
4445 public async Task < AddTicketResDto > CreateTicketAsync ( AddTicketDto addTicketDto , CancellationToken cancellationToken )
4546 {
4647 var transaction = await iceDbContext . Database . BeginTransactionAsync ( cancellationToken ) ;
@@ -66,15 +67,16 @@ public async Task<AddTicketResDto> CreateTicketAsync(AddTicketDto addTicketDto,
6667
6768 iceDbContext . Tickets . Add ( ticket ) ;
6869 await iceDbContext . SaveChangesAsync ( cancellationToken ) ;
69-
70+
7071 // 講師を割り当てる
7172 var targetTutor = await AssignTutorToTicketAsync ( ticket . Id , cancellationToken ) ;
72-
73+
7374 // 課題とチケットの関連付け
74- await LinkAssignmentToTicketAsync ( ticket . Id , addTicketDto . AssignmentId , addTicketDto . StudentGroupId , cancellationToken ) ;
75+ await LinkAssignmentToTicketAsync ( ticket . Id , addTicketDto . AssignmentId , addTicketDto . StudentGroupId ,
76+ cancellationToken ) ;
7577
7678 await transaction . CommitAsync ( cancellationToken ) ;
77-
79+
7880 // SSE通知を送信
7981 await notificationService . NotifyTicketCreatedAsync (
8082 ticket . Id ,
@@ -131,7 +133,8 @@ public async Task DeleteTicketAsync(long ticketId, CancellationToken cancellatio
131133 return await iceDbContext . Tickets
132134 . Include ( t => t . TicketAdminUser )
133135 . ThenInclude ( tau => tau ! . AdminUser )
134- . FirstOrDefaultAsync ( t => t . StudentGroupId == studentGroupId && t . Status == TicketStatus . InProgress , cancellationToken ) ;
136+ . FirstOrDefaultAsync ( t => t . StudentGroupId == studentGroupId && t . Status == TicketStatus . InProgress ,
137+ cancellationToken ) ;
135138 }
136139
137140 public async Task < Tickets > AssignTicketAsync ( AssignTicketReqDto req , CancellationToken cancellationToken )
@@ -182,32 +185,60 @@ public async Task<Tickets> AssignTicketAsync(AssignTicketReqDto req, Cancellatio
182185
183186 private async Task < AdminUsers > AssignTutorToTicketAsync ( long ticketId , CancellationToken cancellationToken )
184187 {
185- // 講師ごとの担当チケット数を一度のクエリで取得
186- var tutorTicketCounts = await iceDbContext . AdminUsers
187- . GroupJoin (
188- iceDbContext . TicketAdminUsers ,
189- admin => admin . Id ,
190- tau => tau . AdminUserId ,
191- ( admin , tickets ) => new
192- {
193- TutorId = admin . Id ,
194- TicketCount = tickets . Count ( )
195- } )
188+ // 対応中のチケットとその担当講師を取得
189+ var inProgressTickets = await iceDbContext . Tickets
190+ . Where ( t => t . Status == TicketStatus . InProgress )
191+ . Include ( t => t . TicketAdminUser )
196192 . ToListAsync ( cancellationToken ) ;
197193
198- if ( tutorTicketCounts . Count == 0 )
194+ // 講師ごとの対応中チケット数をカウント
195+ var inProgressTicketsByTutor = inProgressTickets
196+ . Where ( t => t . TicketAdminUser != null )
197+ . GroupBy ( t => t . TicketAdminUser ! . AdminUserId )
198+ . ToDictionary ( g => g . Key , g => g . Count ( ) ) ;
199+
200+ // すべての講師を取得
201+ var allTutors = await iceDbContext . AdminUsers . ToListAsync ( cancellationToken ) ;
202+
203+ if ( allTutors . Count == 0 )
199204 {
200205 throw new InvalidOperationException ( "講師が登録されていません。" ) ;
201206 }
202207
203- // 最も少ないチケット数の講師を取得
204- var minTicketCount = tutorTicketCounts . Min ( t => t . TicketCount ) ;
205- var leastBusyTutorId = tutorTicketCounts
206- . First ( t => t . TicketCount == minTicketCount )
207- . TutorId ;
208+ // 対応中のチケットを持たない講師を検索
209+ var availableTutors = allTutors
210+ . Where ( tutor => ! inProgressTicketsByTutor . ContainsKey ( tutor . Id ) )
211+ . ToList ( ) ;
208212
209- var targetUser = await iceDbContext . AdminUsers
210- . FirstAsync ( u => u . Id == leastBusyTutorId , cancellationToken ) ;
213+ // 全員が対応中の場合はエラー
214+ if ( availableTutors . Count == 0 )
215+ {
216+ throw new AllStaffCurrentlyAssistingException ( "TA/SAの全員が対応中です。しばらく経ってから再度チケットを作成してください。" ) ;
217+ }
218+
219+ // 対応できる講師の中で最も総チケット数が少ない講師を選択
220+ AdminUsers targetUser ;
221+ if ( availableTutors . Count == allTutors . Count )
222+ {
223+ // 全員が対応中でない場合、総チケット数が最も少ない講師を選択
224+ var ticketCounts = await iceDbContext . TicketAdminUsers
225+ . GroupBy ( tau => tau . AdminUserId )
226+ . Select ( g => new { AdminUserId = g . Key , Count = g . Count ( ) } )
227+ . ToDictionaryAsync ( x => x . AdminUserId , x => x . Count , cancellationToken
228+ ) ;
229+ var minTicketCount = ticketCounts . Values . Min ( ) ;
230+ var candidates = availableTutors
231+ . Where ( tutor => ticketCounts . GetValueOrDefault ( tutor . Id , 0 ) == minTicketCount )
232+ . ToList ( ) ;
233+ targetUser = candidates . First ( ) ;
234+ }
235+ else
236+ {
237+ // 対応中でない講師の中からランダムに選択
238+ var random = new Random ( ) ;
239+ var randomIndex = random . Next ( availableTutors . Count ) ;
240+ targetUser = availableTutors [ randomIndex ] ;
241+ }
211242
212243 var adminUserTicket = new TicketAdminUsers
213244 {
@@ -216,23 +247,24 @@ private async Task<AdminUsers> AssignTutorToTicketAsync(long ticketId, Cancellat
216247 CreatedAt = DateTime . UtcNow ,
217248 UpdatedAt = DateTime . UtcNow
218249 } ;
219-
250+
220251 iceDbContext . TicketAdminUsers . Add ( adminUserTicket ) ;
221252 await iceDbContext . SaveChangesAsync ( cancellationToken ) ;
222-
253+
223254 return targetUser ;
224255 }
225-
226- private async Task LinkAssignmentToTicketAsync ( long ticketId , long assignmentId , long studentGroupId , CancellationToken cancellationToken )
256+
257+ private async Task LinkAssignmentToTicketAsync ( long ticketId , long assignmentId , long studentGroupId ,
258+ CancellationToken cancellationToken )
227259 {
228260 var assignmentExists = await iceDbContext . Assignments
229261 . AnyAsync ( a => a . Id == assignmentId , cancellationToken ) ;
230-
262+
231263 if ( ! assignmentExists )
232264 {
233265 throw new EntityNotFoundException ( $ "課題ID { assignmentId } の課題が見つかりません。") ;
234- }
235-
266+ }
267+
236268 var ticketAssignment = new TicketAssignments
237269 {
238270 TicketId = ticketId ,
@@ -241,7 +273,7 @@ private async Task LinkAssignmentToTicketAsync(long ticketId, long assignmentId,
241273 UpdatedAt = DateTime . UtcNow ,
242274 StudentGroupId = studentGroupId
243275 } ;
244-
276+
245277 iceDbContext . TicketAssignments . Add ( ticketAssignment ) ;
246278 await iceDbContext . SaveChangesAsync ( cancellationToken ) ;
247279 }
0 commit comments