using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; /// /// Encounter retrieval, status transitions, and clinical timeline. /// [ApiController] [Route("api/v1/encounters")] [Produces("application/json")] [Authorize] public class EncountersController : ControllerBase { private readonly IEncounterService _encounters; private readonly IDischargeSummaryService _dischargeSummary; public EncountersController( IEncounterService encounters, IDischargeSummaryService dischargeSummary) { _encounters = encounters; _dischargeSummary = dischargeSummary; } /// /// Lists encounters for ward dashboards with denormalized clinical summary fields. /// /// Optional status filter (DB literal, e.g. ACTIVE). /// Optional department filter (DB literal, e.g. ICU). /// Page number (1-based). /// Results per page. [HttpGet] [AuthorizePermission(ClinicalPermissions.EncountersRead)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] public async Task List( [FromQuery] string? status, [FromQuery] string? department, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { EncounterStatus? parsedStatus = null; if (!string.IsNullOrEmpty(status)) { try { parsedStatus = EncounterStatusExtensions.FromDbString(status); } catch (ArgumentOutOfRangeException) { return BadRequest(ApiResponse.Fail(400, "Invalid status filter.", "INVALID_STATUS")); } } Department? parsedDepartment = null; if (!string.IsNullOrEmpty(department)) { try { parsedDepartment = DepartmentExtensions.FromDbString(department); } catch (ArgumentOutOfRangeException) { return BadRequest(ApiResponse.Fail(400, "Invalid department filter.", "INVALID_DEPARTMENT")); } } var result = await _encounters.ListAsync(parsedStatus, parsedDepartment, page, pageSize); return Ok(ApiResponse.Ok(new { items = result.Items, page = result.Page, pageSize = result.PageSize, totalCount = result.TotalCount, totalPages = result.TotalPages })); } /// /// Gets an encounter with patient, recent observations, and open alerts. /// /// Encounter id. /// The encounter with related data. [HttpGet("{id:guid}")] [AuthorizePermission(ClinicalPermissions.EncountersRead)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task Get(Guid id) { var encounter = await _encounters.GetByIdAsync(id); return Ok(ApiResponse.Ok(encounter)); } /// /// Transitions an encounter to a new status via the encounter state machine. /// /// Encounter id. /// Target status. /// The encounter id and new status. [HttpPatch("{id:guid}/status")] [AuthorizePermission(ClinicalPermissions.EncountersWrite)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] public async Task TransitionStatus(Guid id, [FromBody] TransitionStatusRequest req) { var result = await _encounters.TransitionStatusAsync(id, req.Status, req.DischargeDiagnosis); return Ok(ApiResponse.Ok(new { encounterId = result.EncounterId, newStatus = result.NewStatus, dischargeDiagnosis = req.DischargeDiagnosis })); } /// /// Returns a merged chronological timeline of observations and alerts for an encounter. /// /// Encounter id. /// Ordered timeline events. [HttpGet("{id:guid}/timeline")] [AuthorizePermission(ClinicalPermissions.EncountersRead)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task Timeline(Guid id) { var timeline = await _encounters.GetTimelineAsync(id); return Ok(ApiResponse.Ok(timeline)); } /// /// Returns discharge summary availability for a discharged encounter. /// [HttpGet("{id:guid}/discharge-summary")] [AuthorizePermission(ClinicalPermissions.EncountersRead)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task GetDischargeSummary(Guid id) { var info = await _dischargeSummary.GetInfoAsync(id); return Ok(ApiResponse.Ok(info)); } /// /// Downloads the generated discharge summary document. /// [HttpGet("{id:guid}/discharge-summary/content")] [AuthorizePermission(ClinicalPermissions.EncountersRead)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] public async Task DownloadDischargeSummary(Guid id) { var content = await _dischargeSummary.GetContentAsync(id); return File(content.Stream, content.ContentType, content.FileName); } }