using FluentValidation; using Microsoft.EntityFrameworkCore; using Somnia.GuestLog.Infrastructure; using Somnia.GuestLog.Models; using Somnia.GuestLog.Models.DTOs; using Somnia.GuestLog.Models.Entities; namespace Somnia.GuestLog.Services; public class StayEntryService( ILogger logger, IDbContextFactory contextFactory, IValidator dtoValidator ) : IStayEntryService { public async Task CreateEntryAsync(StayEntryDTO dto) { ArgumentNullException.ThrowIfNull(dto); var valResult = await dtoValidator.ValidateAsync(dto); if (!valResult.IsValid) { logger.LogWarning("StayEntryService - CreateEntryAsync: Dto validation failed. Fix the errors: {errorList}", valResult.Errors); return false; } DateTime createTime = DateTime.UtcNow; StayEntry dbStay = new() { DateIn = dto.DateIn!.Value.ToUniversalTime(), DateOut = dto.DateOut!.Value.ToUniversalTime(), RoomCount = dto.RoomCount!.Value, RoomNumber = dto.RoomNumber, PaymentMethod = dto.PaymentMethod!, CreateTime = createTime, UpdateTime = createTime, }; try { using var dbContext = contextFactory.CreateDbContext(); //Check and add GuestDocuments foreach (GuestDocumentDTO guestDto in dto.GuestDocuments) { //Get it from db. GuestDocument? dbGuest = await dbContext.GuestDocuments.FindAsync(guestDto.DocumentType, guestDto.DocumentNum) ?? throw new InvalidOperationException($"GuestDocument not found! Id: '{guestDto.FullKey}'"); //Check if Guest is already in other stay during the same period. bool isInOtherStay = await this.IsGuestInStayInPeriod(dbGuest.DocumentType, dbGuest.DocumentNum, dbStay.DateIn, dbStay.DateOut); if (isInOtherStay) { throw new InvalidOperationException($"Guest ('{dbGuest.GetFullKey()}') is already in a stay during the period: {dbStay.DateIn}-{dbStay.DateOut}"); } //Add dbGuest to dbStay. dbStay.GuestDocuments.Add(dbGuest); } await dbContext.AddAsync(dbStay); await dbContext.SaveChangesAsync(); logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey()); return true; } catch (Exception ex) { logger.LogError(ex, "StayEntryService - CreateEntryAsync: Error creating StayEntry"); return false; } } public Task UpdateEntryAsync(StayEntryDTO updatedDto) { throw new NotImplementedException(); } public Task DeleteEntryAsync(StayEntryDTO dto) { if (dto.Id is null) { throw new InvalidOperationException("StayEntryService - DeleteEntry: A DTO without key can't be deleted!"); } return DeleteEntryAsync(dto.Id.Value); } public async Task DeleteEntryAsync(long id) { using var dbContext = contextFactory.CreateDbContext(); StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id); if (dbItem is null) { logger.LogWarning("StayEntryService - DeleteEntry: Error deleting StayEntry. Entity not found with key: '{key}'", id); return false; } try { dbContext.StayEntries.Remove(dbItem); await dbContext.SaveChangesAsync(); logger.LogInformation("StayEntryService - DeleteEntry: StayEntry deleted: '{id}'", dbItem.GetFullKey()); return true; } catch (Exception ex) { logger.LogError(ex, "StayEntryService - DeleteEntry: Error deleting StayEntry: '{id}'", dbItem.GetFullKey()); return false; } } public async Task GetDtoByIdAsync(long id) { using var dbContext = contextFactory.CreateDbContext(); StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id); return dbItem is not null ? GetDtoFromDb(dbItem) : null; } public async Task> GetDtoListAsync() { using var dbContext = contextFactory.CreateDbContext(); var dbList = await dbContext.StayEntries .OrderByDescending(e => e.UpdateTime) .ToListAsync(); return dbList.Select(GetDtoFromDb); } public async Task IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime) { using var dbContext = contextFactory.CreateDbContext(); bool rs = await dbContext.StayEntries.AnyAsync( s => s.DateIn < endTime && s.DateOut > startTime && s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum)); return rs; } public async Task> GetStaysWithGuest(string docType, string docNum) { using var dbContext = contextFactory.CreateDbContext(); var rs = await dbContext.StayEntries.Where( s => s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum)) .ToListAsync(); return rs.Select(GetDtoFromDb); } #region Internal Methods private static StayEntryDTO GetDtoFromDb(StayEntry dbItem) { return EntityMapping.MapStayEntryEntityToDTO(dbItem); } #endregion }