Files
meezi/src/Meezi.API/Services/ReservationService.cs
T
soroush.asadi ef15fd6247 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>
2026-05-27 21:33:48 +03:30

134 lines
4.0 KiB
C#

using Meezi.API.Models.Public;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Meezi.API.Services;
public interface IReservationService
{
Task<IReadOnlyList<ReservationDto>> GetReservationsAsync(
string cafeId,
DateOnly? date,
ReservationStatus? status,
CancellationToken cancellationToken = default);
Task<ReservationDto?> CreateAsync(
string cafeId,
CreateReservationRequest request,
CancellationToken cancellationToken = default);
Task<ReservationDto?> UpdateStatusAsync(
string cafeId,
string reservationId,
ReservationStatus status,
CancellationToken cancellationToken = default);
}
public class ReservationService : IReservationService
{
private readonly AppDbContext _db;
private readonly IKdsNotifier _kdsNotifier;
public ReservationService(AppDbContext db, IKdsNotifier kdsNotifier)
{
_db = db;
_kdsNotifier = kdsNotifier;
}
public async Task<IReadOnlyList<ReservationDto>> GetReservationsAsync(
string cafeId,
DateOnly? date,
ReservationStatus? status,
CancellationToken cancellationToken = default)
{
var query = _db.TableReservations
.Include(r => r.Table)
.Where(r => r.CafeId == cafeId);
if (date.HasValue)
query = query.Where(r => r.Date == date.Value);
if (status.HasValue)
query = query.Where(r => r.Status == status.Value);
var list = await query
.OrderBy(r => r.Date)
.ThenBy(r => r.Time)
.ToListAsync(cancellationToken);
return list.Select(Map).ToList();
}
public async Task<ReservationDto?> CreateAsync(
string cafeId,
CreateReservationRequest request,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrEmpty(request.TableId))
{
var tableOk = await _db.Tables.AnyAsync(
t => t.Id == request.TableId && t.CafeId == cafeId,
cancellationToken);
if (!tableOk) return null;
}
var entity = new TableReservation
{
CafeId = cafeId,
TableId = request.TableId,
GuestName = request.GuestName.Trim(),
GuestPhone = request.GuestPhone.Trim(),
Date = request.Date,
Time = request.Time,
PartySize = request.PartySize,
Notes = request.Notes,
Status = ReservationStatus.Confirmed
};
_db.TableReservations.Add(entity);
await _db.SaveChangesAsync(cancellationToken);
if (!string.IsNullOrEmpty(entity.TableId))
await _kdsNotifier.NotifyTableStatusChangedAsync(cafeId, cancellationToken);
var loaded = await _db.TableReservations
.Include(r => r.Table)
.FirstAsync(r => r.Id == entity.Id, cancellationToken);
return Map(loaded);
}
public async Task<ReservationDto?> UpdateStatusAsync(
string cafeId,
string reservationId,
ReservationStatus status,
CancellationToken cancellationToken = default)
{
var entity = await _db.TableReservations
.Include(r => r.Table)
.FirstOrDefaultAsync(r => r.Id == reservationId && r.CafeId == cafeId, cancellationToken);
if (entity is null) return null;
entity.Status = status;
await _db.SaveChangesAsync(cancellationToken);
if (!string.IsNullOrEmpty(entity.TableId))
await _kdsNotifier.NotifyTableStatusChangedAsync(cafeId, cancellationToken);
return Map(entity);
}
internal static ReservationDto Map(TableReservation r) => new(
r.Id,
r.CafeId,
r.TableId,
r.Table?.Number,
r.GuestName,
r.GuestPhone,
r.Date,
r.Time,
r.PartySize,
r.Status,
r.Notes);
}