284 lines
8.2 KiB
C#
284 lines
8.2 KiB
C#
|
|
using Microsoft.EntityFrameworkCore;
|
||
|
|
using WorkClub.Application.Interfaces;
|
||
|
|
using WorkClub.Application.Shifts.DTOs;
|
||
|
|
using WorkClub.Domain.Entities;
|
||
|
|
using WorkClub.Infrastructure.Data;
|
||
|
|
|
||
|
|
namespace WorkClub.Api.Services;
|
||
|
|
|
||
|
|
public class ShiftService
|
||
|
|
{
|
||
|
|
private readonly AppDbContext _context;
|
||
|
|
private readonly ITenantProvider _tenantProvider;
|
||
|
|
|
||
|
|
public ShiftService(AppDbContext context, ITenantProvider tenantProvider)
|
||
|
|
{
|
||
|
|
_context = context;
|
||
|
|
_tenantProvider = tenantProvider;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<ShiftListDto> GetShiftsAsync(DateTimeOffset? from, DateTimeOffset? to, int page, int pageSize)
|
||
|
|
{
|
||
|
|
var query = _context.Shifts.AsQueryable();
|
||
|
|
|
||
|
|
if (from.HasValue)
|
||
|
|
query = query.Where(s => s.StartTime >= from.Value);
|
||
|
|
|
||
|
|
if (to.HasValue)
|
||
|
|
query = query.Where(s => s.StartTime <= to.Value);
|
||
|
|
|
||
|
|
var total = await query.CountAsync();
|
||
|
|
|
||
|
|
var shifts = await query
|
||
|
|
.OrderBy(s => s.StartTime)
|
||
|
|
.Skip((page - 1) * pageSize)
|
||
|
|
.Take(pageSize)
|
||
|
|
.ToListAsync();
|
||
|
|
|
||
|
|
var shiftIds = shifts.Select(s => s.Id).ToList();
|
||
|
|
var signupCounts = await _context.ShiftSignups
|
||
|
|
.Where(ss => shiftIds.Contains(ss.ShiftId))
|
||
|
|
.GroupBy(ss => ss.ShiftId)
|
||
|
|
.Select(g => new { ShiftId = g.Key, Count = g.Count() })
|
||
|
|
.ToDictionaryAsync(x => x.ShiftId, x => x.Count);
|
||
|
|
|
||
|
|
var items = shifts.Select(s => new ShiftListItemDto(
|
||
|
|
s.Id,
|
||
|
|
s.Title,
|
||
|
|
s.StartTime,
|
||
|
|
s.EndTime,
|
||
|
|
s.Capacity,
|
||
|
|
signupCounts.GetValueOrDefault(s.Id, 0)
|
||
|
|
)).ToList();
|
||
|
|
|
||
|
|
return new ShiftListDto(items, total, page, pageSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<ShiftDetailDto?> GetShiftByIdAsync(Guid id)
|
||
|
|
{
|
||
|
|
var shift = await _context.Shifts.FindAsync(id);
|
||
|
|
|
||
|
|
if (shift == null)
|
||
|
|
return null;
|
||
|
|
|
||
|
|
var signups = await _context.ShiftSignups
|
||
|
|
.Where(ss => ss.ShiftId == id)
|
||
|
|
.OrderBy(ss => ss.SignedUpAt)
|
||
|
|
.ToListAsync();
|
||
|
|
|
||
|
|
var signupDtos = signups.Select(ss => new ShiftSignupDto(
|
||
|
|
ss.Id,
|
||
|
|
ss.MemberId,
|
||
|
|
ss.SignedUpAt
|
||
|
|
)).ToList();
|
||
|
|
|
||
|
|
return new ShiftDetailDto(
|
||
|
|
shift.Id,
|
||
|
|
shift.Title,
|
||
|
|
shift.Description,
|
||
|
|
shift.Location,
|
||
|
|
shift.StartTime,
|
||
|
|
shift.EndTime,
|
||
|
|
shift.Capacity,
|
||
|
|
signupDtos,
|
||
|
|
shift.ClubId,
|
||
|
|
shift.CreatedById,
|
||
|
|
shift.CreatedAt,
|
||
|
|
shift.UpdatedAt
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(ShiftDetailDto? shift, string? error)> CreateShiftAsync(CreateShiftRequest request, Guid createdById)
|
||
|
|
{
|
||
|
|
var tenantId = _tenantProvider.GetTenantId();
|
||
|
|
|
||
|
|
var shift = new Shift
|
||
|
|
{
|
||
|
|
Id = Guid.NewGuid(),
|
||
|
|
TenantId = tenantId,
|
||
|
|
Title = request.Title,
|
||
|
|
Description = request.Description,
|
||
|
|
Location = request.Location,
|
||
|
|
StartTime = request.StartTime,
|
||
|
|
EndTime = request.EndTime,
|
||
|
|
Capacity = request.Capacity,
|
||
|
|
ClubId = request.ClubId,
|
||
|
|
CreatedById = createdById,
|
||
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
||
|
|
UpdatedAt = DateTimeOffset.UtcNow
|
||
|
|
};
|
||
|
|
|
||
|
|
_context.Shifts.Add(shift);
|
||
|
|
await _context.SaveChangesAsync();
|
||
|
|
|
||
|
|
var dto = new ShiftDetailDto(
|
||
|
|
shift.Id,
|
||
|
|
shift.Title,
|
||
|
|
shift.Description,
|
||
|
|
shift.Location,
|
||
|
|
shift.StartTime,
|
||
|
|
shift.EndTime,
|
||
|
|
shift.Capacity,
|
||
|
|
new List<ShiftSignupDto>(),
|
||
|
|
shift.ClubId,
|
||
|
|
shift.CreatedById,
|
||
|
|
shift.CreatedAt,
|
||
|
|
shift.UpdatedAt
|
||
|
|
);
|
||
|
|
|
||
|
|
return (dto, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(ShiftDetailDto? shift, string? error, bool isConflict)> UpdateShiftAsync(Guid id, UpdateShiftRequest request)
|
||
|
|
{
|
||
|
|
var shift = await _context.Shifts.FindAsync(id);
|
||
|
|
|
||
|
|
if (shift == null)
|
||
|
|
return (null, "Shift not found", false);
|
||
|
|
|
||
|
|
if (request.Title != null)
|
||
|
|
shift.Title = request.Title;
|
||
|
|
|
||
|
|
if (request.Description != null)
|
||
|
|
shift.Description = request.Description;
|
||
|
|
|
||
|
|
if (request.Location != null)
|
||
|
|
shift.Location = request.Location;
|
||
|
|
|
||
|
|
if (request.StartTime.HasValue)
|
||
|
|
shift.StartTime = request.StartTime.Value;
|
||
|
|
|
||
|
|
if (request.EndTime.HasValue)
|
||
|
|
shift.EndTime = request.EndTime.Value;
|
||
|
|
|
||
|
|
if (request.Capacity.HasValue)
|
||
|
|
shift.Capacity = request.Capacity.Value;
|
||
|
|
|
||
|
|
shift.UpdatedAt = DateTimeOffset.UtcNow;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await _context.SaveChangesAsync();
|
||
|
|
}
|
||
|
|
catch (DbUpdateConcurrencyException)
|
||
|
|
{
|
||
|
|
return (null, "Shift was modified by another user. Please refresh and try again.", true);
|
||
|
|
}
|
||
|
|
|
||
|
|
var signups = await _context.ShiftSignups
|
||
|
|
.Where(ss => ss.ShiftId == id)
|
||
|
|
.OrderBy(ss => ss.SignedUpAt)
|
||
|
|
.ToListAsync();
|
||
|
|
|
||
|
|
var signupDtos = signups.Select(ss => new ShiftSignupDto(
|
||
|
|
ss.Id,
|
||
|
|
ss.MemberId,
|
||
|
|
ss.SignedUpAt
|
||
|
|
)).ToList();
|
||
|
|
|
||
|
|
var dto = new ShiftDetailDto(
|
||
|
|
shift.Id,
|
||
|
|
shift.Title,
|
||
|
|
shift.Description,
|
||
|
|
shift.Location,
|
||
|
|
shift.StartTime,
|
||
|
|
shift.EndTime,
|
||
|
|
shift.Capacity,
|
||
|
|
signupDtos,
|
||
|
|
shift.ClubId,
|
||
|
|
shift.CreatedById,
|
||
|
|
shift.CreatedAt,
|
||
|
|
shift.UpdatedAt
|
||
|
|
);
|
||
|
|
|
||
|
|
return (dto, null, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<bool> DeleteShiftAsync(Guid id)
|
||
|
|
{
|
||
|
|
var shift = await _context.Shifts.FindAsync(id);
|
||
|
|
|
||
|
|
if (shift == null)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
_context.Shifts.Remove(shift);
|
||
|
|
await _context.SaveChangesAsync();
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool success, string? error, bool isConflict)> SignUpForShiftAsync(Guid shiftId, Guid memberId)
|
||
|
|
{
|
||
|
|
var tenantId = _tenantProvider.GetTenantId();
|
||
|
|
|
||
|
|
var shift = await _context.Shifts.FindAsync(shiftId);
|
||
|
|
|
||
|
|
if (shift == null)
|
||
|
|
return (false, "Shift not found", false);
|
||
|
|
|
||
|
|
if (shift.StartTime <= DateTimeOffset.UtcNow)
|
||
|
|
{
|
||
|
|
return (false, "Cannot sign up for past shifts", false);
|
||
|
|
}
|
||
|
|
|
||
|
|
var existingSignup = await _context.ShiftSignups
|
||
|
|
.FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == memberId);
|
||
|
|
|
||
|
|
if (existingSignup != null)
|
||
|
|
{
|
||
|
|
return (false, "Already signed up for this shift", true);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int attempt = 0; attempt < 2; attempt++)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var currentSignups = await _context.ShiftSignups
|
||
|
|
.Where(ss => ss.ShiftId == shiftId)
|
||
|
|
.CountAsync();
|
||
|
|
|
||
|
|
if (currentSignups >= shift.Capacity)
|
||
|
|
{
|
||
|
|
return (false, "Shift is at full capacity", true);
|
||
|
|
}
|
||
|
|
|
||
|
|
var signup = new ShiftSignup
|
||
|
|
{
|
||
|
|
Id = Guid.NewGuid(),
|
||
|
|
TenantId = tenantId,
|
||
|
|
ShiftId = shiftId,
|
||
|
|
MemberId = memberId,
|
||
|
|
SignedUpAt = DateTimeOffset.UtcNow
|
||
|
|
};
|
||
|
|
|
||
|
|
_context.ShiftSignups.Add(signup);
|
||
|
|
await _context.SaveChangesAsync();
|
||
|
|
|
||
|
|
return (true, null, false);
|
||
|
|
}
|
||
|
|
catch (DbUpdateConcurrencyException) when (attempt == 0)
|
||
|
|
{
|
||
|
|
_context.Entry(shift).Reload();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (false, "Shift capacity changed during sign-up", true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool success, string? error)> CancelSignupAsync(Guid shiftId, Guid memberId)
|
||
|
|
{
|
||
|
|
var signup = await _context.ShiftSignups
|
||
|
|
.FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == memberId);
|
||
|
|
|
||
|
|
if (signup == null)
|
||
|
|
{
|
||
|
|
return (false, "Sign-up not found");
|
||
|
|
}
|
||
|
|
|
||
|
|
_context.ShiftSignups.Remove(signup);
|
||
|
|
await _context.SaveChangesAsync();
|
||
|
|
|
||
|
|
return (true, null);
|
||
|
|
}
|
||
|
|
}
|