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
@@ -1,6 +1,7 @@
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Somnia.GuestLog.Infrastructure;
using Somnia.GuestLog.Models;
using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Models.Entities;
using Somnia.GuestLog.Utils;
@@ -35,6 +36,7 @@ public class GuestDocumentService(
ZipCode = dto.ZipCode,
};
DateTime createTime = DateTime.UtcNow;
GuestDocument dbItem = new()
{
DocumentType = dto.DocumentType!,
@@ -50,8 +52,8 @@ public class GuestDocumentService(
PhoneNumber = dto.PhoneNumber,
EmailAddress = dto.EmailAddress,
AddressDetails = addressItem,
CreateTime = DateTime.UtcNow,
UpdateTime = DateTime.UtcNow,
CreateTime = createTime,
UpdateTime = createTime,
};
try
@@ -125,7 +127,7 @@ public class GuestDocumentService(
.OrderByDescending(e => e.UpdateTime)
.ToListAsync();
return dbList.Select(x => GetDtoFromDb(x));
return dbList.Select(GetDtoFromDb);
}
/// <summary>
@@ -163,30 +165,13 @@ public class GuestDocumentService(
return dbList.Select(x => GetDtoFromDb(x));
}
public GuestDocumentDTO GetDtoFromDb(GuestDocument dbItem)
#region Internal Methods
private static GuestDocumentDTO GetDtoFromDb(GuestDocument dbItem)
{
return new()
{
DocumentType = dbItem.DocumentType,
DocumentNum = dbItem.DocumentNum,
IssueDate = dbItem.IssueDate.ToDateTime(TimeOnly.MinValue),
SupportNum = dbItem.SupportNum,
Nationality = dbItem.Nationality,
Name = dbItem.Name,
Surname = dbItem.Surname,
Surname2 = dbItem.Surname2,
BirthDate = dbItem.BirthDate.ToDateTime(TimeOnly.MinValue),
Gender = dbItem.Gender,
PhoneNumber = dbItem.PhoneNumber,
EmailAddress = dbItem.EmailAddress,
Address = dbItem.AddressDetails?.Address ?? "",
AddressCity = dbItem.AddressDetails?.AddressCity ?? "",
AddressProvince = dbItem.AddressDetails?.AddressProvince ?? "",
AddressState = dbItem.AddressDetails?.AddressState ?? "",
AddressCountry = dbItem.AddressDetails?.AddressCountry! ?? "",
ZipCode = dbItem.AddressDetails?.ZipCode ?? "",
CreateTime = dbItem.CreateTime,
UpdateTime = dbItem.UpdateTime,
};
return EntityMapping.MapGuestDocumentEntityToDTO(dbItem);
}
#endregion
}
@@ -0,0 +1,19 @@
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Services;
public interface IStayEntryService
{
public Task<bool> CreateEntryAsync(StayEntryDTO dto);
public Task<bool> UpdateEntryAsync(StayEntryDTO updatedDto);
public Task<bool> DeleteEntryAsync(StayEntryDTO dto);
public Task<bool> DeleteEntryAsync(long id);
public Task<StayEntryDTO?> GetDtoByIdAsync(long id);
public Task<IEnumerable<StayEntryDTO>> GetDtoListAsync();
public Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime);
public Task<IEnumerable<StayEntryDTO>> GetStaysWithGuest(string docType, string docNum);
}
@@ -20,6 +20,7 @@ public class StaticDataListService(
new KeyValuePair<string, HashSet<string>>(GuestConsts.DataList_DocumentTypes, _documentTypeSet),
new KeyValuePair<string, HashSet<string>>(GuestConsts.DataList_GenderOptions, _genderOptionSet),
new KeyValuePair<string, HashSet<string>>(GuestConsts.DataList_CountryCodes, _countryCodeSet),
new KeyValuePair<string, HashSet<string>>(GuestConsts.DataList_PaymentMethods, _paymentMethodSet),
]);
public List<string> GetAvailableLists()
@@ -92,6 +93,14 @@ public class StaticDataListService(
"VIR","VNM","VUT","WLF","WSM","YEM","ZAF","ZMB","ZWE",
];
private static readonly HashSet<string> _paymentMethodSet = [
"EFECT",
"TARJT",
"TRANS",
"MOVIL",
"OTRO",
];
#endregion
}
@@ -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
}