feat(booking-service, web-client-tpos): implement staff schedule creation/deletion and enhance staff name display.
This commit is contained in:
@@ -108,7 +108,10 @@ else if (SubSection == "shifts")
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Nhân viên</label>
|
||||
<select @bind="_newSchedStaffIdStr" style="width:100%;padding:8px 12px;border-radius:8px;border:1px solid var(--admin-border-subtle);background:var(--admin-bg-elevated);color:var(--admin-text-primary);font-size:13px;">
|
||||
<option value="">-- Chọn NV --</option>
|
||||
@foreach (var s in _staff) { <option value="@s.Id">@(s.EmployeeCode ?? s.Id.ToString()[..8])</option> }
|
||||
@foreach (var s in _staff) {
|
||||
var sName = !string.IsNullOrWhiteSpace(s.LastName) || !string.IsNullOrWhiteSpace(s.FirstName) ? $"{s.LastName} {s.FirstName}".Trim() : (s.EmployeeCode ?? s.Id.ToString()[..8]);
|
||||
<option value="@s.Id">@sName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Ngày</label>
|
||||
@@ -148,7 +151,8 @@ else if (SubSection == "shifts")
|
||||
@foreach (var emp in _staff)
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:600;white-space:nowrap;">@(emp.EmployeeCode ?? emp.Id.ToString()[..8])</td>
|
||||
@{ var empName = !string.IsNullOrWhiteSpace(emp.LastName) || !string.IsNullOrWhiteSpace(emp.FirstName) ? $"{emp.LastName} {emp.FirstName}".Trim() : (emp.EmployeeCode ?? emp.Id.ToString()[..8]); }
|
||||
<td style="padding:12px 16px;font-weight:600;white-space:nowrap;">@empName</td>
|
||||
@foreach (var dow in new[] { 1, 2, 3, 4, 5, 6, 0 })
|
||||
{
|
||||
var sched = _staffSchedules.FirstOrDefault(s => s.StaffId == emp.Id && s.DayOfWeek == dow);
|
||||
|
||||
@@ -154,7 +154,7 @@ public class StaffController : ControllerBase
|
||||
public Task<IActionResult> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -163,7 +163,7 @@ public class StaffController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpPost("staff/schedules")]
|
||||
public Task<IActionResult> CreateSchedule([FromBody] JsonElement body) =>
|
||||
_booking.PostAsJsonAsync("/api/v1/schedules", body).ProxyAsync();
|
||||
_booking.PostAsJsonAsync("/api/v1.0/schedules", body).ProxyAsync();
|
||||
|
||||
/// <summary>
|
||||
/// EN: Update a staff schedule — proxies to BookingService.
|
||||
@@ -171,7 +171,7 @@ public class StaffController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpPut("staff/schedules/{scheduleId:guid}")]
|
||||
public Task<IActionResult> UpdateSchedule(Guid scheduleId, [FromBody] JsonElement body) =>
|
||||
_booking.PutAsJsonAsync($"/api/v1/schedules/{scheduleId}", body).ProxyAsync();
|
||||
_booking.PutAsJsonAsync($"/api/v1.0/schedules/{scheduleId}", body).ProxyAsync();
|
||||
|
||||
/// <summary>
|
||||
/// EN: Delete a staff schedule — proxies to BookingService.
|
||||
@@ -179,5 +179,5 @@ public class StaffController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpDelete("staff/schedules/{scheduleId:guid}")]
|
||||
public Task<IActionResult> DeleteSchedule(Guid scheduleId) =>
|
||||
_booking.DeleteAsync($"/api/v1/schedules/{scheduleId}").ProxyAsync();
|
||||
_booking.DeleteAsync($"/api/v1.0/schedules/{scheduleId}").ProxyAsync();
|
||||
}
|
||||
|
||||
@@ -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<SchedulesController> _logger;
|
||||
|
||||
public SchedulesController(IMediator mediator, ILogger<SchedulesController> logger)
|
||||
public SchedulesController(IMediator mediator, IStaffScheduleRepository scheduleRepository, ILogger<SchedulesController> 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<List<StaffScheduleDto>>.Ok(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Create a single schedule entry.
|
||||
/// VI: Tạo một entry lịch làm việc.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(ApiResponse<StaffScheduleDto>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<ApiResponse<StaffScheduleDto>>> CreateSchedule(
|
||||
[FromBody] CreateScheduleRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (request.ShopId == Guid.Empty || request.StaffId == Guid.Empty)
|
||||
return BadRequest(ApiResponse<StaffScheduleDto>.Fail("ShopId and StaffId are required"));
|
||||
|
||||
if (!TimeOnly.TryParse(request.StartTime, out var startTime) ||
|
||||
!TimeOnly.TryParse(request.EndTime, out var endTime))
|
||||
return BadRequest(ApiResponse<StaffScheduleDto>.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<StaffScheduleDto>.Ok(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Delete a schedule entry by ID.
|
||||
/// VI: Xóa một entry lịch làm việc theo ID.
|
||||
/// </summary>
|
||||
[HttpDelete("{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DeleteSchedule(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var schedule = await _scheduleRepository.GetByIdAsync(id, cancellationToken);
|
||||
if (schedule == null)
|
||||
return NotFound(ApiResponse<object>.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";
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@ public interface IStaffScheduleRepository
|
||||
Task<List<StaffSchedule>> GetByStaffIdAsync(Guid staffId, Guid shopId, CancellationToken cancellationToken = default);
|
||||
Task<List<StaffSchedule>> GetByShopIdAndDayAsync(Guid shopId, int dayOfWeek, CancellationToken cancellationToken = default);
|
||||
Task<List<StaffSchedule>> GetByShopIdAsync(Guid shopId, CancellationToken cancellationToken = default);
|
||||
Task<StaffSchedule?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -55,4 +55,9 @@ public class StaffScheduleRepository : IStaffScheduleRepository
|
||||
.ThenBy(s => s.DayOfWeek)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<StaffSchedule?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.StaffSchedules.FirstOrDefaultAsync(s => s.Id == id, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user