2026-04-20 12:01:53 -04:00
using Api.SeaHavenIndustries.Helper ;
using Data.SeaHavenIndustries ;
2026-07-24 17:35:34 -03:00
using Microsoft.AspNetCore.Http ;
2026-04-20 12:01:53 -04:00
using Microsoft.AspNetCore.Mvc ;
2026-07-24 17:35:34 -03:00
using Microsoft.Extensions.Logging ;
using SeaHaven.Services.DTOs ;
using SeaHaven.Services.Interfaces ;
2026-04-20 12:01:53 -04:00
namespace Api.SeaHavenIndustries.Controllers
{
[ApiController]
[Route("api/vendor-portal")]
[Route("api/vendorportal")]
public class VendorPortalController : Controller
{
private const string TokenHeader = "X-Vendor-Token" ;
2026-07-24 17:35:34 -03:00
private readonly IVendorPortalService _portalService ;
private readonly ILogger < VendorPortalController > _logger ;
public VendorPortalController ( IVendorPortalService portalService , ILogger < VendorPortalController > logger )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
_portalService = portalService ;
_logger = logger ;
2026-04-20 12:01:53 -04:00
}
[HttpGet("session")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > Session ( CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
return Ok ( new DataResponse
{
Status = "Success" ,
2026-07-24 17:35:34 -03:00
Data = session
2026-04-20 12:01:53 -04:00
} ) ;
}
[HttpGet("dispatches")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > ListDispatches ( [ FromQuery ] string? status = null , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
var dispatches = await _portalService . ListDispatchesAsync ( session , status , cancellationToken ) ;
2026-04-20 12:01:53 -04:00
return Ok ( new DataResponse { Status = "Success" , Data = dispatches } ) ;
}
[HttpGet("dispatches/{id:int}")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > GetDispatch ( int id , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
var detail = await _portalService . GetDispatchDetailAsync ( session , id , cancellationToken ) ;
if ( detail = = null ) return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
return Ok ( new DataResponse { Status = "Success" , Data = detail } ) ;
2026-04-20 12:01:53 -04:00
}
[HttpPost("dispatches/{id:int}/accept")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > Accept ( int id , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . AcceptDispatchAsync ( session , id , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-04-20 12:01:53 -04:00
}
[HttpPost("dispatches/{id:int}/status")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > ChangeStatus ( int id , [ FromBody ] ChangeStatusRequest body , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . ChangeStatusAsync ( session , id , body ? . Status , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-04-20 12:01:53 -04:00
}
[HttpPost("dispatches/{id:int}/request-cancel")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > RequestCancel ( int id , [ FromBody ] RequestCancelRequest body , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
await _portalService . RequestCancelAsync ( session , id , body ? . Reason , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Message = "Cancel request sent to dispatcher" } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
}
catch ( InvalidOperationException ex )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-04-20 12:01:53 -04:00
}
[HttpPost("dispatches/{id:int}/checklist/{itemId:int}")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > UpdateChecklistItem ( int id , int itemId , [ FromBody ] ChecklistUpdateRequest body , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . UpdateChecklistItemAsync ( session , id , itemId , body . IsCompleted , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-04-20 12:01:53 -04:00
}
[HttpPost("dispatches/{id:int}/signoff")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > AddSignoff ( int id , [ FromBody ] SignoffRequest body , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . AddSignoffAsync ( session , id , body ? . SignoffType , body ? . Name , body ? . Signature , body ? . SignatureMethod , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( InvalidOperationException ex )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
2026-04-20 12:01:53 -04:00
}
}
[HttpPost("dispatches/{id:int}/comments")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > AddComment ( int id , [ FromBody ] CommentRequest body , CancellationToken cancellationToken = default )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 12:01:53 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . AddCommentAsync ( session , id , body ? . CommentText , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 12:01:53 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
}
catch ( InvalidOperationException ex )
2026-04-20 12:01:53 -04:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-04-20 12:01:53 -04:00
}
2026-04-20 13:25:56 -04:00
[HttpPost("dispatches/{id:int}/uplift-request")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > RequestUplift ( int id , [ FromBody ] UpliftRequestBody body , CancellationToken cancellationToken = default )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-04-20 13:25:56 -04:00
2026-07-24 17:35:34 -03:00
try
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . RequestUpliftAsync ( session , id , body ? . RequestedNTE ? ? 0 m , body ? . Reason , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 13:25:56 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
2026-04-20 13:25:56 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( InvalidOperationException ex )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
2026-04-20 13:25:56 -04:00
}
}
[HttpPost("dispatches/{id:int}/uplift-request/{requestId:int}/cancel")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > CancelUpliftRequest ( int id , int requestId , CancellationToken cancellationToken = default )
2026-07-23 19:18:06 -03:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-07-23 19:18:06 -03:00
try
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . CancelUpliftRequestAsync ( session , id , requestId , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-07-23 19:18:06 -03:00
}
2026-07-24 17:35:34 -03:00
catch ( KeyNotFoundException )
2026-07-23 19:18:06 -03:00
{
2026-07-24 17:35:34 -03:00
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
2026-07-23 19:18:06 -03:00
}
2026-07-24 17:35:34 -03:00
catch ( InvalidOperationException ex )
2026-07-23 19:18:06 -03:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The requested action could not be completed for this dispatch" ) } ) ;
}
2026-07-23 19:18:06 -03:00
}
2026-07-24 17:35:34 -03:00
[HttpPost("dispatches/{id:int}/completion-documents")]
[RequestSizeLimit(10_000_000)]
public async Task < IActionResult > UploadCompletionDocument ( int id , [ FromForm ] IFormFile file , CancellationToken cancellationToken = default )
2026-07-23 19:18:06 -03:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
2026-07-23 19:18:06 -03:00
2026-04-20 13:25:56 -04:00
try
{
2026-07-24 17:35:34 -03:00
var result = await _portalService . UploadCompletionDocumentAsync ( session , id , file , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException )
{
return NotFound ( new Response { Status = "Error" , Message = "Dispatch not found" } ) ;
2026-04-20 13:25:56 -04:00
}
2026-07-24 17:35:34 -03:00
catch ( InvalidOperationException ex )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "The completion document could not be uploaded" ) } ) ;
2026-04-20 13:25:56 -04:00
}
}
2026-07-24 17:35:34 -03:00
[HttpGet("dispatches/{id:int}/completion-documents/{documentId:int}")]
public async Task < IActionResult > DownloadCompletionDocument ( int id , int documentId , CancellationToken cancellationToken = default )
2026-07-23 19:18:06 -03:00
{
2026-07-24 17:35:34 -03:00
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
var result = await _portalService . DownloadCompletionDocumentAsync ( session , id , documentId , cancellationToken ) ;
return result . Outcome switch
{
VendorDocumentDownloadOutcome . Ok = > File ( result . Content ! , result . ContentType ! , result . FileName ! ) ,
VendorDocumentDownloadOutcome . Locked = > StatusCode ( StatusCodes . Status423Locked , new Response { Status = "Locked" , Message = "The completion document is not available for download yet." } ) ,
_ = > NotFound ( new Response { Status = "Error" , Message = "Completion document not found" } )
} ;
2026-07-23 19:18:06 -03:00
}
2026-07-24 17:35:34 -03:00
private async Task < VendorPortalSession ? > ResolveSessionAsync ( CancellationToken cancellationToken )
2026-04-20 12:01:53 -04:00
{
if ( ! Request . Headers . TryGetValue ( TokenHeader , out var values ) ) return null ;
var token = values . ToString ( ) ;
2026-07-24 17:35:34 -03:00
return await _portalService . ResolveSessionAsync ( token , cancellationToken ) ;
2026-04-20 12:01:53 -04:00
}
public class ChangeStatusRequest
{
public string? Status { get ; set ; }
}
public class RequestCancelRequest
{
public string? Reason { get ; set ; }
}
public class ChecklistUpdateRequest
{
public bool IsCompleted { get ; set ; }
}
public class SignoffRequest
{
public string? Name { get ; set ; }
public string? Signature { get ; set ; }
public string? SignatureMethod { get ; set ; }
public string? SignoffType { get ; set ; }
}
public class CommentRequest
{
public string? CommentText { get ; set ; }
}
2026-04-20 13:25:56 -04:00
public class UpliftRequestBody
{
public decimal RequestedNTE { get ; set ; }
public string? Reason { get ; set ; }
}
2026-04-20 12:01:53 -04:00
}
}