Compare commits

...
22 Commits
Author SHA256 Message Date
Daniel-Garmig 0ab3957efa Localization - Added Spanish
- Added language selector.
- Translated strings to Spanish.
- Added Package MudBlazor.Translations for translations.
2026-09-18 12:42:04 +02:00
Daniel-Garmig fbe1750510 GuestDocument - Page create - Bugfix
Fixed a bug that caused exceptions when "Create New" button was clicked before searching if GuestDocument already exists.
2026-09-17 17:24:58 +02:00
Daniel-Garmig 23c8d490d8 Updated packages
Updated:
- FluentStorage
- EntityFrameworkCore
- MudBlazor
2026-09-17 17:19:58 +02:00
Daniel-Garmig c5d8487046 Small fixes and improvements:
- GuestDocument - At least one contact field is required.
- GuestDocument - List Page - Hide not useful columns: "version"
- StayEntry - Show stayEntry ID in UI msg after creation.
- StayEntry - Create Page - Time fields don't reset anymore when dates change.
- Show local times in UI instead of UTC times.
- Validators - Use expresions for dates so they are recalculated each time.
2026-09-12 15:28:25 +02:00
Daniel-Garmig 491ecc4cd1 GuestDocument - Add edit mode.
Added Edit mode for GuestDocument in GuestDocument details page.
2026-09-10 19:34:46 +02:00
Daniel-Garmig a1a55aa396 Small fixes and improvements:
- StayExport - SpainParteViajerosProvider - Fixed validation for codigoPostal/codMunicipio.
- AddressQueryDialog - Add loading indication and "debounce" input to reduce API queries.
2026-09-10 18:27:56 +02:00
Daniel-Garmig dc85319da5 StayExport - SpainParteViajerosProvider - Fixes in export format
- Added required XML Namespace (ns2)
- Reorder some elements.
- Fix collection serializations.
- Fix gender code mapping.
- Improve some error messages.
- CodigoMunicipioCatastro - Add padding to codes to follow required format.
2026-09-10 15:05:05 +02:00
Daniel-Garmig 0abdfcd683 GuestDocument - Improvements to GuestDocument creation:
- IssueDate is now Nullable. Is not required for DNI and NIE document types.
- Improvements in SupportNumber for NIE documents.
- Address search can now be skipped.
2026-09-10 11:53:41 +02:00
Daniel-Garmig ae2ea51060 Small fixes and improvements:
- Show app version on Sidebar.
- StayEntryForm - Remove unnecesary refs to components.
- Fixed some result and log messages in entity services.
2026-09-10 11:26:21 +02:00
Daniel-Garmig dcd5bb2aad Deploy configuration
- Added tasks for publishing as container image.
- Improvements for deployment: Db migrations on startup, reverse-proxy forward headers, and DataProtect management.
- Added deploy example using docker-compose.
2026-09-09 17:50:27 +02:00
Daniel-Garmig fb2e00f06c Added Serilog for improved logs
- Logs will be shown by console and stored in files.
2026-09-08 21:13:35 +02:00
Daniel-Garmig 9ec87d56da feat StayExport - Added ExportDetails for StayExports
Store a new field ('ExportDetails') with export details. This is a JSON field in DB that can store information about the export depending on the format provider used.

Changes:
- Updated models and created required migrations.
- Show field in StayExport list.
2026-09-08 19:58:47 +02:00
Daniel-Garmig 7b8020879b feat StayExport - StayExportProvider SpainParteViejeros - Initial implementation
- Added initial implementation to export stays in "SpainParteViajeros" format.
- Added required configurations.
2026-09-08 14:31:00 +02:00
Daniel-Garmig dc5293bdf7 feat StayExport - Added service SpainCodigoMunicipio
- This service gets the "municipio" code of INE using Catastro API services.
2026-09-08 13:27:17 +02:00
Daniel-Garmig 4b72e3b3d3 feat StayExport - Added delete for StayExports. 2026-09-06 22:24:25 +02:00
Daniel-Garmig 6fe5a5e5a3 feat StayExport - Refactor Export Services.
- Created new service: IStayExportService" for common export operations.
- Updated older services to be "IStayExportFormatProvider". This services will only implement the export format.
- Renamed "IStayExportServiceFactory" to "IStayExportFormatProviderFactory" as this factory now is used to get the format providers for the given format.
2026-09-04 22:33:01 +02:00
Daniel-Garmig 058352b7f3 feat StayExport - Added common Interface "IResult" for Result so both (generic and non-generic one) can be used easier. 2026-09-04 20:50:38 +02:00
Daniel-Garmig 313a714a99 feat StayExport - Stay Export Pages - Initial implementation 2026-09-04 20:38:00 +02:00
Daniel-Garmig cf31cbc414 feat StayExport - Added LocalizerService
- Added new "LocalizerService" to get localization for enum values (and other dynamic/db values in the future).
2026-09-02 13:15:10 +02:00
Daniel-Garmig b318f2415c feat StayExport - Moved Entity Services. 2026-09-01 18:14:27 +02:00
Daniel-Garmig c24faedee5 feat StayExport - Initial Implementation
- Created models for DB and DTOs: StayExportFile
- Added required db migrations.
- Added StayExportService and StayExportServiceFactory for stay exporting services.
- Added configuration options required for StayExport.
2026-09-01 18:14:22 +02:00
Daniel-Garmig 7052ec734c feat StayExport - Added lib FluentStorage 2026-09-01 15:23:23 +02:00
75 changed files with 3928 additions and 155 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"files.associations": {
"appsettings*.json": "jsonc",
"*_config.json": "jsonc",
"*_config.json.sample": "jsonc"
},
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modificationsIfAvailable",
}
+36
View File
@@ -0,0 +1,36 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "dotnet",
"task": "build",
"group": "build",
"problemMatcher": [],
"label": "Build Somnia.GuestLog"
},
{
"label": "Publish Container Image",
"type": "shell",
"command": "dotnet",
"args": [
"publish",
"-c",
"Release",
"--os",
"linux",
"--arch",
"x64",
"-p:PublishProfile=DefaultContainer"
],
"options": {
"cwd": "${workspaceFolder}/src/Somnia.GuestLog"
},
"group": "build",
"presentation": {
"reveal": "always"
}
},
]
}
+23
View File
@@ -0,0 +1,23 @@
# Somnia.GuestLog Deployment
## Deploy using docker-compose
Use [docker-compose file](docker-compose.yaml) as example. Configure required enviroment variables.
Notes:
- By default, HTTP-only is deploy. Uncomment required sections to enable HTTPS (and mount required certs).
- Reverse-proxies should work?
[To-Be-Improved]
## Manually update registry
Container images are built by CI/CD pipelines and uploaded to the registry (**not yet!**).
If required, a image can be manually built and uploaded to the registry (for testing or development purposes):
```sh
podman push [IMAGE_ID] [REGISTRY]/somnia/somnia-guestlog:[TAG]
# Example:
podman push localhost/somnia-guestlog:latest gitea.uberelectronnetwork.cc/somnia/somnia-guestlog:devbuild
```
+66
View File
@@ -0,0 +1,66 @@
services:
database:
environment:
POSTGRES_DB: ${POSTGRES_DB:-SomniaGuestLog}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-NotAStrongPassword}
POSTGRES_USER: ${POSTGRES_USER:-somnia}
healthcheck:
retries: 5
start_period: 60s
test:
- CMD
- pg_isready
timeout: 5s
image: postgres:18-alpine
volumes:
- somniaguestlog_database_data:/var/lib/postgresql:rw
somnia-guestlog:
depends_on:
- database
environment:
SOMNIA_ConnectionStrings__DbSomniaGuestLog: Host=database;Port=5432;Username=${POSTGRES_USER:-somnia};Password=${POSTGRES_PASSWORD:-NotAStrongPassword};Database=${POSTGRES_DB:-SomniaGuestLog}
# Configure path for data protection keys. This path must persists between container restarts (use a mount or a volume).
SOMNIA_DataProtectionDir: /SomniaGuestLog/dataprotection
ASPNETCORE_URLS: "http://+:8080"
# Somnia AddressQuery Service - START
SOMNIA_AddressQuery__Provider: "Nominatim"
# Override if required.
# SOMNIA_AddressQuery__ServiceURL: ""
# SOMNIA_AddressQuery__ApiKey: ""
SOMNIA_AddressQuery__UserAgent: Somnia-GuestLog-Dev-Container
SOMNIA_AddressQuery__ResultLang: es
# Somnia AddressQuery Service - END
SOMNIA_StayExport__ExportStorageConnectionString: disk://path=/SomniaGuestLog/StayExportFiles
# Somnia StayExport - SpainParteViajeros Provider - START
# Override vars if required
# SOMNIA_StayExportSpainParteViajeros__InternetAvailable: true
# SOMNIA_StayExportSpainParteViajeros__CodigoEstablecimiento: 123123123
# Somnia StayExport - SpainParteViajeros Provider - END
# Enable for HTTPS - START
# ASPNETCORE_URLS: "https://+:8090;http://+:8080"
# ASPNETCORE_HTTPS_PORTS: 8090
# ASPNETCORE_Kestrel__Certificates__Default__Password: ${CERTIFICATE_CREDENTIAL:-}
# ASPNETCORE_Kestrel__Certificates__Default__Path: /https/aspnetapp.pfx
# Enable for HTTPS - END
image: localhost/somnia-guestlog:latest
# image: gitea.uberelectronnetwork.cc/somnia/somnia-guestlog:devbuild
ports:
- protocol: tcp
published: 8080
target: 8080
# Enable for HTTPS
# - protocol: tcp
# published: 8090
# target: 8090
restart: unless-stopped
volumes:
- somniaguestlog_app_data:/SomniaGuestLog
# Enable for HTTPS and configure path to your certificate.
# - ${HOME}/.aspnet/https:/https/
volumes:
somniaguestlog_database_data: Null
somniaguestlog_app_data: Null
+18 -8
View File
@@ -6,24 +6,34 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Extensions.MudBlazor.StaticInput" Version="4.1.0" /> <PackageVersion Include="Extensions.MudBlazor.StaticInput" Version="4.1.0" />
<PackageVersion Include="FluentStorage" Version="8.0.22" />
<PackageVersion Include="FluentStorage.AWS" Version="8.0.21" />
<PackageVersion Include="FluentValidation" Version="12.1.1" /> <PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.64.0" /> <PackageVersion Include="Grpc.AspNetCore" Version="2.64.0" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.9" /> <PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.9" />
<PackageVersion Include="MudBlazor" Version="9.4.0" /> <PackageVersion Include="MudBlazor" Version="9.10.0" />
<PackageVersion Include="MudBlazor.Translations" Version="3.6.0" />
<PackageVersion Include="Serilog" Version="4.4.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7"> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.12">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageVersion> </PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.12" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" /> <PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+8
View File
@@ -0,0 +1,8 @@
namespace Somnia.GuestLog;
public static class AppSettings
{
public const string AppName = "Somnia-GuestLog-dev";
public const string AppVersion = "v0.0.0-dev"; //2026-09-08
}
+1
View File
@@ -23,6 +23,7 @@
<ReconnectModal /> <ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script> <script src="@Assets["_framework/blazor.web.js"]"></script>
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script> <script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
<script src="js/App.js"></script>
</body> </body>
</html> </html>
@@ -17,13 +17,22 @@
<MudStack row> <MudStack row>
<MudTextField @bind-Value="SearchText" Label="@LanguageLocale.control_AddressQueryDialog_searchText" <MudTextField @bind-Value="SearchText" Label="@LanguageLocale.control_AddressQueryDialog_searchText"
Required /> Required />
<MudIconButton Icon="@Icons.Material.Filled.Search" OnClick="btnSearchAddress" /> <MudIconButton Icon="@Icons.Material.Filled.Search" OnClick="btnSearchAddress" Disabled="@_isDataLoading" />
</MudStack> </MudStack>
<MudPaper Class="mt-4 pa-2"> <MudPaper Class="mt-4 pa-2">
@if (addressResults.Count == 0) @if (addressResults.Count == 0 && !_isDataLoading)
{ {
<MudText Typo="Typo.body1">@LanguageLocale.general_noResults</MudText> <MudElement Style="width: 100%; height: 100px;" HtmlTag="div">
<MudText Typo="Typo.body1" Align="Align.Center">
@LanguageLocale.general_noResults
</MudText>
</MudElement>
}
@if (_isDataLoading)
{
<MudSkeleton Width="100%" Height="100px" />
} }
<MudList T="AddressResultDTO" @bind-SelectedValue="SelectedAddressResult" <MudList T="AddressResultDTO" @bind-SelectedValue="SelectedAddressResult"
@@ -81,6 +90,9 @@
private List<AddressResultDTO> addressResults = []; private List<AddressResultDTO> addressResults = [];
private AddressResultDTO? SelectedAddressResult { get; set; } private AddressResultDTO? SelectedAddressResult { get; set; }
private bool _isDataLoading = false;
private string _lastSearchText = "";
private void Cancel() private void Cancel()
{ {
@@ -99,16 +111,20 @@
private async Task btnSearchAddress() private async Task btnSearchAddress()
{ {
if (SearchText.Length == 0) if (SearchText.Length == 0 || SearchText == _lastSearchText)
{ {
return; return;
} }
try try
{ {
var queryService = addressQueryFactory.GetService(); _isDataLoading = true;
var queryService = addressQueryFactory.GetService();
addressResults = await queryService.SearchAddressAsync(SearchText); addressResults = await queryService.SearchAddressAsync(SearchText);
_lastSearchText = SearchText;
_isDataLoading = false;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -0,0 +1,60 @@
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject IJSRuntime JS
@inject NavigationManager Navigation
<MudItem>
<MudIconButton Icon="@Icons.Material.Outlined.Translate" Color="Color.Primary" DropShadow="false"
OnClick="@BtnToggleOpen" />
<MudPopover Open="_open" AnchorOrigin="Origin.BottomCenter" TransformOrigin="Origin.TopLeft" Paper="false">
<MudPaper Outlined="true" Class="px-4 py-8">
<MudSelect Value="currentCulture" T="CultureInfo" Label="@LanguageLocale.general_languageSelector" FullWidth="true"
ValueChanged="(culture) => CmbSelectedCultureChanged(culture)">
@foreach (CultureInfo culture in supportedCultures)
{
<MudSelectItem Value="culture">
@culture.DisplayName
</MudSelectItem>
}
</MudSelect>
</MudPaper>
</MudPopover>
</MudItem>
@code {
private bool _open;
private CultureInfo? currentCulture;
private List<CultureInfo> supportedCultures = [
new CultureInfo("en-US"),
new CultureInfo("es-ES"),
];
protected override void OnInitialized()
{
currentCulture = CultureInfo.CurrentCulture;
}
private void BtnToggleOpen() => _open = !_open;
private async Task CmbSelectedCultureChanged(CultureInfo? newCulture)
{
if (newCulture is null)
{
return;
}
string cookieValue = CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(newCulture, newCulture));
await JS.InvokeVoidAsync("setBlazorCultureCookie", cookieValue);
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
//Close Popover.
_open = false;
}
}
@@ -6,53 +6,61 @@
@inject IDataListService dataListService; @inject IDataListService dataListService;
@inject IValidator<GuestDocumentDTO> guestDocValidator; @inject IValidator<GuestDocumentDTO> guestDocValidator;
@inject IUserMessageService userMessage;
<MudForm @ref="@_form" Model="@Model" Validation="@((guestDocValidator as GuestDocumentDTOValidator)!.ValidateValue)" ValidationDelay="0" <MudForm @ref="@_form" Model="@Model" Validation="@((guestDocValidator as GuestDocumentDTOValidator)!.ValidateValue)"
ReadOnly="@ReadOnly"> ValidationDelay="0" ReadOnly="@ReadOnly">
<MudGrid> <MudGrid>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudPaper Class="pa-4" Elevation="3" Height="100%"> <MudPaper Class="pa-4" Elevation="3" Height="100%">
<MudText Typo="Typo.subtitle1" Align="Align.Center">@LanguageLocale.control_GuestDocForm_sectionDocument</MudText> <MudText Typo="Typo.subtitle1" Align="Align.Center">
@LanguageLocale.control_GuestDocForm_sectionDocument
</MudText>
<MudDivider DividerType="DividerType.Middle" Class="mb-4" /> <MudDivider DividerType="DividerType.Middle" Class="mb-4" />
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudSelect @bind-Value="Model.DocumentType" For="@(() => Model.DocumentType)" Label="@LanguageLocale.dto_GuestDocument_DocumentType" <MudSelect @bind-Value="Model.DocumentType" For="@(() => Model.DocumentType)"
Required ReadOnly="@ReadOnlyDocId"> Label="@LanguageLocale.dto_GuestDocument_DocumentType" Required ReadOnly="@ReadOnlyDocId">
@foreach (string docType in dataListService.GetDataListByName(GuestConsts.DataList_DocumentTypes)) @foreach (string docType in
dataListService.GetDataListByName(GuestConsts.DataList_DocumentTypes))
{ {
<MudSelectItem Value="docType"> <MudSelectItem Value="docType">
@dataListService.GetLocalizedValue(GuestConsts.DataList_DocumentTypes, docType) @dataListService.GetLocalizedValue(GuestConsts.DataList_DocumentTypes, docType)
</MudSelectItem> </MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudTextField @bind-Value="Model.DocumentNum" For="@(() => Model.DocumentNum)" Label="@LanguageLocale.dto_GuestDocument_DocumentNum" <MudTextField @bind-Value="Model.DocumentNum" For="@(() => Model.DocumentNum)"
Required ReadOnly="@ReadOnlyDocId" /> Label="@LanguageLocale.dto_GuestDocument_DocumentNum" Required ReadOnly="@ReadOnlyDocId" />
</MudStack> </MudStack>
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudDatePicker @bind-Date="Model.IssueDate" For="@(() => Model.IssueDate)" Label="@LanguageLocale.dto_GuestDocument_IssueDate" <MudDatePicker @bind-Date="Model.IssueDate" For="@(() => Model.IssueDate)"
Clearable OpenTo="OpenTo.Year" Editable /> Label="@LanguageLocale.dto_GuestDocument_IssueDate" Clearable OpenTo="OpenTo.Year" Editable />
<MudTextField @bind-Value="Model.SupportNum" For="@(() => Model.SupportNum)" Label="@LanguageLocale.dto_GuestDocument_SupportNum" <MudTextField @bind-Value="Model.SupportNum" For="@(() => Model.SupportNum)"
Required="@isSuportNumberRequired" Mask="@(new PatternMask("aaa000000"))" Label="@LanguageLocale.dto_GuestDocument_SupportNum" Required="@isSuportNumberRequired"
Mask="SuportNumberPatternMask"
OnBlur="@(() => Model.SupportNum = Model.SupportNum?.ToUpper())" /> OnBlur="@(() => Model.SupportNum = Model.SupportNum?.ToUpper())" />
</MudStack> </MudStack>
<MudAutocomplete T="string" @bind-Value="Model.Nationality" For="@(() => Model.Nationality)" Label="@LanguageLocale.dto_GuestDocument_Nationality" <MudAutocomplete T="string" @bind-Value="Model.Nationality" For="@(() => Model.Nationality)"
SearchFunc="@SearchCountryCodes" ToStringFunc="@(e=> string.IsNullOrEmpty(e) ? "" : @dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, e))" Label="@LanguageLocale.dto_GuestDocument_Nationality" SearchFunc="@SearchCountryCodes"
SelectValueOnTab="true" AdornmentIcon="@Icons.Material.Filled.Search" ToStringFunc="@(e=> string.IsNullOrEmpty(e) ? "" : @dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, e))"
style="@FieldMarginY" /> SelectValueOnTab="true" AdornmentIcon="@Icons.Material.Filled.Search" style="@FieldMarginY"
Required />
</MudPaper> </MudPaper>
</MudItem> </MudItem>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudPaper Class="pa-4" Elevation="3" Height="100%"> <MudPaper Class="pa-4" Elevation="3" Height="100%">
<MudText Typo="Typo.subtitle1" Align="Align.Center">@LanguageLocale.control_GuestDocForm_sectionGuest</MudText> <MudText Typo="Typo.subtitle1" Align="Align.Center">
@LanguageLocale.control_GuestDocForm_sectionGuest
</MudText>
<MudDivider DividerType="DividerType.Middle" Class="mb-4" /> <MudDivider DividerType="DividerType.Middle" Class="mb-4" />
<MudTextField @bind-Value="Model.Name" For="@(() => Model.Name)" <MudTextField @bind-Value="Model.Name" For="@(() => Model.Name)" Required
Label="@LanguageLocale.dto_GuestDocument_Name" Label="@LanguageLocale.dto_GuestDocument_Name"
OnBlur="@(() => Model.Name = Model.Name?.ToUpper())" /> OnBlur="@(() => Model.Name = Model.Name?.ToUpper())" />
<MudTextField @bind-Value="Model.Surname" For="@(() => Model.Surname)" <MudTextField @bind-Value="Model.Surname" For="@(() => Model.Surname)" Required
Label="@LanguageLocale.dto_GuestDocument_Surname" Label="@LanguageLocale.dto_GuestDocument_Surname"
OnBlur="@(() => Model.Surname = Model.Surname?.ToUpper())" /> OnBlur="@(() => Model.Surname = Model.Surname?.ToUpper())" />
<MudTextField @bind-Value="Model.Surname2" For="@(() => Model.Surname2)" <MudTextField @bind-Value="Model.Surname2" For="@(() => Model.Surname2)"
@@ -60,11 +68,14 @@
OnBlur="@(() => Model.Surname2 = Model.Surname2?.ToUpper())" /> OnBlur="@(() => Model.Surname2 = Model.Surname2?.ToUpper())" />
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudDatePicker @bind-Date="Model.BirthDate" For="@(() => Model.BirthDate)" Label="@LanguageLocale.dto_GuestDocument_BirthDate" <MudDatePicker @bind-Date="Model.BirthDate" For="@(() => Model.BirthDate)"
Clearable OpenTo="OpenTo.Year" Editable /> Label="@LanguageLocale.dto_GuestDocument_BirthDate" OpenTo="OpenTo.Year" Clearable Required
Editable />
<MudSelect @bind-Value="Model.Gender" For="@(() => Model.Gender)" Label="@LanguageLocale.dto_GuestDocument_Gender"> <MudSelect @bind-Value="Model.Gender" For="@(() => Model.Gender)"
@foreach (string gender in dataListService.GetDataListByName(GuestConsts.DataList_GenderOptions)) Label="@LanguageLocale.dto_GuestDocument_Gender">
@foreach (string gender in
dataListService.GetDataListByName(GuestConsts.DataList_GenderOptions))
{ {
<MudSelectItem Value="gender"> <MudSelectItem Value="gender">
@dataListService.GetLocalizedValue(GuestConsts.DataList_GenderOptions, gender) @dataListService.GetLocalizedValue(GuestConsts.DataList_GenderOptions, gender)
@@ -77,50 +88,58 @@
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudPaper Class="pa-4" Elevation="3" Height="100%"> <MudPaper Class="pa-4" Elevation="3" Height="100%">
<MudText Typo="Typo.subtitle1" Align="Align.Center">@LanguageLocale.control_GuestDocForm_sectionContact</MudText> <MudText Typo="Typo.subtitle1" Align="Align.Center">
@LanguageLocale.control_GuestDocForm_sectionContact
</MudText>
<MudDivider DividerType="DividerType.Middle" Class="mb-4" /> <MudDivider DividerType="DividerType.Middle" Class="mb-4" />
<MudStack Row> <MudStack Row>
<MudTextField @bind-Value="Model.PhoneNumber" For="@(() => Model.PhoneNumber)" <MudTextField @bind-Value="Model.PhoneNumber" For="@(() => Model.PhoneNumber)"
Label="@LanguageLocale.dto_GuestDocument_PhoneNumber" Label="@LanguageLocale.dto_GuestDocument_PhoneNumber" InputType="InputType.Telephone" />
InputType="InputType.Telephone" />
<MudTextField @bind-Value="Model.EmailAddress" For="@(() => Model.EmailAddress)" <MudTextField @bind-Value="Model.EmailAddress" For="@(() => Model.EmailAddress)"
Label="@LanguageLocale.dto_GuestDocument_EmailAddress" Label="@LanguageLocale.dto_GuestDocument_EmailAddress" InputType="InputType.Email" />
InputType="InputType.Email" />
</MudStack> </MudStack>
</MudPaper> </MudPaper>
</MudItem> </MudItem>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudPaper Class="pa-4" Elevation="3" Height="100%"> <MudPaper Class="pa-4" Elevation="3" Height="100%">
<MudText Typo="Typo.subtitle1" Align="Align.Center">@LanguageLocale.control_GuestDocForm_sectionAddress</MudText> <MudText Typo="Typo.subtitle1" Align="Align.Center">
@LanguageLocale.control_GuestDocForm_sectionAddress
</MudText>
<MudDivider DividerType="DividerType.Middle" Class="mb-4" /> <MudDivider DividerType="DividerType.Middle" Class="mb-4" />
@if (!ReadOnly) @if (!ReadOnly)
{ {
<MudButton OnClick="BtnOpenAddressSearchDialog" Variant="Variant.Outlined" FullWidth="true" Class="mb-2"> <MudStack Row>
@LanguageLocale.control_GuestDocForm_btnSearchAddress <MudButton OnClick="BtnOpenAddressSearchDialog" Variant="Variant.Outlined" FullWidth="true"
</MudButton> Class="mb-2">
@LanguageLocale.control_GuestDocForm_btnSearchAddress
</MudButton>
<MudIconButton OnClick="BtnSkipAddressSearch" Variant="Variant.Outlined" Disabled="!ReadOnlyAddress"
Icon="@Icons.Material.Filled.SkipNext" Class="mb-2" />
</MudStack>
} }
<MudTextField @bind-Value="Model.Address" For="@(() => Model.Address)" <MudTextField @bind-Value="Model.Address" For="@(() => Model.Address)"
Label="@LanguageLocale.general_address_address" ReadOnly="@ReadOnlyAddress" /> Label="@LanguageLocale.general_address_address" ReadOnly="@ReadOnlyAddress" />
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudTextField @bind-Value="Model.AddressCity" For="@(() => Model.AddressCity)" <MudTextField @bind-Value="Model.AddressCity" For="@(() => Model.AddressCity)"
Label="@LanguageLocale.general_address_city" ReadOnly="@ReadOnlyAddress" /> Label="@LanguageLocale.general_address_city" ReadOnly="@ReadOnlyAddress" />
<MudTextField @bind-Value="Model.AddressProvince" For="@(() => Model.AddressProvince)" <MudTextField @bind-Value="Model.AddressProvince" For="@(() => Model.AddressProvince)"
Label="@LanguageLocale.general_address_province" ReadOnly="@ReadOnlyAddress" /> Label="@LanguageLocale.general_address_province" ReadOnly="@ReadOnlyAddress" />
<MudTextField @bind-Value="Model.AddressState" For="@(() => Model.AddressState)" <MudTextField @bind-Value="Model.AddressState" For="@(() => Model.AddressState)"
Label="@LanguageLocale.general_address_state" ReadOnly="@ReadOnlyAddress" /> Label="@LanguageLocale.general_address_state" ReadOnly="@ReadOnlyAddress" />
</MudStack> </MudStack>
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudAutocomplete T="string" @bind-Value="Model.AddressCountry" For="@(() => Model.AddressCountry)" <MudAutocomplete T="string" @bind-Value="Model.AddressCountry" For="@(() => Model.AddressCountry)"
Label="@LanguageLocale.general_address_country" ReadOnly="@ReadOnlyAddress" Label="@LanguageLocale.general_address_country" ReadOnly="@ReadOnlyAddress"
SearchFunc="@SearchCountryCodes" ToStringFunc="@(e=> string.IsNullOrEmpty(e) ? "" : @dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, e))" SearchFunc="@SearchCountryCodes"
SelectValueOnTab="true" AdornmentIcon="@Icons.Material.Filled.Search" /> ToStringFunc="@(e=> string.IsNullOrEmpty(e) ? "" : @dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, e))"
SelectValueOnTab="true" AdornmentIcon="@Icons.Material.Filled.Search" />
<MudTextField @bind-Value="Model.ZipCode" For="@(() => Model.ZipCode)" <MudTextField @bind-Value="Model.ZipCode" For="@(() => Model.ZipCode)"
Label="@LanguageLocale.general_address_zipCode" ReadOnly="@ReadOnlyAddress" /> Label="@LanguageLocale.general_address_zipCode" ReadOnly="@ReadOnlyAddress" />
</MudStack> </MudStack>
</MudPaper> </MudPaper>
</MudItem> </MudItem>
@@ -155,7 +174,20 @@
private Dictionary<string, string>? countryDict = null; private Dictionary<string, string>? countryDict = null;
private bool isSuportNumberRequired { get => Model?.Nationality == GuestConsts.CountryCode_ESP; } private bool isSuportNumberRequired
{
get => Model?.DocumentType == GuestConsts.DocumentType_DNI
|| Model?.DocumentType == GuestConsts.DocumentType_NIE;
}
private PatternMask SuportNumberPatternMask
{
get => Model?.DocumentType switch
{
GuestConsts.DocumentType_DNI => new PatternMask("aaa000000"),
GuestConsts.DocumentType_NIE => new PatternMask("a00000000"),
_ => new PatternMask("")
};
}
private string CreateTimeString private string CreateTimeString
{ {
get get
@@ -193,7 +225,7 @@
countryDict = new(countryCodeSet.Count); countryDict = new(countryCodeSet.Count);
//Init dict. //Init dict.
foreach(string code in dataListService.GetDataListByName(GuestConsts.DataList_CountryCodes)) foreach (string code in dataListService.GetDataListByName(GuestConsts.DataList_CountryCodes))
{ {
countryDict.Add(code, dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, code)); countryDict.Add(code, dataListService.GetLocalizedValue(GuestConsts.DataList_CountryCodes, code));
} }
@@ -237,6 +269,14 @@
ReadOnlyAddress = ReadOnly; ReadOnlyAddress = ReadOnly;
} }
private async Task BtnSkipAddressSearch()
{
userMessage.ShowMessage("Address search skipped! Address is editable now", MessageKind.Info);
//Make Address editable (if the form is editable).
ReadOnlyAddress = ReadOnly;
}
#endregion #endregion
} }
@@ -21,12 +21,14 @@
<MudDivider DividerType="DividerType.Middle" Class="mb-4" /> <MudDivider DividerType="DividerType.Middle" Class="mb-4" />
<MudStack Row style="@FieldMarginY"> <MudStack Row style="@FieldMarginY">
<MudDatePicker @ref="_datePickerDateIn" @bind-Date="Model.DateIn" For="@(() => Model.DateIn)" <MudDatePicker Date="Model.DateIn" For="@(() => Model.DateIn)"
DateChanged="@((newVal) => Model.DateIn = GetDateTimeFromDateAndTime(newVal, Model.DateIn?.TimeOfDay))"
Label="@LanguageLocale.dto_StayEntry_DateIn" Clearable OpenTo="OpenTo.Date" Editable Label="@LanguageLocale.dto_StayEntry_DateIn" Clearable OpenTo="OpenTo.Date" Editable
AutoClose="true" /> AutoClose="true" />
<MudTimePicker Time="Model.DateIn?.TimeOfDay" Label=" " Editable <MudTimePicker Time="Model.DateIn?.TimeOfDay" Label=" " Editable
TimeChanged="@((newVal) => Model.DateIn = GetDateTimeFromDateAndTime(Model.DateIn, newVal))" /> TimeChanged="@((newVal) => Model.DateIn = GetDateTimeFromDateAndTime(Model.DateIn, newVal))" />
<MudDatePicker @ref="_datePickerDateOut" @bind-Date="Model.DateOut" For="@(() => Model.DateOut)" <MudDatePicker Date="Model.DateOut" For="@(() => Model.DateOut)"
DateChanged="@((newVal) => Model.DateOut = GetDateTimeFromDateAndTime(newVal, Model.DateOut?.TimeOfDay))"
Label="@LanguageLocale.dto_StayEntry_DateOut" Clearable OpenTo="OpenTo.Date" Editable Label="@LanguageLocale.dto_StayEntry_DateOut" Clearable OpenTo="OpenTo.Date" Editable
AutoClose="true" /> AutoClose="true" />
<MudTimePicker Time="Model.DateOut?.TimeOfDay" Label=" " Editable <MudTimePicker Time="Model.DateOut?.TimeOfDay" Label=" " Editable
@@ -125,8 +127,6 @@
private MudForm _form = null!; private MudForm _form = null!;
private MudDatePicker _datePickerDateIn = null!;
private MudDatePicker _datePickerDateOut = null!;
private const string FieldMarginY = "margin-block: 0.5em"; private const string FieldMarginY = "margin-block: 0.5em";
private IEnumerable<string> PaymentMethodSet private IEnumerable<string> PaymentMethodSet
@@ -19,9 +19,14 @@
<MudToggleIconButton Class="ml-auto" Icon="@Icons.Material.Outlined.LightMode" <MudToggleIconButton Class="ml-auto" Icon="@Icons.Material.Outlined.LightMode"
ToggledIcon="@Icons.Material.Outlined.DarkMode" ToggledColor="Color.Primary" Color="Color.Primary" ToggledIcon="@Icons.Material.Outlined.DarkMode" ToggledColor="Color.Primary" Color="Color.Primary"
Toggled="@_isDarkMode" ToggledChanged="@DarkModeToggle" /> Toggled="@_isDarkMode" ToggledChanged="@DarkModeToggle" />
<LanguageSelector />
</MudAppBar> </MudAppBar>
<MudDrawer id="nav-drawer" @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2"> <MudDrawer id="nav-drawer" @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2">
<NavMenu /> <NavMenu />
<MudSpacer />
<MudText Typo="Typo.caption" Class="mx-2" GutterBottom>
@AppSettings.AppName - @AppSettings.AppVersion
</MudText>
</MudDrawer> </MudDrawer>
<MudMainContent Class="pt-16 pa-4"> <MudMainContent Class="pt-16 pa-4">
@Body @Body
@@ -13,4 +13,7 @@
<MudNavLink Href="@GuestConsts.Page_StayEntry_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Book"> <MudNavLink Href="@GuestConsts.Page_StayEntry_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Book">
@LanguageLocale.navMenu_StayEntryList @LanguageLocale.navMenu_StayEntryList
</MudNavLink> </MudNavLink>
<MudNavLink Href="@GuestConsts.Page_StayExport_List" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.ImportExport">
@LanguageLocale.navMenu_StayExport
</MudNavLink>
</MudNavMenu> </MudNavMenu>
@@ -9,6 +9,7 @@
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IGuestDocumentEntityService guestDocService @inject IGuestDocumentEntityService guestDocService
@inject IStayEntryEntityService stayService @inject IStayEntryEntityService stayService
@inject IDialogService DialogService
<PageTitle>Somnia - Guest Docs - Details</PageTitle> <PageTitle>Somnia - Guest Docs - Details</PageTitle>
@@ -16,14 +17,28 @@
<MudStack Row="true" Class="my-4"> <MudStack Row="true" Class="my-4">
<MudText Typo="Typo.h4" GutterBottom="true">Guest Document: @docId - @documentModel?.FullDocument</MudText> <MudText Typo="Typo.h4" GutterBottom="true">Guest Document: @docId - @documentModel?.FullDocument</MudText>
<MudSpacer /> <MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Edit" @if (_isEditMode)
OnClick="() => BtnEdit()" Disabled> {
@LanguageLocale.general_btnEdit <MudButton Variant="Variant.Filled" Color="Color.Error" Size="Size.Small" StartIcon="@Icons.Material.Filled.Cancel"
</MudButton> OnClick="() => BtnCancelEdit()" Disabled="!_isEditMode">
<MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" @LanguageLocale.general_btnCancel
StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => BtnReturn()"> </MudButton>
@LanguageLocale.general_btnReturn <MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Save"
</MudButton> OnClick="() => BtnSaveEdit()" Disabled="!_isEditMode">
@LanguageLocale.general_btnSave
</MudButton>
}
@if (!_isEditMode)
{
<MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Edit"
OnClick="() => BtnEnableEdit()" Disabled="_isEditMode">
@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> </MudStack>
@if (documentModel is not null) @if (documentModel is not null)
@@ -32,10 +47,11 @@
Centered> Centered>
<MudTabPanel Text="Details" Icon="@Icons.Material.Filled.Info"> <MudTabPanel Text="Details" Icon="@Icons.Material.Filled.Info">
<GuestDocForm @ref="_form" Model="documentModel" ReadOnly="true" /> <GuestDocForm @ref="_form" Model="documentModel" ReadOnly="!_isEditMode" />
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="Stays" ToolTip="Guest stays" OnClick="LoadTabGuestStays" Icon="@Icons.Material.Filled.Bed"> <MudTabPanel Text="Stays" ToolTip="Guest stays" Icon="@Icons.Material.Filled.Bed" OnClick="LoadTabGuestStays"
Disabled="_isEditMode">
<MudText>Total Stays: @(guestStaysLoading ? "Loading..." : guestStayList.Count())</MudText> <MudText>Total Stays: @(guestStaysLoading ? "Loading..." : guestStayList.Count())</MudText>
<MudTable Items="@guestStayList" Hover="true" Loading="@guestStaysLoading" Class="mt-2" Elevation="2"> <MudTable Items="@guestStayList" Hover="true" Loading="@guestStaysLoading" Class="mt-2" Elevation="2">
<HeaderContent> <HeaderContent>
@@ -87,8 +103,16 @@
IEnumerable<StayEntryDTO> guestStayList = []; IEnumerable<StayEntryDTO> guestStayList = [];
bool guestStaysLoading = true; bool guestStaysLoading = true;
private bool _isEditMode = false;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{
//Get model from db.
await LoadGuestDocumentAsync();
}
private async Task LoadGuestDocumentAsync()
{ {
//Get model from db. //Get model from db.
documentModel = await guestDocService.GetLastVersionDtoByIdAsync(docId); documentModel = await guestDocService.GetLastVersionDtoByIdAsync(docId);
@@ -121,9 +145,68 @@
#region Event Handlers #region Event Handlers
private void BtnEdit() private void BtnEnableEdit()
{ {
userMessage.ShowMessage("Not implemented yet!", MessageKind.Info); Debug.Assert(_isEditMode == false);
userMessage.ShowMessage(LanguageLocale.page_GuestDocDetail_msgEditEnabled, MessageKind.Normal);
_isEditMode = true;
}
private async Task BtnCancelEdit()
{
Debug.Assert(_isEditMode == true);
var result = await DialogService.ShowMessageBoxAsync(
LanguageLocale.general_confirmTitle, LanguageLocale.page_GuestDocDetail_msgConfirmCancelEdit,
yesText: "OK", cancelText: LanguageLocale.general_btnCancel);
if (result == null || result.Value == false)
{
// Cancel cancel -> Continue with edit.
return;
}
//Cancel edit -> Reload data from DB.
await LoadGuestDocumentAsync();
userMessage.ShowMessage(LanguageLocale.general_msgEditCanceled, MessageKind.Normal);
_isEditMode = false;
}
private async Task BtnSaveEdit()
{
Debug.Assert(_isEditMode == true && documentModel is not null);
var confirmResult = await DialogService.ShowMessageBoxAsync(
LanguageLocale.general_confirmTitle, LanguageLocale.page_GuestDocDetail_msgConfirmSaveEdit,
yesText: "OK", cancelText: LanguageLocale.general_btnCancel);
if (confirmResult == null || confirmResult.Value == false)
{
// Cancel save -> Continue with edit.
return;
}
//Show Loading dialog...
var loadingDialog = await DialogService.ShowAsync<LoadingDialog>();
//Save edit.
Result updateResult = await guestDocService.UpdateDocumentAsync(documentModel);
if (!updateResult.IsSuccess)
{
string errorMsg = string.Format(LanguageLocale.page_GuestDocDetail_msgEditError, documentModel.FullDocument);
userMessage.ShowResultWithMessage(updateResult, errorMsg);
loadingDialog.Close();
return;
}
//Reload data from DB.
await LoadGuestDocumentAsync();
loadingDialog.Close();
userMessage.ShowMessage(LanguageLocale.general_msgEditSaved, MessageKind.Success);
_isEditMode = false;
} }
private void BtnReturn() private void BtnReturn()
@@ -43,8 +43,9 @@
</MudStack> </MudStack>
</ToolBarContent> </ToolBarContent>
<Columns> <Columns>
<PropertyColumn Property="x => x.Id" Title="@LanguageLocale.dto_GuestDocument_Id" /> <PropertyColumn Property="x => x.Id" Title="@LanguageLocale.dto_GuestDocument_Id" Hideable="false" />
<PropertyColumn Property="x => x.Version" Title="@LanguageLocale.dto_GuestDocument_Version" /> <PropertyColumn Property="x => x.Version" Title="@LanguageLocale.dto_GuestDocument_Version" Hidden
Hideable="true" />
<PropertyColumn Title="@LanguageLocale.dto_GuestDocument_DocumentType" <PropertyColumn Title="@LanguageLocale.dto_GuestDocument_DocumentType"
Property="x => dataListService.GetLocalizedValue(GuestConsts.DataList_DocumentTypes, x.DocumentType)" Property="x => dataListService.GetLocalizedValue(GuestConsts.DataList_DocumentTypes, x.DocumentType)"
Hideable="false" /> Hideable="false" />
@@ -18,10 +18,13 @@
<MudStack Row="true" Class="my-4"> <MudStack Row="true" Class="my-4">
<MudText Typo="Typo.h4" GutterBottom="true">New Guest Document</MudText> <MudText Typo="Typo.h4" GutterBottom="true">New Guest Document</MudText>
<MudSpacer /> <MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Add" @if (currentPageMode == ePageMode.createNew)
OnClick="() => BtnSaveNewGuest()"> {
@LanguageLocale.general_btnCreate <MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Add"
</MudButton> OnClick="() => BtnSaveNewGuest()">
@LanguageLocale.general_btnCreate
</MudButton>
}
<MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" <MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small"
StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => BtnReturn()"> StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => BtnReturn()">
@LanguageLocale.general_btnReturn @LanguageLocale.general_btnReturn
@@ -129,6 +132,8 @@
private async Task BtnSaveNewGuest() private async Task BtnSaveNewGuest()
{ {
Debug.Assert(currentPageMode == ePageMode.createNew && _form != null);
bool isFormValid = await _form.ValidateForm(); bool isFormValid = await _form.ValidateForm();
if (!isFormValid) if (!isFormValid)
{ {
@@ -144,13 +149,13 @@
Result createResult = await guestDocService.CreateDocumentAsync(model); Result createResult = await guestDocService.CreateDocumentAsync(model);
if (!createResult.IsSuccess) if (!createResult.IsSuccess)
{ {
string errorMsg = $"Error creating Document '{model.DocumentNum}'"; string errorMsg = $"Error creating Document '{model.FullDocument}'";
userMessage.ShowResultWithMessage(createResult, errorMsg); userMessage.ShowResultWithMessage(createResult, errorMsg);
loadingDialog.Close(); loadingDialog.Close();
return; return;
} }
userMessage.ShowMessage($"Document '{model.DocumentNum}' Created!", MessageKind.Success); userMessage.ShowMessage($"Document '{model.FullDocument}' Created!", MessageKind.Success);
Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List); Navigation.NavigateTo(GuestConsts.Page_GuestDocs_List);
} }
@@ -60,7 +60,7 @@
return; return;
} }
userMessage.ShowMessage($"StayEntry Created!", MessageKind.Success); userMessage.ShowMessage("StayEntry Created: (Id: {0})", MessageKind.Success, createResult.Context ?? ["???"]);
Navigation.NavigateTo(GuestConsts.Page_StayEntry_List); Navigation.NavigateTo(GuestConsts.Page_StayEntry_List);
} }
@@ -0,0 +1,197 @@
@attribute [Route($"{GuestConsts.Page_StayExport_List}")]
@using System.Text.Json;
@using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Services;
@using Somnia.GuestLog.Services.StayExport;
@inject IStayExportFileEntityService stayExportEntityService
@inject NavigationManager Navigation
@inject IUserMessageService userMessage
@inject IDialogService DialogService
@inject IDataListService dataListService;
@inject ILocalizerService Localizer;
@inject IStayExportService ExportService;
@inject IJSRuntime JS
<PageTitle>Somnia - Stay Export Files</PageTitle>
<MudStack Row="true" Class="my-4">
<MudText Typo="Typo.h4" GutterBottom="true">@LanguageLocale.page_StayExportList_pageTitle</MudText>
<MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small"
StartIcon="@Icons.Material.Filled.Search" OnClick="() => BtnSearchDataAsync()">
@LanguageLocale.general_btnSearch
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small" StartIcon="@Icons.Material.Filled.Add"
OnClick="() => BtnNewExport()">
@LanguageLocale.page_StayExportList_btnNewExport
</MudButton>
</MudStack>
@* <MudPaper Class="pa-4 my-4" Elevation="2" Height="100%">
FILTER
</MudPaper> *@
<MudDataGrid @ref="_dataGrid" T="StayExportFileDTO" Items="@stayExportList" Loading="@_dataLoading" ReadOnly="true"
Elevation="2">
<ToolBarContent>
<MudText Typo="Typo.subtitle1">@LanguageLocale.page_StayExportList_exportList</MudText>
</ToolBarContent>
<Columns>
<PropertyColumn Property="x => x.Id" Hideable="false" Title="@LanguageLocale.dto_StayExportFile_Id" />
<PropertyColumn Property="x => Localizer.GetEnumValue(x.ExportFormat)"
Title="@LanguageLocale.dto_StayExportFile_ExportFormat" />
<TemplateColumn Title="@LanguageLocale.dto_StayExportFile_ExportParams">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Info"
OnClick="() => ShowExportParams(context.Item)" />
</CellTemplate>
</TemplateColumn>
<PropertyColumn Property="x => Localizer.GetEnumValue(x.ExportResult)"
Title="@LanguageLocale.dto_StayExportFile_ExportResult" />
<PropertyColumn Property="x => x.ExportTime" Title="@LanguageLocale.dto_StayExportFile_ExportTime" />
<TemplateColumn Title="@LanguageLocale.dto_StayExportFile_ExportDetails">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Info"
OnClick="() => ShowExportDetails(context.Item)" />
</CellTemplate>
</TemplateColumn>
<TemplateColumn CellClass="d-flex justify-between" Title="Options" StickyRight Hideable="false"
ShowColumnOptions="false">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Download"
OnClick="() => BtnDownloadFile(context.Item)" />
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Delete"
OnClick="() => BtnDeleteItem(context.Item)" />
</CellTemplate>
</TemplateColumn>
</Columns>
<PagerContent>
<MudDataGridPager T="StayExportFileDTO" />
</PagerContent>
</MudDataGrid>
<ConfirmDeleteMsgBox @ref="_confirmDeleteMsgBox" />
@code {
private bool _dataLoading = true;
private const int resultLimit = 200;
private IEnumerable<StayExportFileDTO> stayExportList = [];
private MudDataGrid<StayExportFileDTO> _dataGrid = null!;
private ConfirmDeleteMsgBox _confirmDeleteMsgBox = null!;
protected override async Task OnInitializedAsync()
{
await LoadDataAsync(showMsg: false);
}
private async Task LoadDataAsync(bool showMsg = true)
{
_dataLoading = true;
stayExportList = await stayExportEntityService.GetDtoListAsync(limit: resultLimit);
if (showMsg)
{
userMessage.ShowMessage("Data loaded from DB", MessageKind.Info);
}
_dataLoading = false;
}
#region Event Handlers
private async Task BtnSearchDataAsync()
{
await LoadDataAsync();
}
private void BtnNewExport()
{
Navigation.NavigateTo(GuestConsts.Page_StayExport_New);
}
private async Task ShowExportParams(StayExportFileDTO dto)
{
string jsonParams = JsonSerializer.Serialize(dto.ExportParams, options: new() { WriteIndented = true });
await DialogService.ShowMessageBoxAsync("Details", (MarkupString)$"<pre>{jsonParams}</pre>");
}
private async Task ShowExportDetails(StayExportFileDTO dto)
{
if (string.IsNullOrEmpty(dto.ExportDetails))
{
userMessage.ShowMessage("This ExportFile doesn't have any export details", MessageKind.Info);
return;
}
using var jDoc = JsonDocument.Parse(dto.ExportDetails);
string jsonDetails = JsonSerializer.Serialize(jDoc, new JsonSerializerOptions { WriteIndented = true });
await DialogService.ShowMessageBoxAsync("Details", (MarkupString)$"<pre>{jsonDetails}</pre>");
}
private async Task BtnDownloadFile(StayExportFileDTO item)
{
if (item.ExportResult == Enums.Models.StayExportFileResult.NoData)
{
userMessage.ShowMessage("A Export without data can't be downloaded!", MessageKind.Warning);
return;
}
if (string.IsNullOrEmpty(item.ExportedPath))
{
userMessage.ShowMessage("File can't be downloaded: no path found!", MessageKind.Warning);
return;
}
using Stream objectStream = new MemoryStream();
Result loadResult = await ExportService.GetExportedFileAsync(item, objectStream);
if (!loadResult.IsSuccess)
{
userMessage.ShowResultWithMessage(loadResult, "Exported File can't be downloaded");
return;
}
objectStream.Seek(0, SeekOrigin.Begin);
using var streamRef = new DotNetStreamReference(stream: objectStream);
var fileName = Path.GetFileName(item.ExportedPath);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
userMessage.ShowMessage("Exported File downloaded!", MessageKind.Success);
}
private async Task BtnDeleteItem(StayExportFileDTO item)
{
bool? confirmDelete = await _confirmDeleteMsgBox.MsgBox.ShowAsync();
if (confirmDelete is null || confirmDelete.Value == false)
{
return;
}
Result deleteResult = await ExportService.DeleteExportedFileAsync(item);
if (!deleteResult.IsSuccess)
{
string errorMsg = $"Error deleting Export file {item.Id}";
userMessage.ShowResultWithMessage(deleteResult, errorMsg);
return;
}
userMessage.ShowMessage($"Export file {item.Id} deleted!", MessageKind.Success);
await LoadDataAsync();
}
#endregion
}
@@ -0,0 +1,162 @@
@attribute [Route($"{GuestConsts.Page_StayExport_New}")]
@using FluentValidation;
@using Somnia.GuestLog.Models.DTOs;
@using Somnia.GuestLog.Enums.Models;
@using Somnia.GuestLog.Services;
@using Somnia.GuestLog.Validators;
@using Somnia.GuestLog.Services.StayExport;
@inject IUserMessageService userMessage
@inject IDialogService DialogService
@inject NavigationManager Navigation
@inject IValidator<StayExportParamsDTO> ExportParamsValidator;
@inject ILocalizerService Localizer;
@inject IStayExportService ExportService;
@inject IJSRuntime JS
<PageTitle>Somnia - Stay Export - New</PageTitle>
<MudStack Row="true" Class="my-4">
<MudText Typo="Typo.h4" GutterBottom="true">
@LanguageLocale.page_StayExportNew_pageTitle
</MudText>
<MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Success" Size="Size.Small" StartIcon="@Icons.Material.Filled.Add"
OnClick="() => BtnCreateExport()" Disabled="@_isExported">
@LanguageLocale.general_btnCreate
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small"
StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => BtnReturn()">
@LanguageLocale.general_btnReturn
</MudButton>
</MudStack>
<MudForm @ref="@_form" Model="@exportParams"
Validation="@((ExportParamsValidator as StayExportParamsDTOValidator)!.ValidateValue)" ReadOnly="@_isExported">
<MudPaper Class="pa-4 my-4" Elevation="3" Height="100%">
<MudStack Row style="margin-block: 0.2em;">
<MudSelect @bind-Value="exportFormat" Label="@LanguageLocale.dto_StayExportFile_ExportFormat" FullWidth
Clearable>
@foreach (StayExportFormat option in Enum.GetValues<StayExportFormat>())
{
<MudSelectItem Value="new Nullable<StayExportFormat>(option)">
@Localizer.GetEnumValue(option)
</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="exportParams.ExportType" For="@(() => exportParams.ExportType)"
Label="@LanguageLocale.dto_StayExportParams_ExportType" FullWidth Clearable>
@foreach (StayExportParamType option in Enum.GetValues<StayExportParamType>())
{
<MudSelectItem Value="new Nullable<StayExportParamType>(option)">
@Localizer.GetEnumValue(option)
</MudSelectItem>
}
</MudSelect>
</MudStack>
</MudPaper>
@if (_showPeriodFields)
{
<MudPaper Class="pa-4" Elevation="3" Height="100%">
<MudStack Row>
<MudDatePicker @bind-Date="exportParams.ExportPeriodStart" For="@(() => exportParams.ExportPeriodStart)"
Label="@LanguageLocale.dto_StayExportParams_ExportPeriodStart" Clearable OpenTo="OpenTo.Date" Editable
AutoClose="true" />
<MudDatePicker @bind-Date="exportParams.ExportPeriodEnd" For="@(() => exportParams.ExportPeriodEnd)"
Label="@LanguageLocale.dto_StayExportParams_ExportPeriodEnd" Clearable OpenTo="OpenTo.Date" Editable
AutoClose="true" />
</MudStack>
</MudPaper>
}
</MudForm>
@if (_isExported && exportedFile != null)
{
<MudPaper Class="pa-4 my-6" Elevation="3" Height="100%">
<MudStack>
<MudText Typo="Typo.h4">File Exported!</MudText>
<MudTextField Value="@Localizer.GetEnumValue(exportedFile.ExportResult)"
Label="@LanguageLocale.dto_StayExportFile_ExportResult" FullWidth ReadOnly="true" />
<MudTextField Value="@Localizer.GetEnumValue(exportedFile.ExportFormat)"
Label="@LanguageLocale.dto_StayExportFile_ExportFormat" FullWidth ReadOnly="true" />
<MudButton OnClick="BtnDownloadExportedFile">Download Exported File</MudButton>
</MudStack>
</MudPaper>
}
@code {
MudForm _form = null!;
StayExportFormat? exportFormat = null;
StayExportParamsDTO exportParams = new();
StayExportFileDTO? exportedFile = null;
bool _showPeriodFields => exportParams.ExportType == StayExportParamType.PeriodDateIn;
bool _isExported => exportedFile != null;
private void BtnReturn()
{
Navigation.NavigateTo(GuestConsts.Page_StayExport_List);
}
private async Task BtnCreateExport()
{
await _form.ValidateAsync();
if (!_form.IsValid || exportFormat == null)
{
userMessage.ShowMessage("Invalid Form!", MessageKind.Warning);
return;
}
var loadingDialog = await DialogService.ShowAsync<LoadingDialog>();
var exportResult = await ExportService.ExportStaysByParamsAsync(exportFormat.Value, exportParams);
exportedFile = exportResult.Value;
loadingDialog.Close();
if (!exportResult.IsSuccess)
{
userMessage.ShowResultWithMessage(exportResult, $"Error exporting stays");
return;
}
userMessage.ShowMessage(
$"Stay exported: {exportResult.ResultCode} with export result: {exportedFile?.ExportResult}",
MessageKind.Success);
}
private async Task BtnDownloadExportedFile()
{
Debug.Assert(exportedFile != null);
if (string.IsNullOrEmpty(exportedFile.ExportedPath))
{
userMessage.ShowMessage("Exported File can't be downloaded as no file was saved", MessageKind.Warning);
return;
}
using Stream dataStream = new MemoryStream();
Result loadResult = await ExportService.GetExportedFileAsync(exportedFile, dataStream);
if (!loadResult.IsSuccess)
{
userMessage.ShowResultWithMessage(loadResult, "Exported File can't be downloaded");
return;
}
dataStream.Seek(0, SeekOrigin.Begin);
using var streamRef = new DotNetStreamReference(stream: dataStream);
var fileName = Path.GetFileName(exportedFile.ExportedPath);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
userMessage.ShowMessage("Exported File downloaded!", MessageKind.Success);
}
}
@@ -1,2 +1,3 @@
@using Somnia.GuestLog.Components.Controls @using Somnia.GuestLog.Components.Controls
@using Somnia.GuestLog.Services.Entity; @using Somnia.GuestLog.Services.Entity;
@using System.Diagnostics;
@@ -0,0 +1,50 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.File"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"Enrich": [
"FromLogContext",
"WithThreadId",
"WithProcessId"
],
"Properties": {
"ApplicationName": "Somnia-GuestLog"
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"restrictedToMinimumLevel": "Information"
}
},
{
"Name": "File",
"Args": {
// Logs data path
"path": "/SomniaGuestLog/logs/log_.log",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{ProcessId}-{ThreadId}] [{Level}] {JobKey}{Message}{NewLine}{Exception}",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"fileSizeLimitBytes": 1073741824,
"retainedFileCountLimit": 30,
"restrictedToMinimumLevel": "Information"
}
}
]
}
}
@@ -0,0 +1,9 @@
namespace Somnia.GuestLog.Enums.Models;
public enum StayExportFileResult
{
OK = 0,
NoData = 1,
ExportError = 2,
UploadError = 3,
}
@@ -0,0 +1,7 @@
namespace Somnia.GuestLog.Enums.Models;
public enum StayExportFormat
{
GuestLogJson = 0,
SpainParteViajeros = 1,
}
@@ -0,0 +1,6 @@
namespace Somnia.GuestLog.Enums.Models;
public enum StayExportParamType
{
PeriodDateIn = 0,
}
@@ -7,6 +7,7 @@ public enum ResultCode
UnknownError, UnknownError,
FailedValidation, FailedValidation,
NotFound, NotFound,
FileStorageError,
//Specific Codes //Specific Codes
GuestInStay, GuestInStay,
+10
View File
@@ -13,6 +13,9 @@ public static class GuestConsts
public const string Page_StayEntry_Detail = $"/stayEntries/d/{Page_StayEntry_Detail_Param_Id}"; 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 Page_StayEntry_Detail_Param_Id = "{StayId:long}";
public const string Page_StayExport_List = "/stayExport";
public const string Page_StayExport_New = "/stayExport/new";
public const string QueryParam_ReturnUrl = "returnUrl"; public const string QueryParam_ReturnUrl = "returnUrl";
@@ -29,5 +32,12 @@ public static class GuestConsts
public const string DocumentType_NIE = "NIE"; public const string DocumentType_NIE = "NIE";
public const string DocumentType_PASSPORT = "PASSPORT"; public const string DocumentType_PASSPORT = "PASSPORT";
public const string GenderOptions_MALE = "M";
public const string GenderOptions_FEMALE = "F";
public const string GenderOptions_OTHER = "O";
public const int GuestDocument_FirstVersion = 1; public const int GuestDocument_FirstVersion = 1;
public const string EmptyField = "-";
} }
@@ -5,4 +5,8 @@ public static class DbConsts
public const string DbType_Timestamptz = "timestamp with time zone"; public const string DbType_Timestamptz = "timestamp with time zone";
public const string DbType_Timestamptz_0 = "timestamp(0) with time zone"; public const string DbType_Timestamptz_0 = "timestamp(0) with time zone";
public const string DbType_Timestamptz_3 = "timestamp(3) with time zone"; public const string DbType_Timestamptz_3 = "timestamp(3) with time zone";
public const string DbType_jsonb = "jsonb";
public const string PK_NotApply = "N/A";
} }
@@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Somnia.GuestLog.Models.Entities;
namespace Somnia.GuestLog.Infrastructure.EntityConfigs;
public class StayExportFileEntityConfig : IEntityTypeConfiguration<StayExportFile>
{
public void Configure(EntityTypeBuilder<StayExportFile> builder)
{
builder.ToTable("StayExportFile");
//PK
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.IsRequired()
.UseIdentityByDefaultColumn();
builder.ComplexProperty(e => e.ExportParams, e =>
{
e.ToJson();
});
builder.Property(e => e.ExportedPath)
.HasMaxLength(256);
builder.Property(e => e.ExportDetails)
.HasColumnType(DbConsts.DbType_jsonb);
builder.Property(e => e.ExportTime)
.IsRequired()
.HasColumnType(DbConsts.DbType_Timestamptz);
}
}
@@ -13,11 +13,14 @@ public partial class GuestLogContext : DbContext
public DbSet<GuestDocument> GuestDocuments { get; set; } public DbSet<GuestDocument> GuestDocuments { get; set; }
public DbSet<StayEntry> StayEntries { get; set; } public DbSet<StayEntry> StayEntries { get; set; }
public DbSet<StayExportFile> StayExportFiles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.ApplyConfiguration<GuestDocument>(new GuestDocumentEntityConfig()); modelBuilder.ApplyConfiguration<GuestDocument>(new GuestDocumentEntityConfig());
modelBuilder.ApplyConfiguration<StayEntry>(new StayEntryEntityConfig()); modelBuilder.ApplyConfiguration<StayEntry>(new StayEntryEntityConfig());
modelBuilder.ApplyConfiguration<StayExportFile>(new StayExportFileEntityConfig());
OnModelCreatingPartial(modelBuilder); OnModelCreatingPartial(modelBuilder);
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@@ -0,0 +1,249 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Somnia.GuestLog.Infrastructure;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
[DbContext(typeof(GuestLogContext))]
[Migration("20260901161146_AddTable_StayExportFile")]
partial class AddTable_StayExportFile
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.Property<long>("StayEntryId")
.HasColumnType("bigint");
b.Property<long>("GuestDocumentsId")
.HasColumnType("bigint");
b.Property<int>("GuestDocumentsVersion")
.HasColumnType("integer");
b.HasKey("StayEntryId", "GuestDocumentsId", "GuestDocumentsVersion");
b.HasIndex("GuestDocumentsId", "GuestDocumentsVersion");
b.ToTable("GuestDocumentStayEntry");
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.GuestDocument", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("Version")
.HasColumnType("integer");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp(3) with time zone");
b.Property<string>("DocumentNum")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("DocumentType")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b.Property<string>("EmailAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Gender")
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<bool>("IsLastVersion")
.HasColumnType("boolean");
b.Property<DateOnly>("IssueDate")
.HasColumnType("date");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("SupportNum")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("Surname")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Surname2")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp(3) with time zone");
b.ComplexProperty(typeof(Dictionary<string, object>), "AddressDetails", "Somnia.GuestLog.Models.Entities.GuestDocument.AddressDetails#GuestAddress", b1 =>
{
b1.Property<string>("Address")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCity")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCountry")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b1.Property<string>("AddressProvince")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressState")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("ZipCode")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
});
b.HasKey("Id", "Version");
b.ToTable("GuestDocument", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayEntry", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateIn")
.HasColumnType("timestamp(0) with time zone");
b.Property<DateTime>("DateOut")
.HasColumnType("timestamp(0) with time zone");
b.Property<string>("PaymentMethod")
.IsRequired()
.HasMaxLength(5)
.HasColumnType("character varying(5)");
b.Property<int>("RoomCount")
.HasColumnType("integer");
b.Property<string>("RoomNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("StayEntry", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayExportFile", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("ExportFormat")
.HasColumnType("integer");
b.Property<int>("ExportResult")
.HasColumnType("integer");
b.Property<DateTime>("ExportTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExportedPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.ComplexProperty(typeof(Dictionary<string, object>), "ExportParams", "Somnia.GuestLog.Models.Entities.StayExportFile.ExportParams#StayExportParams", b1 =>
{
b1.IsRequired();
b1.Property<DateTime?>("ExportPeriodEnd");
b1.Property<DateTime?>("ExportPeriodStart");
b1.Property<int>("ExportType");
b1
.ToJson("ExportParams")
.HasColumnType("jsonb");
});
b.HasKey("Id");
b.ToTable("StayExportFile", (string)null);
});
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.HasOne("Somnia.GuestLog.Models.Entities.StayEntry", null)
.WithMany()
.HasForeignKey("StayEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Somnia.GuestLog.Models.Entities.GuestDocument", null)
.WithMany()
.HasForeignKey("GuestDocumentsId", "GuestDocumentsVersion")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,40 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddTable_StayExportFile : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StayExportFile",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ExportFormat = table.Column<int>(type: "integer", nullable: false),
ExportResult = table.Column<int>(type: "integer", nullable: false),
ExportedPath = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ExportTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExportParams = table.Column<string>(type: "jsonb", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StayExportFile", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StayExportFile");
}
}
}
@@ -0,0 +1,252 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Somnia.GuestLog.Infrastructure;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
[DbContext(typeof(GuestLogContext))]
[Migration("20260908172441_StayExportFile_AddColumn_ExportDetails")]
partial class StayExportFile_AddColumn_ExportDetails
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.Property<long>("StayEntryId")
.HasColumnType("bigint");
b.Property<long>("GuestDocumentsId")
.HasColumnType("bigint");
b.Property<int>("GuestDocumentsVersion")
.HasColumnType("integer");
b.HasKey("StayEntryId", "GuestDocumentsId", "GuestDocumentsVersion");
b.HasIndex("GuestDocumentsId", "GuestDocumentsVersion");
b.ToTable("GuestDocumentStayEntry");
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.GuestDocument", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("Version")
.HasColumnType("integer");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp(3) with time zone");
b.Property<string>("DocumentNum")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("DocumentType")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b.Property<string>("EmailAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Gender")
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<bool>("IsLastVersion")
.HasColumnType("boolean");
b.Property<DateOnly>("IssueDate")
.HasColumnType("date");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("SupportNum")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("Surname")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Surname2")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp(3) with time zone");
b.ComplexProperty(typeof(Dictionary<string, object>), "AddressDetails", "Somnia.GuestLog.Models.Entities.GuestDocument.AddressDetails#GuestAddress", b1 =>
{
b1.Property<string>("Address")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCity")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCountry")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b1.Property<string>("AddressProvince")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressState")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("ZipCode")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
});
b.HasKey("Id", "Version");
b.ToTable("GuestDocument", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayEntry", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateIn")
.HasColumnType("timestamp(0) with time zone");
b.Property<DateTime>("DateOut")
.HasColumnType("timestamp(0) with time zone");
b.Property<string>("PaymentMethod")
.IsRequired()
.HasMaxLength(5)
.HasColumnType("character varying(5)");
b.Property<int>("RoomCount")
.HasColumnType("integer");
b.Property<string>("RoomNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("StayEntry", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayExportFile", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ExportDetails")
.HasColumnType("jsonb");
b.Property<int>("ExportFormat")
.HasColumnType("integer");
b.Property<int>("ExportResult")
.HasColumnType("integer");
b.Property<DateTime>("ExportTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExportedPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.ComplexProperty(typeof(Dictionary<string, object>), "ExportParams", "Somnia.GuestLog.Models.Entities.StayExportFile.ExportParams#StayExportParams", b1 =>
{
b1.IsRequired();
b1.Property<DateTime?>("ExportPeriodEnd");
b1.Property<DateTime?>("ExportPeriodStart");
b1.Property<int>("ExportType");
b1
.ToJson("ExportParams")
.HasColumnType("jsonb");
});
b.HasKey("Id");
b.ToTable("StayExportFile", (string)null);
});
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.HasOne("Somnia.GuestLog.Models.Entities.StayEntry", null)
.WithMany()
.HasForeignKey("StayEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Somnia.GuestLog.Models.Entities.GuestDocument", null)
.WithMany()
.HasForeignKey("GuestDocumentsId", "GuestDocumentsVersion")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class StayExportFile_AddColumn_ExportDetails : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ExportDetails",
table: "StayExportFile",
type: "jsonb",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExportDetails",
table: "StayExportFile");
}
}
}
@@ -0,0 +1,252 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Somnia.GuestLog.Infrastructure;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
[DbContext(typeof(GuestLogContext))]
[Migration("20260910093310_GuestDocument_IssueDate_MakeNullable")]
partial class GuestDocument_IssueDate_MakeNullable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.Property<long>("StayEntryId")
.HasColumnType("bigint");
b.Property<long>("GuestDocumentsId")
.HasColumnType("bigint");
b.Property<int>("GuestDocumentsVersion")
.HasColumnType("integer");
b.HasKey("StayEntryId", "GuestDocumentsId", "GuestDocumentsVersion");
b.HasIndex("GuestDocumentsId", "GuestDocumentsVersion");
b.ToTable("GuestDocumentStayEntry");
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.GuestDocument", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("Version")
.HasColumnType("integer");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp(3) with time zone");
b.Property<string>("DocumentNum")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("DocumentType")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b.Property<string>("EmailAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Gender")
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<bool>("IsLastVersion")
.HasColumnType("boolean");
b.Property<DateOnly?>("IssueDate")
.HasColumnType("date");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("SupportNum")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("Surname")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Surname2")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp(3) with time zone");
b.ComplexProperty(typeof(Dictionary<string, object>), "AddressDetails", "Somnia.GuestLog.Models.Entities.GuestDocument.AddressDetails#GuestAddress", b1 =>
{
b1.Property<string>("Address")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCity")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressCountry")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)");
b1.Property<string>("AddressProvince")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("AddressState")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b1.Property<string>("ZipCode")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
});
b.HasKey("Id", "Version");
b.ToTable("GuestDocument", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayEntry", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateIn")
.HasColumnType("timestamp(0) with time zone");
b.Property<DateTime>("DateOut")
.HasColumnType("timestamp(0) with time zone");
b.Property<string>("PaymentMethod")
.IsRequired()
.HasMaxLength(5)
.HasColumnType("character varying(5)");
b.Property<int>("RoomCount")
.HasColumnType("integer");
b.Property<string>("RoomNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("StayEntry", (string)null);
});
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayExportFile", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ExportDetails")
.HasColumnType("jsonb");
b.Property<int>("ExportFormat")
.HasColumnType("integer");
b.Property<int>("ExportResult")
.HasColumnType("integer");
b.Property<DateTime>("ExportTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExportedPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.ComplexProperty(typeof(Dictionary<string, object>), "ExportParams", "Somnia.GuestLog.Models.Entities.StayExportFile.ExportParams#StayExportParams", b1 =>
{
b1.IsRequired();
b1.Property<DateTime?>("ExportPeriodEnd");
b1.Property<DateTime?>("ExportPeriodStart");
b1.Property<int>("ExportType");
b1
.ToJson("ExportParams")
.HasColumnType("jsonb");
});
b.HasKey("Id");
b.ToTable("StayExportFile", (string)null);
});
modelBuilder.Entity("GuestDocumentStayEntry", b =>
{
b.HasOne("Somnia.GuestLog.Models.Entities.StayEntry", null)
.WithMany()
.HasForeignKey("StayEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Somnia.GuestLog.Models.Entities.GuestDocument", null)
.WithMany()
.HasForeignKey("GuestDocumentsId", "GuestDocumentsVersion")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Somnia.GuestLog.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class GuestDocument_IssueDate_MakeNullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateOnly>(
name: "IssueDate",
table: "GuestDocument",
type: "date",
nullable: true,
oldClrType: typeof(DateOnly),
oldType: "date");
//Make null any IssueDate column with "default" value:
migrationBuilder.Sql("""UPDATE "GuestDocument" SET "IssueDate" = NULL WHERE "IssueDate" = '1970-01-01';""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
//Set value to all IssueDate null columns.
migrationBuilder.Sql("""UPDATE "GuestDocument" SET "IssueDate" = '1970-01-01' WHERE "IssueDate" IS NULL;""");
migrationBuilder.AlterColumn<DateOnly>(
name: "IssueDate",
table: "GuestDocument",
type: "date",
nullable: false,
oldClrType: typeof(DateOnly),
oldType: "date",
oldNullable: true);
}
}
}
@@ -82,7 +82,7 @@ namespace Somnia.GuestLog.Infrastructure.Migrations
b.Property<bool>("IsLastVersion") b.Property<bool>("IsLastVersion")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<DateOnly>("IssueDate") b.Property<DateOnly?>("IssueDate")
.HasColumnType("date"); .HasColumnType("date");
b.Property<string>("Name") b.Property<string>("Name")
@@ -185,6 +185,50 @@ namespace Somnia.GuestLog.Infrastructure.Migrations
b.ToTable("StayEntry", (string)null); b.ToTable("StayEntry", (string)null);
}); });
modelBuilder.Entity("Somnia.GuestLog.Models.Entities.StayExportFile", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ExportDetails")
.HasColumnType("jsonb");
b.Property<int>("ExportFormat")
.HasColumnType("integer");
b.Property<int>("ExportResult")
.HasColumnType("integer");
b.Property<DateTime>("ExportTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExportedPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.ComplexProperty(typeof(Dictionary<string, object>), "ExportParams", "Somnia.GuestLog.Models.Entities.StayExportFile.ExportParams#StayExportParams", b1 =>
{
b1.IsRequired();
b1.Property<DateTime?>("ExportPeriodEnd");
b1.Property<DateTime?>("ExportPeriodStart");
b1.Property<int>("ExportType");
b1
.ToJson("ExportParams")
.HasColumnType("jsonb");
});
b.HasKey("Id");
b.ToTable("StayExportFile", (string)null);
});
modelBuilder.Entity("GuestDocumentStayEntry", b => modelBuilder.Entity("GuestDocumentStayEntry", b =>
{ {
b.HasOne("Somnia.GuestLog.Models.Entities.StayEntry", null) b.HasOne("Somnia.GuestLog.Models.Entities.StayEntry", null)
@@ -0,0 +1,10 @@
namespace Somnia.GuestLog.Models.Configuration;
public class StayExportConfig
{
public const string SectionName = "StayExport";
public bool EnableStayExport { get; set; } = false;
public string? ExportStorageConnectionString { get; set; }
public bool UseSubdirectoryByFormat { get; set; }
}
@@ -0,0 +1,9 @@
namespace Somnia.GuestLog.Models.Configuration;
public class StayExportSpainParteViajerosConfig
{
public const string SectionName = "StayExportSpainParteViajeros";
public bool InternetAvailable { get; set; } = false;
public string? CodigoEstablecimiento { get; set; }
}
+20 -2
View File
@@ -2,9 +2,22 @@ using Somnia.GuestLog.Enums.Services;
namespace Somnia.GuestLog.Models.DTOs; namespace Somnia.GuestLog.Models.DTOs;
public sealed record Result(bool IsSuccess, ResultCode ResultCode, params object[]? Context) public interface IResult
{
bool IsSuccess { get; }
ResultCode ResultCode { get; }
object[]? Context { get; }
}
public sealed record Result(
bool IsSuccess,
ResultCode ResultCode,
params object[]? Context
) : IResult
{ {
public static Result Ok() => new(IsSuccess: true, ResultCode: ResultCode.Ok, Context: null); public static Result Ok() => new(IsSuccess: true, ResultCode: ResultCode.Ok, Context: null);
public static Result OkWithContext(params object[] context)
=> new(IsSuccess: true, ResultCode: ResultCode.Ok, Context: context);
public static Result Fail(ResultCode resultCode) => new(IsSuccess: false, resultCode, Context: null); public static Result Fail(ResultCode resultCode) => new(IsSuccess: false, resultCode, Context: null);
public static Result FailWithContext(ResultCode resultCode, params object[] context) public static Result FailWithContext(ResultCode resultCode, params object[] context)
@@ -15,7 +28,12 @@ public sealed record Result(bool IsSuccess, ResultCode ResultCode, params object
public bool IsUnknownError => !IsSuccess && ResultCode == ResultCode.UnknownError; public bool IsUnknownError => !IsSuccess && ResultCode == ResultCode.UnknownError;
} }
public sealed record Result<T>(bool IsSuccess, T? Value, ResultCode ResultCode, params object[]? Context) public sealed record Result<T>(
bool IsSuccess,
T? Value,
ResultCode ResultCode,
params object[]? Context
) : IResult
{ {
public static Result<T> Ok(T value) => new(IsSuccess: true, value, ResultCode: ResultCode.Ok, Context: null); public static Result<T> Ok(T value) => new(IsSuccess: true, value, ResultCode: ResultCode.Ok, Context: null);
public static Result<T> Fail(ResultCode resultCode) => new(IsSuccess: false, Value: default, resultCode, Context: null); public static Result<T> Fail(ResultCode resultCode) => new(IsSuccess: false, Value: default, resultCode, Context: null);
@@ -0,0 +1,29 @@
using Somnia.GuestLog.Enums.Models;
namespace Somnia.GuestLog.Models.DTOs;
public class StayExportFileDTO
{
public long? Id { get; set; }
public StayExportFormat? ExportFormat { get; set; }
public StayExportParamsDTO? ExportParams { get; set; }
public StayExportFileResult? ExportResult { get; set; }
public string? ExportedPath { get; set; }
public string? ExportDetails { get; set; }
public DateTime? ExportTime { get; set; }
}
public class StayExportParamsDTO
{
public StayExportParamType? ExportType { get; set; }
public DateTime? ExportPeriodStart { get; set; }
public DateTime? ExportPeriodEnd { get; set; }
}
@@ -1,3 +1,5 @@
using Somnia.GuestLog.Infrastructure;
namespace Somnia.GuestLog.Models.Entities; namespace Somnia.GuestLog.Models.Entities;
public class GuestAddress : BaseEntity public class GuestAddress : BaseEntity
@@ -9,5 +11,5 @@ public class GuestAddress : BaseEntity
public required string AddressCountry { get; set; } public required string AddressCountry { get; set; }
public string? ZipCode { get; set; } public string? ZipCode { get; set; }
public override string GetFullKey() => "N/A"; public override string GetFullKey() => DbConsts.PK_NotApply;
} }
@@ -8,7 +8,7 @@ public class GuestDocument : BaseEntity
public required string DocumentType { get; set; } public required string DocumentType { get; set; }
public required string DocumentNum { get; set; } public required string DocumentNum { get; set; }
public required DateOnly IssueDate { get; set; } public required DateOnly? IssueDate { get; set; }
public string? SupportNum { get; set; } public string? SupportNum { get; set; }
public required string Nationality { get; set; } public required string Nationality { get; set; }
@@ -33,4 +33,5 @@ public class GuestDocument : BaseEntity
public override string GetFullKey() => $"{Id}-{Version}"; public override string GetFullKey() => $"{Id}-{Version}";
public string FullDocument => $"{DocumentType}-{DocumentNum}";
} }
@@ -0,0 +1,33 @@
using Somnia.GuestLog.Enums.Models;
namespace Somnia.GuestLog.Models.Entities;
public class StayExportFile : BaseEntity
{
public long Id { get; set; }
public required StayExportFormat ExportFormat { get; set; }
public required StayExportParams ExportParams { get; set; }
public required StayExportFileResult ExportResult { get; set; }
/// <summary>
/// Path to exported data.
/// Should be relative to configured Storage.
/// </summary>
public string? ExportedPath { get; set; }
/// <summary>
/// A JSON column to store details about the exported file.
/// This details can contain information about errors, which stays where included, etc.
/// </summary>
public string? ExportDetails { get; set; }
/// <summary>
/// Date/Time when export file was generated.
/// </summary>
public required DateTime ExportTime { get; set; }
public override string GetFullKey() => $"{Id}";
}
@@ -0,0 +1,19 @@
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Infrastructure;
namespace Somnia.GuestLog.Models.Entities;
/// <summary>
/// This class store params used on StayExport.
/// Different params apply for different export types.
/// </summary>
public class StayExportParams : BaseEntity
{
public required StayExportParamType ExportType { get; set; }
public DateTime? ExportPeriodStart { get; set; }
public DateTime? ExportPeriodEnd { get; set; }
public override string GetFullKey() => DbConsts.PK_NotApply;
}
+26 -5
View File
@@ -14,7 +14,7 @@ internal static class EntityMapping
Version = entity.Version, Version = entity.Version,
DocumentType = entity.DocumentType, DocumentType = entity.DocumentType,
DocumentNum = entity.DocumentNum, DocumentNum = entity.DocumentNum,
IssueDate = entity.IssueDate.ToDateTime(TimeOnly.MinValue), IssueDate = entity.IssueDate?.ToDateTime(TimeOnly.MinValue) ?? null,
SupportNum = entity.SupportNum, SupportNum = entity.SupportNum,
Nationality = entity.Nationality, Nationality = entity.Nationality,
Name = entity.Name, Name = entity.Name,
@@ -30,8 +30,8 @@ internal static class EntityMapping
AddressState = entity.AddressDetails?.AddressState ?? "", AddressState = entity.AddressDetails?.AddressState ?? "",
AddressCountry = entity.AddressDetails?.AddressCountry! ?? "", AddressCountry = entity.AddressDetails?.AddressCountry! ?? "",
ZipCode = entity.AddressDetails?.ZipCode ?? "", ZipCode = entity.AddressDetails?.ZipCode ?? "",
CreateTime = entity.CreateTime, CreateTime = entity.CreateTime.ToLocalTime(),
UpdateTime = entity.UpdateTime, UpdateTime = entity.UpdateTime.ToLocalTime(),
}; };
} }
@@ -45,10 +45,31 @@ internal static class EntityMapping
RoomCount = entity.RoomCount, RoomCount = entity.RoomCount,
RoomNumber = entity.RoomNumber, RoomNumber = entity.RoomNumber,
PaymentMethod = entity.PaymentMethod, PaymentMethod = entity.PaymentMethod,
CreateTime = entity.CreateTime, CreateTime = entity.CreateTime.ToLocalTime(),
UpdateTime = entity.UpdateTime, UpdateTime = entity.UpdateTime.ToLocalTime(),
GuestDocuments = includeGuests ? [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))] : null, GuestDocuments = includeGuests ? [.. entity.GuestDocuments.Select(e => MapGuestDocumentEntityToDTO(e))] : null,
}; };
} }
public static StayExportFileDTO MapStayExportFileEntityToDTO(StayExportFile entity)
{
StayExportParamsDTO exportParamsDto = new()
{
ExportType = entity.ExportParams.ExportType,
ExportPeriodStart = entity.ExportParams.ExportPeriodStart,
ExportPeriodEnd = entity.ExportParams.ExportPeriodEnd,
};
return new()
{
Id = entity.Id,
ExportFormat = entity.ExportFormat,
ExportParams = exportParamsDto,
ExportResult = entity.ExportResult,
ExportedPath = entity.ExportedPath,
ExportDetails = entity.ExportDetails,
ExportTime = entity.ExportTime.ToLocalTime(),
};
}
} }
+67 -2
View File
@@ -1,8 +1,15 @@
using FluentStorage;
using FluentValidation; using FluentValidation;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using MudBlazor.Services; using MudBlazor.Services;
using MudBlazor.Translations;
using Serilog;
using Serilog.Extensions.Logging;
using Somnia.GuestLog;
using Somnia.GuestLog.Components; using Somnia.GuestLog.Components;
using Somnia.GuestLog.Infrastructure; using Somnia.GuestLog.Infrastructure;
using Somnia.GuestLog.Models.Configuration; using Somnia.GuestLog.Models.Configuration;
@@ -10,11 +17,41 @@ using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Services; using Somnia.GuestLog.Services;
using Somnia.GuestLog.Services.AddressQuery; using Somnia.GuestLog.Services.AddressQuery;
using Somnia.GuestLog.Services.Entity; using Somnia.GuestLog.Services.Entity;
using Somnia.GuestLog.Services.SpainCodigoMunicipio;
using Somnia.GuestLog.Services.StayExport;
using Somnia.GuestLog.Utils; using Somnia.GuestLog.Utils;
using Somnia.GuestLog.Validators; using Somnia.GuestLog.Validators;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
//Load config files.
builder.Configuration.AddJsonFile("Config/log_Config.json", optional: false, reloadOnChange: false);
builder.Configuration.AddEnvironmentVariables("SOMNIA_");
//Create and configure Logger (Serilog)
var serilog = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.CreateLogger();
builder.Services.AddSerilog(serilog);
var logger = new SerilogLoggerFactory(serilog).CreateLogger<Program>();
logger.LogInformation("\n\n\n -------------- Starting {appName} {appVersion} --------------", AppSettings.AppName, AppSettings.AppVersion);
if (!builder.Environment.IsDevelopment())
{
string dataProtectionPath = builder.Configuration.GetValue<string>("DataProtectionDir", defaultValue: "/SomniaGuestLog/dataprotection");
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionPath))
.SetApplicationName("SomniaGuestLog");
}
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
});
var connectionStringGuestLog = builder.Configuration.GetConnectionString("DbSomniaGuestLog") var connectionStringGuestLog = builder.Configuration.GetConnectionString("DbSomniaGuestLog")
?? throw new InvalidOperationException("Connection string 'DbSomniaGuestLog' not found."); ?? throw new InvalidOperationException("Connection string 'DbSomniaGuestLog' not found.");
builder.Services.AddDbContextFactory<GuestLogContext>(options => builder.Services.AddDbContextFactory<GuestLogContext>(options =>
@@ -48,29 +85,43 @@ builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); .AddInteractiveServerComponents();
builder.Services.AddLocalization(); builder.Services.AddLocalization();
builder.Services.AddMudTranslations();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
// Add Validation Services // Add Validation Services
builder.Services.AddSingleton<IValidator<GuestDocumentDTO>, GuestDocumentDTOValidator>(); builder.Services.AddSingleton<IValidator<GuestDocumentDTO>, GuestDocumentDTOValidator>();
builder.Services.AddSingleton<IValidator<GuestDocIdDTO>, GuestDocIdDTOValidator>(); builder.Services.AddSingleton<IValidator<GuestDocIdDTO>, GuestDocIdDTOValidator>();
builder.Services.AddSingleton<IValidator<StayEntryDTO>, StayEntryDTOValidator>(); builder.Services.AddSingleton<IValidator<StayEntryDTO>, StayEntryDTOValidator>();
builder.Services.AddSingleton<IValidator<StayExportParamsDTO>, StayExportParamsDTOValidator>();
// Add Services // Add Entity Services
builder.Services.AddSingleton<IGuestDocumentEntityService, GuestDocumentEntityService>(); builder.Services.AddSingleton<IGuestDocumentEntityService, GuestDocumentEntityService>();
builder.Services.AddSingleton<IStayEntryEntityService, StayEntryEntityService>(); builder.Services.AddSingleton<IStayEntryEntityService, StayEntryEntityService>();
builder.Services.AddSingleton<IDataListService, StaticDataListService>(); builder.Services.AddSingleton<IStayExportFileEntityService, StayExportFileEntityService>();
// Add Services
builder.Services.AddSingleton<IDataListService, StaticDataListService>();
builder.Services.AddScoped<IUserMessageService, UserMessageService>(); builder.Services.AddScoped<IUserMessageService, UserMessageService>();
builder.Services.AddSingleton<ILocalizerService, LocalizerService>();
builder.Services.AddSingleton<IStayExportService, StayExportService>();
builder.Services.AddSingleton<ISpainCodigoMunicipioService, SpainCodigoMunicipioCatastroService>();
// Add Factories (and it's services) // Add Factories (and it's services)
builder.Services.AddFactoryDI<IAddressQueryFactory, ConfigFileAddressQueryFactory>(); builder.Services.AddFactoryDI<IAddressQueryFactory, ConfigFileAddressQueryFactory>();
builder.Services.AddFactoryDI<IStayExportFormatProviderFactory, StayExportFormatProviderFactory>();
// Add Options config // Add Options config
builder.Services.AddOptions<AddressQueryConfig>() builder.Services.AddOptions<AddressQueryConfig>()
.Bind(builder.Configuration.GetSection(AddressQueryConfig.SectionName)); .Bind(builder.Configuration.GetSection(AddressQueryConfig.SectionName));
builder.Services.AddOptions<StayExportConfig>()
.Bind(builder.Configuration.GetSection(StayExportConfig.SectionName));
builder.Services.AddOptions<StayExportSpainParteViajerosConfig>()
.Bind(builder.Configuration.GetSection(StayExportSpainParteViajerosConfig.SectionName));
var app = builder.Build(); var app = builder.Build();
app.UseForwardedHeaders();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
@@ -102,4 +153,18 @@ app.MapStaticAssets();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
//Activate S3 storage.
AwsS3Storage.Use();
//Run database migrations
bool runMigrationsOnStartup = builder.Configuration.GetValue<bool>("RunDatabaseMigrationOnStartup", defaultValue: true);
if (runMigrationsOnStartup)
{
logger.LogInformation("Running database migrations...");
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<GuestLogContext>();
await db.Database.MigrateAsync();
logger.LogInformation("Database migrations OK");
}
app.Run(); app.Run();
@@ -0,0 +1,397 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing"
mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework
object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0"
msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"
msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="dataList.CountryCode.ABW" xml:space="preserve"><value>Aruba</value></data>
<data name="dataList.CountryCode.AFG" xml:space="preserve"><value>Afganistán</value></data>
<data name="dataList.CountryCode.AGO" xml:space="preserve"><value>Angola</value></data>
<data name="dataList.CountryCode.AIA" xml:space="preserve"><value>Anguila</value></data>
<data name="dataList.CountryCode.ALA" xml:space="preserve"><value>Åland, (las) Islas</value></data>
<data name="dataList.CountryCode.ALB" xml:space="preserve"><value>Albania</value></data>
<data name="dataList.CountryCode.AND" xml:space="preserve"><value>Andorra</value></data>
<data name="dataList.CountryCode.ARE" xml:space="preserve"><value>Emiratos Árabes Unidos (los)</value></data>
<data name="dataList.CountryCode.ARG" xml:space="preserve"><value>Argentina</value></data>
<data name="dataList.CountryCode.ARM" xml:space="preserve"><value>Armenia</value></data>
<data name="dataList.CountryCode.ASM" xml:space="preserve"><value>Samoa Americana</value></data>
<data name="dataList.CountryCode.ATA" xml:space="preserve"><value>Antártida</value></data>
<data name="dataList.CountryCode.ATF" xml:space="preserve"><value>Tierras Australes Francesas (las)</value></data>
<data name="dataList.CountryCode.ATG" xml:space="preserve"><value>Antigua y Barbuda</value></data>
<data name="dataList.CountryCode.AUS" xml:space="preserve"><value>Australia</value></data>
<data name="dataList.CountryCode.AUT" xml:space="preserve"><value>Austria</value></data>
<data name="dataList.CountryCode.AZE" xml:space="preserve"><value>Azerbaiyán</value></data>
<data name="dataList.CountryCode.BDI" xml:space="preserve"><value>Burundi</value></data>
<data name="dataList.CountryCode.BEL" xml:space="preserve"><value>Bélgica</value></data>
<data name="dataList.CountryCode.BEN" xml:space="preserve"><value>Benín</value></data>
<data name="dataList.CountryCode.BES" xml:space="preserve"><value>Bonaire, San Eustaquio y Saba</value></data>
<data name="dataList.CountryCode.BFA" xml:space="preserve"><value>Burkina Faso</value></data>
<data name="dataList.CountryCode.BGD" xml:space="preserve"><value>Bangladés</value></data>
<data name="dataList.CountryCode.BGR" xml:space="preserve"><value>Bulgaria</value></data>
<data name="dataList.CountryCode.BHR" xml:space="preserve"><value>Baréin</value></data>
<data name="dataList.CountryCode.BHS" xml:space="preserve"><value>Bahamas (las)</value></data>
<data name="dataList.CountryCode.BIH" xml:space="preserve"><value>Bosnia y Herzegovina</value></data>
<data name="dataList.CountryCode.BLM" xml:space="preserve"><value>San Barthélemy</value></data>
<data name="dataList.CountryCode.BLR" xml:space="preserve"><value>Belarús</value></data>
<data name="dataList.CountryCode.BLZ" xml:space="preserve"><value>Belice</value></data>
<data name="dataList.CountryCode.BMU" xml:space="preserve"><value>Bermuda</value></data>
<data name="dataList.CountryCode.BOL" xml:space="preserve"><value>Bolivia (Estado Plurinacional de)</value></data>
<data name="dataList.CountryCode.BRA" xml:space="preserve"><value>Brasil</value></data>
<data name="dataList.CountryCode.BRB" xml:space="preserve"><value>Barbados</value></data>
<data name="dataList.CountryCode.BRN" xml:space="preserve"><value>Brunei Darussalam</value></data>
<data name="dataList.CountryCode.BTN" xml:space="preserve"><value>Bhután</value></data>
<data name="dataList.CountryCode.BVT" xml:space="preserve"><value>Bouvet, Isla</value></data>
<data name="dataList.CountryCode.BWA" xml:space="preserve"><value>Botsuana</value></data>
<data name="dataList.CountryCode.CAF" xml:space="preserve"><value>República Centroafricana (la)</value></data>
<data name="dataList.CountryCode.CAN" xml:space="preserve"><value>Canadá</value></data>
<data name="dataList.CountryCode.CCK" xml:space="preserve"><value>Cocos / Keeling, (las) Islas</value></data>
<data name="dataList.CountryCode.CHE" xml:space="preserve"><value>Suiza</value></data>
<data name="dataList.CountryCode.CHL" xml:space="preserve"><value>Chile</value></data>
<data name="dataList.CountryCode.CHN" xml:space="preserve"><value>China</value></data>
<data name="dataList.CountryCode.CIV" xml:space="preserve"><value>Côte d'Ivoire</value></data>
<data name="dataList.CountryCode.CMR" xml:space="preserve"><value>Camerún</value></data>
<data name="dataList.CountryCode.COD" xml:space="preserve"><value>Congo (la República Democrática del)</value></data>
<data name="dataList.CountryCode.COG" xml:space="preserve"><value>Congo (el)</value></data>
<data name="dataList.CountryCode.COK" xml:space="preserve"><value>Cook, (las) Islas</value></data>
<data name="dataList.CountryCode.COL" xml:space="preserve"><value>Colombia</value></data>
<data name="dataList.CountryCode.COM" xml:space="preserve"><value>Comoras (las)</value></data>
<data name="dataList.CountryCode.CPV" xml:space="preserve"><value>Cabo Verde</value></data>
<data name="dataList.CountryCode.CRI" xml:space="preserve"><value>Costa Rica</value></data>
<data name="dataList.CountryCode.CUB" xml:space="preserve"><value>Cuba</value></data>
<data name="dataList.CountryCode.CUW" xml:space="preserve"><value>Curazao</value></data>
<data name="dataList.CountryCode.CXR" xml:space="preserve"><value>Christmas, Isla</value></data>
<data name="dataList.CountryCode.CYM" xml:space="preserve"><value>Caimán, (las) Islas</value></data>
<data name="dataList.CountryCode.CYP" xml:space="preserve"><value>Chipre</value></data>
<data name="dataList.CountryCode.CZE" xml:space="preserve"><value>Chequia</value></data>
<data name="dataList.CountryCode.DEU" xml:space="preserve"><value>Alemania</value></data>
<data name="dataList.CountryCode.DJI" xml:space="preserve"><value>Yibuti</value></data>
<data name="dataList.CountryCode.DMA" xml:space="preserve"><value>Dominica</value></data>
<data name="dataList.CountryCode.DNK" xml:space="preserve"><value>Dinamarca</value></data>
<data name="dataList.CountryCode.DOM" xml:space="preserve"><value>Dominicana, (la) República</value></data>
<data name="dataList.CountryCode.DZA" xml:space="preserve"><value>Argelia</value></data>
<data name="dataList.CountryCode.ECU" xml:space="preserve"><value>Ecuador</value></data>
<data name="dataList.CountryCode.EGY" xml:space="preserve"><value>Egipto</value></data>
<data name="dataList.CountryCode.ERI" xml:space="preserve"><value>Eritrea</value></data>
<data name="dataList.CountryCode.ESH" xml:space="preserve"><value>Sáhara Occidental</value></data>
<data name="dataList.CountryCode.ESP" xml:space="preserve"><value>España</value></data>
<data name="dataList.CountryCode.EST" xml:space="preserve"><value>Estonia</value></data>
<data name="dataList.CountryCode.ETH" xml:space="preserve"><value>Etiopía</value></data>
<data name="dataList.CountryCode.FIN" xml:space="preserve"><value>Finlandia</value></data>
<data name="dataList.CountryCode.FJI" xml:space="preserve"><value>Fiyi</value></data>
<data name="dataList.CountryCode.FLK" xml:space="preserve"><value>Malvinas [Falkland], (las) Islas</value></data>
<data name="dataList.CountryCode.FRA" xml:space="preserve"><value>Francia</value></data>
<data name="dataList.CountryCode.FRO" xml:space="preserve"><value>Feroe, (las) Islas</value></data>
<data name="dataList.CountryCode.FSM" xml:space="preserve"><value>Micronesia (Estados Federados de)</value></data>
<data name="dataList.CountryCode.GAB" xml:space="preserve"><value>Gabón</value></data>
<data name="dataList.CountryCode.GBR" xml:space="preserve"><value>Reino Unido de Gran Bretaña e Irlanda del Norte</value></data>
<data name="dataList.CountryCode.GEO" xml:space="preserve"><value>Georgia</value></data>
<data name="dataList.CountryCode.GGY" xml:space="preserve"><value>Guernsey</value></data>
<data name="dataList.CountryCode.GHA" xml:space="preserve"><value>Ghana</value></data>
<data name="dataList.CountryCode.GIB" xml:space="preserve"><value>Gibraltar</value></data>
<data name="dataList.CountryCode.GIN" xml:space="preserve"><value>Guinea</value></data>
<data name="dataList.CountryCode.GLP" xml:space="preserve"><value>Guadalupe</value></data>
<data name="dataList.CountryCode.GMB" xml:space="preserve"><value>Gambia (la)</value></data>
<data name="dataList.CountryCode.GNB" xml:space="preserve"><value>Guinea-Bissau</value></data>
<data name="dataList.CountryCode.GNQ" xml:space="preserve"><value>Guinea Ecuatorial</value></data>
<data name="dataList.CountryCode.GRC" xml:space="preserve"><value>Grecia</value></data>
<data name="dataList.CountryCode.GRD" xml:space="preserve"><value>Granada</value></data>
<data name="dataList.CountryCode.GRL" xml:space="preserve"><value>Groenlandia</value></data>
<data name="dataList.CountryCode.GTM" xml:space="preserve"><value>Guatemala</value></data>
<data name="dataList.CountryCode.GUF" xml:space="preserve"><value>Guayana Francesa</value></data>
<data name="dataList.CountryCode.GUM" xml:space="preserve"><value>Guam</value></data>
<data name="dataList.CountryCode.GUY" xml:space="preserve"><value>Guyana</value></data>
<data name="dataList.CountryCode.HKG" xml:space="preserve"><value>Hong Kong</value></data>
<data name="dataList.CountryCode.HMD" xml:space="preserve"><value>Heard (Isla) e Islas McDonald</value></data>
<data name="dataList.CountryCode.HND" xml:space="preserve"><value>Honduras</value></data>
<data name="dataList.CountryCode.HRV" xml:space="preserve"><value>Croacia</value></data>
<data name="dataList.CountryCode.HTI" xml:space="preserve"><value>Haití</value></data>
<data name="dataList.CountryCode.HUN" xml:space="preserve"><value>Hungría</value></data>
<data name="dataList.CountryCode.IDN" xml:space="preserve"><value>Indonesia</value></data>
<data name="dataList.CountryCode.IMN" xml:space="preserve"><value>Isla de Man</value></data>
<data name="dataList.CountryCode.IND" xml:space="preserve"><value>India</value></data>
<data name="dataList.CountryCode.IOT" xml:space="preserve"><value>Territorio Británico del Océano Índico (el)</value></data>
<data name="dataList.CountryCode.IRL" xml:space="preserve"><value>Irlanda</value></data>
<data name="dataList.CountryCode.IRN" xml:space="preserve"><value>Irán (República Islámica de)</value></data>
<data name="dataList.CountryCode.IRQ" xml:space="preserve"><value>Irak</value></data>
<data name="dataList.CountryCode.ISL" xml:space="preserve"><value>Islandia</value></data>
<data name="dataList.CountryCode.ISR" xml:space="preserve"><value>Israel</value></data>
<data name="dataList.CountryCode.ITA" xml:space="preserve"><value>Italia</value></data>
<data name="dataList.CountryCode.JAM" xml:space="preserve"><value>Jamaica</value></data>
<data name="dataList.CountryCode.JEY" xml:space="preserve"><value>Jersey</value></data>
<data name="dataList.CountryCode.JOR" xml:space="preserve"><value>Jordania</value></data>
<data name="dataList.CountryCode.JPN" xml:space="preserve"><value>Japón</value></data>
<data name="dataList.CountryCode.KAZ" xml:space="preserve"><value>Kazajistán</value></data>
<data name="dataList.CountryCode.KEN" xml:space="preserve"><value>Kenia</value></data>
<data name="dataList.CountryCode.KGZ" xml:space="preserve"><value>Kirguistán</value></data>
<data name="dataList.CountryCode.KHM" xml:space="preserve"><value>Camboya</value></data>
<data name="dataList.CountryCode.KIR" xml:space="preserve"><value>Kiribati</value></data>
<data name="dataList.CountryCode.KNA" xml:space="preserve"><value>Saint Kitts y Nevis</value></data>
<data name="dataList.CountryCode.KOR" xml:space="preserve"><value>Corea (la República de)</value></data>
<data name="dataList.CountryCode.KWT" xml:space="preserve"><value>Kuwait</value></data>
<data name="dataList.CountryCode.LAO" xml:space="preserve"><value>Lao, (la) República Democrática Popular</value></data>
<data name="dataList.CountryCode.LBN" xml:space="preserve"><value>Líbano</value></data>
<data name="dataList.CountryCode.LBR" xml:space="preserve"><value>Liberia</value></data>
<data name="dataList.CountryCode.LBY" xml:space="preserve"><value>Libia</value></data>
<data name="dataList.CountryCode.LCA" xml:space="preserve"><value>Santa Lucía</value></data>
<data name="dataList.CountryCode.LIE" xml:space="preserve"><value>Liechtenstein</value></data>
<data name="dataList.CountryCode.LKA" xml:space="preserve"><value>Sri Lanka</value></data>
<data name="dataList.CountryCode.LSO" xml:space="preserve"><value>Lesoto</value></data>
<data name="dataList.CountryCode.LTU" xml:space="preserve"><value>Lituania</value></data>
<data name="dataList.CountryCode.LUX" xml:space="preserve"><value>Luxemburgo</value></data>
<data name="dataList.CountryCode.LVA" xml:space="preserve"><value>Letonia</value></data>
<data name="dataList.CountryCode.MAC" xml:space="preserve"><value>Macao</value></data>
<data name="dataList.CountryCode.MAF" xml:space="preserve"><value>San Martín (parte francesa)</value></data>
<data name="dataList.CountryCode.MAR" xml:space="preserve"><value>Marruecos</value></data>
<data name="dataList.CountryCode.MCO" xml:space="preserve"><value>Mónaco</value></data>
<data name="dataList.CountryCode.MDA" xml:space="preserve"><value>Moldova (la República de)</value></data>
<data name="dataList.CountryCode.MDG" xml:space="preserve"><value>Madagascar</value></data>
<data name="dataList.CountryCode.MDV" xml:space="preserve"><value>Maldivas</value></data>
<data name="dataList.CountryCode.MEX" xml:space="preserve"><value>México</value></data>
<data name="dataList.CountryCode.MHL" xml:space="preserve"><value>Marshall, (las) Islas</value></data>
<data name="dataList.CountryCode.MKD" xml:space="preserve"><value>Macedonia del Norte</value></data>
<data name="dataList.CountryCode.MLI" xml:space="preserve"><value>Malí</value></data>
<data name="dataList.CountryCode.MLT" xml:space="preserve"><value>Malta</value></data>
<data name="dataList.CountryCode.MMR" xml:space="preserve"><value>Myanmar</value></data>
<data name="dataList.CountryCode.MNE" xml:space="preserve"><value>Montenegro</value></data>
<data name="dataList.CountryCode.MNG" xml:space="preserve"><value>Mongolia</value></data>
<data name="dataList.CountryCode.MNP" xml:space="preserve"><value>Marianas Septentrionales, (las) Islas</value></data>
<data name="dataList.CountryCode.MOZ" xml:space="preserve"><value>Mozambique</value></data>
<data name="dataList.CountryCode.MRT" xml:space="preserve"><value>Mauritania</value></data>
<data name="dataList.CountryCode.MSR" xml:space="preserve"><value>Montserrat</value></data>
<data name="dataList.CountryCode.MTQ" xml:space="preserve"><value>Martinica</value></data>
<data name="dataList.CountryCode.MUS" xml:space="preserve"><value>Mauricio</value></data>
<data name="dataList.CountryCode.MWI" xml:space="preserve"><value>Malaui</value></data>
<data name="dataList.CountryCode.MYS" xml:space="preserve"><value>Malasia</value></data>
<data name="dataList.CountryCode.MYT" xml:space="preserve"><value>Mayotte</value></data>
<data name="dataList.CountryCode.NAM" xml:space="preserve"><value>Namibia</value></data>
<data name="dataList.CountryCode.NCL" xml:space="preserve"><value>Nueva Caledonia</value></data>
<data name="dataList.CountryCode.NER" xml:space="preserve"><value>Níger (el)</value></data>
<data name="dataList.CountryCode.NFK" xml:space="preserve"><value>Norfolk, Isla</value></data>
<data name="dataList.CountryCode.NGA" xml:space="preserve"><value>Nigeria</value></data>
<data name="dataList.CountryCode.NIC" xml:space="preserve"><value>Nicaragua</value></data>
<data name="dataList.CountryCode.NIU" xml:space="preserve"><value>Niue</value></data>
<data name="dataList.CountryCode.NLD" xml:space="preserve"><value>Países Bajos (los)</value></data>
<data name="dataList.CountryCode.NOR" xml:space="preserve"><value>Noruega</value></data>
<data name="dataList.CountryCode.NPL" xml:space="preserve"><value>Nepal</value></data>
<data name="dataList.CountryCode.NRU" xml:space="preserve"><value>Nauru</value></data>
<data name="dataList.CountryCode.NZL" xml:space="preserve"><value>Nueva Zelandia</value></data>
<data name="dataList.CountryCode.OMN" xml:space="preserve"><value>Omán</value></data>
<data name="dataList.CountryCode.PAK" xml:space="preserve"><value>Pakistán</value></data>
<data name="dataList.CountryCode.PAN" xml:space="preserve"><value>Panamá</value></data>
<data name="dataList.CountryCode.PCN" xml:space="preserve"><value>Pitcairn, (las) Islas</value></data>
<data name="dataList.CountryCode.PER" xml:space="preserve"><value>Perú</value></data>
<data name="dataList.CountryCode.PHL" xml:space="preserve"><value>Filipinas (las)</value></data>
<data name="dataList.CountryCode.PLW" xml:space="preserve"><value>Palau</value></data>
<data name="dataList.CountryCode.PNG" xml:space="preserve"><value>Papúa Nueva Guinea</value></data>
<data name="dataList.CountryCode.POL" xml:space="preserve"><value>Polonia</value></data>
<data name="dataList.CountryCode.PRI" xml:space="preserve"><value>Puerto Rico</value></data>
<data name="dataList.CountryCode.PRK" xml:space="preserve"><value>Corea (la República Popular Democrática de)</value></data>
<data name="dataList.CountryCode.PRT" xml:space="preserve"><value>Portugal</value></data>
<data name="dataList.CountryCode.PRY" xml:space="preserve"><value>Paraguay</value></data>
<data name="dataList.CountryCode.PSE" xml:space="preserve"><value>Palestina, Estado de</value></data>
<data name="dataList.CountryCode.PYF" xml:space="preserve"><value>Polinesia Francesa</value></data>
<data name="dataList.CountryCode.QAT" xml:space="preserve"><value>Catar</value></data>
<data name="dataList.CountryCode.REU" xml:space="preserve"><value>Reunión</value></data>
<data name="dataList.CountryCode.ROU" xml:space="preserve"><value>Rumania</value></data>
<data name="dataList.CountryCode.RUS" xml:space="preserve"><value>Rusia, (la) Federación de</value></data>
<data name="dataList.CountryCode.RWA" xml:space="preserve"><value>Ruanda</value></data>
<data name="dataList.CountryCode.SAU" xml:space="preserve"><value>Arabia Saudita</value></data>
<data name="dataList.CountryCode.SDN" xml:space="preserve"><value>Sudán</value></data>
<data name="dataList.CountryCode.SEN" xml:space="preserve"><value>Senegal</value></data>
<data name="dataList.CountryCode.SGP" xml:space="preserve"><value>Singapur</value></data>
<data name="dataList.CountryCode.SGS" xml:space="preserve"><value>Georgia del Sur (la) y las Islas Sandwich del Sur</value></data>
<data name="dataList.CountryCode.SHN" xml:space="preserve"><value>Santa Elena, Ascensión y Tristán de Acuña</value></data>
<data name="dataList.CountryCode.SJM" xml:space="preserve"><value>Svalbard y Jan Mayen</value></data>
<data name="dataList.CountryCode.SLB" xml:space="preserve"><value>Salomón, (las) Islas</value></data>
<data name="dataList.CountryCode.SLE" xml:space="preserve"><value>Sierra Leona</value></data>
<data name="dataList.CountryCode.SLV" xml:space="preserve"><value>El Salvador</value></data>
<data name="dataList.CountryCode.SMR" xml:space="preserve"><value>San Marino</value></data>
<data name="dataList.CountryCode.SOM" xml:space="preserve"><value>Somalia</value></data>
<data name="dataList.CountryCode.SPM" xml:space="preserve"><value>San Pedro y Miquelón</value></data>
<data name="dataList.CountryCode.SRB" xml:space="preserve"><value>Serbia</value></data>
<data name="dataList.CountryCode.SSD" xml:space="preserve"><value>Sudán del Sur</value></data>
<data name="dataList.CountryCode.STP" xml:space="preserve"><value>Santo Tomé y Príncipe</value></data>
<data name="dataList.CountryCode.SUR" xml:space="preserve"><value>Suriname</value></data>
<data name="dataList.CountryCode.SVK" xml:space="preserve"><value>Eslovaquia</value></data>
<data name="dataList.CountryCode.SVN" xml:space="preserve"><value>Eslovenia</value></data>
<data name="dataList.CountryCode.SWE" xml:space="preserve"><value>Suecia</value></data>
<data name="dataList.CountryCode.SWZ" xml:space="preserve"><value>Eswatini</value></data>
<data name="dataList.CountryCode.SXM" xml:space="preserve"><value>San Martín (parte neerlandesa)</value></data>
<data name="dataList.CountryCode.SYC" xml:space="preserve"><value>Seychelles</value></data>
<data name="dataList.CountryCode.SYR" xml:space="preserve"><value>República Árabe Siria</value></data>
<data name="dataList.CountryCode.TCA" xml:space="preserve"><value>Turcas y Caicos, (las) Islas</value></data>
<data name="dataList.CountryCode.TCD" xml:space="preserve"><value>Chad</value></data>
<data name="dataList.CountryCode.TGO" xml:space="preserve"><value>Togo</value></data>
<data name="dataList.CountryCode.THA" xml:space="preserve"><value>Tailandia</value></data>
<data name="dataList.CountryCode.TJK" xml:space="preserve"><value>Tayikistán</value></data>
<data name="dataList.CountryCode.TKL" xml:space="preserve"><value>Tokelau</value></data>
<data name="dataList.CountryCode.TKM" xml:space="preserve"><value>Turkmenistán</value></data>
<data name="dataList.CountryCode.TLS" xml:space="preserve"><value>Timor-Leste</value></data>
<data name="dataList.CountryCode.TON" xml:space="preserve"><value>Tonga</value></data>
<data name="dataList.CountryCode.TTO" xml:space="preserve"><value>Trinidad y Tabago</value></data>
<data name="dataList.CountryCode.TUN" xml:space="preserve"><value>Túnez</value></data>
<data name="dataList.CountryCode.TUR" xml:space="preserve"><value>Turquía</value></data>
<data name="dataList.CountryCode.TUV" xml:space="preserve"><value>Tuvalu</value></data>
<data name="dataList.CountryCode.TWN" xml:space="preserve"><value>Taiwán (Provincia de China)</value></data>
<data name="dataList.CountryCode.TZA" xml:space="preserve"><value>Tanzania, República Unida de</value></data>
<data name="dataList.CountryCode.UGA" xml:space="preserve"><value>Uganda</value></data>
<data name="dataList.CountryCode.UKR" xml:space="preserve"><value>Ucrania</value></data>
<data name="dataList.CountryCode.UMI" xml:space="preserve"><value>Islas menores alejadas de Estados Unidos (las)</value></data>
<data name="dataList.CountryCode.URY" xml:space="preserve"><value>Uruguay</value></data>
<data name="dataList.CountryCode.USA" xml:space="preserve"><value>Estados Unidos de América (los)</value></data>
<data name="dataList.CountryCode.UZB" xml:space="preserve"><value>Uzbekistán</value></data>
<data name="dataList.CountryCode.VAT" xml:space="preserve"><value>Santa Sede (la)</value></data>
<data name="dataList.CountryCode.VCT" xml:space="preserve"><value>San Vincente y las Granadinas</value></data>
<data name="dataList.CountryCode.VEN" xml:space="preserve"><value>Venezuela (República Bolivariana de)</value></data>
<data name="dataList.CountryCode.VGB" xml:space="preserve"><value>Vírgenes Británicas, (las) Islas</value></data>
<data name="dataList.CountryCode.VIR" xml:space="preserve"><value>Vírgenes de los Estados Unidos, (las) Islas</value></data>
<data name="dataList.CountryCode.VNM" xml:space="preserve"><value>Viet Nam</value></data>
<data name="dataList.CountryCode.VUT" xml:space="preserve"><value>Vanuatu</value></data>
<data name="dataList.CountryCode.WLF" xml:space="preserve"><value>Wallis y Futuna</value></data>
<data name="dataList.CountryCode.WSM" xml:space="preserve"><value>Samoa</value></data>
<data name="dataList.CountryCode.YEM" xml:space="preserve"><value>Yemen</value></data>
<data name="dataList.CountryCode.ZAF" xml:space="preserve"><value>Sudáfrica</value></data>
<data name="dataList.CountryCode.ZMB" xml:space="preserve"><value>Zambia</value></data>
<data name="dataList.CountryCode.ZWE" xml:space="preserve"><value>Zimbabue</value></data>
<data name="dataList.DocumentType.DNI" xml:space="preserve"><value>DNI</value></data>
<data name="dataList.DocumentType.NIE" xml:space="preserve"><value>NIE</value></data>
<data name="dataList.DocumentType.PASSPORT" xml:space="preserve"><value>Pasaporte</value></data>
<data name="dataList.DocumentType.ID" xml:space="preserve"><value>ID Card</value></data>
<data name="dataList.DocumentType.OTHER" xml:space="preserve"><value>Otro</value></data>
<data name="dataList.GenderOption.M" xml:space="preserve"><value>Hombre</value></data>
<data name="dataList.GenderOption.F" xml:space="preserve"><value>Mujer</value></data>
<data name="dataList.GenderOption.O" xml:space="preserve"><value>Otro</value></data>
<data name="dataList.PaymentMethod.EFECT" xml:space="preserve"><value>Efectivo</value></data>
<data name="dataList.PaymentMethod.TARJT" xml:space="preserve"><value>Tarjeta</value></data>
<data name="dataList.PaymentMethod.TRANS" xml:space="preserve"><value>Transferencia</value></data>
<data name="dataList.PaymentMethod.MOVIL" xml:space="preserve"><value>Pago por Móvil</value></data>
<data name="dataList.PaymentMethod.OTRO" xml:space="preserve"><value>Otro</value></data>
</root>
@@ -123,25 +123,147 @@
Culture=neutral, PublicKeyToken=b77a5c561934e089</value> Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="dto.GuestDocument.Id" xml:space="preserve"><value>ID</value></data>
<data name="dto.GuestDocument.Version" xml:space="preserve"><value>Versión</value></data>
<data name="dto.GuestDocument.DocumentType" xml:space="preserve"><value>Tipo Documento</value></data> <data name="dto.GuestDocument.DocumentType" xml:space="preserve"><value>Tipo Documento</value></data>
<data name="dto.GuestDocument.DocumentNum" xml:space="preserve"><value>Número Documento</value></data> <data name="dto.GuestDocument.DocumentNum" xml:space="preserve"><value>Número Documento</value></data>
<data name="dto.GuestDocument.IssueDate" xml:space="preserve"><value>Fecha emisión</value></data> <data name="dto.GuestDocument.IssueDate" xml:space="preserve"><value>Fecha Emisión</value></data>
<data name="dto.GuestDocument.SupportNum" xml:space="preserve"><value>Número Soporte</value></data> <data name="dto.GuestDocument.SupportNum" xml:space="preserve"><value>Número Soporte</value></data>
<data name="dto.GuestDocument.Nationality" xml:space="preserve"><value>Nacionalidad</value></data> <data name="dto.GuestDocument.Nationality" xml:space="preserve"><value>Nacionalidad</value></data>
<data name="dto.GuestDocument.Name" xml:space="preserve"><value>Nombre</value></data> <data name="dto.GuestDocument.Name" xml:space="preserve"><value>Nombre</value></data>
<data name="dto.GuestDocument.Surname" xml:space="preserve"><value>Apellido 1</value></data> <data name="dto.GuestDocument.Surname" xml:space="preserve"><value>Apellido</value></data>
<data name="dto.GuestDocument.Surname2" xml:space="preserve"><value>Apellido 2</value></data> <data name="dto.GuestDocument.Surname2" xml:space="preserve"><value>Apellido 2</value></data>
<data name="dto.GuestDocument.BirthDate" xml:space="preserve"><value>Fecha Nacimiento</value></data> <data name="dto.GuestDocument.BirthDate" xml:space="preserve"><value>Fecha Nacimiento</value></data>
<data name="dto.GuestDocument.Gender" xml:space="preserve"><value>Sexo</value></data> <data name="dto.GuestDocument.Gender" xml:space="preserve"><value>Sexo</value></data>
<data name="dto.GuestDocument.PhoneNumber" xml:space="preserve"><value>Número Teléfono</value></data> <data name="dto.GuestDocument.PhoneNumber" xml:space="preserve"><value>Número Teléfono</value></data>
<data name="dto.GuestDocument.EmailAddress" xml:space="preserve"><value>Dirección Email</value></data> <data name="dto.GuestDocument.EmailAddress" xml:space="preserve"><value>Correo Electrónico</value></data>
<data name="dto.GuestDocument.Address" xml:space="preserve"><value>Dirección</value></data>
<data name="dto.GuestDocument.AddressCity" xml:space="preserve"><value>Ciudad</value></data>
<data name="dto.GuestDocument.AddressProvince" xml:space="preserve"><value>Provincia</value></data>
<data name="dto.GuestDocument.AddressState" xml:space="preserve"><value>Región</value></data>
<data name="dto.GuestDocument.AddressCountry" xml:space="preserve"><value>País</value></data>
<data name="dto.GuestDocument.ZipCode" xml:space="preserve"><value>Código Postal</value></data>
<data name="dto.GuestDocument.CreateTime" xml:space="preserve"><value>Fecha Creación</value></data> <data name="dto.GuestDocument.CreateTime" xml:space="preserve"><value>Fecha Creación</value></data>
<data name="dto.GuestDocument.UpdateTime" xml:space="preserve"><value>Fecha Modificación</value></data> <data name="dto.GuestDocument.UpdateTime" xml:space="preserve"><value>Fecha Modificación</value></data>
<data name="dto.StayEntry.Id" xml:space="preserve"><value>Id</value></data>
<data name="dto.StayEntry.DateIn" xml:space="preserve"><value>Fecha Entrada</value></data>
<data name="dto.StayEntry.DateOut" xml:space="preserve"><value>Fecha Salida</value></data>
<data name="dto.StayEntry.RoomCount" xml:space="preserve"><value>Cantidad Habitaciones</value></data>
<data name="dto.StayEntry.RoomNumber" xml:space="preserve"><value>Número Habitaciones</value></data>
<data name="dto.StayEntry.PaymentMethod" xml:space="preserve"><value>Forma de Pago</value></data>
<data name="dto.StayEntry.GuestDocuments" xml:space="preserve"><value>Huéspedes</value></data>
<data name="dto.StayEntry.CreateTime" xml:space="preserve"><value>Fecha Creación</value></data>
<data name="dto.StayEntry.UpdateTime" xml:space="preserve"><value>Fecha Modificación</value></data>
<data name="dto.StayExportFile.Id" xml:space="preserve"><value>Id</value></data>
<data name="dto.StayExportFile.ExportFormat" xml:space="preserve"><value>Formato Exportación</value></data>
<data name="dto.StayExportFile.ExportParams" xml:space="preserve"><value>Parámetros Exportación</value></data>
<data name="dto.StayExportFile.ExportResult" xml:space="preserve"><value>Resultado Exportación</value></data>
<data name="dto.StayExportFile.ExportedPath" xml:space="preserve"><value>Ruta Exportación</value></data>
<data name="dto.StayExportFile.ExportDetails" xml:space="preserve"><value>Detalles Exportación</value></data>
<data name="dto.StayExportFile.ExportTime" xml:space="preserve"><value>Fecha Exportación</value></data>
<data name="dto.StayExportParams.ExportType" xml:space="preserve"><value>Tipo Exportación</value></data>
<data name="dto.StayExportParams.ExportPeriodStart" xml:space="preserve"><value>Fecha Desde</value></data>
<data name="dto.StayExportParams.ExportPeriodEnd" xml:space="preserve"><value>Fecha Hasta</value></data>
<data name="tmp" xml:space="preserve"><value>TMP</value></data>
<data name="general.actions" xml:space="preserve"><value>Acciones</value></data>
<data name="general.reloadData" xml:space="preserve"><value>Recargar Datos</value></data>
<data name="general.inProgressMsg" xml:space="preserve"><value>En Progreso...</value></data>
<data name="general.ConfigureColumns" xml:space="preserve"><value>Configurar Columnas</value></data>
<data name="general.confirmDeleteMsg" xml:space="preserve"><value>¡Eliminar no se puede deshacer!</value></data>
<data name="general.confirmTitle" xml:space="preserve"><value>¿Confirmar?</value></data>
<data name="general.btnDelete" xml:space="preserve"><value>Eliminar</value></data>
<data name="general.btnCreate" xml:space="preserve"><value>Crear</value></data>
<data name="general.btnEdit" xml:space="preserve"><value>Editar</value></data>
<data name="general.btnReturn" xml:space="preserve"><value>Volver</value></data>
<data name="general.btnClose" xml:space="preserve"><value>Cerrar</value></data>
<data name="general.btnSearch" xml:space="preserve"><value>Buscar</value></data>
<data name="general.btnSave" xml:space="preserve"><value>Guaradar</value></data>
<data name="general.btnCancel" xml:space="preserve"><value>Cancelar</value></data>
<data name="general.noResults" xml:space="preserve"><value>No hay resultados</value></data>
<data name="general.showAllElements" xml:space="preserve"><value>Mostrar todos los elementos</value></data>
<data name="general.wildcardHelperText" xml:space="preserve"><value>Usa '%' como comodín</value></data>
<data name="general.dateStart" xml:space="preserve"><value>Fecha Inicio</value></data>
<data name="general.dateEnd" xml:space="preserve"><value>Fecha Fin</value></data>
<data name="general.loading" xml:space="preserve"><value>Cargando...</value></data>
<data name="general.msgEditSaved" xml:space="preserve"><value>Cambios Guardados...</value></data>
<data name="general.msgEditCanceled" xml:space="preserve"><value>¡Edición cancelada!</value></data>
<data name="general.languageSelector" xml:space="preserve"><value>Idioma</value></data>
<data name="general.address.address" xml:space="preserve"><value>Dirección</value></data>
<data name="general.address.city" xml:space="preserve"><value>Ciudad</value></data>
<data name="general.address.province" xml:space="preserve"><value>Provincia</value></data>
<data name="general.address.state" xml:space="preserve"><value>Región</value></data>
<data name="general.address.country" xml:space="preserve"><value>País</value></data>
<data name="general.address.zipCode" xml:space="preserve"><value>Código Postal</value></data>
<!-- Common codes -->
<data name="enum.ResultCode.Ok" xml:space="preserve"><value>OK</value></data>
<data name="enum.ResultCode.UnknownError" xml:space="preserve"><value>Error desconocido</value></data>
<data name="enum.ResultCode.FailedValidation" xml:space="preserve"><value>Fallo de validación de datos</value></data>
<data name="enum.ResultCode.NotFound" xml:space="preserve"><value>¡No encontrado!</value></data>
<data name="enum.ResultCode.FileStorageError" xml:space="preserve"><value>Error de almacenamiento de ficheros</value></data>
<!-- Specific codes -->
<data name="enum.ResultCode.GuestInStay" xml:space="preserve"><value>Huésped (ID: '{0}') está en una Estancia.</value></data>
<data name="enum.ResultCode.GuestCurrentlyInStay" xml:space="preserve"><value>Huésped (ID: '{0}') ya está en una estancia en el periodo: {1}</value></data>
<data name="enum.StayExportFileResult.OK" xml:space="preserve"><value>OK</value></data>
<data name="enum.StayExportFileResult.NoData" xml:space="preserve"><value>Sin datos</value></data>
<data name="enum.StayExportFileResult.ExportError" xml:space="preserve"><value>Error de Exportación</value></data>
<data name="enum.StayExportFileResult.UploadError" xml:space="preserve"><value>Error de ficheros</value></data>
<data name="enum.StayExportFormat.GuestLogJson" xml:space="preserve"><value>GuestLog JSON</value></data>
<data name="enum.StayExportFormat.SpainParteViajeros" xml:space="preserve"><value>Spain Parte Viajeros</value></data>
<data name="enum.StayExportParamType.PeriodDateIn" xml:space="preserve"><value>'Date Entrada' Estancia</value></data>
<data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Huéspedes</value></data>
<data name="navMenu.StayEntryList" xml:space="preserve"><value>Estancias</value></data>
<data name="navMenu.StayExport" xml:space="preserve"><value>Exportación de Estancias</value></data>
<data name="page.GuestDocList.pageTitle" xml:space="preserve"><value>Documentos Huéspedes</value></data>
<data name="page.GuestDocList.documentList" xml:space="preserve"><value>Lista de Documentos</value></data>
<data name="page.GuestDocList.btnNewGuest" xml:space="preserve"><value>Nuevo Huésped</value></data>
<data name="page.GuestDocNew.msgAlreadyExists" xml:space="preserve"><value>¡Ya existe un huésped con este documento!</value></data>
<data name="page.GuestDocNew.btnGoExistingDoc" xml:space="preserve"><value>Ver huésped existente</value></data>
<data name="page.GuestDocDetail.msgEditEnabled" xml:space="preserve"><value>Se ha entrado en modo edición</value></data>
<data name="page.GuestDocDetail.msgConfirmSaveEdit" xml:space="preserve"><value>Guardar cambios en el documento</value></data>
<data name="page.GuestDocDetail.msgConfirmCancelEdit" xml:space="preserve"><value>Cancelar edición. Se perderán los cambios</value></data>
<data name="page.GuestDocDetail.msgEditError" xml:space="preserve"><value>Error al guardar el documento '{0}'</value></data>
<data name="page.StayEntryList.pageTitle" xml:space="preserve"><value>Lista Estancias</value></data>
<data name="page.StayEntryList.entryList" xml:space="preserve"><value>Lista Estancias</value></data>
<data name="page.StayEntryList.btnNewStay" xml:space="preserve"><value>Nueva Estancia</value></data>
<data name="page.StayEntryNew.pageTitle" xml:space="preserve"><value>Nueva Estancia</value></data>
<data name="page.StayExportList.pageTitle" xml:space="preserve"><value>Exportación de Estancias</value></data>
<data name="page.StayExportList.exportList" xml:space="preserve"><value>Listado de Exportaciones</value></data>
<data name="page.StayExportList.btnNewExport" xml:space="preserve"><value>Nueva Exportación</value></data>
<data name="page.StayExportNew.pageTitle" xml:space="preserve"><value>Nueva Exportación de Estancias</value></data>
<data name="control.GuestDocForm.sectionDocument" xml:space="preserve"><value>Datos Documento</value></data>
<data name="control.GuestDocForm.sectionGuest" xml:space="preserve"><value>Datos Huésped</value></data>
<data name="control.GuestDocForm.sectionContact" xml:space="preserve"><value>Datos de contacto</value></data>
<data name="control.GuestDocForm.sectionAddress" xml:space="preserve"><value>Datos Dirección</value></data>
<data name="control.GuestDocForm.btnSearchAddress" xml:space="preserve"><value>Buscar Dirección</value></data>
<data name="control.StayEntryForm.sectionDetails" xml:space="preserve"><value>Detalles estancia</value></data>
<data name="control.StayEntryForm.sectionGuests" xml:space="preserve"><value>Lista de Huéspedes</value></data>
<data name="control.StayEntryForm.btnAddGuest" xml:space="preserve"><value>Añadir Huésped</value></data>
<data name="control.AddressQueryDialog.dialogTitle" xml:space="preserve"><value>Buscar dirección</value></data>
<data name="control.AddressQueryDialog.searchText" xml:space="preserve"><value>Buscar dirección</value></data>
<data name="control.AddressQueryDialog.btnSearch" xml:space="preserve"><value>Buscar dirección</value></data>
<data name="control.AddressQueryDialog.btnSelectAddress" xml:space="preserve"><value>Usar dirección</value></data>
<data name="control.GuestDocumentSearchDialog.dialogTitle" xml:space="preserve"><value>Buscar Huésped</value></data>
<data name="control.GuestDocumentSearchDialog.btnSelectDocument" xml:space="preserve"><value>Usar Huésped</value></data>
<data name="control.GuestDocumentSearchDialog.txtSuggestedGuests" xml:space="preserve"><value>Huéspedes sugeridos</value></data>
</root> </root>
@@ -149,6 +149,18 @@
<data name="dto.StayEntry.CreateTime" xml:space="preserve"><value>Creation Time</value></data> <data name="dto.StayEntry.CreateTime" xml:space="preserve"><value>Creation Time</value></data>
<data name="dto.StayEntry.UpdateTime" xml:space="preserve"><value>Update Time</value></data> <data name="dto.StayEntry.UpdateTime" xml:space="preserve"><value>Update Time</value></data>
<data name="dto.StayExportFile.Id" xml:space="preserve"><value>Id</value></data>
<data name="dto.StayExportFile.ExportFormat" xml:space="preserve"><value>Export Format</value></data>
<data name="dto.StayExportFile.ExportParams" xml:space="preserve"><value>Export Params</value></data>
<data name="dto.StayExportFile.ExportResult" xml:space="preserve"><value>Export Result</value></data>
<data name="dto.StayExportFile.ExportedPath" xml:space="preserve"><value>Export Path</value></data>
<data name="dto.StayExportFile.ExportDetails" xml:space="preserve"><value>Export Details</value></data>
<data name="dto.StayExportFile.ExportTime" xml:space="preserve"><value>Export Time</value></data>
<data name="dto.StayExportParams.ExportType" xml:space="preserve"><value>Export Type</value></data>
<data name="dto.StayExportParams.ExportPeriodStart" xml:space="preserve"><value>Date From</value></data>
<data name="dto.StayExportParams.ExportPeriodEnd" xml:space="preserve"><value>Date To</value></data>
<data name="tmp" xml:space="preserve"><value>TMP</value></data> <data name="tmp" xml:space="preserve"><value>TMP</value></data>
<data name="general.actions" xml:space="preserve"><value>Actions</value></data> <data name="general.actions" xml:space="preserve"><value>Actions</value></data>
@@ -170,6 +182,10 @@
<data name="general.wildcardHelperText" xml:space="preserve"><value>Use '%' as wildcard</value></data> <data name="general.wildcardHelperText" xml:space="preserve"><value>Use '%' as wildcard</value></data>
<data name="general.dateStart" xml:space="preserve"><value>Date Start</value></data> <data name="general.dateStart" xml:space="preserve"><value>Date Start</value></data>
<data name="general.dateEnd" xml:space="preserve"><value>Date End</value></data> <data name="general.dateEnd" xml:space="preserve"><value>Date End</value></data>
<data name="general.loading" xml:space="preserve"><value>Loading...</value></data>
<data name="general.msgEditSaved" xml:space="preserve"><value>Changes saved...</value></data>
<data name="general.msgEditCanceled" xml:space="preserve"><value>Edit Canceled!</value></data>
<data name="general.languageSelector" xml:space="preserve"><value>Language</value></data>
<data name="general.address.address" xml:space="preserve"><value>Address</value></data> <data name="general.address.address" xml:space="preserve"><value>Address</value></data>
<data name="general.address.city" xml:space="preserve"><value>City</value></data> <data name="general.address.city" xml:space="preserve"><value>City</value></data>
@@ -180,17 +196,29 @@
<!-- Common codes --> <!-- Common codes -->
<data name="resultCode.Ok" xml:space="preserve"><value>OK</value></data> <data name="enum.ResultCode.Ok" xml:space="preserve"><value>OK</value></data>
<data name="resultCode.UnknownError" xml:space="preserve"><value>Unknown Error</value></data> <data name="enum.ResultCode.UnknownError" xml:space="preserve"><value>Unknown Error</value></data>
<data name="resultCode.FailedValidation" xml:space="preserve"><value>Failed entity validation</value></data> <data name="enum.ResultCode.FailedValidation" xml:space="preserve"><value>Failed entity validation</value></data>
<data name="resultCode.NotFound" xml:space="preserve"><value>Not found</value></data> <data name="enum.ResultCode.NotFound" xml:space="preserve"><value>Not found</value></data>
<data name="enum.ResultCode.FileStorageError" xml:space="preserve"><value>File Storage Error</value></data>
<!-- Specific codes --> <!-- Specific codes -->
<data name="resultCode.GuestInStay" xml:space="preserve"><value>Guest (ID: '{0}') is in a stay.</value></data> <data name="enum.ResultCode.GuestInStay" xml:space="preserve"><value>Guest (ID: '{0}') is in a stay.</value></data>
<data name="resultCode.GuestCurrentlyInStay" xml:space="preserve"><value>Guest (ID: '{0}') is already in one stay during the period: {1}</value></data> <data name="enum.ResultCode.GuestCurrentlyInStay" xml:space="preserve"><value>Guest (ID: '{0}') is already in one stay during the period: {1}</value></data>
<data name="enum.StayExportFileResult.OK" xml:space="preserve"><value>OK</value></data>
<data name="enum.StayExportFileResult.NoData" xml:space="preserve"><value>No Data</value></data>
<data name="enum.StayExportFileResult.ExportError" xml:space="preserve"><value>Export Error</value></data>
<data name="enum.StayExportFileResult.UploadError" xml:space="preserve"><value>File Error</value></data>
<data name="enum.StayExportFormat.GuestLogJson" xml:space="preserve"><value>GuestLog JSON</value></data>
<data name="enum.StayExportFormat.SpainParteViajeros" xml:space="preserve"><value>Spain Parte Viajeros</value></data>
<data name="enum.StayExportParamType.PeriodDateIn" xml:space="preserve"><value>Stay 'Date In' Period</value></data>
<data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Guests</value></data> <data name="navMenu.GuestDocumentList" xml:space="preserve"><value>Guests</value></data>
<data name="navMenu.StayEntryList" xml:space="preserve"><value>Stays</value></data> <data name="navMenu.StayEntryList" xml:space="preserve"><value>Stays</value></data>
<data name="navMenu.StayExport" xml:space="preserve"><value>Stay Export</value></data>
<data name="page.GuestDocList.pageTitle" xml:space="preserve"><value>Guest Documents</value></data> <data name="page.GuestDocList.pageTitle" xml:space="preserve"><value>Guest Documents</value></data>
@@ -200,12 +228,24 @@
<data name="page.GuestDocNew.msgAlreadyExists" xml:space="preserve"><value>This Guest Document Already exists!!</value></data> <data name="page.GuestDocNew.msgAlreadyExists" xml:space="preserve"><value>This Guest Document Already exists!!</value></data>
<data name="page.GuestDocNew.btnGoExistingDoc" xml:space="preserve"><value>Go to existing document</value></data> <data name="page.GuestDocNew.btnGoExistingDoc" xml:space="preserve"><value>Go to existing document</value></data>
<data name="page.GuestDocDetail.msgEditEnabled" xml:space="preserve"><value>Entered Edit mode</value></data>
<data name="page.GuestDocDetail.msgConfirmSaveEdit" xml:space="preserve"><value>Save changes to GuestDocument</value></data>
<data name="page.GuestDocDetail.msgConfirmCancelEdit" xml:space="preserve"><value>Cancel edit. Changes will be lost!</value></data>
<data name="page.GuestDocDetail.msgEditError" xml:space="preserve"><value>Error saving Document '{0}'</value></data>
<data name="page.StayEntryList.pageTitle" xml:space="preserve"><value>Stay Entries</value></data> <data name="page.StayEntryList.pageTitle" xml:space="preserve"><value>Stay Entries</value></data>
<data name="page.StayEntryList.entryList" xml:space="preserve"><value>Entry List</value></data> <data name="page.StayEntryList.entryList" xml:space="preserve"><value>Entry List</value></data>
<data name="page.StayEntryList.btnNewStay" xml:space="preserve"><value>New Stay</value></data> <data name="page.StayEntryList.btnNewStay" xml:space="preserve"><value>New Stay</value></data>
<data name="page.StayEntryNew.pageTitle" xml:space="preserve"><value>New Stay</value></data> <data name="page.StayEntryNew.pageTitle" xml:space="preserve"><value>New Stay</value></data>
<data name="page.StayExportList.pageTitle" xml:space="preserve"><value>Stay Export Files</value></data>
<data name="page.StayExportList.exportList" xml:space="preserve"><value>Export List</value></data>
<data name="page.StayExportList.btnNewExport" xml:space="preserve"><value>New Export</value></data>
<data name="page.StayExportNew.pageTitle" xml:space="preserve"><value>New Stay Export</value></data>
<data name="control.GuestDocForm.sectionDocument" xml:space="preserve"><value>Document Details</value></data> <data name="control.GuestDocForm.sectionDocument" xml:space="preserve"><value>Document Details</value></data>
<data name="control.GuestDocForm.sectionGuest" xml:space="preserve"><value>Guest Details</value></data> <data name="control.GuestDocForm.sectionGuest" xml:space="preserve"><value>Guest Details</value></data>
<data name="control.GuestDocForm.sectionContact" xml:space="preserve"><value>Contact Details</value></data> <data name="control.GuestDocForm.sectionContact" xml:space="preserve"><value>Contact Details</value></data>
@@ -44,7 +44,7 @@ public class GuestDocumentEntityService(
Version = GuestConsts.GuestDocument_FirstVersion, Version = GuestConsts.GuestDocument_FirstVersion,
DocumentType = dto.DocumentType!, DocumentType = dto.DocumentType!,
DocumentNum = dto.DocumentNum!, DocumentNum = dto.DocumentNum!,
IssueDate = DateOnly.FromDateTime(dto.IssueDate!.Value), IssueDate = dto.IssueDate != null ? DateOnly.FromDateTime(dto.IssueDate!.Value) : null,
SupportNum = dto.SupportNum, SupportNum = dto.SupportNum,
Nationality = dto.Nationality!, Nationality = dto.Nationality!,
Name = dto.Name!, Name = dto.Name!,
@@ -76,9 +76,77 @@ public class GuestDocumentEntityService(
} }
} }
public Task<Result> UpdateDocumentAsync(GuestDocumentDTO dto) public async Task<Result> UpdateDocumentAsync(GuestDocumentDTO dto)
{ {
throw new NotImplementedException(); var valResult = dtoValidator.Validate(dto);
if (!valResult.IsValid)
{
logger.LogWarning("GuestDocumentService - UpdateDocument: Dto validation failed. Fix the errors: {errorList}", valResult.Errors);
return Result.Fail(ResultCode.FailedValidation);
}
try
{
using var dbContext = contextFactory.CreateDbContext();
//Get current entity from DB.
GuestDocument? dbItem = await dbContext.FindAsync<GuestDocument>(dto.Id, dto.Version);
if (dbItem == null)
{
logger.LogWarning("GuestDocumentService - UpdateDocument: Error updating GuestDocument (ID: '{id}'): GuestDocument not found!", dto.FullKey);
return Result.Fail(ResultCode.NotFound);
}
//Dto must be valid, so null warings shouldn't matter.
GuestAddress addressItem = new()
{
Address = dto.Address,
AddressCity = dto.AddressCity,
AddressProvince = dto.AddressProvince,
AddressState = dto.AddressState,
AddressCountry = dto.AddressCountry!,
ZipCode = dto.ZipCode,
};
DateTime updateTime = DateTime.UtcNow;
GuestDocument updatedItem = new()
{
Id = dbItem.Id,
Version = dbItem.Version,
DocumentType = dbItem.DocumentType,
DocumentNum = dbItem.DocumentNum,
IssueDate = dto.IssueDate != null ? DateOnly.FromDateTime(dto.IssueDate!.Value) : null,
SupportNum = dto.SupportNum,
Nationality = dto.Nationality!,
Name = dto.Name!,
Surname = dto.Surname!,
Surname2 = dto.Surname2,
BirthDate = DateOnly.FromDateTime(dto.BirthDate!.Value),
Gender = dto.Gender,
PhoneNumber = dto.PhoneNumber,
EmailAddress = dto.EmailAddress,
AddressDetails = addressItem,
CreateTime = dbItem.CreateTime,
UpdateTime = updateTime,
IsLastVersion = dbItem.IsLastVersion,
};
dbContext.Entry(dbItem).CurrentValues.SetValues(updatedItem);
var result = await dbContext.SaveChangesAsync();
if (result != 1)
{
logger.LogWarning("GuestDocumentService - UpdateDocument: Unexpected updated entity count: {count}", result);
}
logger.LogInformation("GuestDocumentService - UpdateDocument: GuestDocument Updated OK {id}", dbItem.GetFullKey());
return Result.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "GuestDocumentService - UpdateDocument: Error updating GuestDocument: '{id}'", dto.FullKey);
return Result.FailUnknownError();
}
} }
public Task<Result> DeleteDocumentAsync(GuestDocumentDTO dto) public Task<Result> DeleteDocumentAsync(GuestDocumentDTO dto)
@@ -0,0 +1,19 @@
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Services.Entity;
public interface IStayExportFileEntityService
{
public Task<Result> CreateStayExportAsync(StayExportFileDTO dto);
public Task<Result> UpdateStayExportAsync(StayExportFileDTO dto);
/// <summary>
/// Delete StayExport entry from DB.
/// Exported File won't be deleted from Storage.
/// </summary>
public Task<Result> DeleteStayExportAsync(StayExportFileDTO dto);
public Task<StayExportFileDTO?> GetDtoByIdAsync(long id);
//public Task<IEnumerable<StayExportFileDTO>> GetDtoListByFilterAsync(StayExportFileFilterDTO filter, int limit = 0);
public Task<IEnumerable<StayExportFileDTO>> GetDtoListAsync(int limit = 0);
}
@@ -56,7 +56,7 @@ public class StayEntryEntityService(
if (isInOtherStay) if (isInOtherStay)
{ {
string period = $"{dbStay.DateIn}-{dbStay.DateOut}"; string period = $"{dbStay.DateIn}-{dbStay.DateOut}";
return Result.FailWithContext(ResultCode.GuestCurrentlyInStay, dbGuest.GetFullKey(), period); return Result.FailWithContext(ResultCode.GuestCurrentlyInStay, dbGuest.FullDocument, period);
} }
//Add dbGuest to dbStay. //Add dbGuest to dbStay.
@@ -67,7 +67,7 @@ public class StayEntryEntityService(
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey()); logger.LogInformation("StayEntryService - CreateEntryAsync: StayEntry Created: '{id}'", dbStay.GetFullKey());
return Result.Ok(); return Result.OkWithContext(dbStay.GetFullKey());
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -155,7 +155,8 @@ public class StayEntryEntityService(
query = query.WhereIf(!string.IsNullOrEmpty(filter.PaymentMethod), query = query.WhereIf(!string.IsNullOrEmpty(filter.PaymentMethod),
e => e.PaymentMethod == filter.PaymentMethod); e => e.PaymentMethod == filter.PaymentMethod);
query = query.OrderByDescending(e => e.DateIn); query = query.OrderByDescending(e => e.DateIn)
.ThenByDescending(e => e.UpdateTime);
if (limit > 0) if (limit > 0)
{ {
@@ -0,0 +1,141 @@
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.Utils;
namespace Somnia.GuestLog.Services.Entity;
public class StayExportFileEntityService(
ILogger<StayExportFileEntityService> logger,
IDbContextFactory<GuestLogContext> contextFactory
) : IStayExportFileEntityService
{
public async Task<Result> CreateStayExportAsync(StayExportFileDTO dto)
{
ArgumentNullException.ThrowIfNull(dto, nameof(dto));
//Check for null values in the DTO.
if (dto.ExportFormat == null || dto.ExportResult == null || dto.ExportTime == null)
{
throw new InvalidOperationException("StayExportFile dto contains null fields");
}
if (dto.ExportParams == null)
{
throw new InvalidOperationException("StayExportFile dto contains null ExportParams");
}
StayExportParams exportParams = GetExportParamsFromDTO(dto.ExportParams);
StayExportFile dbItem = new()
{
ExportFormat = dto.ExportFormat.Value,
ExportParams = exportParams,
ExportResult = dto.ExportResult.Value,
ExportedPath = dto.ExportedPath,
ExportDetails = dto.ExportDetails,
ExportTime = dto.ExportTime?.ToUniversalTime() ?? DateTime.UtcNow,
};
try
{
using var dbContext = contextFactory.CreateDbContext();
await dbContext.AddAsync(dbItem);
await dbContext.SaveChangesAsync();
logger.LogInformation(
"StayExportFileService - CreateStayExportAsync: StayExport Created (ID: '{id}') with result: '{result}'",
dbItem.GetFullKey(), dbItem.ExportResult);
return Result.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "StayExportFileService - CreateStayExportAsync: Error creating StayExport");
return Result.FailUnknownError();
}
}
public Task<Result> UpdateStayExportAsync(StayExportFileDTO dto)
{
throw new NotImplementedException();
}
public async Task<Result> DeleteStayExportAsync(StayExportFileDTO dto)
{
try
{
using var dbContext = contextFactory.CreateDbContext();
int deletedCount = await dbContext.StayExportFiles
.Where(e => e.Id == dto.Id)
.ExecuteDeleteAsync();
if (deletedCount == 0)
{
logger.LogWarning("StayExportFileService - DeleteStayExportAsync: Error deleting StayExport. Entity not found with ID: '{ID}'", dto.Id);
return Result.Fail(ResultCode.NotFound);
}
logger.LogInformation(
"StayExportFileService - DeleteStayExportAsync: StayExport deleted with Id: '{id}'.", dto.Id);
return Result.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "StayExportFileService - DeleteStayExportAsync: Error deleting StayExport");
return Result.FailUnknownError();
}
}
public async Task<StayExportFileDTO?> GetDtoByIdAsync(long id)
{
using var dbContext = contextFactory.CreateDbContext();
StayExportFile? dbItem = await dbContext.StayExportFiles
.Where(e => e.Id == id)
.FirstOrDefaultAsync();
return dbItem is not null ? GetDtoFromDb(dbItem) : null;
}
public async Task<IEnumerable<StayExportFileDTO>> GetDtoListAsync(int limit = 0)
{
using var dbContext = contextFactory.CreateDbContext();
IQueryable<StayExportFile> query = dbContext.StayExportFiles
.OrderByDescending(e => e.ExportTime)
.WithLimit(limit);
var dbList = await query.ToListAsync();
return dbList.Select(e => GetDtoFromDb(e));
}
#region Internal Methods
private static StayExportFileDTO GetDtoFromDb(StayExportFile dbItem)
{
return EntityMapping.MapStayExportFileEntityToDTO(dbItem);
}
private static StayExportParams GetExportParamsFromDTO(StayExportParamsDTO dto)
{
if (dto.ExportType == null)
{
throw new InvalidOperationException("Dto with invalid data");
}
return new()
{
ExportType = dto.ExportType.Value,
ExportPeriodStart = dto.ExportPeriodStart?.ToUniversalTime(),
ExportPeriodEnd = dto.ExportPeriodEnd?.ToUniversalTime()
};
}
#endregion
}
@@ -0,0 +1,7 @@
namespace Somnia.GuestLog.Services;
public interface ILocalizerService
{
public string GetEnumValue<TEnum>(TEnum? enumValue) where TEnum : struct, Enum;
public string GetEnumValue<TEnum>(TEnum enumValue) where TEnum : struct, Enum;
}
@@ -1,5 +1,5 @@
using Somnia.GuestLog.Enums.UI; using Somnia.GuestLog.Enums.UI;
using Somnia.GuestLog.Models.DTOs; using IResult = Somnia.GuestLog.Models.DTOs.IResult;
namespace Somnia.GuestLog.Services; namespace Somnia.GuestLog.Services;
@@ -7,6 +7,6 @@ public interface IUserMessageService
{ {
public void ShowMessage(string text, MessageKind kind, params object[] parameters); public void ShowMessage(string text, MessageKind kind, params object[] parameters);
public void ShowResult(Result resultDto); public void ShowResult(IResult resultDto);
public void ShowResultWithMessage(Result resultDto, string message); public void ShowResultWithMessage(IResult resultDto, string message);
} }
@@ -0,0 +1,37 @@
using Microsoft.Extensions.Localization;
using Somnia.GuestLog.Resources;
namespace Somnia.GuestLog.Services;
public class LocalizerService(
IServiceProvider serviceProvider
) : ILocalizerService
{
private IStringLocalizer<LanguageLocale> Localizer
{
get
{
field ??= serviceProvider.GetRequiredService<IStringLocalizer<LanguageLocale>>();
return field;
}
}
public string GetEnumValue<TEnum>(TEnum? enumValue) where TEnum : struct, Enum
{
if (enumValue == null)
{
return GuestConsts.EmptyField;
}
return GetEnumValue(enumValue.Value);
}
public string GetEnumValue<TEnum>(TEnum enumValue) where TEnum : struct, Enum
{
string enumName = typeof(TEnum).Name;
string? strEnumValue = Enum.GetName(enumValue);
string localeKey = $"enum.{enumName}.{strEnumValue}";
return Localizer[localeKey];
}
}
@@ -0,0 +1,6 @@
namespace Somnia.GuestLog.Services.SpainCodigoMunicipio;
public interface ISpainCodigoMunicipioService
{
public Task<string?> GetCodigoMunicipioAsync(string municipio, string province);
}
@@ -0,0 +1,123 @@
using System.Xml.Serialization;
using Microsoft.AspNetCore.WebUtilities;
namespace Somnia.GuestLog.Services.SpainCodigoMunicipio;
public class SpainCodigoMunicipioCatastroService(
ILogger<SpainCodigoMunicipioCatastroService> logger,
IHttpClientFactory httpFactory
) : ISpainCodigoMunicipioService
{
private const string Api_Host = "https://ovc.catastro.meh.es";
private const string Api_Address = "OVCServWeb/OVCWcfCallejero/COVCCallejero.svc/rest/ObtenerMunicipios";
private const string Api_Param_Provincia = "Provincia";
private const string Api_Param_Municipio = "Municipio";
//Result Cache.
readonly Dictionary<(string provincia, string municipio), string> codigoMunicipioCache = [];
public async Task<string?> GetCodigoMunicipioAsync(string municipio, string province)
{
var cacheKey = (province.ToUpper(), municipio.ToUpper());
//Try get from cache.
if (codigoMunicipioCache.TryGetValue(cacheKey, out string? cacheCode))
{
logger.LogInformation("SpainCodigoMunicipioCatastroService - Municipio found in cache for: '{key}'", cacheKey);
return cacheCode;
}
try
{
string uri = GetRequestUri(municipio, province);
logger.LogInformation("SpainCodigoMunicipioCatastroService - Getting municipios from API with query: '{addr}'", uri);
var httpClient = httpFactory.CreateClient();
using var rsStream = await httpClient.GetStreamAsync(uri);
XmlSerializer serializer = new(typeof(CatastroApiResult));
if (serializer.Deserialize(rsStream) is not CatastroApiResult rs)
{
logger.LogError("SpainCodigoMunicipioCatastroService - Result can't be deserialized!");
return null;
}
if (rs.municipios.Count == 0)
{
logger.LogWarning("SpainCodigoMunicipioCatastroService - No municipios found for query: '{addr}'", uri);
return null;
}
if (rs.municipios.Count > 1)
{
logger.LogWarning("SpainCodigoMunicipioCatastroService - More than 1 municipio was returned. First one will be used!");
}
CatastroApiResultCodigoIne? codIne = rs.municipios.First().codigoIne;
if (codIne == null || codIne.codigoMunicipio == null || codIne.codigoProvincia == null)
{
logger.LogWarning("SpainCodigoMunicipioCatastroService - Returned Municipio without data!");
return null;
}
string finalCode = $"{codIne.codigoProvincia.PadLeft(2, '0')}{codIne.codigoMunicipio.PadLeft(3, '0')}";
codigoMunicipioCache.Add(cacheKey, finalCode);
return finalCode;
}
catch (Exception ex)
{
logger.LogError(ex, "SpainCodigoMunicipioCatastroService - Error getting codigo municipio from catastro API!");
return null;
}
}
#region Internal Methods
private static string GetRequestUri(string municipio, string province)
{
string baseAddress = $"{Api_Host}/{Api_Address}";
return QueryHelpers.AddQueryString(baseAddress, [
new KeyValuePair<string, string?>(Api_Param_Provincia, province),
new KeyValuePair<string, string?>(Api_Param_Municipio, municipio),
]);
}
#endregion
#region API Model
[XmlRoot(ElementName = "consulta_municipiero", Namespace = "http://www.catastro.meh.es/")]
public class CatastroApiResult()
{
[XmlArray(ElementName = "municipiero")]
[XmlArrayItem(ElementName = "muni")]
public required List<CatastroApiResultMunicipio> municipios;
}
public class CatastroApiResultMunicipio()
{
[XmlElement(ElementName = "nm")]
public string? municipio;
[XmlElement(ElementName = "loine")]
public CatastroApiResultCodigoIne? codigoIne;
}
public class CatastroApiResultCodigoIne
{
[XmlElement(ElementName = "cp")]
public string? codigoProvincia;
[XmlElement(ElementName = "cm")]
public string? codigoMunicipio;
}
#endregion
}
@@ -0,0 +1,19 @@
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Services.StayExport;
public interface IStayExportFormatProvider
{
public StayExportFormat GetFormat();
public string GetFilename(DateTime exportTime);
/// <summary>
/// Export Data with the corresponding format.
/// Data is copied to given stream (no other operations are performed to the stream).
/// </summary>
/// <param name="stayList">List of Stays to export</param>
/// <param name="dataStream">Stream to copy exported data.</param>
public Task<(StayExportFileResult fileResult, string? exportDetails)> ExportDataAsync(IEnumerable<StayEntryDTO> stayList, Stream dataStream);
}
@@ -0,0 +1,11 @@
using Somnia.GuestLog.Enums.Models;
namespace Somnia.GuestLog.Services.StayExport;
public interface IStayExportFormatProviderFactory
{
/// <summary>
/// Returns the StayExport service implementation given current configuration.
/// </summary>
public IStayExportFormatProvider GetProvider(StayExportFormat exportFormat);
}
@@ -0,0 +1,14 @@
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Services.StayExport;
public interface IStayExportService
{
public Task<Result<StayExportFileDTO>> ExportStaysByParamsAsync(StayExportFormat format, StayExportParamsDTO exportParams);
public Task<Result> GetExportedFileAsync(StayExportFileDTO item, Stream dataStream);
public Task<Result> DeleteExportedFileAsync(StayExportFileDTO item);
}
@@ -0,0 +1,30 @@
using Microsoft.Extensions.Options;
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.Configuration;
using Somnia.GuestLog.Utils.DI;
namespace Somnia.GuestLog.Services.StayExport;
public class StayExportFormatProviderFactory(
IServiceProvider serviceProvider,
IOptions<StayExportConfig> config
) : IStayExportFormatProviderFactory, IFactoryDI
{
public static void RegisterFactoryItems(IServiceCollection services)
{
services.AddKeyedSingleton<IStayExportFormatProvider, StayExportProviderJson>(StayExportFormat.GuestLogJson);
services.AddKeyedSingleton<IStayExportFormatProvider, StayExportProviderSpainParteViajeros>(StayExportFormat.SpainParteViajeros);
}
public IStayExportFormatProvider GetProvider(StayExportFormat exportFormat)
{
if (config.Value.EnableStayExport == false)
{
throw new InvalidOperationException("StayExport is disabled. Enable and configure it first!");
}
return serviceProvider.GetKeyedService<IStayExportFormatProvider>(exportFormat)
?? throw new InvalidOperationException($"StayExport service not found for ExportFormat: '{exportFormat}'");
}
}
@@ -0,0 +1,30 @@
using System.Text.Json;
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Services.StayExport;
public class StayExportProviderJson : IStayExportFormatProvider
{
private const string fileExtension = "json";
public StayExportFormat GetFormat() => StayExportFormat.GuestLogJson;
public string GetFilename(DateTime exportTime)
=> $"{Enum.GetName(GetFormat())}_{exportTime.ToFileTimeUtc()}.{fileExtension}";
public async Task<(StayExportFileResult fileResult, string? exportDetails)> ExportDataAsync(IEnumerable<StayEntryDTO> stayList, Stream dataStream)
{
if (!stayList.Any())
{
return (fileResult: StayExportFileResult.NoData, exportDetails: null);
}
// Convert to JSON.
await JsonSerializer.SerializeAsync(dataStream, stayList);
List<string> exportDetails = [.. stayList.Where(s => s.Id != null).Select(s => s.Id?.ToString() ?? "")];
string details = JsonSerializer.Serialize(exportDetails);
return (fileResult: StayExportFileResult.OK, exportDetails: details);
}
}
@@ -0,0 +1,421 @@
using System.Diagnostics;
using System.Text.Json;
using System.Xml.Serialization;
using FluentValidation;
using Microsoft.Extensions.Options;
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.Configuration;
using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Services.SpainCodigoMunicipio;
namespace Somnia.GuestLog.Services.StayExport;
public class StayExportProviderSpainParteViajeros(
ILogger<StayExportProviderSpainParteViajeros> logger,
ISpainCodigoMunicipioService codigoMunicipioService,
IOptions<StayExportSpainParteViajerosConfig> configOptions
) : IStayExportFormatProvider
{
private const string fileExtension = "xml";
private const string ExportDetailsResult_Error = "Error";
private const string ExportDetailsResult_Ok = "Ok";
private const string PersonRole = "VI";
private const string PersonRelation = "OT";
private const string PersonTipoDoc_NIF = "NIF";
private const string PersonTipoDoc_NIE = "NIE";
private const string PersonTipoDoc_PAS = "PAS";
private const string PersonTipoDoc_OTRO = "OTRO";
private const string PersonDireccionPais_ESP = "ESP";
private const string XmlNamespace_Peticion = "http://www.neg.hospedajes.mir.es/altaParteHospedaje";
private const string XmlNamespace_Peticion_Prefix = "ns2";
private readonly StayExportSpainParteViajerosConfig config = configOptions.Value;
public StayExportFormat GetFormat() => StayExportFormat.SpainParteViajeros;
public string GetFilename(DateTime exportTime)
=> $"{Enum.GetName(GetFormat())}_{exportTime.ToFileTimeUtc()}.{fileExtension}";
public async Task<(StayExportFileResult fileResult, string? exportDetails)> ExportDataAsync(IEnumerable<StayEntryDTO> stayList, Stream dataStream)
{
if (string.IsNullOrEmpty(config.CodigoEstablecimiento))
{
logger.LogError("StayExportProviderSpainParteViajeros - Config error: CodigoEstablecimiento is not configured!");
return (fileResult: StayExportFileResult.ExportError, exportDetails: null);
}
if (!stayList.Any())
{
return (fileResult: StayExportFileResult.NoData, exportDetails: null);
}
List<StayExportDetails> stayExportDetails = [];
ComunicacionValidator comunicacionValidator = new();
List<Comunicacion> comunicacionsList = [];
foreach (StayEntryDTO stay in stayList)
{
(Comunicacion? comunicacion, string errorMsg) = await GetComunicacionFromStay(stay);
if (comunicacion == null)
{
logger.LogWarning("StayExportProviderSpainParteViajeros - Stay (ID: {id}) can't be exported: Error: {code}", stay.Id, errorMsg);
stayExportDetails.Add(new(StayId: stay.Id ?? -1, ExportResult: ExportDetailsResult_Error, Details: errorMsg));
continue;
}
var errors = comunicacionValidator.Validate(comunicacion);
if (!errors.IsValid)
{
logger.LogWarning("StayExportProviderSpainParteViajeros - Stay (ID: {id}) is invalid: Errors: {errors}", stay.Id, errors.Errors);
stayExportDetails.Add(new(StayId: stay.Id ?? -1, ExportResult: ExportDetailsResult_Error, Details: errors.ToString()));
continue;
}
comunicacionsList.Add(comunicacion);
logger.LogInformation("StayExportProviderSpainParteViajeros - Stay (ID: {id}) Exported OK", stay.Id);
stayExportDetails.Add(new(StayId: stay.Id ?? -1, ExportResult: ExportDetailsResult_Ok, Details: null));
}
Peticion peticion = new()
{
solicitud = new()
{
codigoEstablecimiento = config.CodigoEstablecimiento,
comunicacion = comunicacionsList,
}
};
//Create XmlSerializerNamespaces to add prefix-namespace pairs.
var docNamespaces = new XmlSerializerNamespaces();
docNamespaces.Add(XmlNamespace_Peticion_Prefix, XmlNamespace_Peticion);
XmlSerializer serializer = new(typeof(Peticion));
serializer.Serialize(stream: dataStream, o: peticion, namespaces: docNamespaces);
string details = JsonSerializer.Serialize(stayExportDetails);
return (fileResult: StayExportFileResult.OK, exportDetails: details);
}
#region Internal Methods
private async Task<(Comunicacion? comunicacion, string errorMsg)> GetComunicacionFromStay(StayEntryDTO stay)
{
Debug.Assert(stay.Id != null
&& stay.DateIn != null
&& stay.DateOut != null
&& stay.GuestDocuments != null
);
//Call Fn to validate Stay.
Contrato contrato = new()
{
referencia = stay.Id.Value.ToString(),
fechaContrato = stay.DateIn.Value.Date,
fechaEntrada = stay.DateIn.Value,
fechaSalida = stay.DateOut.Value,
internet = config.InternetAvailable,
numHabitaciones = stay.RoomCount ?? -1,
numPersonas = stay.GuestDocuments.Count,
pago = new()
{
tipoPago = GetTipoPagoFromStay(stay)!
}
};
List<Persona> personasList = [];
foreach (GuestDocumentDTO doc in stay.GuestDocuments)
{
Direccion dir = new()
{
direccion = doc.Address!,
codigoPostal = doc.ZipCode!,
pais = doc.AddressCountry!,
};
//Get Municipio given current country.
if (doc.AddressCountry != PersonDireccionPais_ESP)
{
List<string?> addressParts = [doc.AddressCity, doc.AddressProvince, doc.AddressState];
dir.nombreMunicipio = string.Join(", ", addressParts.Where(p => !string.IsNullOrEmpty(p)));
}
if (doc.AddressCountry == PersonDireccionPais_ESP)
{
dir.codigoMunicipio = await GetMunicipioFromGuestAddressAsync(doc);
if (dir.codigoMunicipio == null)
{
return (
comunicacion: null,
errorMsg: $"Can't get codigo municipio for {doc.AddressProvince}-{doc.AddressCity} in doc {doc.FullDocument}"
);
}
}
Persona persona = new()
{
rol = PersonRole,
nombre = doc.Name!,
apellido1 = doc.Surname!,
apellido2 = doc.Surname2,
tipoDocumento = GetTipoDocumentoFromGuest(doc)!,
numeroDocumento = doc.DocumentNum!,
soporteDocumento = doc.SupportNum,
fechaNacimiento = doc.BirthDate?.Date ?? DateTime.MaxValue,
nacionalidad = doc.Nationality!,
sexo = GetSexoFromGuest(doc)!,
telefono = doc.PhoneNumber,
correo = doc.EmailAddress,
parentesco = PersonRelation,
direccion = dir,
};
personasList.Add(persona);
}
return (
comunicacion: new()
{
contrato = contrato,
persona = personasList
},
errorMsg: "OK");
}
private static string? GetTipoDocumentoFromGuest(GuestDocumentDTO doc)
{
return doc.DocumentType switch
{
GuestConsts.DocumentType_DNI => PersonTipoDoc_NIF,
GuestConsts.DocumentType_NIE => PersonTipoDoc_NIE,
GuestConsts.DocumentType_PASSPORT => PersonTipoDoc_PAS,
null => null,
_ => PersonTipoDoc_OTRO
};
}
private static string? GetSexoFromGuest(GuestDocumentDTO doc)
{
return doc.Gender switch
{
GuestConsts.GenderOptions_MALE => "H",
GuestConsts.GenderOptions_FEMALE => "M",
null => null,
_ => "O"
};
}
private static string? GetTipoPagoFromStay(StayEntryDTO stay)
{
return stay.PaymentMethod;
}
private async Task<string?> GetMunicipioFromGuestAddressAsync(GuestDocumentDTO doc)
{
if (string.IsNullOrEmpty(doc.AddressCity) || string.IsNullOrEmpty(doc.AddressProvince))
{
logger.LogWarning("Found doc ({docNum}) without full address", doc.FullDocument);
return null;
}
//Get codMunicipio from API.
return await codigoMunicipioService.GetCodigoMunicipioAsync(doc.AddressCity, doc.AddressProvince);
}
#endregion
#region Validator
private sealed class ComunicacionValidator : AbstractValidator<Comunicacion>
{
//TODO: Validate values in list for: Pago.tipoPago, Persona.sexo, Persona.tipoDocumento
public ComunicacionValidator()
{
RuleFor(comm => comm.contrato)
.NotNull()
.ChildRules(contrato =>
{
contrato.RuleFor(p => p.referencia)
.NotEmpty()
.MaximumLength(50);
contrato.RuleFor(p => p.fechaEntrada)
.LessThan(p => p.fechaSalida);
contrato.RuleFor(p => p.fechaSalida)
.GreaterThan(p => p.fechaEntrada);
contrato.RuleFor(p => p.numHabitaciones)
.GreaterThanOrEqualTo(0);
//BLOQUE PAGO
contrato.RuleFor(p => p.pago)
.ChildRules(e =>
{
e.RuleFor(p => p.tipoPago)
.NotEmpty();
});
});
RuleForEach(comm => comm.persona)
.ChildRules(person =>
{
person.RuleFor(p => p.rol)
.Equal(PersonRole);
person.RuleFor(p => p.nombre)
.NotEmpty()
.MaximumLength(50);
person.RuleFor(p => p.apellido1)
.NotEmpty()
.MaximumLength(50);
person.RuleFor(p => p.apellido2)
.MaximumLength(50);
person.RuleFor(p => p.tipoDocumento)
.NotEmpty()
.MaximumLength(5);
person.RuleFor(p => p.numeroDocumento)
.NotEmpty()
.MaximumLength(15);
person.RuleFor(p => p.soporteDocumento)
.NotEmpty()
.When(p => p.tipoDocumento == PersonTipoDoc_NIF || p.tipoDocumento == PersonTipoDoc_NIE);
person.RuleFor(p => p.fechaNacimiento)
.NotEmpty();
person.RuleFor(p => p.sexo)
.MaximumLength(1);
person.RuleFor(p => p.telefono)
.MaximumLength(20);
person.RuleFor(p => p.correo)
.MaximumLength(250);
person.RuleFor(p => p.direccion)
.ChildRules(d =>
{
d.RuleFor(p => p.direccion)
.NotEmpty()
.MaximumLength(100);
d.RuleFor(p => p.codigoMunicipio)
.NotEmpty()
.When(p => p.pais == PersonDireccionPais_ESP)
.MaximumLength(5);
d.RuleFor(p => p.nombreMunicipio)
.NotEmpty()
.When(p => p.pais != PersonDireccionPais_ESP)
.MaximumLength(100);
d.RuleFor(p => p.codigoPostal)
.NotEmpty()
.MaximumLength(20);
d.RuleFor(p => p.pais)
.NotEmpty()
.Length(3);
});
});
RuleForEach(comm => comm.persona)
.Must(p => !string.IsNullOrEmpty(p.telefono) || !string.IsNullOrEmpty(p.correo))
.WithMessage($"It's required to have at least one of the following fields: '{nameof(Persona.telefono)}', '{nameof(Persona.correo)}'");
}
}
#endregion
#region Export Data Classes
private record StayExportDetails(long StayId, string ExportResult, string? Details);
[XmlRoot(ElementName = "peticion", Namespace = XmlNamespace_Peticion)]
public class Peticion
{
[XmlElement(Namespace = "")]
public required Solicitud solicitud;
}
[XmlRoot(ElementName = "solicitud")]
public class Solicitud
{
public required string codigoEstablecimiento;
[XmlElement(ElementName = "comunicacion")]
public required List<Comunicacion> comunicacion;
}
public class Comunicacion
{
[XmlElement(ElementName = "contrato")]
public required Contrato contrato;
[XmlElement(ElementName = "persona")]
public required List<Persona> persona;
}
public class Contrato
{
public required string referencia;
[XmlElement(DataType = "date")]
public required DateTime fechaContrato;
[XmlElement(DataType = "dateTime")]
public required DateTime fechaEntrada;
[XmlElement(DataType = "dateTime")]
public required DateTime fechaSalida;
public required int numPersonas;
public int? numHabitaciones;
public bool? internet;
//BLOQUE PAGO
public required Pago pago;
}
public class Persona
{
public required string rol;
public required string nombre;
public required string apellido1;
public string? apellido2;
public required string tipoDocumento;
public required string numeroDocumento;
public string? soporteDocumento;
[XmlElement(DataType = "date")]
public required DateTime fechaNacimiento;
public required string nacionalidad;
public required string sexo;
// BLOQUE DIRECCION
public required Direccion direccion;
public string? telefono;
public string? correo;
public required string parentesco;
}
public class Pago
{
public required string tipoPago;
// [XmlElement(DataType = "date")]
// public required DateTime fechaPago;
public string? medioPago;
public string? titular;
public string? caducidadTarjeta;
}
public class Direccion
{
public required string direccion;
public string? codigoMunicipio;
public string? nombreMunicipio;
public required string codigoPostal;
public required string pais;
}
#endregion
}
@@ -0,0 +1,247 @@
using System.Diagnostics;
using FluentStorage;
using FluentStorage.Storage;
using Microsoft.Extensions.Options;
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Enums.Services;
using Somnia.GuestLog.Models.Configuration;
using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Models.QueryConfigs;
using Somnia.GuestLog.Services.Entity;
namespace Somnia.GuestLog.Services.StayExport;
public class StayExportService(
ILogger<StayExportProviderJson> logger,
IOptions<StayExportConfig> exportConfig,
IStayExportFileEntityService stayExportEntityService,
IStayEntryEntityService stayEntityService,
IStayExportFormatProviderFactory providerFactory
) : IStayExportService
{
private static readonly StayEntryQueryConfig queryConfig =
new(IncludeGuests: true, UseSplitQueries: true, DisableTracking: true);
private readonly StayExportConfig config = exportConfig.Value;
public async Task<Result<StayExportFileDTO>> ExportStaysByParamsAsync(StayExportFormat format, StayExportParamsDTO exportParams)
{
//Get data to export.
IEnumerable<StayEntryDTO>? stayList;
try
{
stayList = await GetStaysToExportByParamsAsync(exportParams);
}
catch (Exception ex)
{
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error retriving stays to export!");
return Result<StayExportFileDTO>.Fail(ResultCode.UnknownError);
}
//Create initial StayExportFile
DateTime exportTime = DateTime.UtcNow;
StayExportFileDTO stayExport = new()
{
ExportFormat = format,
ExportParams = exportParams,
ExportTime = exportTime,
};
//Export data.
using Stream exportedDataStream = new MemoryStream();
//Generate data to export.
StayExportFileResult exportResult;
string? exportFilename = null;
try
{
//Create Format provider and export data to stream.
var provider = providerFactory.GetProvider(format);
(exportResult, stayExport.ExportDetails) = await provider.ExportDataAsync(stayList, exportedDataStream);
// "Rewind" stream so it can be read from the begining when uploading.
exportedDataStream.Seek(0, SeekOrigin.Begin);
exportFilename = provider.GetFilename(exportTime);
}
catch (Exception ex)
{
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error exporting data!");
exportResult = StayExportFileResult.ExportError;
}
//If data exported OK, save to file.
if (exportResult == StayExportFileResult.OK)
{
try
{
Debug.Assert(exportFilename != null);
string exportPathSubdir = "";
if (config.UseSubdirectoryByFormat)
{
exportPathSubdir = Enum.GetName(format) ?? "";
}
string exportPath = Path.Combine(exportPathSubdir, exportFilename);
//Store data.
using IStore exportStore = GetExportStore();
await exportStore.SetObject(objectPath: exportPath, dataStream: exportedDataStream);
//Update path in item.
stayExport.ExportedPath = exportPath;
}
catch (Exception ex)
{
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error Uploading exported data!");
exportResult = StayExportFileResult.UploadError;
}
}
//Store StayExport in DB.
stayExport.ExportResult = exportResult;
Result? createResult = null;
try
{
createResult = await stayExportEntityService.CreateStayExportAsync(stayExport);
}
catch (Exception ex)
{
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error storing StayExportFile entity to DB: {stayExport}", stayExport);
}
//If everything is OK, return OK.
if (createResult != null && createResult.ResultCode == ResultCode.Ok)
{
return Result<StayExportFileDTO>.Ok(stayExport);
}
//Saving export to DB failed. Delete exported file (if exported)
if (!string.IsNullOrEmpty(stayExport.ExportedPath))
{
try
{
using IStore exportStore = GetExportStore();
await exportStore.DeleteObject(objectPath: stayExport.ExportedPath);
}
catch (Exception ex)
{
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error deleting exported file after db saving error: {exportedPath}", stayExport.ExportedPath);
}
}
logger.LogError(
"JsonStayExportService - ExportStaysByDateIn: Error (code '{code}') storing StayExportFile entity to DB: {stayExport}",
createResult?.ResultCode, stayExport);
return Result<StayExportFileDTO>.Fail(ResultCode.UnknownError);
}
public async Task<Result> GetExportedFileAsync(StayExportFileDTO item, Stream dataStream)
{
if (string.IsNullOrEmpty(item.ExportedPath))
{
logger.LogWarning("Trying to get exported file without path!! Exported file can't be loaded!");
return Result.Fail(ResultCode.NotFound);
}
try
{
IStore store = GetExportStore();
await store.GetObject(item.ExportedPath, dataStream);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting exported file '{id}' from path '{path}'", item.Id, item.ExportedPath);
return Result.FailUnknownError();
}
return Result.Ok();
}
public async Task<Result> DeleteExportedFileAsync(StayExportFileDTO item)
{
//Try to delete existing file.
if (!string.IsNullOrEmpty(item.ExportedPath))
{
bool fileDeletedFromStore = await DeleteExportFileFromStoreAsync(item);
if (!fileDeletedFromStore)
{
return Result.Fail(ResultCode.FileStorageError);
}
}
//Delete entry from DB.
Result deleteResult = await stayExportEntityService.DeleteStayExportAsync(item);
if (!deleteResult.IsSuccess)
{
return Result.Fail(deleteResult.ResultCode);
}
return Result.Ok();
}
#region Internal Methods
private async Task<IEnumerable<StayEntryDTO>> GetStaysToExportByParamsAsync(StayExportParamsDTO exportParams)
{
//Todo - Improve type selection (when there are more types).
if (exportParams.ExportType == StayExportParamType.PeriodDateIn)
{
if (exportParams.ExportPeriodStart == null || exportParams.ExportPeriodEnd == null)
{
throw new InvalidOperationException($"StayExportFile - ExportType {exportParams.ExportType} require date start/end. Null dates found");
}
//Get Stays in period -> Local time should be used so it match with entities local-time.
StayEntryFilterDTO filter = new()
{
DateInStart = exportParams.ExportPeriodStart,
DateInEnd = exportParams.ExportPeriodEnd
};
return await stayEntityService.GetDtoListByFilterAsync(filter, limit: 0, queryConfig: queryConfig);
}
throw new InvalidOperationException($"StayExportFile - Error getting stays using params: export type invalid: {exportParams.ExportType}");
}
private async Task<bool> DeleteExportFileFromStoreAsync(StayExportFileDTO item)
{
Debug.Assert(!string.IsNullOrEmpty(item.ExportedPath));
try
{
IStore store = GetExportStore();
bool objectExist = await store.ObjectExists(item.ExportedPath);
if (!objectExist)
{
logger.LogWarning("StayExportService - File for item {id} not found in store! Skip delete!", item.Id);
return true;
}
await store.DeleteObject(item.ExportedPath);
logger.LogInformation("StayExportService - File for item {id} deleted from path {path}", item.Id, item.ExportedPath);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "StayExportService - Error deleting export file of item {id}", item.Id);
return false;
}
}
private IStore GetExportStore()
{
string? storageConnString = config.ExportStorageConnectionString;
if (string.IsNullOrEmpty(storageConnString))
{
throw new InvalidOperationException("No Stay Export Storage configured. Stays can't be exported");
}
return StorageFactory.FromConnectionString(storageConnString);
}
#endregion
}
@@ -1,26 +1,16 @@
using Microsoft.Extensions.Localization;
using MudBlazor; using MudBlazor;
using Somnia.GuestLog.Enums.Services; using Somnia.GuestLog.Enums.Services;
using Somnia.GuestLog.Enums.UI; using Somnia.GuestLog.Enums.UI;
using Somnia.GuestLog.Models.DTOs; using Somnia.GuestLog.Models.DTOs;
using Somnia.GuestLog.Resources; using IResult = Somnia.GuestLog.Models.DTOs.IResult;
namespace Somnia.GuestLog.Services; namespace Somnia.GuestLog.Services;
public class UserMessageService( public class UserMessageService(
IServiceProvider serviceProvider, ILocalizerService Localizer,
ISnackbar snackbar ISnackbar snackbar
) : IUserMessageService ) : IUserMessageService
{ {
private IStringLocalizer<LanguageLocale> _localizer
{
get
{
return serviceProvider.GetRequiredService<IStringLocalizer<LanguageLocale>>();
}
}
public void ShowMessage(string text, MessageKind kind, params object[] parameters) public void ShowMessage(string text, MessageKind kind, params object[] parameters)
{ {
string formatedMsg = GetFormatedMessage(text, parameters); string formatedMsg = GetFormatedMessage(text, parameters);
@@ -28,12 +18,12 @@ public class UserMessageService(
} }
public void ShowResult(Result resultDto) public void ShowResult(IResult resultDto)
{ {
ShowResultWithMessage(resultDto, message: null); ShowResultWithMessage(resultDto, message: null);
} }
public void ShowResultWithMessage(Result resultDto, string? message) public void ShowResultWithMessage(IResult resultDto, string? message)
{ {
string resultMsg = GetMsgForResultCode(resultDto.ResultCode); string resultMsg = GetMsgForResultCode(resultDto.ResultCode);
resultMsg = GetFormatedMessage(resultMsg, resultDto.Context); resultMsg = GetFormatedMessage(resultMsg, resultDto.Context);
@@ -61,7 +51,7 @@ public class UserMessageService(
private string GetMsgForResultCode(ResultCode resultCode) private string GetMsgForResultCode(ResultCode resultCode)
{ {
return _localizer[$"resultCode.{Enum.GetName(resultCode)}"]; return Localizer.GetEnumValue(resultCode);
} }
private static string GetFormatedMessage(string template, params object[]? parameters) private static string GetFormatedMessage(string template, params object[]? parameters)
@@ -7,9 +7,24 @@
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException> <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<PublishProfile>DefaultContainer</PublishProfile>
<ContainerImageFormat>OCI</ContainerImageFormat>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentStorage" />
<PackageReference Include="FluentStorage.AWS" />
<PackageReference Include="FluentValidation" /> <PackageReference Include="FluentValidation" />
<PackageReference Include="MudBlazor" /> <PackageReference Include="MudBlazor" />
<PackageReference Include="MudBlazor.Translations" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Enrichers.Process" />
<PackageReference Include="Serilog.Enrichers.Thread" />
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -8,4 +8,9 @@ public static class QueryExtensions
{ {
return condition ? source.Where(predicate) : source; return condition ? source.Where(predicate) : source;
} }
public static IQueryable<T> WithLimit<T>(this IQueryable<T> source, int limit)
{
return limit > 0 ? source.Take(limit) : source;
}
} }
+3 -1
View File
@@ -11,7 +11,9 @@ public static partial class RegexUtils
public static partial Regex RegexNieFormat(); public static partial Regex RegexNieFormat();
[GeneratedRegex("^[A-Z]{3}[0-9]{6}$")] [GeneratedRegex("^[A-Z]{3}[0-9]{6}$")]
public static partial Regex RegexGuestDocSuportNumber(); public static partial Regex RegexGuestDocSupportNumberDNI();
[GeneratedRegex("^E[0-9]{8}$")]
public static partial Regex RegexGuestDocSupportNumberNIE();
[GeneratedRegex(@"^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$")] [GeneratedRegex(@"^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$")]
public static partial Regex RegexPhoneNumber(); public static partial Regex RegexPhoneNumber();
@@ -22,14 +22,22 @@ public class GuestDocumentDTOValidator : ValueValidator<GuestDocumentDTO>
.Length(6, 32); .Length(6, 32);
RuleFor(x => x.IssueDate) RuleFor(x => x.IssueDate)
.Cascade(CascadeMode.Stop)
.NotEmpty() .NotEmpty()
.LessThanOrEqualTo(DateTime.Today); .When(x => x.Nationality != GuestConsts.CountryCode_ESP)
.LessThanOrEqualTo(x => DateTime.Today);
RuleFor(x => x.SupportNum) RuleFor(x => x.SupportNum)
.Cascade(CascadeMode.Stop) .Cascade(CascadeMode.Stop)
.NotEmpty() .NotEmpty()
.Matches(RegexUtils.RegexGuestDocSuportNumber()).WithMessage("Invalid Format. Expected 'AAA000000'.") .Matches(RegexUtils.RegexGuestDocSupportNumberDNI()).WithMessage("Invalid Format for DNI. Expected 'AAA000000'.")
.When(x => x.Nationality == GuestConsts.CountryCode_ESP); .When(x => x.DocumentType == GuestConsts.DocumentType_DNI);
RuleFor(x => x.SupportNum)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.Matches(RegexUtils.RegexGuestDocSupportNumberNIE()).WithMessage("Invalid Format for NIE. Expected 'E00000000'.")
.When(x => x.DocumentType == GuestConsts.DocumentType_NIE);
RuleFor(x => x.Name) RuleFor(x => x.Name)
.NotEmpty() .NotEmpty()
@@ -41,7 +49,7 @@ public class GuestDocumentDTOValidator : ValueValidator<GuestDocumentDTO>
RuleFor(x => x.BirthDate) RuleFor(x => x.BirthDate)
.NotEmpty() .NotEmpty()
.LessThanOrEqualTo(DateTime.Today); .LessThanOrEqualTo(x => DateTime.Today);
RuleFor(x => x.Nationality) RuleFor(x => x.Nationality)
.Cascade(CascadeMode.Stop) .Cascade(CascadeMode.Stop)
@@ -58,6 +66,12 @@ public class GuestDocumentDTOValidator : ValueValidator<GuestDocumentDTO>
.Matches(RegexUtils.RegexPhoneNumber()).WithMessage("Invalid format") .Matches(RegexUtils.RegexPhoneNumber()).WithMessage("Invalid format")
.When(x => !string.IsNullOrEmpty(x.PhoneNumber)); .When(x => !string.IsNullOrEmpty(x.PhoneNumber));
// Phone is required if mail is empty.
RuleFor(x => x.PhoneNumber)
.NotNull()
.When(x => string.IsNullOrEmpty(x.EmailAddress))
.WithMessage("At least 1 contact field (phone or email) is required!");
RuleFor(x => x.AddressCountry) RuleFor(x => x.AddressCountry)
.NotEmpty() .NotEmpty()
.Must(val => val != null && dataListService.IsValueInList(GuestConsts.DataList_CountryCodes, val)); .Must(val => val != null && dataListService.IsValueInList(GuestConsts.DataList_CountryCodes, val));
@@ -10,7 +10,7 @@ public class StayEntryDTOValidator : ValueValidator<StayEntryDTO>
{ {
RuleFor(e => e.DateIn) RuleFor(e => e.DateIn)
.NotEmpty() .NotEmpty()
.LessThanOrEqualTo(DateTime.Today.AddDays(1).AddSeconds(-1)); .LessThanOrEqualTo(x => DateTime.Today.AddDays(1).AddSeconds(-1));
RuleFor(e => e.DateOut) RuleFor(e => e.DateOut)
.NotEmpty() .NotEmpty()
@@ -0,0 +1,23 @@
using FluentValidation;
using Somnia.GuestLog.Enums.Models;
using Somnia.GuestLog.Models.DTOs;
namespace Somnia.GuestLog.Validators;
public class StayExportParamsDTOValidator : ValueValidator<StayExportParamsDTO>
{
public StayExportParamsDTOValidator()
{
RuleFor(e => e.ExportType)
.NotEmpty();
RuleFor(e => e.ExportPeriodStart)
.NotEmpty()
.When(e => e.ExportType == StayExportParamType.PeriodDateIn);
RuleFor(e => e.ExportPeriodEnd)
.NotEmpty()
.When(e => e.ExportType == StayExportParamType.PeriodDateIn);
}
}
@@ -4,5 +4,6 @@
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
} },
"RunDatabaseMigrationOnStartup": false
} }
+11 -6
View File
@@ -2,12 +2,8 @@
"ConnectionStrings": { "ConnectionStrings": {
"DbSomniaGuestLog": "Host=localhost;Port=5432;Username=postgres;Password=P@ssw0rd;Database=SomniaGuestLog" "DbSomniaGuestLog": "Host=localhost;Port=5432;Username=postgres;Password=P@ssw0rd;Database=SomniaGuestLog"
}, },
"Logging": { "RunDatabaseMigrationOnStartup": true,
"LogLevel": { "DataProtectionDir": "/SomniaGuestLog/dataprotection",
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*", "AllowedHosts": "*",
"AddressQuery": { "AddressQuery": {
"EnableAddressQuery": true, "EnableAddressQuery": true,
@@ -20,5 +16,14 @@
"ApiKey": "", "ApiKey": "",
"UserAgent": "Somnia-GuestLog-Dev", "UserAgent": "Somnia-GuestLog-Dev",
"ResultLang": "es" "ResultLang": "es"
},
"StayExport": {
"EnableStayExport": true,
"ExportStorageConnectionString": "disk://path=/SomniaGuestLog/StayExportFiles",
"UseSubdirectoryByFormat": true
},
"StayExportSpainParteViajeros": {
"InternetAvailable": true,
"CodigoEstablecimiento": ""
} }
} }
+19
View File
@@ -0,0 +1,19 @@
// Function to download files from Blazor pages
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
// Function to update Language cookie.
window.setBlazorCultureCookie = async (newCulture) => {
await window.cookieStore.set('.AspNetCore.Culture', newCulture);
console.log("Updated cookie to " + newCulture);
};