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 ;
2026-09-23 01:24:43 -03:00
using SeaHaven.Services.Exceptions ;
2026-09-24 20:52:44 -03:00
using SeaHaven.Services.Helpers ;
2026-07-24 17:35:34 -03:00
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
}
2026-08-10 13:00:32 -03:00
[HttpPost("dispatches/{id:int}/refuse")]
public async Task < IActionResult > Refuse ( int id , [ FromBody ] RefuseRequest ? body , CancellationToken cancellationToken = default )
{
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
try
{
var result = await _portalService . RefuseDispatchAsync ( session , id , body ? . Reason , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException )
{
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-08-11 08:58:19 -03:00
var result = await _portalService . RequestUpliftAsync ( session , id , body ? . RequestedNTE ? ? 0 m , body ? . Reason , body ? . RequestKey , body ? . EvidenceDocumentId , cancellationToken ) ;
2026-07-24 17:35:34 -03:00
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 13:25:56 -04:00
}
2026-09-23 01:24:43 -03:00
catch ( UpliftConflictException ex )
{
return Conflict ( new Response { Status = "Error" , Message = ex . Message } ) ;
}
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-08-11 08:58:19 -03:00
// SH-101: vendor withdraw (canonical) endpoint.
[HttpPost("dispatches/{id:int}/uplift-request/{requestId:int}/withdraw")]
public async Task < IActionResult > WithdrawUpliftRequest ( int id , int requestId , CancellationToken cancellationToken = default )
{
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
try
{
var result = await _portalService . WithdrawUpliftAsync ( session , id , requestId , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException )
{
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" ) } ) ;
}
}
// SH-101: vendor revise endpoint on ChangesRequested.
[HttpPost("dispatches/{id:int}/uplift-request/{requestId:int}/revise")]
public async Task < IActionResult > ReviseUpliftRequest ( int id , int requestId , [ FromBody ] UpliftRequestBody ? body , CancellationToken cancellationToken = default )
{
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
try
{
var result = await _portalService . ReviseUpliftAsync ( session , id , requestId , body ? . RequestedNTE ? ? 0 m , body ? . Reason , body ? . EvidenceDocumentId , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException )
{
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-07-24 17:35:34 -03:00
[HttpPost("dispatches/{id:int}/completion-documents")]
2026-07-28 13:57:45 -03:00
[HttpPost("dispatches/{id:int}/documents")]
2026-09-24 20:52:44 -03:00
[RequestSizeLimit(WorkOrderMediaContract.MaxUploadRequestBytes)]
2026-07-28 13:57:45 -03:00
public async Task < IActionResult > UploadCompletionDocument (
int id ,
[FromForm] IFormFile file ,
[FromForm] int? replacesDocumentId = null ,
2026-08-11 08:58:19 -03:00
[FromForm] string? purpose = null ,
2026-07-28 13:57:45 -03:00
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-28 13:57:45 -03:00
var result = await _portalService . UploadCompletionDocumentAsync (
session ,
id ,
file ,
replacesDocumentId ,
2026-08-11 08:58:19 -03:00
purpose ,
2026-07-28 13:57:45 -03:00
cancellationToken ) ;
2026-07-24 17:35:34 -03:00
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}")]
2026-07-28 13:57:45 -03:00
[HttpGet("dispatches/{id:int}/documents/{documentId:int}")]
2026-07-24 17:35:34 -03:00
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-08-11 08:58:19 -03:00
[HttpGet("dispatches/{id:int}/documents/{documentId:int}/status")]
public async Task < IActionResult > GetDocumentStatus ( int id , int documentId , CancellationToken cancellationToken = default )
{
var session = await ResolveSessionAsync ( cancellationToken ) ;
if ( session = = null ) return Unauthorized ( new Response { Status = "Error" , Message = "Invalid or expired token" } ) ;
var result = await _portalService . GetDocumentStatusAsync ( session , id , documentId , cancellationToken ) ;
return result = = null
? NotFound ( new Response { Status = "Error" , Message = "Document not found" } )
: Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
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 ; }
}
2026-08-10 13:00:32 -03:00
public class RefuseRequest
{
public string? Reason { get ; set ; }
}
2026-04-20 12:01:53 -04:00
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-08-11 08:58:19 -03:00
// SH-101: client idempotency key and supporting evidence.
public string? RequestKey { get ; set ; }
public int? EvidenceDocumentId { get ; set ; }
2026-04-20 13:25:56 -04:00
}
2026-04-20 12:01:53 -04:00
}
}