GuestLog - StayEntry - Initial Implementation

- Added new entity "StayEntry" to log stays of guests.
- Added pages for StayEntry management (List and creation).
- New DataList for PaymentMethods.
- Other general changes and improvements.
This commit is contained in:
2026-08-15 19:33:32 +02:00
parent ec7efd867b
commit 6dc2d2426b
29 changed files with 1348 additions and 30 deletions
@@ -0,0 +1,162 @@
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<StayEntryService> logger,
IDbContextFactory<GuestLogContext> contextFactory,
IValidator<StayEntryDTO> dtoValidator
) : IStayEntryService
{
public async Task<bool> 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<bool> UpdateEntryAsync(StayEntryDTO updatedDto)
{
throw new NotImplementedException();
}
public Task<bool> 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<bool> 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<StayEntryDTO?> 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<IEnumerable<StayEntryDTO>> GetDtoListAsync()
{
using var dbContext = contextFactory.CreateDbContext();
var dbList = await dbContext.StayEntries
.OrderByDescending(e => e.UpdateTime)
.ToListAsync();
return dbList.Select(GetDtoFromDb);
}
public async Task<bool> 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<IEnumerable<StayEntryDTO>> 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
}