296 lines
9.0 KiB
C#
296 lines
9.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using WorkClub.Application.Interfaces;
|
|
using WorkClub.Application.Tasks.DTOs;
|
|
using WorkClub.Domain.Entities;
|
|
using WorkClub.Domain.Enums;
|
|
using WorkClub.Infrastructure.Data;
|
|
|
|
namespace WorkClub.Api.Services;
|
|
|
|
public class TaskService
|
|
{
|
|
private readonly AppDbContext _context;
|
|
private readonly ITenantProvider _tenantProvider;
|
|
|
|
public TaskService(AppDbContext context, ITenantProvider tenantProvider)
|
|
{
|
|
_context = context;
|
|
_tenantProvider = tenantProvider;
|
|
}
|
|
|
|
public async Task<TaskListDto> GetTasksAsync(string? statusFilter, int page, int pageSize, string? currentExternalUserId = null)
|
|
{
|
|
var query = _context.WorkItems.AsQueryable();
|
|
|
|
if (!string.IsNullOrEmpty(statusFilter))
|
|
{
|
|
if (Enum.TryParse<WorkItemStatus>(statusFilter, ignoreCase: true, out var status))
|
|
{
|
|
query = query.Where(w => w.Status == status);
|
|
}
|
|
}
|
|
|
|
var total = await query.CountAsync();
|
|
|
|
var items = await query
|
|
.OrderBy(w => w.CreatedAt)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
|
|
Guid? memberId = null;
|
|
if (currentExternalUserId != null)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
memberId = await _context.Members
|
|
.Where(m => m.ExternalUserId == currentExternalUserId && m.TenantId == tenantId)
|
|
.Select(m => m.Id)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
var itemDtos = items.Select(w => new TaskListItemDto(
|
|
w.Id,
|
|
w.Title,
|
|
w.Status.ToString(),
|
|
w.AssigneeId,
|
|
w.CreatedAt,
|
|
memberId != null && w.AssigneeId == memberId
|
|
)).ToList();
|
|
|
|
return new TaskListDto(itemDtos, total, page, pageSize);
|
|
}
|
|
|
|
public async Task<TaskDetailDto?> GetTaskByIdAsync(Guid id, string? currentExternalUserId = null)
|
|
{
|
|
var workItem = await _context.WorkItems.FindAsync(id);
|
|
|
|
if (workItem == null)
|
|
return null;
|
|
|
|
Guid? memberId = null;
|
|
if (currentExternalUserId != null)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
memberId = await _context.Members
|
|
.Where(m => m.ExternalUserId == currentExternalUserId && m.TenantId == tenantId)
|
|
.Select(m => m.Id)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
return new TaskDetailDto(
|
|
workItem.Id,
|
|
workItem.Title,
|
|
workItem.Description,
|
|
workItem.Status.ToString(),
|
|
workItem.AssigneeId,
|
|
workItem.CreatedById,
|
|
workItem.ClubId,
|
|
workItem.DueDate,
|
|
workItem.CreatedAt,
|
|
workItem.UpdatedAt,
|
|
memberId != null && workItem.AssigneeId == memberId
|
|
);
|
|
}
|
|
|
|
public async Task<(TaskDetailDto? task, string? error)> CreateTaskAsync(CreateTaskRequest request, Guid createdById)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
|
|
var workItem = new WorkItem
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = tenantId,
|
|
Title = request.Title,
|
|
Description = request.Description,
|
|
Status = WorkItemStatus.Open,
|
|
ClubId = request.ClubId,
|
|
AssigneeId = request.AssigneeId,
|
|
DueDate = request.DueDate,
|
|
CreatedById = createdById,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
UpdatedAt = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
_context.WorkItems.Add(workItem);
|
|
await _context.SaveChangesAsync();
|
|
|
|
var dto = new TaskDetailDto(
|
|
workItem.Id,
|
|
workItem.Title,
|
|
workItem.Description,
|
|
workItem.Status.ToString(),
|
|
workItem.AssigneeId,
|
|
workItem.CreatedById,
|
|
workItem.ClubId,
|
|
workItem.DueDate,
|
|
workItem.CreatedAt,
|
|
workItem.UpdatedAt,
|
|
false
|
|
);
|
|
|
|
return (dto, null);
|
|
}
|
|
|
|
public async Task<(TaskDetailDto? task, string? error, bool isConflict)> UpdateTaskAsync(Guid id, UpdateTaskRequest request, string? currentExternalUserId = null)
|
|
{
|
|
var workItem = await _context.WorkItems.FindAsync(id);
|
|
|
|
if (workItem == null)
|
|
return (null, "Task not found", false);
|
|
|
|
if (request.Title != null)
|
|
workItem.Title = request.Title;
|
|
|
|
if (request.Description != null)
|
|
workItem.Description = request.Description;
|
|
|
|
if (request.AssigneeId.HasValue)
|
|
workItem.AssigneeId = request.AssigneeId;
|
|
|
|
if (request.DueDate.HasValue)
|
|
workItem.DueDate = request.DueDate;
|
|
|
|
if (request.Status != null)
|
|
{
|
|
if (!Enum.TryParse<WorkItemStatus>(request.Status, ignoreCase: true, out var newStatus))
|
|
{
|
|
return (null, $"Invalid status: {request.Status}", false);
|
|
}
|
|
|
|
if (!workItem.CanTransitionTo(newStatus))
|
|
{
|
|
return (null, $"Cannot transition from {workItem.Status} to {newStatus}", false);
|
|
}
|
|
|
|
workItem.TransitionTo(newStatus);
|
|
}
|
|
|
|
workItem.UpdatedAt = DateTimeOffset.UtcNow;
|
|
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
return (null, "Task was modified by another user. Please refresh and try again.", true);
|
|
}
|
|
|
|
Guid? memberId = null;
|
|
if (currentExternalUserId != null)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
memberId = await _context.Members
|
|
.Where(m => m.ExternalUserId == currentExternalUserId && m.TenantId == tenantId)
|
|
.Select(m => m.Id)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
var dto = new TaskDetailDto(
|
|
workItem.Id,
|
|
workItem.Title,
|
|
workItem.Description,
|
|
workItem.Status.ToString(),
|
|
workItem.AssigneeId,
|
|
workItem.CreatedById,
|
|
workItem.ClubId,
|
|
workItem.DueDate,
|
|
workItem.CreatedAt,
|
|
workItem.UpdatedAt,
|
|
memberId != null && workItem.AssigneeId == memberId
|
|
);
|
|
|
|
return (dto, null, false);
|
|
}
|
|
|
|
public async Task<bool> DeleteTaskAsync(Guid id)
|
|
{
|
|
var workItem = await _context.WorkItems.FindAsync(id);
|
|
|
|
if (workItem == null)
|
|
return false;
|
|
|
|
_context.WorkItems.Remove(workItem);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<(bool success, string? error)> AssignToMeAsync(Guid taskId, string externalUserId)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
var memberId = await _context.Members
|
|
.Where(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId)
|
|
.Select(m => m.Id)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (memberId == Guid.Empty)
|
|
return (false, "User is not a member of this club");
|
|
|
|
var workItem = await _context.WorkItems.FindAsync(taskId);
|
|
if (workItem == null)
|
|
return (false, "Task not found");
|
|
|
|
if (workItem.AssigneeId.HasValue)
|
|
return (false, "Task is already assigned");
|
|
|
|
workItem.AssigneeId = memberId;
|
|
|
|
if (workItem.CanTransitionTo(WorkItemStatus.Assigned))
|
|
workItem.TransitionTo(WorkItemStatus.Assigned);
|
|
|
|
workItem.UpdatedAt = DateTimeOffset.UtcNow;
|
|
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
return (false, "Task was modified by another user");
|
|
}
|
|
|
|
return (true, null);
|
|
}
|
|
|
|
public async Task<(bool success, string? error)> UnassignFromMeAsync(Guid taskId, string externalUserId)
|
|
{
|
|
var tenantId = _tenantProvider.GetTenantId();
|
|
var memberId = await _context.Members
|
|
.Where(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId)
|
|
.Select(m => m.Id)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (memberId == Guid.Empty)
|
|
return (false, "User is not a member of this club");
|
|
|
|
var workItem = await _context.WorkItems.FindAsync(taskId);
|
|
if (workItem == null)
|
|
return (false, "Task not found");
|
|
|
|
if (workItem.AssigneeId != memberId)
|
|
return (false, "Task is not assigned to you");
|
|
|
|
workItem.AssigneeId = null;
|
|
|
|
if (workItem.Status == WorkItemStatus.Assigned || workItem.Status == WorkItemStatus.InProgress)
|
|
{
|
|
// Transition back to open if no longer assigned and not marked Review/Done
|
|
workItem.Status = WorkItemStatus.Open;
|
|
}
|
|
|
|
workItem.UpdatedAt = DateTimeOffset.UtcNow;
|
|
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
return (false, "Task was modified by another user");
|
|
}
|
|
|
|
return (true, null);
|
|
}
|
|
}
|