feat(fnb-engine): add shopId and status filters to kitchen tickets

Add shopId and status query params to GET /api/v1/kitchen/tickets.
Joins through Session to resolve shopId since KitchenTicket only
has SessionId. Backward-compatible: without shopId falls back to
existing pending-by-station behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-04 10:37:55 +07:00
parent 4cd172bee5
commit ce61b4d3db
5 changed files with 85 additions and 4 deletions

View File

@@ -0,0 +1,11 @@
// EN: Query to get kitchen tickets by shop with optional status filter.
// VI: Query lấy phiếu bếp theo shop với bộ lọc trạng thái tùy chọn.
using MediatR;
namespace FnbEngine.API.Application.Queries;
public record GetTicketsByShopQuery(
Guid ShopId,
string? Status = null
) : IRequest<IEnumerable<KitchenTicketDto>>;

View File

@@ -0,0 +1,33 @@
// EN: Handler for GetTicketsByShopQuery.
// VI: Handler cho GetTicketsByShopQuery.
using MediatR;
using FnbEngine.Domain.AggregatesModel.KitchenAggregate;
namespace FnbEngine.API.Application.Queries;
public class GetTicketsByShopQueryHandler : IRequestHandler<GetTicketsByShopQuery, IEnumerable<KitchenTicketDto>>
{
private readonly IKitchenTicketRepository _repository;
public GetTicketsByShopQueryHandler(IKitchenTicketRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task<IEnumerable<KitchenTicketDto>> Handle(GetTicketsByShopQuery request, CancellationToken cancellationToken)
{
var tickets = await _repository.GetByShopAsync(request.ShopId, request.Status, cancellationToken);
return tickets.Select(t => new KitchenTicketDto(
t.Id,
t.SessionId,
t.OrderItemId,
t.ItemName,
t.Station,
t.Priority,
t.Status,
t.CreatedAt
));
}
}

View File

@@ -22,16 +22,28 @@ public class KitchenController : ControllerBase
}
/// <summary>
/// EN: Get pending kitchen tickets.
/// VI: Lấy danh sách phiếu bếp chờ.
/// EN: Get kitchen tickets. Filter by shopId+status or by station (pending only).
/// VI: Lấy phiếu bếp. Lọc theo shopId+status hoặc theo station (chỉ pending).
/// </summary>
[HttpGet("tickets")]
[ProducesResponseType(typeof(ApiResponse<IEnumerable<KitchenTicketDto>>), 200)]
public async Task<ActionResult<ApiResponse<IEnumerable<KitchenTicketDto>>>> GetPendingTickets(
public async Task<ActionResult<ApiResponse<IEnumerable<KitchenTicketDto>>>> GetTickets(
[FromQuery] Guid? shopId = null,
[FromQuery] string? status = null,
[FromQuery] string? station = null,
CancellationToken ct = default)
{
var result = await _mediator.Send(new GetPendingTicketsQuery(station), ct);
IEnumerable<KitchenTicketDto> result;
if (shopId.HasValue && shopId.Value != Guid.Empty)
{
result = await _mediator.Send(new GetTicketsByShopQuery(shopId.Value, status), ct);
}
else
{
result = await _mediator.Send(new GetPendingTicketsQuery(station), ct);
}
return Ok(new ApiResponse<IEnumerable<KitchenTicketDto>> { Success = true, Data = result });
}

View File

@@ -40,4 +40,10 @@ public interface IKitchenTicketRepository : IRepository<KitchenTicket>
/// VI: Lấy danh sách phiếu theo session ID.
/// </summary>
Task<IEnumerable<KitchenTicket>> GetBySessionAsync(Guid sessionId, CancellationToken cancellationToken = default);
/// <summary>
/// EN: Get tickets by shop ID with optional status filter.
/// VI: Lấy danh sách phiếu theo shop ID với bộ lọc trạng thái tùy chọn.
/// </summary>
Task<IEnumerable<KitchenTicket>> GetByShopAsync(Guid shopId, string? status = null, CancellationToken cancellationToken = default);
}

View File

@@ -61,4 +61,23 @@ public class KitchenTicketRepository : IKitchenTicketRepository
.OrderBy(kt => kt.CreatedAt)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<KitchenTicket>> GetByShopAsync(Guid shopId, string? status = null, CancellationToken cancellationToken = default)
{
var sessionIds = await _context.Sessions
.Where(s => s.ShopId == shopId)
.Select(s => s.Id)
.ToListAsync(cancellationToken);
var query = _context.KitchenTickets
.Where(kt => sessionIds.Contains(kt.SessionId));
if (!string.IsNullOrWhiteSpace(status))
query = query.Where(kt => kt.Status == status);
return await query
.OrderByDescending(kt => kt.Priority)
.ThenBy(kt => kt.CreatedAt)
.ToListAsync(cancellationToken);
}
}