shoc-backend/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
Arthur Bassi de0f6da8fb fix(work-orders): scope legacy GET GetComments by account [SH-221]
Pass ClaimsPrincipal into GetCommentsAsync and filter via
GetAllForAccountAsync so account-scoped callers cannot enumerate
cross-tenant comments. ADR + cross-account tests updated.
2026-08-11 15:18:29 -03:00

433 lines
18 KiB
C#

using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.Helper;
using Data.SeaHavenIndustries;
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SeaHaven.DataServices.Models;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Exceptions;
using SeaHaven.Services.Interfaces;
using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/WorkOrder")]
[Route("api/workorders")]
public class WorkOrderController : Controller
{
private readonly IWorkOrderService _workOrderService;
private readonly IWorkOrderAccountResolver _accountResolver;
private readonly ILogger<WorkOrderController> _logger;
public WorkOrderController(
IWorkOrderService workOrderService,
IWorkOrderAccountResolver accountResolver,
ILogger<WorkOrderController> logger)
{
_workOrderService = workOrderService;
_accountResolver = accountResolver;
_logger = logger;
}
[HttpPost]
[Route("AddWorkorder")]
public async Task<IActionResult> Addworkorder([FromForm] Workorder_DTO model)
{
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var input = new CreateWorkOrderWithDetailsInput
{
WorkerOrderTitle = model.WorkerOrderTitle ?? "",
Description = model.Description,
Priority = model.Priority?.ToString(),
LocationId = model.LocationId,
AssignTo = model.AssignTo,
DueDate = model.DueDate,
ContactIds = model.ContactIds,
CategoryIds = model.CategoryIds,
Attachments = model.Attachments,
BeforPhotoAttachment = model.BeforPhotoAttachment,
AfterPhotoAttachment = model.AfterPhotoAttachment,
SignOffAttachment = model.SignOffAttachment,
Customer = model.Customer
};
var workOrderId = await _workOrderService.CreateWorkOrderWithDetailsAsync(input, User, userId!);
return Ok(new DataResponse { Message = "Work order created successfully", Status = "200", Data = workOrderId });
}
catch (ValidationException vex)
{
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
return BadRequest(new Response { Status = "Validation Error", Message = errors });
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
catch (WorkOrderBoardValidationException ex)
{
return UnprocessableEntity(new Response { Status = "Error", Message = ex.Message });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpPost]
[HttpPut("{id:int}")]
[Route("EditWorkorder")]
public async Task<IActionResult> Editworkorder([FromForm] EditWorkorder_DTO model)
{
try
{
if (!model.Id.HasValue)
return BadRequest(new Response { Status = "Error", Message = "Work order ID is required" });
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var input = new UpdateWorkOrderWithDetailsInput
{
Id = model.Id.Value,
WorkerOrderTitle = model.WorkerOrderTitle,
Description = model.Description,
Status = model.Status?.ToString(),
Priority = model.Priority?.ToString(),
LocationId = model.LocationId,
AssignTo = model.AssignTo,
DueDate = model.DueDate,
CompletedDate = model.CompletedDate,
ContactIds = model.ContactIds,
CategoryIds = model.CategoryIds,
Attachments = model.Attachments,
BeforPhotoAttachment = model.BeforPhotoAttachment,
AfterPhotoAttachment = model.AfterPhotoAttachment,
SignOffAttachment = model.SignOffAttachment
};
var success = await _workOrderService.UpdateWorkOrderWithDetailsAsync(input, userId!);
if (!success)
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (ValidationException vex)
{
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
return BadRequest(new Response { Status = "Validation Error", Message = errors });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpGet("GetWorkOrderList")]
public async Task<IActionResult> GetWorkOrderList(string? search = "", int page = 1, int pageSize = 12, string? sortBy = "lastUpdated", string? sortDir = "desc", string? status = "", string? assignee = "", int? locationId = null)
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var (data, totalCount) = await _workOrderService.GetWorkOrderListPagedAsync(
page, pageSize, search, status, assignee, locationId, sortBy, sortDir, accountId);
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(viewModel);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
[Authorize(Roles = "Admin")]
[HttpDelete]
[Route("DeleteWorkorder")]
public async Task<IActionResult> Deleteworkorder([FromBody] EditWorkorder_DTO model)
{
int id = model.Id.HasValue ? model.Id.Value : 0;
if (id == 0)
return BadRequest("Id is required");
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var success = await _workOrderService.DeleteWorkOrderCascadeAsync(id, userId!);
if (!success)
return BadRequest("Work order not found!");
return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpGet]
[Route("GetFilteredWorkorder")]
public async Task<IActionResult> GetFilteredWorkorder(
[FromQuery] string[] assignto,
[FromQuery] int[]? location,
[FromQuery] string[]? priority,
[FromQuery] string[]? status,
[FromQuery] string[]? duedate,
[FromQuery] string search = "",
[FromQuery] string sort = "Sort Order",
[FromQuery] string sortby = "Sort By",
int page = 1,
int pageSize = 10)
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var result = await _workOrderService.GetFilteredWorkOrdersAsync(
assignto, location, priority, status, duedate, search, sort, sortby, page, pageSize, accountId);
return Ok(result);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
catch (Exception)
{
return StatusCode(500, "Internal Server Error");
}
}
[HttpGet("{id:int}")]
[HttpGet("GetWorkorderById")]
public async Task<IActionResult> GetWorkorderById(int id)
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var detail = await _workOrderService.GetWorkOrderDetailAsync(id, accountId);
if (detail == null)
return BadRequest(new Response { Status = "Error", Message = "ID not found!" });
return Ok(detail);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("GetFilteredWorkorder2")]
public async Task<IActionResult> GetFilteredWorkorder2(
[FromQuery] string[] assignto,
[FromQuery] int[]? location,
[FromQuery] string[]? priority,
[FromQuery] string[]? status,
[FromQuery] string[]? duedate,
[FromQuery] string search = "",
[FromQuery] string sort = "Sort Order",
[FromQuery] string sortby = "Sort By",
int page = 1,
int pageSize = 10)
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var result = await _workOrderService.GetFilteredWorkOrders2Async(
assignto, location, priority, status, duedate, search, sort, sortby, page, pageSize, accountId);
return Ok(result);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
catch (Exception)
{
var totalCount = await _workOrderService.GetTotalWorkOrderCountAsync();
return Ok(new Pagination_DTO
{
Data = Array.Empty<FilteredWorkOrderReadModel>(),
PageNumber = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
});
}
}
[HttpPost]
[Route("ChangeStatus")]
public async Task<IActionResult> ChangeStatus(int id, string status)
{
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _workOrderService.ChangeStatusAsync(id, status, userId!);
if (result == null)
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = result });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpPost]
[Route("ChangeAssignment")]
public async Task<IActionResult> ChangeAssignment(int id, string? assignTo)
{
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _workOrderService.ChangeAssignmentAsync(id, assignTo, userId!);
if (result == null)
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpPost]
[Route("AddComment")]
public async Task<IActionResult> AddComment([FromForm] Comments_DTO model)
{
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var input = new AddCommentInput
{
WorkorderId = model.WorkorderId,
Text = model.Text,
CommentType = model.CommentType,
Document = model.Document
};
var saved = await _workOrderService.AddCommentAsync(input, User, userId!);
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = saved });
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound")
{
return NotFound(new Response { Status = "Error", Message = ex.Message });
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpPost]
[Route("AddCommentJson")]
public async Task<IActionResult> AddCommentJson([FromBody] Comments_DTO model)
{
try
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var input = new AddCommentInput
{
WorkorderId = model.WorkorderId,
Text = model.Text,
CommentType = model.CommentType
};
var result = await _workOrderService.AddCommentJsonAsync(input, User, userId!);
return Ok(result);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound")
{
return NotFound(new Response { Status = "Error", Message = ex.Message });
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
}
}
[HttpGet]
[Route("GetComments")]
public async Task<IActionResult> GetComments()
{
try
{
var data = await _workOrderService.GetCommentsAsync(User);
return Ok(data);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("GetCommentsByWorkorderId")]
public async Task<IActionResult> GetCommentsByWorkorderId(int woid)
{
try
{
var data = await _workOrderService.GetCommentsByWorkorderIdAsync(woid, User);
if (data == null)
return NotFound(new Response { Status = "Error", Message = "Work order not found." });
return Ok(data);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("GetworkordersDD")]
public async Task<IActionResult> GetworkordersDD()
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var data = await _workOrderService.GetWorkordersDDAsync(accountId);
return Ok(data);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("Getworkorders")]
public async Task<IActionResult> Getworkorders()
{
try
{
var accountId = _accountResolver.ResolveAccountFilter(User);
var data = await _workOrderService.GetWorkordersAsync(accountId);
return Ok(data);
}
catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden")
{
return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = ex.Message });
}
}
}
}