shoc-backend/SeaHaven.Services/Implementation/WorkOrderCommentService.cs

135 lines
5.8 KiB
C#

using System.Security.Claims;
using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Exceptions;
using SeaHaven.Services.Helpers;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class WorkOrderCommentService : IWorkOrderCommentService
{
private readonly IWorkOrderDetailDataService _detailData;
private readonly ICommentDataService _commentData;
private readonly IUserDataService _userDataService;
private readonly IWorkOrderAccountResolver _accountResolver;
public WorkOrderCommentService(
IWorkOrderDetailDataService detailData,
ICommentDataService commentData,
IUserDataService userDataService,
IWorkOrderAccountResolver accountResolver)
{
_detailData = detailData;
_commentData = commentData;
_userDataService = userDataService;
_accountResolver = accountResolver;
}
public async Task<IReadOnlyList<WorkOrderCommentDto>?> GetCommentsAsync(
int workOrderId,
ClaimsPrincipal user)
{
var accountId = _accountResolver.ResolveAccountFilter(user);
if (!await _detailData.ExistsAsync(workOrderId, CancellationToken.None, accountId))
return null;
var comments = await _detailData.GetCommentsAsync(workOrderId);
var authorNames = await ResolveAuthorNamesAsync(comments);
return WorkOrderCommentProjection.ToDtos(comments, authorNames);
}
public async Task<WorkOrderCommentDto> AddCommentAsync(
int workOrderId,
WorkOrderCommentCreateDto request,
ClaimsPrincipal user,
string? actorId,
string? documentUrl = null)
{
var accountId = _accountResolver.ResolveAccountFilter(user);
var workOrder = await _detailData.GetWorkOrderForMediaAsync(
workOrderId, CancellationToken.None, accountId);
if (workOrder == null)
throw new WorkOrderBoardValidationException("NotFound", "Work order not found.");
EnsureCommentMutationAllowed(workOrder.LifecycleStatus);
if (string.IsNullOrWhiteSpace(request.Text))
throw new WorkOrderBoardValidationException("InvalidValue", "Comment text is required.");
var comment = new Comments
{
WorkerOrderId = workOrderId,
UserId = actorId,
Commenttext = request.Text.Trim(),
CommentType = "General",
RecordType = "WorkOrder",
Documents = documentUrl,
CreatedDate = DateTime.UtcNow
};
var saved = await _commentData.AddAsync(comment);
var authorNames = await ResolveAuthorNamesAsync(new[] { saved });
return WorkOrderCommentProjection.ToDto(saved, authorNames);
}
public async Task<WorkOrderCommentDto> UpdateCommentAsync(
int workOrderId,
int commentId,
WorkOrderCommentCreateDto request,
ClaimsPrincipal user,
string? actorId)
{
var accountId = _accountResolver.ResolveAccountFilter(user);
var workOrder = await _detailData.GetWorkOrderForMediaAsync(
workOrderId, CancellationToken.None, accountId);
if (workOrder == null)
throw new WorkOrderBoardValidationException("NotFound", "Work order not found.");
EnsureCommentMutationAllowed(workOrder.LifecycleStatus);
if (string.IsNullOrWhiteSpace(request.Text))
throw new WorkOrderBoardValidationException("InvalidValue", "Comment text is required.");
var comment = await _commentData.GetByIdAsync(commentId);
if (comment == null || comment.WorkerOrderId != workOrderId || comment.IsDeleted == true)
throw new WorkOrderBoardValidationException("NotFound", "Comment not found.");
if (!IsBoardEditableComment(comment))
throw new WorkOrderBoardValidationException(
"NotEditable",
"Only board comments (CommentType General) can be edited via this endpoint.");
var isAuthor = !string.IsNullOrWhiteSpace(actorId)
&& string.Equals(comment.UserId, actorId, StringComparison.Ordinal);
if (!isAuthor)
throw new WorkOrderBoardValidationException("Forbidden", "You are not allowed to edit this comment.");
comment.Commenttext = request.Text.Trim();
comment.LastModificationTime = DateTime.UtcNow;
await _commentData.UpdateAsync(comment);
var authorNames = await ResolveAuthorNamesAsync(new[] { comment });
return WorkOrderCommentProjection.ToDto(comment, authorNames);
}
private static void EnsureCommentMutationAllowed(LifecycleStatus? status)
{
if (!WorkOrderBoardMutationRules.CanMutateComments(status))
throw new WorkOrderBoardValidationException("ReadOnly", "Work order is read-only in its current status.");
}
private static bool IsBoardEditableComment(Comments comment)
=> string.Equals(comment.CommentType, "General", StringComparison.OrdinalIgnoreCase)
&& string.Equals(comment.RecordType, "WorkOrder", StringComparison.OrdinalIgnoreCase);
private async Task<IReadOnlyDictionary<string, string>> ResolveAuthorNamesAsync(
IEnumerable<Comments> comments)
{
var authorIds = WorkOrderCommentProjection.CollectAuthorUserIds(comments);
return await _userDataService.GetDisplayNamesByIdsAsync(authorIds);
}
}
}