public async Task <ActionResult <EncounterDropDto> > PutAssign(long id, [FromBody] AwardDropSubmissionDto dto, [FromServices] TimeZoneInfo realmTimeZoneInfo, [FromServices] IAuthorizationService auth, [FromServices] DiscordClientProvider dcp) { var now = realmTimeZoneInfo.TimeZoneNow(); var drop = await _context.Drops.FindAsync(id); if (drop is null) { return(NotFound()); } var teamId = await _context.Raids .AsNoTracking() .Where(r => r.Id == drop.EncounterKillRaidId) .Select(r => (long?)r.RaidTeamId) .FirstOrDefaultAsync(); if (!teamId.HasValue) { return(NotFound()); } var authResult = await auth.AuthorizeAsync(User, teamId, AppPolicies.LootMaster); if (!authResult.Succeeded) { return(Unauthorized()); } if (dto.WinnerId.HasValue && drop.WinnerId.HasValue) { ModelState.AddModelError(nameof(dto.WinnerId), "Existing winner must be cleared before setting a new winner."); return(ValidationProblem()); } drop.AwardedAt = now; drop.AwardedBy = User.GetDiscordId(); var scope = await _context.GetCurrentPriorityScopeAsync(); var observedDates = await _context.Raids .AsNoTracking() .Where(r => r.RaidTeamId == teamId) .OrderByDescending(r => r.StartedAt) .Select(r => r.StartedAt.Date) .Distinct() .Take(scope.ObservedAttendances) .ToListAsync(); var presentTeamRaiders = await _context.CharacterEncounterKills .AsTracking() .Where(cek => cek.EncounterKillEncounterId == drop.EncounterKillEncounterId && cek.EncounterKillRaidId == drop.EncounterKillRaidId) .Select(c => new { Id = c.CharacterId, c.Character.TeamId, c.Character.MemberStatus, Attended = c.Character.Attendances.Where(x => !x.IgnoreAttendance && x.Raid.RaidTeamId == teamId && x.RemovalId == null && observedDates.Contains(x.Raid.StartedAt.Date)) .Select(x => x.Raid.StartedAt.Date) .Distinct() .OrderByDescending(x => x) .Take(scope.ObservedAttendances) .Count(), Entry = _context.LootListEntries.Where(e => !e.DropId.HasValue && e.LootList.CharacterId == c.CharacterId && (e.ItemId == drop.ItemId || e.Item !.RewardFromId == drop.ItemId)) .OrderByDescending(e => e.Rank) .Select(e => new { e.Id, e.Rank, e.LootList.Status, Passes = e.Passes.Count(p => p.RemovalId == null) }) .FirstOrDefault() })
public async Task <ActionResult <EncounterDropDto> > PutAssign(long id, [FromBody] AwardDropSubmissionDto dto, [FromServices] DiscordClientProvider dcp) { var now = _realmTimeZone.TimeZoneNow(); var drop = await _context.Drops.FindAsync(id); if (drop is null) { return(NotFound()); } var raid = await _context.Raids .AsNoTracking() .Where(r => r.Id == drop.EncounterKillRaidId) .Select(r => new { r.RaidTeamId, r.LocksAt }) .FirstOrDefaultAsync(); if (raid is null) { return(NotFound()); } var authResult = await _authorizationService.AuthorizeAsync(User, raid.RaidTeamId, AppPolicies.LootMaster); if (!authResult.Succeeded) { return(Unauthorized()); } if (DateTimeOffset.UtcNow > raid.LocksAt) { return(Problem("Can't alter a locked raid.")); } if (dto.WinnerId.HasValue && drop.WinnerId.HasValue) { ModelState.AddModelError(nameof(dto.WinnerId), "Existing winner must be cleared before setting a new winner."); return(ValidationProblem()); } drop.AwardedAt = now; drop.AwardedBy = User.GetDiscordId(); var scope = await _context.GetCurrentPriorityScopeAsync(); var teamId = raid.RaidTeamId; var attendances = await _context.GetAttendanceTableAsync(teamId, scope.ObservedAttendances); var donationMatrix = await _context.GetDonationMatrixAsync(d => d.Character.Attendances.Any(a => a.RaidId == drop.EncounterKillRaidId), scope); var presentTeamRaiders = await _context.CharacterEncounterKills .AsTracking() .Where(cek => cek.EncounterKillEncounterId == drop.EncounterKillEncounterId && cek.EncounterKillRaidId == drop.EncounterKillRaidId && cek.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(cek => cek.Character) .Select(ConvertToDropInfo(drop.ItemId)) .ToListAsync(); await _context.Entry(drop).Collection(drop => drop.Passes).LoadAsync(); drop.Passes.Clear(); if (dto.WinnerId.HasValue) { var winner = presentTeamRaiders.Find(e => e.Id == dto.WinnerId); if (winner is null) { ModelState.AddModelError(nameof(dto.WinnerId), "Character was not present for the kill."); return(ValidationProblem()); } int?winnerPrio = null; if (winner.Entry is not null) { winnerPrio = winner.Entry.Rank; var passes = await _context.DropPasses .AsTracking() .Where(p => p.LootListEntryId == winner.Entry.Id && p.RemovalId == null) .ToListAsync(); Debug.Assert(passes.Count == winner.Entry.Passes); var donated = donationMatrix.GetCreditForMonth(winner.Id, now); attendances.TryGetValue(winner.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, winner.MemberStatus, donated, passes.Count, winner.Enchanted, winner.Prepared)) { winnerPrio = winnerPrio.Value + bonus.Value; } drop.WinningEntry = await _context.LootListEntries.FindAsync(winner.Entry.Id); Debug.Assert(drop.WinningEntry is not null); drop.WinningEntry.Drop = drop; drop.WinningEntry.DropId = drop.Id; foreach (var pass in passes) { pass.WonEntryId = winner.Entry.Id; } } drop.WinnerId = winner.Id; foreach (var killer in presentTeamRaiders) { if (killer.Entry is not null && killer.TeamId == teamId && killer != winner) { var thisPrio = (int)killer.Entry.Rank; var donated = donationMatrix.GetCreditForMonth(killer.Id, now); attendances.TryGetValue(killer.Id, out int attended); foreach (var bonus in PrioCalculator.GetAllBonuses(scope, attended, killer.MemberStatus, donated, killer.Entry.Passes, killer.Enchanted, killer.Prepared)) { thisPrio += bonus.Value; } _context.DropPasses.Add(new DropPass { Drop = drop, DropId = drop.Id, CharacterId = killer.Id, RelativePriority = thisPrio - (winnerPrio ?? 0), LootListEntryId = killer.Entry.Id }); } } } else { var oldWinningEntry = await _context.LootListEntries .AsTracking() .Where(e => e.DropId == drop.Id) .SingleOrDefaultAsync(); drop.Winner = null; drop.WinnerId = null; drop.WinningEntry = null; if (oldWinningEntry is not null) { oldWinningEntry.Drop = null; oldWinningEntry.DropId = null; await foreach (var pass in _context.DropPasses .AsTracking() .Where(pass => pass.WonEntryId == oldWinningEntry.Id) .AsAsyncEnumerable()) { pass.WonEntryId = null; } } } await _context.SaveChangesAsync(); var kill = await _context.EncounterKills .AsNoTracking() .Where(kill => kill.EncounterId == drop.EncounterKillEncounterId && kill.RaidId == drop.EncounterKillRaidId && kill.TrashIndex == drop.EncounterKillTrashIndex) .Select(kill => new { kill.DiscordMessageId, kill.KilledAt, kill.RaidId, TeamName = kill.Raid.RaidTeam.Name, EncounterName = kill.Encounter.Name }) .FirstAsync(); var drops = new List <(uint, string, string?)>(); await foreach (var d in _context.Drops .AsNoTracking() .Where(d => d.EncounterKillEncounterId == drop.EncounterKillEncounterId && d.EncounterKillRaidId == drop.EncounterKillRaidId && d.EncounterKillTrashIndex == drop.EncounterKillTrashIndex) .Select(d => new { d.ItemId, ItemName = d.Item.Name, WinnerName = (string?)d.Winner !.Name })