SHA256
feat StayExport - StayExportProvider SpainParteViejeros - Initial implementation
- Added initial implementation to export stays in "SpainParteViajeros" format. - Added required configurations.
This commit is contained in:
@@ -1,20 +1,399 @@
|
||||
using System.Diagnostics;
|
||||
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 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 readonly StayExportSpainParteViajerosConfig config = configOptions.Value;
|
||||
|
||||
public StayExportFormat GetFormat() => StayExportFormat.SpainParteViajeros;
|
||||
public string GetFilename(DateTime exportTime)
|
||||
=> $"{Enum.GetName(GetFormat())}_{exportTime.ToFileTimeUtc()}.{fileExtension}";
|
||||
|
||||
public Task<StayExportFileResult> ExportDataAsync(IEnumerable<StayEntryDTO> stayList, Stream dataStream)
|
||||
public async Task<StayExportFileResult> ExportDataAsync(IEnumerable<StayEntryDTO> stayList, Stream dataStream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (string.IsNullOrEmpty(config.CodigoEstablecimiento))
|
||||
{
|
||||
logger.LogError("StayExportProviderSpainParteViajeros - Config error: CodigoEstablecimiento is not configured!");
|
||||
return StayExportFileResult.ExportError;
|
||||
}
|
||||
|
||||
if (!stayList.Any())
|
||||
{
|
||||
return StayExportFileResult.NoData;
|
||||
}
|
||||
|
||||
ComunicacionValidator comunicacionValidator = new();
|
||||
|
||||
List<Comunicacion> comunicacionsList = [];
|
||||
foreach (StayEntryDTO stay in stayList)
|
||||
{
|
||||
(Comunicacion? comunicacion, string code) = await GetComunicacionFromStay(stay);
|
||||
if (comunicacion == null)
|
||||
{
|
||||
logger.LogWarning("StayExportProviderSpainParteViajeros - Stay (ID: {id}) can't be exported: Code: {code}", stay.Id, code);
|
||||
continue;
|
||||
}
|
||||
|
||||
var errors = comunicacionValidator.Validate(comunicacion);
|
||||
if (!errors.IsValid)
|
||||
{
|
||||
logger.LogWarning("StayExportProviderSpainParteViajeros - Stay (ID: {id}) is invalid: Errors: {errors}", stay.Id, errors.Errors);
|
||||
continue;
|
||||
}
|
||||
|
||||
comunicacionsList.Add(comunicacion);
|
||||
logger.LogInformation("StayExportProviderSpainParteViajeros - Stay (ID: {id}) Exported OK", stay.Id);
|
||||
}
|
||||
|
||||
Peticion peticion = new()
|
||||
{
|
||||
solicitud = new()
|
||||
{
|
||||
codigoEstablecimiento = config.CodigoEstablecimiento,
|
||||
comunicacion = comunicacionsList,
|
||||
}
|
||||
};
|
||||
|
||||
XmlSerializer serializer = new(typeof(Peticion));
|
||||
serializer.Serialize(dataStream, peticion);
|
||||
|
||||
return StayExportFileResult.OK;
|
||||
}
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
private async Task<(Comunicacion? comunicacion, string code)> 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,
|
||||
code: "NoCodigoMunicipio"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
code: "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 => "F",
|
||||
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))
|
||||
{
|
||||
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.codigoMunicipio)
|
||||
.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
|
||||
|
||||
[XmlRoot(ElementName = "peticion", Namespace = XmlNamespace_Peticion)]
|
||||
public class Peticion
|
||||
{
|
||||
public required Solicitud solicitud;
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "solicitud")]
|
||||
public class Solicitud
|
||||
{
|
||||
public required string codigoEstablecimiento;
|
||||
public required List<Comunicacion> comunicacion;
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "comunicacion")]
|
||||
public class Comunicacion
|
||||
{
|
||||
public required Contrato contrato;
|
||||
public required List<Persona> persona;
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "contrato")]
|
||||
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;
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "persona")]
|
||||
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;
|
||||
|
||||
public string? telefono;
|
||||
public string? correo;
|
||||
public required string parentesco;
|
||||
|
||||
// BLOQUE DIRECCION
|
||||
public required Direccion direccion;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user