SHA256
GuestLog - StayEntry detail view and improvements:
- Added detail view for StayEntries. - Added button to delete StayEntries. - Improve queries of StayEntries using a "query config" record. - Enabled LazyLoading for all database relations. This will make easier to find cases where required relations aren't explicitly loaded.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<MudMessageBox @ref="_msgBox" Title="@LanguageLocale.general_confirmTitle"
|
||||
CancelText="@LanguageLocale.general_btnCancel">
|
||||
<MessageContent>
|
||||
@LanguageLocale.general_confirmDeleteMsg
|
||||
</MessageContent>
|
||||
<YesButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">
|
||||
@LanguageLocale.general_btnDelete
|
||||
</MudButton>
|
||||
</YesButton>
|
||||
</MudMessageBox>
|
||||
|
||||
@code {
|
||||
public MudMessageBox MsgBox => _msgBox;
|
||||
|
||||
MudMessageBox _msgBox = null!;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@using System.Diagnostics
|
||||
@using FluentValidation;
|
||||
@using Microsoft.AspNetCore.SignalR
|
||||
@using Somnia.GuestLog.Models.DTOs;
|
||||
@using Somnia.GuestLog.Services;
|
||||
@using Somnia.GuestLog.Validators;
|
||||
@@ -143,7 +143,7 @@
|
||||
{
|
||||
await _form.ValidateAsync();
|
||||
|
||||
if (Model.GuestDocuments.Count == 0)
|
||||
if (Model.GuestDocuments is null || Model.GuestDocuments.Count == 0)
|
||||
{
|
||||
snackbar.Add("At least 1 guest is required!", MudBlazor.Severity.Warning);
|
||||
return false;
|
||||
@@ -198,12 +198,21 @@
|
||||
}
|
||||
|
||||
//Check if this guest is already added.
|
||||
bool alreadyAdded = Model.GuestDocuments.Any(e => e.DocumentType == resultData.DocumentType
|
||||
&& e.DocumentNum == resultData.DocumentNum);
|
||||
if (alreadyAdded)
|
||||
if (Model.GuestDocuments is not null && Model.GuestDocuments.Count > 0)
|
||||
{
|
||||
snackbar.Add("GuestDocument already added!", MudBlazor.Severity.Warning);
|
||||
return;
|
||||
bool alreadyAdded = Model.GuestDocuments.Any(e => e.DocumentType == resultData.DocumentType
|
||||
&& e.DocumentNum == resultData.DocumentNum);
|
||||
if (alreadyAdded)
|
||||
{
|
||||
snackbar.Add("GuestDocument already added!", MudBlazor.Severity.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Init List.
|
||||
if (Model.GuestDocuments is null)
|
||||
{
|
||||
Model.GuestDocuments = [];
|
||||
}
|
||||
|
||||
Model.GuestDocuments.Add(resultData);
|
||||
@@ -211,8 +220,12 @@
|
||||
|
||||
private void BtnDeleteGuest(string docType, string docNum)
|
||||
{
|
||||
Debug.Assert(Model.GuestDocuments is not null);
|
||||
|
||||
//Filter out given GuestDocument from model.
|
||||
Model.GuestDocuments = Model.GuestDocuments.Where(e => !(e.DocumentType == docType && e.DocumentNum == docNum)).ToList();
|
||||
Model.GuestDocuments = Model.GuestDocuments
|
||||
.Where(e => !(e.DocumentType == docType && e.DocumentNum == docNum))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -69,17 +69,7 @@
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
<MudMessageBox @ref="_msgBoxConfirmDelete" Title="@LanguageLocale.general_confirmTitle"
|
||||
CancelText="@LanguageLocale.general_btnCancel">
|
||||
<MessageContent>
|
||||
@LanguageLocale.general_confirmDeleteMsg
|
||||
</MessageContent>
|
||||
<YesButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">
|
||||
@LanguageLocale.general_btnDelete
|
||||
</MudButton>
|
||||
</YesButton>
|
||||
</MudMessageBox>
|
||||
<ConfirmDeleteMsgBox @ref="_msgBoxConfirmDelete" />
|
||||
|
||||
|
||||
@code {
|
||||
@@ -92,7 +82,7 @@
|
||||
private GuestDocFilterDTO docFilter = new GuestDocFilterDTO();
|
||||
|
||||
private MudDataGrid<GuestDocumentDTO> _dataGrid = null!;
|
||||
private MudMessageBox _msgBoxConfirmDelete = null!;
|
||||
private ConfirmDeleteMsgBox _msgBoxConfirmDelete = null!;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -145,7 +135,7 @@
|
||||
|
||||
private async Task BtnDeleteItem(GuestDocumentDTO item)
|
||||
{
|
||||
bool? confirmDelete = await _msgBoxConfirmDelete.ShowAsync();
|
||||
bool? confirmDelete = await _msgBoxConfirmDelete.MsgBox.ShowAsync();
|
||||
|
||||
if (confirmDelete is null || confirmDelete.Value == false)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
@attribute [Route($"{GuestConsts.Page_StayEntry_Detail}")]
|
||||
|
||||
@using Somnia.GuestLog.Models.DTOs;
|
||||
@using Somnia.GuestLog.Services;
|
||||
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IGuestDocumentService guestDocService
|
||||
@inject IStayEntryService stayService
|
||||
|
||||
|
||||
<PageTitle>Somnia - Guest Docs - Details</PageTitle>
|
||||
|
||||
<MudStack Row="true" Class="my-4">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Stay Entry - "@StayId"</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Edit"
|
||||
OnClick="() => BtnEdit()" Disabled>
|
||||
@LanguageLocale.general_btnEdit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => BtnReturn()">
|
||||
@LanguageLocale.general_btnReturn
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if (stayModel is not null)
|
||||
{
|
||||
<StayEntryForm Model="stayModel" ReadOnly="true" />
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public long StayId { get; set; }
|
||||
|
||||
StayEntryDTO? stayModel = null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@* if (StayId is default)
|
||||
{
|
||||
Snackbar.Add("No Id provided!!", Severity.Error);
|
||||
Navigation.NotFound();
|
||||
return;
|
||||
} *@
|
||||
|
||||
//Get model from db.
|
||||
stayModel = await stayService.GetDtoByIdAsync(StayId, queryConfig: new(IncludeGuests: true));
|
||||
if (stayModel is null)
|
||||
{
|
||||
Snackbar.Add($"Stay not found with ID: '{StayId}'", Severity.Error);
|
||||
Navigation.NotFound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnEdit()
|
||||
{
|
||||
Snackbar.Add("Not implemented yet!", Severity.Info);
|
||||
}
|
||||
|
||||
private void BtnReturn()
|
||||
{
|
||||
Navigation.NavigateTo(GuestConsts.Page_StayEntry_List);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<PropertyColumn Property="x => x.UpdateTime" Title="@LanguageLocale.dto_GuestDocument_UpdateTime" />
|
||||
|
||||
@* <TemplateColumn CellClass="d-flex justify-between" Title="Options" StickyRight Hideable="false"
|
||||
<TemplateColumn CellClass="d-flex justify-between" Title="Options" StickyRight Hideable="false"
|
||||
ShowColumnOptions="false">
|
||||
<CellTemplate>
|
||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Info"
|
||||
@@ -51,13 +51,15 @@
|
||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Delete"
|
||||
OnClick="() => BtnDeleteItem(context.Item)" />
|
||||
</CellTemplate>
|
||||
</TemplateColumn> *@
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="StayEntryDTO" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
<ConfirmDeleteMsgBox @ref="_confirmDeleteMsgBox" />
|
||||
|
||||
@code {
|
||||
|
||||
private bool _dataLoading = true;
|
||||
@@ -67,6 +69,7 @@
|
||||
private IEnumerable<StayEntryDTO> stayEntryList = [];
|
||||
|
||||
private MudDataGrid<StayEntryDTO> _dataGrid = null!;
|
||||
private ConfirmDeleteMsgBox _confirmDeleteMsgBox = null!;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -102,6 +105,34 @@
|
||||
Navigation.NavigateTo(GuestConsts.Page_StayEntry_New);
|
||||
}
|
||||
|
||||
|
||||
private async Task BtnViewDetails(StayEntryDTO item)
|
||||
{
|
||||
string itemId = $"{item.Id ?? -1}";
|
||||
string uri = GuestConsts.Page_StayEntry_Detail.Replace(GuestConsts.Page_StayEntry_Detail_Param_Id, itemId);
|
||||
Navigation.NavigateTo(uri);
|
||||
}
|
||||
|
||||
private async Task BtnDeleteItem(StayEntryDTO item)
|
||||
{
|
||||
bool? confirmDelete = await _confirmDeleteMsgBox.MsgBox.ShowAsync();
|
||||
|
||||
if (confirmDelete is null || confirmDelete.Value == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool deleted = await StayEntryService.DeleteEntryAsync(item);
|
||||
if (!deleted)
|
||||
{
|
||||
Snackbar.Add($"Error deleting Stay {item.Id}", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Snackbar.Add($"Stay {item.Id} deleted!", Severity.Success);
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -10,6 +10,8 @@ public static class GuestConsts
|
||||
|
||||
public const string Page_StayEntry_List = "/stayEntries";
|
||||
public const string Page_StayEntry_New = "/stayEntries/new";
|
||||
public const string Page_StayEntry_Detail = $"/stayEntries/d/{Page_StayEntry_Detail_Param_Id}";
|
||||
public const string Page_StayEntry_Detail_Param_Id = "{StayId:long}";
|
||||
|
||||
|
||||
public const string DataList_DocumentTypes = "DocumentType";
|
||||
|
||||
@@ -12,7 +12,7 @@ public class StayEntryDTO
|
||||
public string? PaymentMethod { get; set; }
|
||||
|
||||
|
||||
public List<GuestDocumentDTO> GuestDocuments { get; set; } = [];
|
||||
public List<GuestDocumentDTO>? GuestDocuments { get; set; } = null;
|
||||
|
||||
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
@@ -33,7 +33,7 @@ internal static class EntityMapping
|
||||
};
|
||||
}
|
||||
|
||||
public static StayEntryDTO MapStayEntryEntityToDTO(StayEntry entity)
|
||||
public static StayEntryDTO MapStayEntryEntityToDTO(StayEntry entity, bool includeGuests)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
@@ -45,7 +45,7 @@ internal static class EntityMapping
|
||||
PaymentMethod = entity.PaymentMethod,
|
||||
CreateTime = entity.CreateTime,
|
||||
UpdateTime = entity.UpdateTime,
|
||||
GuestDocuments = [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))],
|
||||
GuestDocuments = includeGuests ? [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))] : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Somnia.GuestLog.Models.Entities;
|
||||
|
||||
namespace Somnia.GuestLog.Models.QueryConfigs;
|
||||
|
||||
public record StayEntryQueryConfig(bool IncludeGuests = false, bool UseSplitQueries = true, bool DisableTracking = false)
|
||||
{
|
||||
public readonly static StayEntryQueryConfig DefaultQueryConfig = new();
|
||||
}
|
||||
|
||||
|
||||
public static class StayEntryQueryExtensions
|
||||
{
|
||||
public static IQueryable<StayEntry> AddQueryConfig(this IQueryable<StayEntry> query, StayEntryQueryConfig? config)
|
||||
{
|
||||
config ??= StayEntryQueryConfig.DefaultQueryConfig;
|
||||
|
||||
if (config.IncludeGuests)
|
||||
{
|
||||
query = query.Include(e => e.GuestDocuments);
|
||||
}
|
||||
|
||||
if (config.UseSplitQueries)
|
||||
{
|
||||
query = query.AsSplitQuery();
|
||||
}
|
||||
if (config.DisableTracking)
|
||||
{
|
||||
query = query.AsNoTracking();
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using MudBlazor.Services;
|
||||
using Somnia.GuestLog.Components;
|
||||
using Somnia.GuestLog.Infrastructure;
|
||||
@@ -17,7 +18,17 @@ var connectionStringGuestLog = builder.Configuration.GetConnectionString("DbSomn
|
||||
?? throw new InvalidOperationException("Connection string 'DbSomniaGuestLog' not found.");
|
||||
builder.Services.AddDbContextFactory<GuestLogContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(connectionStringGuestLog);
|
||||
options.UseNpgsql(connectionStringGuestLog)
|
||||
.UseLazyLoadingProxies()
|
||||
.ConfigureWarnings(w => w.Log((CoreEventId.NavigationLazyLoading, LogLevel.Warning)));
|
||||
|
||||
// The following options help with debugging, but should
|
||||
// be changed or removed for production.
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
options.EnableSensitiveDataLogging()
|
||||
.EnableDetailedErrors();
|
||||
}
|
||||
});
|
||||
|
||||
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
using Somnia.GuestLog.Models.QueryConfigs;
|
||||
|
||||
namespace Somnia.GuestLog.Services;
|
||||
|
||||
@@ -9,11 +10,11 @@ public interface IStayEntryService
|
||||
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<StayEntryDTO?> GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null);
|
||||
public Task<IEnumerable<StayEntryDTO>> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null);
|
||||
|
||||
|
||||
public Task<bool> IsGuestInStayInPeriod(string docType, string docNum, DateTime startTime, DateTime endTime);
|
||||
public Task<IEnumerable<StayEntryDTO>> GetStaysWithGuest(string docType, string docNum);
|
||||
public Task<IEnumerable<StayEntryDTO>> GetStaysWithGuest(string docType, string docNum, StayEntryQueryConfig? queryConfig = null);
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Somnia.GuestLog.Infrastructure;
|
||||
using Somnia.GuestLog.Models;
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
using Somnia.GuestLog.Models.Entities;
|
||||
using Somnia.GuestLog.Models.QueryConfigs;
|
||||
|
||||
namespace Somnia.GuestLog.Services;
|
||||
|
||||
@@ -41,7 +42,7 @@ public class StayEntryService(
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
|
||||
//Check and add GuestDocuments
|
||||
foreach (GuestDocumentDTO guestDto in dto.GuestDocuments)
|
||||
foreach (GuestDocumentDTO guestDto in dto.GuestDocuments!)
|
||||
{
|
||||
//Get it from db.
|
||||
GuestDocument? dbGuest = await dbContext.GuestDocuments.FindAsync(guestDto.DocumentType, guestDto.DocumentNum)
|
||||
@@ -111,21 +112,27 @@ public class StayEntryService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StayEntryDTO?> GetDtoByIdAsync(long id)
|
||||
public async Task<StayEntryDTO?> GetDtoByIdAsync(long id, StayEntryQueryConfig? queryConfig = null)
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
StayEntry? dbItem = await dbContext.StayEntries.FindAsync(id);
|
||||
return dbItem is not null ? GetDtoFromDb(dbItem) : null;
|
||||
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<IEnumerable<StayEntryDTO>> GetDtoListAsync()
|
||||
public async Task<IEnumerable<StayEntryDTO>> GetDtoListAsync(StayEntryQueryConfig? queryConfig = null)
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
|
||||
var dbList = await dbContext.StayEntries
|
||||
.AddQueryConfig(queryConfig)
|
||||
.OrderByDescending(e => e.UpdateTime)
|
||||
.ToListAsync();
|
||||
|
||||
return dbList.Select(GetDtoFromDb);
|
||||
return dbList.Select(e => GetDtoFromDb(e, queryConfig));
|
||||
}
|
||||
|
||||
|
||||
@@ -133,29 +140,33 @@ public class StayEntryService(
|
||||
{
|
||||
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));
|
||||
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)
|
||||
public async Task<IEnumerable<StayEntryDTO>> GetStaysWithGuest(string docType, string docNum, StayEntryQueryConfig? queryConfig = null)
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
|
||||
var rs = await dbContext.StayEntries.Where(
|
||||
s => s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum))
|
||||
var rs = await dbContext.StayEntries
|
||||
.Where(s => s.GuestDocuments.Any(g => g.DocumentType == docType && g.DocumentNum == docNum))
|
||||
.AddQueryConfig(queryConfig)
|
||||
.ToListAsync();
|
||||
|
||||
return rs.Select(GetDtoFromDb);
|
||||
return rs.Select(e => GetDtoFromDb(e, queryConfig));
|
||||
}
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
private static StayEntryDTO GetDtoFromDb(StayEntry dbItem)
|
||||
private static StayEntryDTO GetDtoFromDb(StayEntry dbItem, StayEntryQueryConfig? queryConfig)
|
||||
{
|
||||
return EntityMapping.MapStayEntryEntityToDTO(dbItem);
|
||||
queryConfig ??= StayEntryQueryConfig.DefaultQueryConfig;
|
||||
|
||||
return EntityMapping.MapStayEntryEntityToDTO(dbItem, queryConfig.IncludeGuests);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ public class StayEntryDTOValidator : ValueValidator<StayEntryDTO>
|
||||
.Must(val => val != null && dataListService.IsValueInList(GuestConsts.DataList_PaymentMethods, val));
|
||||
|
||||
RuleFor(e => e.GuestDocuments)
|
||||
.NotNull()
|
||||
.NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user