Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add project documentation #245

Merged
merged 9 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
<PackageVersion Include="CPlugin.Net.Attributes" Version="1.0.0" />
<PackageVersion Include="CopySqlFilesToOutputDirectory" Version="1.0.0" />
<PackageVersion Include="YeSql.Net" Version="1.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageVersion Include="Twilio" Version="6.2.1" />
<PackageVersion Include="coverlet.msbuild" Version="3.1.2" />
<PackageVersion Include="FluentAssertions" Version="6.10.0" />
Expand Down
35 changes: 17 additions & 18 deletions src/Core/AppointmentScheduling/SchedulingController.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,51 @@
namespace DentallApp.Core.AppointmentScheduling;

/// <summary>
/// Define las acciones necesarias para el agendamiento de una cita.
/// </summary>
[AuthorizeByRole(RoleName.Secretary)]
[Route("scheduling")]
[ApiController]
public class SchedulingController(ISchedulingQueries schedulingQueries)
{
/// <summary>
/// Obtiene los consultorios activos para el agendamiento.
/// El consultorio debe tener al menos un horario activo.
/// Gets the active offices for scheduling.
/// The office must have at least one active schedule.
/// </summary>
/// <remarks>
/// Solicitud de muestra:
///
/// Sample request:
///
/// GET /office
/// {
/// "title": "Mapasingue",
/// "value": "1"
/// "title": "Mapasingue",
/// "value": "1"
/// }
/// Nota: La propiedad <c>value</c> almacena el ID del consultorio.
///
/// Note: The <c>value</c> property stores the ID of the office.
/// </remarks>
[HttpGet("office")]
public async Task<List<SchedulingResponse>> GetOffices()
=> await schedulingQueries.GetOfficesAsync();

/// <summary>
/// Obtiene los servicios dentales activos para el agendamiento.
/// El servicio dental debe tener al menos un tratamiento específico.
/// Gets the active dental services for scheduling.
/// The dental service must have at least one specific treatment.
/// </summary>
/// <remarks>
/// Solicitud de muestra:
/// Sample request:
///
/// GET /dental-service
/// {
/// "title": "Ortodoncia/brackets",
/// "value": "1"
/// "title": "Ortodoncia/brackets",
/// "value": "1"
/// }
/// Nota: La propiedad <c>value</c> almacena el ID del servicio dental.
///
/// Note: The <c>value</c> property stores the ID of the dental service.
/// </remarks>
[HttpGet("dental-service")]
public async Task<List<SchedulingResponse>> GetDentalServices()
=> await schedulingQueries.GetDentalServicesAsync();

/// <summary>
/// Obtiene los odontólogos activos de un consultorio para el agendamiento.
/// El odontólogo debe tener al menos un horario activo.
/// Gets the active dentists in a office for scheduling.
/// The dentist must have at least one active schedule.
/// </summary>
[HttpGet("dentist")]
public async Task<List<SchedulingResponse>> GetDentists([FromQuery]SchedulingGetDentistsRequest request)
Expand Down
3 changes: 3 additions & 0 deletions src/Core/AppointmentStatuses/AppointmentStatusController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ namespace DentallApp.Core.AppointmentStatuses;
[ApiController]
public class AppointmentStatusController
{
/// <summary>
/// Gets the appointment statuses.
/// </summary>
[HttpGet]
public async Task<IEnumerable<GetAppointmentStatusesResponse>> GetAll(
GetAppointmentStatusesUseCase useCase)
Expand Down
54 changes: 39 additions & 15 deletions src/Core/Appointments/AppointmentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
public class AppointmentController
{
/// <summary>
/// Crea una cita médica para cualquier persona.
/// Creates a medical appointment for a customer.
/// </summary>
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Result>(StatusCodes.Status422UnprocessableEntity)]
[AuthorizeByRole(RoleName.Secretary)]
[HttpPost]
public async Task<Result<CreatedId>> Create(
Expand All @@ -15,8 +18,13 @@ public async Task<Result<CreatedId>> Create(
=> await useCase.ExecuteAsync(request);

/// <summary>
/// Actualiza el estado de una cita por su ID.
/// Updates the status of an appointment by ID.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Result>(StatusCodes.Status404NotFound)]
[ProducesResponseType<Result>(StatusCodes.Status409Conflict)]
[ProducesResponseType<Result>(StatusCodes.Status403Forbidden)]
[AuthorizeByRole(RoleName.Secretary, RoleName.Dentist, RoleName.Admin, RoleName.Superadmin)]
[HttpPut("{id}")]
public async Task<Result> Update(
Expand All @@ -26,8 +34,11 @@ public async Task<Result> Update(
=> await useCase.ExecuteAsync(id, request);

/// <summary>
/// Permite al usuario básico cancelar su cita agendada.
/// Allows the current basic user to cancel their scheduled appointment.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status404NotFound)]
[ProducesResponseType<Result>(StatusCodes.Status403Forbidden)]
[AuthorizeByRole(RoleName.BasicUser)]
[HttpDelete("{id}/basic-user")]
public async Task<Result> CancelBasicUserAppointment(
Expand All @@ -36,14 +47,20 @@ public async Task<Result> CancelBasicUserAppointment(
=> await useCase.ExecuteAsync(id);

/// <summary>
/// Permite cancelar las citas agendadas de los odontólogos.
/// Cancels scheduled appointments of any dentist.
/// </summary>
/// <remarks>
/// Detalles a tomar en cuenta:
/// <para>- El odontólogo solo podrá cancelar sus propias citas.</para>
/// <para>- La secretaria/admin solo pueden cancelar las citas del consultorio al que pertenecen.</para>
/// <para>- El superadmin puede cancelar las citas de cualquier consultorio.</para>
/// Details to consider:
/// <para>- The dentist may only cancel his own appointments.</para>
/// <para>- The secretary/admin can only cancel appointments for the office to which they belong.</para>
/// <para>- The superadmin can cancel appointments for any office.</para>
/// </remarks>
/// <response code="400">
/// Returns appointments that could not be cancelled.
/// Past appointments cannot be cancelled.
/// </response>
[ProducesResponseType<Result>(StatusCodes.Status200OK)]
[ProducesResponseType<Result<CancelAppointmentsResponse>>(StatusCodes.Status400BadRequest)]
[AuthorizeByRole(RoleName.Secretary, RoleName.Dentist, RoleName.Admin, RoleName.Superadmin)]
[HttpPost("cancel/dentist")]
public async Task<Result<CancelAppointmentsResponse>> CancelAppointments(
Expand All @@ -52,23 +69,27 @@ public async Task<Result<CancelAppointmentsResponse>> CancelAppointments(
=> await useCase.ExecuteAsync(request);

/// <summary>
/// Obtiene el historial de citas del usuario básico.
/// Gets the appointment history of the current basic user.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[AuthorizeByRole(RoleName.BasicUser)]
[HttpGet("basic-user")]
public async Task<IEnumerable<GetAppointmentsByCurrentUserIdResponse>> GetByCurrentUserId(
GetAppointmentsByCurrentUserIdUseCase useCase)
=> await useCase.ExecuteAsync();

/// <summary>
/// Obtiene las citas de los odontólogos para un empleado.
/// Gets the appointments from a specified date range.
/// </summary>
/// <remarks>
/// Detalles a tomar en cuenta:
/// <para>- Sí <see cref="GetAppointmentsByDateRangeRequest.OfficeId"/> es <c>0</c>, traerá las citas de TODOS los consultorios.</para>
/// <para>- Sí <see cref="GetAppointmentsByDateRangeRequest.DentistId"/> es <c>0</c>, traerá las citas de TODOS los odontólogos.</para>
/// <para>- Sí <see cref="GetAppointmentsByDateRangeRequest.StatusId"/> es <c>0</c>, traerá las citas de TODOS los estados.</para>
/// Details to consider:
/// <para>- If <c>OfficeId</c> is <c>0</c>, it will retrieve appointments from all offices.</para>
/// <para>- If <c>DentistId</c> is <c>0</c>, it will retrieve appointments from all dentists.</para>
/// <para>- If <c>StatusId</c> is <c>0</c>, it will retrieve appointments from all statuses.</para>
/// </remarks>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Result>(StatusCodes.Status403Forbidden)]
[AuthorizeByRole(RoleName.Secretary, RoleName.Dentist, RoleName.Admin, RoleName.Superadmin)]
[HttpPost("dentist")]
public async Task<ListedResult<GetAppointmentsByDateRangeResponse>> GetByDateRange(
Expand All @@ -77,8 +98,11 @@ public async Task<ListedResult<GetAppointmentsByDateRangeResponse>> GetByDateRan
=> await useCase.ExecuteAsync(request);

/// <summary>
/// Obtiene las horas disponibles para la reserva de una cita.
/// Gets the available hours for the reservation of an appointment.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Result>(StatusCodes.Status422UnprocessableEntity)]
[AuthorizeByRole(RoleName.Secretary)]
[Route("available-hours")]
[HttpPost]
Expand Down
32 changes: 20 additions & 12 deletions src/Core/Appointments/UseCases/GetAvailableHours/Availability.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
namespace DentallApp.Core.Appointments.UseCases.GetAvailableHours;

/// <summary>
/// Representa la disponibilidad de horas para el agendamiento de citas.
/// Represents the availability of hours for appointment scheduling.
/// </summary>
public static class Availability
{
/// <summary>
/// Comprueba sí la nueva hora de inicio y finalización no están disponible para una reserva de cita.
/// Checks if the new start and end hour is not available for an appointment booking.
/// </summary>
/// <remarks>El método verificará sí la nueva franja de horario entra en conflicto con la franja de horario ocupada.</remarks>
/// <param name="newStartHour">La nueva hora de inicio generada.</param>
/// <param name="newEndHour">La nueva hora de finalización generada.</param>
/// <param name="unavailableTimeRange">Una instancia con el rango de tiempo no disponible.</param>
/// <returns><c>true</c> sí la nueva franja de horario no está disponible, de lo contrario devuelve <c>false</c>.</returns>
/// <remarks>
/// The method will check if the new time slot conflicts with the occupied time slot.
/// </remarks>
/// <param name="newStartHour">The new start hour generated.</param>
/// <param name="newEndHour">The new end hour generated.</param>
/// <param name="unavailableTimeRange">An instance with the time range not available.</param>
/// <returns>
/// true if the new time slot is not available, otherwise it returns false.
/// </returns>
public static bool IsNotAvailable(ref TimeSpan newStartHour, ref TimeSpan newEndHour, UnavailableTimeRangeResponse unavailableTimeRange)
=> unavailableTimeRange.StartHour < newEndHour && newStartHour < unavailableTimeRange.EndHour;

/// <summary>
/// Calcula las horas disponibles para la reserva de una cita médica.
/// Calculate the hours available to book a medical appointment.
/// </summary>
/// <param name="options">Una instancia con las opciones requeridas para obtener las horas disponibles.</param>
/// <returns>Una colección con las horas disponibles, de lo contrario devuelve <c>null</c>.</returns>
/// <param name="options">
/// An instance with the required options to obtain the available hours.
/// </param>
/// <returns>
/// A collection with the available hours, otherwise returns <c>null</c>.
/// </returns>
public static List<AvailableTimeRangeResponse> CalculateAvailableHours(AvailabilityOptions options)
{
if (options.ServiceDuration == TimeSpan.Zero)
Expand All @@ -29,7 +37,7 @@ public static List<AvailableTimeRangeResponse> CalculateAvailableHours(Availabil
var availableHours = new List<AvailableTimeRangeResponse>();
int unavailableTimeRangeIndex = 0;
int totalUnavailableHours = options.Unavailables.Count;
// Para verificar sí la fecha de la cita no es la fecha actual.
// To verify if the date of the appointment is not the current date.
bool appointmentDateIsNotCurrentDate = options.CurrentTimeAndDate.Date != options.AppointmentDate;
TimeSpan currentTime = options.CurrentTimeAndDate.TimeOfDay;
TimeSpan newStartHour = options.DentistStartHour;
Expand Down Expand Up @@ -65,7 +73,7 @@ public static List<AvailableTimeRangeResponse> CalculateAvailableHours(Availabil
}

/// <summary>
/// Obtiene el siguiente índice de rango de tiempo no disponible.
/// Gets the following unavailable time range index.
/// </summary>
private static void MoveNextUnavailableTimeRangeIndex(this ref int i) => i++;
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
namespace DentallApp.Core.Appointments.UseCases.GetAvailableHours;

/// <summary>
/// Representa las opciones que se utilizan para el método <see cref="Availability.CalculateAvailableHours(AvailabilityOptions)"/>.
/// Represents the options used for the method called
/// <see cref="Availability.CalculateAvailableHours(AvailabilityOptions)"/>.
/// </summary>
public class AvailabilityOptions
{
/// <summary>
/// Obtiene o establece la hora de trabajo de inicio del odontólogo.
/// Gets or sets the dentist's start hour.
/// </summary>
public TimeSpan DentistStartHour { get; init; }

/// <summary>
/// Obtiene o establece la hora de trabajo de finalización del odontólogo.
/// Gets or sets the dentist's end hour.
/// </summary>
public TimeSpan DentistEndHour { get; init; }

/// <summary>
/// Obtiene o establece el tiempo de duración de un servicio dental (debe estar expresado en minutos).
/// Gets or sets the duration time of a dental service (must be expressed in minutes).
/// </summary>
public TimeSpan ServiceDuration { get; init; }

/// <summary>
/// Obtiene o establece una colección con los rangos de tiempos no disponibles.
/// La colección debe estar ordenada de forma ascendente y no debe tener franjas de horario duplicadas.
/// Gets or sets a collection of unavailable time ranges.
/// </summary>
/// <remarks>
/// The collection should be sorted in ascending order and should not have duplicate time slots.
/// </remarks>
public List<UnavailableTimeRangeResponse> Unavailables { get; init; }

/// <summary>
/// Obtiene o establece la fecha de la cita.
/// Gets or sets the date of the appointment.
/// </summary>
public DateTime? AppointmentDate { get; init; }

/// <summary>
/// Obtiene o establece la fecha y hora actual.
/// Gets or sets the current date and time.
/// </summary>
public DateTime CurrentTimeAndDate { get; init; } = DateTime.Now;
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ public async Task<ListedResult<AvailableTimeRangeResponse>> ExecuteAsync(Availab
}

/// <summary>
/// Obtiene el tiempo libre (o punto de descanso) del empleado.
/// Este tiempo se descarta para el cálculo de los horarios disponibles.
/// Gets the employee's time off.
/// </summary>
/// <remarks>This time is discarded for the calculation of the available hours.</remarks>
private static UnavailableTimeRangeResponse GetTimeOff(EmployeeScheduleResponse employeeSchedule) => new()
{
StartHour = (TimeSpan)employeeSchedule.MorningEndHour,
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Appointments/UseCases/GetByCurrentUserId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public class GetAppointmentsByCurrentUserIdResponse
public string EndHour { get; init; }
public string DentalServiceName { get; init; }
/// <summary>
/// Obtiene o establece el nombre del parentesco.
/// Por ejemplo: Hijo/a, Esposo/a, y Otros.
/// Gets or sets the name of the kinship.
/// For example: Child, Spouse and Other.
/// </summary>
public string KinshipName { get; init; }
public string Status { get; init; }
Expand Down
1 change: 1 addition & 0 deletions src/Core/DentallApp.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<PackageReference Include="SimpleResults.FluentValidation" />
<PackageReference Include="linq2db.EntityFrameworkCore" />
<PackageReference Include="Dapper" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
</ItemGroup>

<ItemGroup>
Expand Down
22 changes: 22 additions & 0 deletions src/Core/Dependents/DependentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,47 @@ namespace DentallApp.Core.Dependents;
[ApiController]
public class DependentController
{
/// <summary>
/// Creates a dependent for a specific basic user.
/// </summary>
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[HttpPost]
public async Task<Result<CreatedId>> Create(
[FromBody]CreateDependentRequest request,
CreateDependentUseCase useCase)
=> await useCase.ExecuteAsync(request);

/// <summary>
/// Deletes a dependent of a basic user by ID.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status404NotFound)]
[ProducesResponseType<Result>(StatusCodes.Status403Forbidden)]
[HttpDelete("{id}")]
public async Task<Result> Delete(
int id,
DeleteDependentUseCase useCase)
=> await useCase.ExecuteAsync(id);

/// <summary>
/// Updates a dependent of a basic user by ID.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<Result>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Result>(StatusCodes.Status404NotFound)]
[ProducesResponseType<Result>(StatusCodes.Status403Forbidden)]
[HttpPut("{id}")]
public async Task<Result> Update(
int id,
[FromBody]UpdateDependentRequest request,
UpdateDependentUseCase useCase)
=> await useCase.ExecuteAsync(id, request);

/// <summary>
/// Gets the dependents of the current basic user.
/// </summary>
[ProducesResponseType(StatusCodes.Status200OK)]
[Route("user")]
[HttpGet]
public async Task<IEnumerable<GetDependentsByCurrentUserIdResponse>> GetByCurrentUserId(
Expand Down
Loading
Loading