using FluentValidation; using Microsoft.AspNetCore.Mvc; using Meezi.API.Models.Tables; using Meezi.API.Services; using Meezi.Core.Authorization; using Meezi.Core.Enums; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; [Route("api/cafes/{cafeId}/branches/{branchId}/tables")] public class BranchTablesController : CafeApiControllerBase { private readonly ITableService _tables; private readonly IValidator _createTableValidator; private readonly IValidator _patchTableValidator; private readonly IValidator _createSectionValidator; private readonly IValidator _patchSectionValidator; private readonly IValidator _cleaningValidator; public BranchTablesController( ITableService tables, IValidator createTableValidator, IValidator patchTableValidator, IValidator createSectionValidator, IValidator patchSectionValidator, IValidator cleaningValidator) { _tables = tables; _createTableValidator = createTableValidator; _patchTableValidator = patchTableValidator; _createSectionValidator = createSectionValidator; _patchSectionValidator = patchSectionValidator; _cleaningValidator = cleaningValidator; } [HttpGet("board")] public async Task GetBoard( string cafeId, string branchId, ITenantContext tenant, [FromQuery] bool activeOnly = true, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var data = await _tables.GetBranchTableBoardAsync(cafeId, branchId, activeOnly, ct); return Ok(new ApiResponse>(true, data)); } [HttpGet] public async Task GetTables( string cafeId, string branchId, ITenantContext tenant, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var data = await _tables.GetBranchTablesAsync(cafeId, branchId, ct); if (data is null) return NotFoundError("Branch not found."); return Ok(new ApiResponse>(true, data)); } [HttpPost] public async Task CreateTable( string cafeId, string branchId, [FromBody] CreateBranchTableRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var validation = await _createTableValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var result = await _tables.CreateBranchTableAsync(cafeId, branchId, request, ct); return BranchOpResult(result); } [HttpPatch("{id}")] public async Task PatchTable( string cafeId, string branchId, string id, [FromBody] PatchBranchTableRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var validation = await _patchTableValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var result = await _tables.PatchBranchTableAsync(cafeId, branchId, id, request, ct); return BranchOpResult(result); } [HttpDelete("{id}")] public async Task DeleteTable( string cafeId, string branchId, string id, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var result = await _tables.DeleteBranchTableAsync(cafeId, branchId, id, ct); return BranchOpResult(result); } [HttpPatch("{id}/cleaning")] public async Task SetCleaning( string cafeId, string branchId, string id, [FromBody] SetTableCleaningRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var validation = await _cleaningValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var data = await _tables.SetTableCleaningAsync(cafeId, id, request.IsCleaning, ct); if (data is null || data.BranchId != branchId) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpGet("{id}/qr")] public async Task GetQrPng( string cafeId, string branchId, string id, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var png = await _tables.GetQrPngAsync(cafeId, id, ct); if (png is null) return NotFoundError(); return File(png, "image/png", $"table-{id}-qr.png"); } [HttpGet("sections")] public async Task GetSections( string cafeId, string branchId, ITenantContext tenant, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var data = await _tables.GetBranchSectionsAsync(cafeId, branchId, ct); if (data is null) return NotFoundError("Branch not found."); return Ok(new ApiResponse>(true, data)); } [HttpPost("sections")] public async Task CreateSection( string cafeId, string branchId, [FromBody] CreateTableSectionRequest request, ITenantContext tenant, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var validation = await _createSectionValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var result = await _tables.CreateBranchSectionAsync(cafeId, branchId, request, ct); return BranchOpResult(result); } [HttpPatch("sections/{sectionId}")] public async Task PatchSection( string cafeId, string branchId, string sectionId, [FromBody] PatchTableSectionRequest request, ITenantContext tenant, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var validation = await _patchSectionValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var result = await _tables.PatchBranchSectionAsync(cafeId, branchId, sectionId, request, ct); return BranchOpResult(result); } [HttpDelete("sections/{sectionId}")] public async Task DeleteSection( string cafeId, string branchId, string sectionId, ITenantContext tenant, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.ManageTables) is { } permDenied) return permDenied; if (!await _tables.CanAccessBranchAsync(cafeId, branchId, tenant.UserId, tenant.Role, ct)) return Forbid(); var result = await _tables.DeleteBranchSectionAsync(cafeId, branchId, sectionId, ct); return BranchOpResult(result); } private IActionResult BranchOpResult(BranchTableOperationResult result) { if (result.Success && result.Data is not null) return Ok(new ApiResponse(true, result.Data)); var code = result.ErrorCode ?? "REQUEST_FAILED"; var status = code is "TABLE_HAS_OPEN_ORDER" or "TABLE_SECTION_HAS_TABLES" ? StatusCodes.Status409Conflict : StatusCodes.Status400BadRequest; return StatusCode(status, new ApiResponse(false, null, new ApiError(code, result.Message ?? code))); } }