| 1 | using iknow_api.Data;
|
|---|
| 2 | using iknow_api.DTOs;
|
|---|
| 3 | using iknow_api.Models;
|
|---|
| 4 | using Microsoft.EntityFrameworkCore;
|
|---|
| 5 |
|
|---|
| 6 | namespace iknow_api.Services
|
|---|
| 7 | {
|
|---|
| 8 | /// <summary>
|
|---|
| 9 | /// Backs UC012 (subjects), UC013 (active semesters) and UC014 (professor
|
|---|
| 10 | /// assignments). Every method reports constraint problems as a sentence
|
|---|
| 11 | /// rather than letting a database exception reach the controller.
|
|---|
| 12 | /// </summary>
|
|---|
| 13 | public class AdminService : IAdminService
|
|---|
| 14 | {
|
|---|
| 15 | private readonly AppDbContext _context;
|
|---|
| 16 |
|
|---|
| 17 | public AdminService(AppDbContext context)
|
|---|
| 18 | {
|
|---|
| 19 | _context = context;
|
|---|
| 20 | }
|
|---|
| 21 |
|
|---|
| 22 | private static string SemesterName(ActiveSemesters s) =>
|
|---|
| 23 | $"{(s.Type == sType.winter ? "Зимски" : "Летен")}({s.Year}/{s.Year + 1})";
|
|---|
| 24 |
|
|---|
| 25 | // ============================================================
|
|---|
| 26 | // UC012 - subjects
|
|---|
| 27 | // ============================================================
|
|---|
| 28 |
|
|---|
| 29 | public async Task<List<AdminSubjectDto>> GetSubjectsAsync()
|
|---|
| 30 | {
|
|---|
| 31 | var subjects = await _context.Subjects.AsNoTracking().ToListAsync();
|
|---|
| 32 |
|
|---|
| 33 | var majorLinks = await _context.MajorSubjects.AsNoTracking()
|
|---|
| 34 | .Include(ms => ms.Majors).ToListAsync();
|
|---|
| 35 |
|
|---|
| 36 | var prerequisites = await _context.DependencySubjects.AsNoTracking().ToListAsync();
|
|---|
| 37 |
|
|---|
| 38 | // A subject that students already took cannot be deleted.
|
|---|
| 39 | var enrolledCounts = await _context.SemesterSubjects.AsNoTracking()
|
|---|
| 40 | .GroupBy(ss => ss.SubjectId)
|
|---|
| 41 | .Select(g => new { SubjectId = g.Key, Count = g.Count() })
|
|---|
| 42 | .ToDictionaryAsync(x => x.SubjectId, x => x.Count);
|
|---|
| 43 |
|
|---|
| 44 | var byId = subjects.ToDictionary(s => s.Id);
|
|---|
| 45 |
|
|---|
| 46 | return subjects
|
|---|
| 47 | .OrderBy(s => s.Code)
|
|---|
| 48 | .Select(s => new AdminSubjectDto
|
|---|
| 49 | {
|
|---|
| 50 | Id = s.Id,
|
|---|
| 51 | Name = s.Name,
|
|---|
| 52 | Code = s.Code,
|
|---|
| 53 | AwardedCredits = s.AwardedCredits ?? 0,
|
|---|
| 54 | DependencyCredit = s.DependencyCredit,
|
|---|
| 55 | EnrolledCount = enrolledCounts.TryGetValue(s.Id, out var c) ? c : 0,
|
|---|
| 56 | Majors = majorLinks
|
|---|
| 57 | .Where(ms => ms.SubjectId == s.Id)
|
|---|
| 58 | .Select(ms => new SubjectMajorDto
|
|---|
| 59 | {
|
|---|
| 60 | MajorId = ms.MajorId,
|
|---|
| 61 | MajorName = ms.Majors?.Name,
|
|---|
| 62 | MandatorySemester = ms.MandatorySemester
|
|---|
| 63 | })
|
|---|
| 64 | .OrderBy(m => m.MajorName)
|
|---|
| 65 | .ToList(),
|
|---|
| 66 | Prerequisites = prerequisites
|
|---|
| 67 | .Where(d => d.SubjectId == s.Id && byId.ContainsKey(d.DependencyId))
|
|---|
| 68 | .Select(d => new SubjectRefDto
|
|---|
| 69 | {
|
|---|
| 70 | Id = d.DependencyId,
|
|---|
| 71 | Name = byId[d.DependencyId].Name,
|
|---|
| 72 | Code = byId[d.DependencyId].Code
|
|---|
| 73 | })
|
|---|
| 74 | .OrderBy(p => p.Code)
|
|---|
| 75 | .ToList()
|
|---|
| 76 | })
|
|---|
| 77 | .ToList();
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | public async Task<AdminResultDto> CreateSubjectAsync(SubjectWriteDto request)
|
|---|
| 81 | {
|
|---|
| 82 | if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Code))
|
|---|
| 83 | {
|
|---|
| 84 | return Fail("Name and code are required.");
|
|---|
| 85 | }
|
|---|
| 86 | if (request.AwardedCredits <= 0)
|
|---|
| 87 | {
|
|---|
| 88 | return Fail("Awarded credits must be greater than zero.");
|
|---|
| 89 | }
|
|---|
| 90 | // subjects.name and subjects.code are both UNIQUE.
|
|---|
| 91 | if (await _context.Subjects.AnyAsync(s => s.Name == request.Name))
|
|---|
| 92 | {
|
|---|
| 93 | return Fail($"A subject named '{request.Name}' already exists.");
|
|---|
| 94 | }
|
|---|
| 95 | if (await _context.Subjects.AnyAsync(s => s.Code == request.Code))
|
|---|
| 96 | {
|
|---|
| 97 | return Fail($"Subject code '{request.Code}' is already taken.");
|
|---|
| 98 | }
|
|---|
| 99 |
|
|---|
| 100 | var subject = new Subject
|
|---|
| 101 | {
|
|---|
| 102 | Name = request.Name,
|
|---|
| 103 | Code = request.Code,
|
|---|
| 104 | AwardedCredits = request.AwardedCredits,
|
|---|
| 105 | DependencyCredit = request.DependencyCredit
|
|---|
| 106 | };
|
|---|
| 107 | _context.Subjects.Add(subject);
|
|---|
| 108 | await _context.SaveChangesAsync();
|
|---|
| 109 |
|
|---|
| 110 | return Ok($"Subject '{subject.Name}' created.", subject.Id);
|
|---|
| 111 | }
|
|---|
| 112 |
|
|---|
| 113 | public async Task<AdminResultDto> UpdateSubjectAsync(int subjectId, SubjectWriteDto request)
|
|---|
| 114 | {
|
|---|
| 115 | var subject = await _context.Subjects.FirstOrDefaultAsync(s => s.Id == subjectId);
|
|---|
| 116 | if (subject is null) return Fail("Subject not found.");
|
|---|
| 117 |
|
|---|
| 118 | if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Code))
|
|---|
| 119 | {
|
|---|
| 120 | return Fail("Name and code are required.");
|
|---|
| 121 | }
|
|---|
| 122 | if (request.AwardedCredits <= 0)
|
|---|
| 123 | {
|
|---|
| 124 | return Fail("Awarded credits must be greater than zero.");
|
|---|
| 125 | }
|
|---|
| 126 | if (await _context.Subjects.AnyAsync(s => s.Name == request.Name && s.Id != subjectId))
|
|---|
| 127 | {
|
|---|
| 128 | return Fail($"Another subject is already named '{request.Name}'.");
|
|---|
| 129 | }
|
|---|
| 130 | if (await _context.Subjects.AnyAsync(s => s.Code == request.Code && s.Id != subjectId))
|
|---|
| 131 | {
|
|---|
| 132 | return Fail($"Subject code '{request.Code}' is already taken.");
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | subject.Name = request.Name;
|
|---|
| 136 | subject.Code = request.Code;
|
|---|
| 137 | subject.AwardedCredits = request.AwardedCredits;
|
|---|
| 138 | subject.DependencyCredit = request.DependencyCredit;
|
|---|
| 139 | await _context.SaveChangesAsync();
|
|---|
| 140 |
|
|---|
| 141 | return Ok($"Subject '{subject.Name}' updated.", subject.Id);
|
|---|
| 142 | }
|
|---|
| 143 |
|
|---|
| 144 | public async Task<AdminResultDto> DeleteSubjectAsync(int subjectId)
|
|---|
| 145 | {
|
|---|
| 146 | var subject = await _context.Subjects.FirstOrDefaultAsync(s => s.Id == subjectId);
|
|---|
| 147 | if (subject is null) return Fail("Subject not found.");
|
|---|
| 148 |
|
|---|
| 149 | // semesters_subjects references subjects, so a taken subject cannot go.
|
|---|
| 150 | var taken = await _context.SemesterSubjects.CountAsync(ss => ss.SubjectId == subjectId);
|
|---|
| 151 | if (taken > 0)
|
|---|
| 152 | {
|
|---|
| 153 | return Fail($"Cannot delete: {taken} enrolment(s) already contain this subject.");
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | await using var transaction = await _context.Database.BeginTransactionAsync();
|
|---|
| 157 | try
|
|---|
| 158 | {
|
|---|
| 159 | // The mapping rows must go first; their foreign keys would block the delete.
|
|---|
| 160 | var prereqs = await _context.DependencySubjects
|
|---|
| 161 | .Where(d => d.SubjectId == subjectId || d.DependencyId == subjectId)
|
|---|
| 162 | .ToListAsync();
|
|---|
| 163 | _context.DependencySubjects.RemoveRange(prereqs);
|
|---|
| 164 |
|
|---|
| 165 | var majors = await _context.MajorSubjects
|
|---|
| 166 | .Where(ms => ms.SubjectId == subjectId).ToListAsync();
|
|---|
| 167 | _context.MajorSubjects.RemoveRange(majors);
|
|---|
| 168 |
|
|---|
| 169 | var teaching = await _context.ProfessorSubjects
|
|---|
| 170 | .Where(ps => ps.SubjectId == subjectId).ToListAsync();
|
|---|
| 171 | _context.ProfessorSubjects.RemoveRange(teaching);
|
|---|
| 172 |
|
|---|
| 173 | _context.Subjects.Remove(subject);
|
|---|
| 174 | await _context.SaveChangesAsync();
|
|---|
| 175 | await transaction.CommitAsync();
|
|---|
| 176 |
|
|---|
| 177 | return Ok($"Subject '{subject.Name}' deleted.");
|
|---|
| 178 | }
|
|---|
| 179 | catch
|
|---|
| 180 | {
|
|---|
| 181 | await transaction.RollbackAsync();
|
|---|
| 182 | throw;
|
|---|
| 183 | }
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | public async Task<AdminResultDto> LinkMajorAsync(int subjectId, MajorLinkDto request)
|
|---|
| 187 | {
|
|---|
| 188 | if (!await _context.Subjects.AnyAsync(s => s.Id == subjectId))
|
|---|
| 189 | return Fail("Subject not found.");
|
|---|
| 190 | if (!await _context.Majors.AnyAsync(m => m.Id == request.MajorId))
|
|---|
| 191 | return Fail("Study programme not found.");
|
|---|
| 192 | if (request.MandatorySemester < 1)
|
|---|
| 193 | return Fail("Semester must be 1 or greater.");
|
|---|
| 194 |
|
|---|
| 195 | var existing = await _context.MajorSubjects
|
|---|
| 196 | .FirstOrDefaultAsync(ms => ms.SubjectId == subjectId && ms.MajorId == request.MajorId);
|
|---|
| 197 |
|
|---|
| 198 | if (existing is not null)
|
|---|
| 199 | {
|
|---|
| 200 | existing.MandatorySemester = request.MandatorySemester;
|
|---|
| 201 | await _context.SaveChangesAsync();
|
|---|
| 202 | return Ok("Study programme mapping updated.");
|
|---|
| 203 | }
|
|---|
| 204 |
|
|---|
| 205 | _context.MajorSubjects.Add(new MajorSubjects
|
|---|
| 206 | {
|
|---|
| 207 | SubjectId = subjectId,
|
|---|
| 208 | MajorId = request.MajorId,
|
|---|
| 209 | MandatorySemester = request.MandatorySemester
|
|---|
| 210 | });
|
|---|
| 211 | await _context.SaveChangesAsync();
|
|---|
| 212 | return Ok("Subject added to the study programme.");
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | public async Task<AdminResultDto> UnlinkMajorAsync(int subjectId, int majorId)
|
|---|
| 216 | {
|
|---|
| 217 | var row = await _context.MajorSubjects
|
|---|
| 218 | .FirstOrDefaultAsync(ms => ms.SubjectId == subjectId && ms.MajorId == majorId);
|
|---|
| 219 | if (row is null) return Fail("That subject is not in that study programme.");
|
|---|
| 220 |
|
|---|
| 221 | _context.MajorSubjects.Remove(row);
|
|---|
| 222 | await _context.SaveChangesAsync();
|
|---|
| 223 | return Ok("Subject removed from the study programme.");
|
|---|
| 224 | }
|
|---|
| 225 |
|
|---|
| 226 | public async Task<AdminResultDto> AddPrerequisiteAsync(int subjectId, PrerequisiteDto request)
|
|---|
| 227 | {
|
|---|
| 228 | if (subjectId == request.DependencyId)
|
|---|
| 229 | {
|
|---|
| 230 | // Mirrors CHECK (subject_id <> dependency_id).
|
|---|
| 231 | return Fail("A subject cannot be its own prerequisite.");
|
|---|
| 232 | }
|
|---|
| 233 | if (!await _context.Subjects.AnyAsync(s => s.Id == subjectId))
|
|---|
| 234 | return Fail("Subject not found.");
|
|---|
| 235 | if (!await _context.Subjects.AnyAsync(s => s.Id == request.DependencyId))
|
|---|
| 236 | return Fail("Prerequisite subject not found.");
|
|---|
| 237 | if (await _context.DependencySubjects.AnyAsync(
|
|---|
| 238 | d => d.SubjectId == subjectId && d.DependencyId == request.DependencyId))
|
|---|
| 239 | return Fail("That prerequisite is already set.");
|
|---|
| 240 |
|
|---|
| 241 | // Refuse a direct cycle: A requires B while B already requires A.
|
|---|
| 242 | if (await _context.DependencySubjects.AnyAsync(
|
|---|
| 243 | d => d.SubjectId == request.DependencyId && d.DependencyId == subjectId))
|
|---|
| 244 | return Fail("That would create a circular prerequisite.");
|
|---|
| 245 |
|
|---|
| 246 | _context.DependencySubjects.Add(new DependencySubject
|
|---|
| 247 | {
|
|---|
| 248 | SubjectId = subjectId,
|
|---|
| 249 | DependencyId = request.DependencyId
|
|---|
| 250 | });
|
|---|
| 251 | await _context.SaveChangesAsync();
|
|---|
| 252 | return Ok("Prerequisite added.");
|
|---|
| 253 | }
|
|---|
| 254 |
|
|---|
| 255 | public async Task<AdminResultDto> RemovePrerequisiteAsync(int subjectId, int dependencyId)
|
|---|
| 256 | {
|
|---|
| 257 | var row = await _context.DependencySubjects
|
|---|
| 258 | .FirstOrDefaultAsync(d => d.SubjectId == subjectId && d.DependencyId == dependencyId);
|
|---|
| 259 | if (row is null) return Fail("That prerequisite is not set.");
|
|---|
| 260 |
|
|---|
| 261 | _context.DependencySubjects.Remove(row);
|
|---|
| 262 | await _context.SaveChangesAsync();
|
|---|
| 263 | return Ok("Prerequisite removed.");
|
|---|
| 264 | }
|
|---|
| 265 |
|
|---|
| 266 | // ============================================================
|
|---|
| 267 | // UC013 - active semesters
|
|---|
| 268 | // ============================================================
|
|---|
| 269 |
|
|---|
| 270 | public async Task<List<AdminSemesterDto>> GetSemestersAsync()
|
|---|
| 271 | {
|
|---|
| 272 | var semesters = await _context.ActiveSemesters.AsNoTracking().ToListAsync();
|
|---|
| 273 | var subjects = await _context.Subjects.AsNoTracking().ToListAsync();
|
|---|
| 274 | var teaching = await _context.ProfessorSubjects.AsNoTracking().ToListAsync();
|
|---|
| 275 |
|
|---|
| 276 | var enrolments = await _context.EnrolledSemesters.AsNoTracking()
|
|---|
| 277 | .GroupBy(es => es.SemesterId)
|
|---|
| 278 | .Select(g => new { SemesterId = g.Key, Count = g.Count() })
|
|---|
| 279 | .ToDictionaryAsync(x => x.SemesterId, x => x.Count);
|
|---|
| 280 |
|
|---|
| 281 | return semesters
|
|---|
| 282 | .OrderByDescending(s => s.Year)
|
|---|
| 283 | .ThenByDescending(s => s.Type == sType.summer)
|
|---|
| 284 | .Select(s =>
|
|---|
| 285 | {
|
|---|
| 286 | var covered = teaching
|
|---|
| 287 | .Where(t => t.SemesterId == s.Id)
|
|---|
| 288 | .Select(t => t.SubjectId)
|
|---|
| 289 | .ToHashSet();
|
|---|
| 290 |
|
|---|
| 291 | return new AdminSemesterDto
|
|---|
| 292 | {
|
|---|
| 293 | Id = s.Id,
|
|---|
| 294 | Year = s.Year,
|
|---|
| 295 | Type = s.Type.ToString(),
|
|---|
| 296 | Name = SemesterName(s),
|
|---|
| 297 | EnrolmentCount = enrolments.TryGetValue(s.Id, out var c) ? c : 0,
|
|---|
| 298 | UncoveredSubjects = subjects
|
|---|
| 299 | .Where(sub => !covered.Contains(sub.Id))
|
|---|
| 300 | .OrderBy(sub => sub.Code)
|
|---|
| 301 | .Select(sub => new SubjectRefDto { Id = sub.Id, Name = sub.Name, Code = sub.Code })
|
|---|
| 302 | .ToList()
|
|---|
| 303 | };
|
|---|
| 304 | })
|
|---|
| 305 | .ToList();
|
|---|
| 306 | }
|
|---|
| 307 |
|
|---|
| 308 | public async Task<AdminResultDto> CreateSemesterAsync(SemesterWriteDto request)
|
|---|
| 309 | {
|
|---|
| 310 | if (!Enum.TryParse<sType>(request.Type, ignoreCase: true, out var type))
|
|---|
| 311 | {
|
|---|
| 312 | return Fail("Semester type must be 'winter' or 'summer'.");
|
|---|
| 313 | }
|
|---|
| 314 | if (request.Year < 2000 || request.Year > 2100)
|
|---|
| 315 | {
|
|---|
| 316 | return Fail("Year looks wrong; expected something between 2000 and 2100.");
|
|---|
| 317 | }
|
|---|
| 318 | // active_semesters has UNIQUE (year, type).
|
|---|
| 319 | if (await _context.ActiveSemesters.AnyAsync(a => a.Year == request.Year && a.Type == type))
|
|---|
| 320 | {
|
|---|
| 321 | return Fail("That semester is already open.");
|
|---|
| 322 | }
|
|---|
| 323 |
|
|---|
| 324 | var semester = new ActiveSemesters { Year = request.Year, Type = type };
|
|---|
| 325 | _context.ActiveSemesters.Add(semester);
|
|---|
| 326 | await _context.SaveChangesAsync();
|
|---|
| 327 |
|
|---|
| 328 | return Ok($"Semester {SemesterName(semester)} opened.", semester.Id);
|
|---|
| 329 | }
|
|---|
| 330 |
|
|---|
| 331 | // ============================================================
|
|---|
| 332 | // UC014 - professor assignments
|
|---|
| 333 | // ============================================================
|
|---|
| 334 |
|
|---|
| 335 | public async Task<ScheduleDto?> GetScheduleAsync(int semesterId)
|
|---|
| 336 | {
|
|---|
| 337 | var semester = await _context.ActiveSemesters.AsNoTracking()
|
|---|
| 338 | .FirstOrDefaultAsync(a => a.Id == semesterId);
|
|---|
| 339 | if (semester is null) return null;
|
|---|
| 340 |
|
|---|
| 341 | var subjects = await _context.Subjects.AsNoTracking().ToListAsync();
|
|---|
| 342 | var professors = await _context.User.AsNoTracking()
|
|---|
| 343 | .Where(u => u.Role == Models.UserRole.Professor).ToListAsync();
|
|---|
| 344 | var assignments = await _context.ProfessorSubjects.AsNoTracking()
|
|---|
| 345 | .Where(ps => ps.SemesterId == semesterId).ToListAsync();
|
|---|
| 346 |
|
|---|
| 347 | var subjectById = subjects.ToDictionary(s => s.Id);
|
|---|
| 348 | var profById = professors.ToDictionary(p => p.Id);
|
|---|
| 349 | var covered = assignments.Select(a => a.SubjectId).ToHashSet();
|
|---|
| 350 |
|
|---|
| 351 | return new ScheduleDto
|
|---|
| 352 | {
|
|---|
| 353 | SemesterId = semester.Id,
|
|---|
| 354 | SemesterName = SemesterName(semester),
|
|---|
| 355 | Assignments = assignments
|
|---|
| 356 | .Where(a => subjectById.ContainsKey(a.SubjectId) && profById.ContainsKey(a.ProfessorId))
|
|---|
| 357 | .Select(a => new ScheduleRowDto
|
|---|
| 358 | {
|
|---|
| 359 | SubjectId = a.SubjectId,
|
|---|
| 360 | SubjectName = subjectById[a.SubjectId].Name,
|
|---|
| 361 | SubjectCode = subjectById[a.SubjectId].Code,
|
|---|
| 362 | ProfessorId = a.ProfessorId,
|
|---|
| 363 | ProfessorName = $"{profById[a.ProfessorId].Name} {profById[a.ProfessorId].Surname}".Trim()
|
|---|
| 364 | })
|
|---|
| 365 | .OrderBy(r => r.SubjectCode)
|
|---|
| 366 | .ToList(),
|
|---|
| 367 | Load = professors
|
|---|
| 368 | .Select(p => new ProfessorLoadDto
|
|---|
| 369 | {
|
|---|
| 370 | ProfessorId = p.Id,
|
|---|
| 371 | ProfessorName = $"{p.Name} {p.Surname}".Trim(),
|
|---|
| 372 | Subjects = assignments.Count(a => a.ProfessorId == p.Id)
|
|---|
| 373 | })
|
|---|
| 374 | .OrderByDescending(l => l.Subjects).ThenBy(l => l.ProfessorName)
|
|---|
| 375 | .ToList(),
|
|---|
| 376 | UncoveredSubjects = subjects
|
|---|
| 377 | .Where(s => !covered.Contains(s.Id))
|
|---|
| 378 | .OrderBy(s => s.Code)
|
|---|
| 379 | .Select(s => new SubjectRefDto { Id = s.Id, Name = s.Name, Code = s.Code })
|
|---|
| 380 | .ToList(),
|
|---|
| 381 | AllSubjects = subjects
|
|---|
| 382 | .OrderBy(s => s.Code)
|
|---|
| 383 | .Select(s => new SubjectRefDto { Id = s.Id, Name = s.Name, Code = s.Code })
|
|---|
| 384 | .ToList(),
|
|---|
| 385 | Professors = professors
|
|---|
| 386 | .OrderBy(p => p.Surname)
|
|---|
| 387 | .Select(p => new ProfessorRefDto { Id = p.Id, Name = $"{p.Name} {p.Surname}".Trim() })
|
|---|
| 388 | .ToList()
|
|---|
| 389 | };
|
|---|
| 390 | }
|
|---|
| 391 |
|
|---|
| 392 | public async Task<AdminResultDto> AssignAsync(ScheduleWriteDto request)
|
|---|
| 393 | {
|
|---|
| 394 | var professor = await _context.User
|
|---|
| 395 | .FirstOrDefaultAsync(u => u.Id == request.ProfessorId);
|
|---|
| 396 | if (professor is null) return Fail("Professor not found.");
|
|---|
| 397 | if (professor.Role != Models.UserRole.Professor)
|
|---|
| 398 | return Fail("That user is not a professor.");
|
|---|
| 399 | if (!await _context.ActiveSemesters.AnyAsync(a => a.Id == request.SemesterId))
|
|---|
| 400 | return Fail("Semester not found.");
|
|---|
| 401 | if (!await _context.Subjects.AnyAsync(s => s.Id == request.SubjectId))
|
|---|
| 402 | return Fail("Subject not found.");
|
|---|
| 403 |
|
|---|
| 404 | var exists = await _context.ProfessorSubjects.AnyAsync(
|
|---|
| 405 | ps => ps.ProfessorId == request.ProfessorId
|
|---|
| 406 | && ps.SemesterId == request.SemesterId
|
|---|
| 407 | && ps.SubjectId == request.SubjectId);
|
|---|
| 408 | if (exists) return Ok("That assignment already exists.");
|
|---|
| 409 |
|
|---|
| 410 | _context.ProfessorSubjects.Add(new ProfessorSubjects
|
|---|
| 411 | {
|
|---|
| 412 | ProfessorId = request.ProfessorId,
|
|---|
| 413 | SemesterId = request.SemesterId,
|
|---|
| 414 | SubjectId = request.SubjectId
|
|---|
| 415 | });
|
|---|
| 416 | await _context.SaveChangesAsync();
|
|---|
| 417 | return Ok("Professor assigned to the subject.");
|
|---|
| 418 | }
|
|---|
| 419 |
|
|---|
| 420 | public async Task<AdminResultDto> UnassignAsync(ScheduleWriteDto request)
|
|---|
| 421 | {
|
|---|
| 422 | var row = await _context.ProfessorSubjects.FirstOrDefaultAsync(
|
|---|
| 423 | ps => ps.ProfessorId == request.ProfessorId
|
|---|
| 424 | && ps.SemesterId == request.SemesterId
|
|---|
| 425 | && ps.SubjectId == request.SubjectId);
|
|---|
| 426 | if (row is null) return Fail("That assignment does not exist.");
|
|---|
| 427 |
|
|---|
| 428 | _context.ProfessorSubjects.Remove(row);
|
|---|
| 429 | await _context.SaveChangesAsync();
|
|---|
| 430 | return Ok("Assignment removed.");
|
|---|
| 431 | }
|
|---|
| 432 |
|
|---|
| 433 | public async Task<List<SubjectRefDto>> GetMajorsAsync() =>
|
|---|
| 434 | await _context.Majors.AsNoTracking()
|
|---|
| 435 | .OrderBy(m => m.Name)
|
|---|
| 436 | .Select(m => new SubjectRefDto { Id = m.Id, Name = m.Name, Code = null })
|
|---|
| 437 | .ToListAsync();
|
|---|
| 438 |
|
|---|
| 439 | private static AdminResultDto Ok(string message, int? id = null) =>
|
|---|
| 440 | new() { Ok = true, Message = message, Id = id };
|
|---|
| 441 |
|
|---|
| 442 | private static AdminResultDto Fail(string message) =>
|
|---|
| 443 | new() { Ok = false, Message = message };
|
|---|
| 444 | }
|
|---|
| 445 | }
|
|---|