using FluentValidation; using Microsoft.EntityFrameworkCore; using Somnia.GuestLog.Enums.Services; using Somnia.GuestLog.Infrastructure; using Somnia.GuestLog.Models; using Somnia.GuestLog.Models.DTOs; using Somnia.GuestLog.Models.Entities; using Somnia.GuestLog.Models.QueryConfigs; using Somnia.GuestLog.Utils; namespace Somnia.GuestLog.Services.Entity; public class StayEntryEntityService( ILogger logger, IDbContextFactory contextFactory, IValidator dtoValidator ) : IStayEntryEntityService { 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 Result.Fail(ResultCode.FailedValidation); } 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 guest from db. GuestDocument dbGuest = await dbContext.GuestDocuments .Where(gd => gd.Id == guestDto.Id && gd.Version == guestDto.Version) .SingleAsync(); //Check if Guest is already in other stay during the same period. bool isInOtherStay = await this.IsGuestInStayInPeriod(dbGuest.Id, dbStay.DateIn, dbStay.DateOut); if (isInOtherStay) { string period = $"{dbStay.DateIn}-{dbStay.DateOut}"; return Result.FailWithContext(ResultCode.GuestCurrentlyInStay, dbGuest.FullDocument, period); } //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 Result.OkWithContext(dbStay.GetFullKey()); } catch (Exception ex) { logger.LogError(ex, "StayEntryService - CreateEntryAsync: Error creating StayEntry"); return Result.FailUnknownError(); } } public Task UpdateEntryAsync(StayEntryDTO updatedDto) { throw new NotImplementedException(); } public async Task DeleteEntryAsync(StayEntryDTO dto) { if (dto.Id is null) { throw new InvalidOperationException("StayEntryService - DeleteEntry: A DTO without key can't be deleted!"); } return await 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 Result.Fail(ResultCode.NotFound); } try { dbContext.StayEntries.Remove(dbItem); await dbContext.SaveChangesAsync(); logger.LogInformation("StayEntryService - DeleteEntry: StayEntry deleted: '{id}'", dbItem.GetFullKey()); return Result.Ok(); } catch (Exception ex) { logger.LogError(ex, "StayEntryService - DeleteEntry: Error deleting StayEntry: '{id}'", dbItem.GetFullKey()); return Result.FailUnknownError(); } } public async Task GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); StayEntry? dbItem = await dbContext.StayEntries .Where(e => e.Id == id) .AddQueryConfig(queryConfig) .FirstOrDefaultAsync(); return dbItem is not null ? GetDtoFromDb(dbItem, queryConfig) : null; } public async Task> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); var dbList = await dbContext.StayEntries .AddQueryConfig(queryConfig) .OrderByDescending(e => e.UpdateTime) .ToListAsync(); return dbList.Select(e => GetDtoFromDb(e, queryConfig)); } public async Task> GetDtoListByFilterAsync(StayEntryFilterDTO filter, int limit = 0, StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); var query = dbContext.StayEntries .AddQueryConfig(queryConfig) .AsQueryable(); query = query.WhereIf(filter.DateInStart is not null && filter.DateInEnd is not null, e => e.DateIn >= filter.DateInStart!.Value.ToUniversalTime() && e.DateIn < filter.DateInEnd!.Value.ToUniversalTime()); query = query.WhereIf(!string.IsNullOrEmpty(filter.RoomNumber), e => EF.Functions.Like(e.RoomNumber, $"%{filter.RoomNumber}%")); query = query.WhereIf(!string.IsNullOrEmpty(filter.PaymentMethod), e => e.PaymentMethod == filter.PaymentMethod); query = query.OrderByDescending(e => e.DateIn) .ThenByDescending(e => e.UpdateTime); if (limit > 0) { query = query.Take(limit); } var dbList = await query .ToListAsync(); return dbList.Select(x => GetDtoFromDb(x, queryConfig)); } public async Task IsGuestInStay(long guestDocId) { using var dbContext = contextFactory.CreateDbContext(); bool rs = await dbContext.StayEntries .AnyAsync(s => s.GuestDocuments.Any(g => g.Id == guestDocId)); return rs; } public async Task IsGuestInStayInPeriod(long guestDocId, 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.Id == guestDocId) ); return rs; } public async Task> GetStaysWithGuest(long guestDocId, StayEntryQueryConfig? queryConfig = null) { using var dbContext = contextFactory.CreateDbContext(); var rs = await dbContext.StayEntries .Where(s => s.GuestDocuments.Any(g => g.Id == guestDocId)) .AddQueryConfig(queryConfig) .OrderByDescending(e => e.DateIn) .ToListAsync(); return rs.Select(e => GetDtoFromDb(e, queryConfig)); } #region Internal Methods private static StayEntryDTO GetDtoFromDb(StayEntry dbItem, StayEntryQueryConfig? queryConfig) { queryConfig ??= StayEntryQueryConfig.DefaultQueryConfig; return EntityMapping.MapStayEntryEntityToDTO(dbItem, queryConfig.IncludeGuests); } #endregion }