From 3f1ecc81228c9d70b234b27d557cf26f699669f4 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Thu, 5 Mar 2026 16:19:46 +0700 Subject: [PATCH] feat(booking-service, web-client-tpos): implement staff schedule creation/deletion and enhance staff name display. --- .../Pages/Admin/Shop/ShopSchedule.razor | 8 ++- .../Controllers/StaffController.cs | 8 +-- .../Controllers/SchedulesController.cs | 71 ++++++++++++++++++- .../Repositories/IStaffScheduleRepository.cs | 1 + .../Repositories/StaffScheduleRepository.cs | 5 ++ 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSchedule.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSchedule.razor index b24ef791..366dccae 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSchedule.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSchedule.razor @@ -108,7 +108,10 @@ else if (SubSection == "shifts")
@@ -148,7 +151,8 @@ else if (SubSection == "shifts") @foreach (var emp in _staff) { - @(emp.EmployeeCode ?? emp.Id.ToString()[..8]) + @{ var empName = !string.IsNullOrWhiteSpace(emp.LastName) || !string.IsNullOrWhiteSpace(emp.FirstName) ? $"{emp.LastName} {emp.FirstName}".Trim() : (emp.EmployeeCode ?? emp.Id.ToString()[..8]); } + @empName @foreach (var dow in new[] { 1, 2, 3, 4, 5, 6, 0 }) { var sched = _staffSchedules.FirstOrDefault(s => s.StaffId == emp.Id && s.DayOfWeek == dow); diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs index ba3c19cd..27513716 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs @@ -154,7 +154,7 @@ public class StaffController : ControllerBase public Task GetStaffSchedules([FromQuery] Guid? shopId = null) { var qs = shopId.HasValue ? $"?shopId={shopId}" : ""; - return _booking.GetAsync($"/api/v1/schedules{qs}").ProxyAsync(); + return _booking.GetAsync($"/api/v1.0/schedules{qs}").ProxyAsync(); } /// @@ -163,7 +163,7 @@ public class StaffController : ControllerBase /// [HttpPost("staff/schedules")] public Task CreateSchedule([FromBody] JsonElement body) => - _booking.PostAsJsonAsync("/api/v1/schedules", body).ProxyAsync(); + _booking.PostAsJsonAsync("/api/v1.0/schedules", body).ProxyAsync(); /// /// EN: Update a staff schedule — proxies to BookingService. @@ -171,7 +171,7 @@ public class StaffController : ControllerBase /// [HttpPut("staff/schedules/{scheduleId:guid}")] public Task UpdateSchedule(Guid scheduleId, [FromBody] JsonElement body) => - _booking.PutAsJsonAsync($"/api/v1/schedules/{scheduleId}", body).ProxyAsync(); + _booking.PutAsJsonAsync($"/api/v1.0/schedules/{scheduleId}", body).ProxyAsync(); /// /// EN: Delete a staff schedule — proxies to BookingService. @@ -179,5 +179,5 @@ public class StaffController : ControllerBase /// [HttpDelete("staff/schedules/{scheduleId:guid}")] public Task DeleteSchedule(Guid scheduleId) => - _booking.DeleteAsync($"/api/v1/schedules/{scheduleId}").ProxyAsync(); + _booking.DeleteAsync($"/api/v1.0/schedules/{scheduleId}").ProxyAsync(); } diff --git a/services/booking-service-net/src/BookingService.API/Controllers/SchedulesController.cs b/services/booking-service-net/src/BookingService.API/Controllers/SchedulesController.cs index 27af32d2..44b74962 100644 --- a/services/booking-service-net/src/BookingService.API/Controllers/SchedulesController.cs +++ b/services/booking-service-net/src/BookingService.API/Controllers/SchedulesController.cs @@ -5,6 +5,8 @@ using Asp.Versioning; using BookingService.API.Application.DTOs; using BookingService.API.Application.Queries; using BookingService.API.Models.Responses; +using BookingService.Domain.AggregatesModel.StaffAggregate; +using BookingService.Infrastructure.Repositories; using MediatR; using Microsoft.AspNetCore.Mvc; @@ -17,11 +19,13 @@ namespace BookingService.API.Controllers; public class SchedulesController : ControllerBase { private readonly IMediator _mediator; + private readonly IStaffScheduleRepository _scheduleRepository; private readonly ILogger _logger; - public SchedulesController(IMediator mediator, ILogger logger) + public SchedulesController(IMediator mediator, IStaffScheduleRepository scheduleRepository, ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _scheduleRepository = scheduleRepository ?? throw new ArgumentNullException(nameof(scheduleRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -44,4 +48,69 @@ public class SchedulesController : ControllerBase return Ok(ApiResponse>.Ok(result)); } + + /// + /// EN: Create a single schedule entry. + /// VI: Tạo một entry lịch làm việc. + /// + [HttpPost] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task>> CreateSchedule( + [FromBody] CreateScheduleRequest request, + CancellationToken cancellationToken = default) + { + if (request.ShopId == Guid.Empty || request.StaffId == Guid.Empty) + return BadRequest(ApiResponse.Fail("ShopId and StaffId are required")); + + if (!TimeOnly.TryParse(request.StartTime, out var startTime) || + !TimeOnly.TryParse(request.EndTime, out var endTime)) + return BadRequest(ApiResponse.Fail("Invalid time format. Use HH:mm")); + + var schedule = new StaffSchedule(request.StaffId, request.ShopId, request.DayOfWeek, startTime, endTime); + _scheduleRepository.Add(schedule); + await _scheduleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation("Schedule created: {Id} for staff {StaffId}", schedule.Id, request.StaffId); + + var dto = new StaffScheduleDto + { + Id = schedule.Id, + StaffId = schedule.StaffId, + ShopId = schedule.ShopId, + DayOfWeek = schedule.DayOfWeek, + StartTime = schedule.StartTime, + EndTime = schedule.EndTime + }; + return Created($"/api/v1/schedules/{schedule.Id}", ApiResponse.Ok(dto)); + } + + /// + /// EN: Delete a schedule entry by ID. + /// VI: Xóa một entry lịch làm việc theo ID. + /// + [HttpDelete("{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteSchedule(Guid id, CancellationToken cancellationToken = default) + { + var schedule = await _scheduleRepository.GetByIdAsync(id, cancellationToken); + if (schedule == null) + return NotFound(ApiResponse.Fail("Schedule not found")); + + _scheduleRepository.Remove(schedule); + await _scheduleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation("Schedule deleted: {Id}", id); + return Ok(new { success = true, message = "Schedule deleted" }); + } +} + +public record CreateScheduleRequest +{ + public Guid ShopId { get; init; } + public Guid StaffId { get; init; } + public int DayOfWeek { get; init; } + public string StartTime { get; init; } = "08:00"; + public string EndTime { get; init; } = "17:00"; } diff --git a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/IStaffScheduleRepository.cs b/services/booking-service-net/src/BookingService.Infrastructure/Repositories/IStaffScheduleRepository.cs index 82d2099b..04634d23 100644 --- a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/IStaffScheduleRepository.cs +++ b/services/booking-service-net/src/BookingService.Infrastructure/Repositories/IStaffScheduleRepository.cs @@ -15,4 +15,5 @@ public interface IStaffScheduleRepository Task> GetByStaffIdAsync(Guid staffId, Guid shopId, CancellationToken cancellationToken = default); Task> GetByShopIdAndDayAsync(Guid shopId, int dayOfWeek, CancellationToken cancellationToken = default); Task> GetByShopIdAsync(Guid shopId, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); } diff --git a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/StaffScheduleRepository.cs b/services/booking-service-net/src/BookingService.Infrastructure/Repositories/StaffScheduleRepository.cs index 802af07b..a02ae8f5 100644 --- a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/StaffScheduleRepository.cs +++ b/services/booking-service-net/src/BookingService.Infrastructure/Repositories/StaffScheduleRepository.cs @@ -55,4 +55,9 @@ public class StaffScheduleRepository : IStaffScheduleRepository .ThenBy(s => s.DayOfWeek) .ToListAsync(cancellationToken); } + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await _context.StaffSchedules.FirstOrDefaultAsync(s => s.Id == id, cancellationToken); + } }