feat(api): .NET 10 multi-tenant REST API
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Meezi.API.Models.Queue;
|
||||
using Meezi.API.Services;
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Core.Interfaces;
|
||||
using Meezi.Shared;
|
||||
|
||||
namespace Meezi.API.Controllers;
|
||||
|
||||
[Route("api/cafes/{cafeId}/queue")]
|
||||
public class QueueController : CafeApiControllerBase
|
||||
{
|
||||
private readonly IQueueService _queue;
|
||||
|
||||
public QueueController(IQueueService queue)
|
||||
{
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
[HttpGet("today")]
|
||||
public async Task<IActionResult> GetToday(
|
||||
string cafeId,
|
||||
ITenantContext tenant,
|
||||
[FromQuery] string? branchId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
|
||||
var board = await _queue.GetTodayBoardAsync(cafeId, branchId, ct);
|
||||
return Ok(new ApiResponse<QueueBoardDto>(true, board));
|
||||
}
|
||||
|
||||
[HttpPost("next")]
|
||||
public async Task<IActionResult> IssueNext(
|
||||
string cafeId,
|
||||
[FromBody] IssueQueueTicketRequest request,
|
||||
ITenantContext tenant,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
|
||||
var (ticket, error) = await _queue.IssueNextAsync(cafeId, tenant.UserId, request, ct);
|
||||
if (error == "BRANCH_NOT_FOUND")
|
||||
return NotFound(new ApiResponse<object>(false, null, new ApiError(error, "Branch not found.")));
|
||||
if (error == "ORDER_NOT_FOUND")
|
||||
return NotFound(new ApiResponse<object>(false, null, new ApiError(error, "Order not found.")));
|
||||
return Ok(new ApiResponse<QueueTicketDto>(true, ticket));
|
||||
}
|
||||
|
||||
[HttpPatch("{ticketId}/status")]
|
||||
public async Task<IActionResult> UpdateStatus(
|
||||
string cafeId,
|
||||
string ticketId,
|
||||
[FromBody] UpdateQueueTicketStatusRequest request,
|
||||
ITenantContext tenant,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
|
||||
var (ticket, error) = await _queue.UpdateStatusAsync(cafeId, ticketId, request.Status, ct);
|
||||
if (error == "NOT_FOUND")
|
||||
return NotFound(new ApiResponse<object>(false, null, new ApiError(error, "Ticket not found.")));
|
||||
if (error == "TICKET_EXPIRED")
|
||||
return BadRequest(new ApiResponse<object>(false, null,
|
||||
new ApiError(error, "Ticket is from a previous day.")));
|
||||
return Ok(new ApiResponse<QueueTicketDto>(true, ticket));
|
||||
}
|
||||
|
||||
[HttpPost("call-next")]
|
||||
public async Task<IActionResult> CallNext(
|
||||
string cafeId,
|
||||
ITenantContext tenant,
|
||||
[FromQuery] string? branchId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
|
||||
var board = await _queue.GetTodayBoardAsync(cafeId, branchId, ct);
|
||||
var next = board.Tickets.FirstOrDefault(t => t.Status == QueueTicketStatus.Waiting);
|
||||
if (next is null)
|
||||
return Ok(new ApiResponse<QueueBoardDto>(true, board));
|
||||
|
||||
foreach (var called in board.Tickets.Where(t => t.Status == QueueTicketStatus.Called))
|
||||
{
|
||||
await _queue.UpdateStatusAsync(cafeId, called.Id, QueueTicketStatus.Done, ct);
|
||||
}
|
||||
|
||||
await _queue.UpdateStatusAsync(cafeId, next.Id, QueueTicketStatus.Called, ct);
|
||||
var updated = await _queue.GetTodayBoardAsync(cafeId, branchId, ct);
|
||||
return Ok(new ApiResponse<QueueBoardDto>(true, updated));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user