mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
208 lines
8.3 KiB
C#
208 lines
8.3 KiB
C#
using Api.SeaHavenIndustries.Helper;
|
|
using Data.SeaHavenIndustries;
|
|
using FluentValidation;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using SeaHaven.DataServices.Models;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Interfaces;
|
|
using System.Security.Claims;
|
|
|
|
namespace Api.SeaHavenIndustries.Controllers
|
|
{
|
|
[Authorize]
|
|
[ApiController]
|
|
[Route("api/vendor-company-roster")]
|
|
public class VendorCompanyRosterController : Controller
|
|
{
|
|
private readonly IVendorCompanyRosterService _rosterService;
|
|
private readonly ILogger<VendorCompanyRosterController> _logger;
|
|
|
|
public VendorCompanyRosterController(
|
|
IVendorCompanyRosterService rosterService,
|
|
ILogger<VendorCompanyRosterController> logger)
|
|
{
|
|
_rosterService = rosterService;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> Get(
|
|
[FromQuery] int? vendorId,
|
|
[FromQuery] int? companyId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
|
|
try
|
|
{
|
|
var roster = await _rosterService.GetRosterAsync(vendorId, companyId, userId, cancellationToken);
|
|
if (roster == null)
|
|
return NotFound(new Response { Status = "Error", Message = "Vendor roster not found" });
|
|
|
|
return Ok(roster);
|
|
}
|
|
catch (ValidationException vex)
|
|
{
|
|
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
|
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> Create([FromBody] CreateVendorRosterDTO model, CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
|
|
try
|
|
{
|
|
var roster = await _rosterService.CreateRosterAsync(model, userId, cancellationToken);
|
|
return Ok(roster);
|
|
}
|
|
catch (ValidationException vex)
|
|
{
|
|
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
|
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
}
|
|
catch (VendorRosterDuplicateNameException dup)
|
|
{
|
|
return Conflict(new
|
|
{
|
|
Status = "Conflict",
|
|
Message = _logger.Sanitize(dup, "Another vendor company already uses that name."),
|
|
Code = "duplicate_vendor_company_name"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPut("{companyId:int}")]
|
|
public async Task<IActionResult> Reconcile(
|
|
int companyId,
|
|
[FromBody] ReconcileVendorRosterDTO model,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
|
|
try
|
|
{
|
|
var roster = await _rosterService.ReconcileRosterAsync(companyId, model, userId, cancellationToken);
|
|
return Ok(roster);
|
|
}
|
|
catch (ValidationException vex)
|
|
{
|
|
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
|
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
return NotFound(new Response { Status = "Error", Message = "Vendor company not found" });
|
|
}
|
|
catch (VendorRosterConflictException conflict)
|
|
{
|
|
return Conflict(new
|
|
{
|
|
Status = "Conflict",
|
|
Message = _logger.Sanitize(conflict, "Vendor roster changes conflict with open work orders"),
|
|
BlockedWorkOrders = conflict.BlockedWorkOrders
|
|
});
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
return Conflict(new
|
|
{
|
|
Status = "Conflict",
|
|
Message = "The vendor company was modified by another user. Refresh and retry.",
|
|
Code = 409
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
// Additive partial update (SH-250): inserts the submitted technicians and
|
|
// optionally updates company fields. A technician absent from the payload is
|
|
// never removed, so 409 here can only mean a stale row version.
|
|
[HttpPatch("{companyId:int}")]
|
|
public async Task<IActionResult> AddTechnicians(
|
|
int companyId,
|
|
[FromBody] AddTechniciansVendorRosterDTO model,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
|
|
try
|
|
{
|
|
var roster = await _rosterService.AddTechniciansAsync(companyId, model, userId, cancellationToken);
|
|
return Ok(roster);
|
|
}
|
|
catch (ValidationException vex)
|
|
{
|
|
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
|
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
return NotFound(new Response { Status = "Error", Message = "Vendor company not found" });
|
|
}
|
|
catch (VendorRosterDuplicateNameException dup)
|
|
{
|
|
// SH-250: a colliding rename is a client conflict, not a server fault.
|
|
return Conflict(new
|
|
{
|
|
Status = "Conflict",
|
|
Message = _logger.Sanitize(dup, "Another vendor company already uses that name."),
|
|
Code = 409
|
|
});
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
return Conflict(new
|
|
{
|
|
Status = "Conflict",
|
|
Message = "The vendor company was modified by another user. Refresh and retry.",
|
|
Code = 409
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
}
|
|
}
|