using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.Helper; using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using FluentValidation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; using System.Security.Claims; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/WorkOrder")] [Route("api/workorders")] public class WorkOrderBoardController : Controller { private readonly IWorkOrderBoardService _workOrderBoardService; private readonly IWorkOrderBoardUpdateService _boardUpdateService; private readonly IWorkOrderBoardCreateService _boardCreateService; private readonly IWorkOrderBoardCancelService _boardCancelService; private readonly IWorkOrderAdvancedSearchService _advancedSearchService; public WorkOrderBoardController( IWorkOrderBoardService workOrderBoardService, IWorkOrderBoardUpdateService boardUpdateService, IWorkOrderBoardCreateService boardCreateService, IWorkOrderBoardCancelService boardCancelService, IWorkOrderAdvancedSearchService advancedSearchService) { _workOrderBoardService = workOrderBoardService; _boardUpdateService = boardUpdateService; _boardCreateService = boardCreateService; _boardCancelService = boardCancelService; _advancedSearchService = advancedSearchService; } /// /// Weekly board payload. /// When is true, the authenticated user filter wins and /// is ignored (not AND-combined). /// Weekend WOs in a Mon–Sun window may have dayGroup = null; FE should handle that. /// [HttpGet("board")] public async Task GetBoard( [FromQuery, BindRequired] DateOnly weekStart, [FromQuery] DateOnly? weekEnd = null, [FromQuery] List? dispatchers = null, [FromQuery] bool myWorkOrders = false, [FromQuery] List? types = null, [FromQuery] bool overdue = false, [FromQuery] string? search = null, CancellationToken cancellationToken = default) { var resolvedWeekEnd = weekEnd ?? weekStart.AddDays(4); var weekValidationError = WorkOrderOperationalWeek.ValidateWeekWindow(weekStart, resolvedWeekEnd); if (weekValidationError != null) return BadRequest(weekValidationError); try { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var query = new WorkOrderBoardQueryDto { WeekStart = weekStart, WeekEnd = weekEnd, Dispatchers = dispatchers, MyWorkOrders = myWorkOrders, Types = types, Overdue = overdue, Search = search }; var result = await _workOrderBoardService.GetBoardAsync(query, User, userId, cancellationToken); return Ok(result); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (ArgumentException ex) { return BadRequest(ex.Message); } } [HttpGet("board/search")] public async Task SearchBoard([FromQuery] WorkOrderAdvancedSearchQueryDto query) { try { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var result = await _advancedSearchService.SearchAsync(query, User, userId); return Ok(result); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (ArgumentException ex) { return BadRequest(new Response { Status = "Error", Message = ex.Message }); } } /// /// Dispatcher dropdown options. Currently returns all non-deleted users /// (role-filtered assignee list deferred until roles settle). /// [HttpGet("lookups/dispatchers")] public async Task GetDispatcherLookups() { var result = await _workOrderBoardService.GetDispatcherLookupsAsync(); return Ok(result); } [HttpPatch("{id:int}/board")] public async Task PatchBoardField(int id, [FromBody] WorkOrderBoardPatchRequestDto request) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var row = await _boardUpdateService.PatchFieldAsync(id, request, actorId); return Ok(row); } catch (WorkOrderBoardConcurrencyException ex) { return Conflict(new WorkOrderBoardConflictDto { CurrentState = ex.CurrentState }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (ArgumentException ex) { return BadRequest(new Response { Status = "Error", Message = ex.Message }); } } [HttpPost("board")] public async Task CreateBoardWorkOrder( [FromBody] WorkOrderBoardCreateRequestDto request, CancellationToken cancellationToken) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var row = await _boardCreateService.CreateAsync(request, User, actorId, cancellationToken); return Ok(row); } 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 WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "DuplicateWoNumber") { return Conflict(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } [HttpPost("{id:int}/cancel")] public async Task CancelBoardWorkOrder(int id) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var row = await _boardCancelService.CancelAsync(id, User, actorId); return Ok(row); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } } }