diff --git a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommand.cs b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommand.cs index 98f3956..66f6ce8 100644 --- a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommand.cs +++ b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommand.cs @@ -17,6 +17,8 @@ public record GetPaymentLinkCommand : IRequest public DateOnly PassangerBirthDate { get; set; } + public string? PassangerEmail { get; set; } + public ICollection Tickets { get; set; } diff --git a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandHandler.cs b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandHandler.cs index d5ac855..48b6e00 100644 --- a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandHandler.cs +++ b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandHandler.cs @@ -21,16 +21,27 @@ public class GetPaymentLinkCommandHandler : private readonly IStringLocalizer _localizer; + private readonly EmailSenderService _emailSender; + + private readonly SessionTimeZoneService _sessionTimeZoneService; + private readonly SessionCultureService _sessionCultureService; + public GetPaymentLinkCommandHandler( UnitOfWork unitOfWork, CurrencyConverterService currencyConverterService, LiqPayPaymentService liqPayPaymentService, - IStringLocalizer localizer) + IStringLocalizer localizer, + EmailSenderService emailSender, + SessionTimeZoneService SessionTimeZoneService, + SessionCultureService sessionCultureService) { _unitOfWork = unitOfWork; _currencyConverterService = currencyConverterService; _liqPayPaymentService = liqPayPaymentService; _localizer = localizer; + _emailSender = emailSender; + _sessionTimeZoneService = SessionTimeZoneService; + _sessionCultureService = sessionCultureService; } public async Task Handle( @@ -336,7 +347,7 @@ public class GetPaymentLinkCommandHandler : var costToDeparture = verad - .TakeWhile(rad => rad.Id != departureRouteAddressId) + .TakeWhile(rad => rad.RouteAddressId != departureRouteAddressId) .Aggregate((decimal)0, (sum, next) => sum + next.CostToNextAddress); @@ -412,6 +423,7 @@ public class GetPaymentLinkCommandHandler : PurchaseTime = DateTimeOffset.UtcNow, Status = TicketStatus.Reserved, TravelTime = travelTime, + PassangerEmail = request.PassangerEmail, Tickets = request.Tickets.Select( t => { @@ -428,12 +440,6 @@ public class GetPaymentLinkCommandHandler : var detail = ticketsDetails .SingleOrDefault(td => td.order == t.Order); - var currency = Currency.UAH; - var cost = _currencyConverterService - .ConvertAsync( - detail.cost, detail.currency, currency, - cancellationToken).Result; - return new Ticket() { DepartureRouteAddressId = departureRouteAddress.Id, @@ -441,8 +447,8 @@ public class GetPaymentLinkCommandHandler : ArrivalRouteAddressId = arrivalRouteAddress.Id, ArrivalRouteAddress = arrivalRouteAddress, Order = t.Order, - Cost = cost, - Currency = currency, + Cost = detail.cost, + Currency = detail.currency, VehicleEnrollmentId = ve.Id }; }) @@ -456,7 +462,11 @@ public class GetPaymentLinkCommandHandler : _unitOfWork.Dispose(); - var amount = entity.Tickets.Sum(e => e.Cost); + var amount = entity.Tickets.Sum(e => + _currencyConverterService + .ConvertAsync( + e.Cost, e.Currency, Currency.UAH, + cancellationToken).Result); var guid = entity.Guid; var validity = TimeSpan.FromMinutes(10); var resultPath = request.ResultPath; @@ -465,9 +475,31 @@ public class GetPaymentLinkCommandHandler : var paymentLink = _liqPayPaymentService .GetPaymentLink( amount, Currency.UAH, guid.ToString(), validity, - _localizer["PaymentProcessing.TicketPaymentDescription"], + _localizer["PaymentProcessing.Ticket.PaymentDescription"], resultPath, callbackPath); + if (request.PassangerEmail != null) + { + var validUntil = DateTimeOffset.UtcNow + .Add(validity) + .ToOffset(_sessionTimeZoneService.TimeZone.BaseUtcOffset); + + var subject = + _localizer["PaymentProcessing.Ticket" + + ".Email.PaymentCreated.Subject"]; + + var body = String.Format( + _sessionCultureService.Culture, + _localizer["PaymentProcessing.Ticket" + + ".Email.PaymentCreated.Body"], + Currency.UAH.Round(amount), Currency.UAH.Name, + validUntil, paymentLink); + + await _emailSender.SendAsync( + new[] { request.PassangerEmail }, subject, + body, cancellationToken); + } + return new PaymentLinkDto() { PaymentLink = paymentLink }; } } diff --git a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandValidator.cs b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandValidator.cs index fa1f227..0662c2f 100644 --- a/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandValidator.cs +++ b/src/Application/Payments/LiqPay/TicketGroups/Commands/GetPaymentLink/GetPaymentLinkCommandValidator.cs @@ -60,6 +60,15 @@ public class GetPaymentLinkCommandValidator : cultureService.Culture, localizer["FluentValidation.GreaterThanOrEqualTo"], DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100)))); + + When(tg => tg.PassangerEmail != null, () => + { + RuleFor(v => v.PassangerEmail) + .NotEmpty() + .WithMessage(localizer["FluentValidation.NotEmpty"]) + .IsEmail() + .WithMessage(localizer["FluentValidation.IsEmail"]); + }); RuleFor(tg => tg.Tickets) .IsUnique(t => t.VehicleEnrollmentGuid) diff --git a/src/Application/Payments/LiqPay/TicketGroups/Commands/ProcessCallback/ProcessCallbackCommandHandler.cs b/src/Application/Payments/LiqPay/TicketGroups/Commands/ProcessCallback/ProcessCallbackCommandHandler.cs index ade9098..4064da6 100644 --- a/src/Application/Payments/LiqPay/TicketGroups/Commands/ProcessCallback/ProcessCallbackCommandHandler.cs +++ b/src/Application/Payments/LiqPay/TicketGroups/Commands/ProcessCallback/ProcessCallbackCommandHandler.cs @@ -5,6 +5,8 @@ using cuqmbr.TravelGuide.Application.Common.Exceptions; using System.Text; using Newtonsoft.Json; using cuqmbr.TravelGuide.Domain.Enums; +using Microsoft.Extensions.Localization; +using cuqmbr.TravelGuide.Domain.Entities; namespace cuqmbr.TravelGuide.Application.Payments.LiqPay .TicketGroups.Commands.ProcessCallback; @@ -13,20 +15,31 @@ public class ProcessCallbackCommandHandler : IRequestHandler { private readonly UnitOfWork _unitOfWork; + private readonly LiqPayPaymentService _liqPayPaymentService; + private readonly IStringLocalizer _localizer; + + private readonly EmailSenderService _emailSender; + public ProcessCallbackCommandHandler( UnitOfWork unitOfWork, - LiqPayPaymentService liqPayPaymentService) + LiqPayPaymentService liqPayPaymentService, + IStringLocalizer localizer, + EmailSenderService emailSender) { _unitOfWork = unitOfWork; _liqPayPaymentService = liqPayPaymentService; + _localizer = localizer; + _emailSender = emailSender; } public async Task Handle( ProcessCallbackCommand request, CancellationToken cancellationToken) { + // Validate signature. + var isSignatureValid = _liqPayPaymentService .IsValidSignature(request.Data, request.Signature); @@ -35,6 +48,9 @@ public class ProcessCallbackCommandHandler : throw new ForbiddenException(); } + + // Parse request data. + var dataBytes = Convert.FromBase64String(request.Data); var dataJson = Encoding.UTF8.GetString(dataBytes); @@ -42,9 +58,11 @@ public class ProcessCallbackCommandHandler : string status = data.status; + var ticketGroupGuid = Guid.Parse((string)data.order_id); var ticketGroup = await _unitOfWork.TicketGroupRepository - .GetOneAsync(e => e.Guid == ticketGroupGuid, cancellationToken); + .GetOneAsync(e => e.Guid == ticketGroupGuid, + e => e.Tickets, cancellationToken); if (ticketGroup == null || ticketGroup.Status == TicketStatus.Purchased) @@ -52,6 +70,9 @@ public class ProcessCallbackCommandHandler : throw new ForbiddenException(); } + + // Process callback status + if (status.Equals("error") || status.Equals("failure")) { await _unitOfWork.TicketGroupRepository @@ -59,12 +80,228 @@ public class ProcessCallbackCommandHandler : } else if (status.Equals("success")) { + // Update ticket status + ticketGroup.Status = TicketStatus.Purchased; await _unitOfWork.TicketGroupRepository .UpdateOneAsync(ticketGroup, cancellationToken); + + + // Hydrate ticket group + + var vehicleEnrollmentIds = + ticketGroup.Tickets.Select(t => t.VehicleEnrollmentId); + var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository + .GetPageAsync( + ve => vehicleEnrollmentIds.Contains(ve.Id), + ve => ve.Route.RouteAddresses, + 1, vehicleEnrollmentIds.Count(), cancellationToken)) + .Items; + + var routeAddressIds = vehicleEnrollments + .SelectMany(ve => ve.Route.RouteAddresses) + .Select(ra => ra.Id); + var routeAddressDetails = (await _unitOfWork.RouteAddressDetailRepository + .GetPageAsync( + rad => routeAddressIds.Contains(rad.RouteAddressId), + 1, routeAddressIds.Count(), cancellationToken)) + .Items; + + var addressIds = vehicleEnrollments + .SelectMany(ve => ve.Route.RouteAddresses) + .Select(ra => ra.AddressId); + var addresses = (await _unitOfWork.AddressRepository + .GetPageAsync( + a => addressIds.Contains(a.Id), + a => a.City.Region.Country, + 1, addressIds.Count(), cancellationToken)) + .Items; + + var vehicleIds = vehicleEnrollments + .Select(ve => ve.VehicleId); + var vehicles = (await _unitOfWork.VehicleRepository + .GetPageAsync( + v => vehicleIds.Contains(v.Id), + v => v.Company, + 1, vehicleIds.Count(), cancellationToken)) + .Items; + + foreach (var ve in vehicleEnrollments) + { + ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId); + + foreach (var ra in ve.Route.RouteAddresses) + { + ra.Address = addresses.Single(a => a.Id == ra.AddressId); + ra.Details = routeAddressDetails + .Where(rad => rad.RouteAddressId == ra.Id) + .ToArray(); + } + } + + foreach (var t in ticketGroup.Tickets) + { + t.VehicleEnrollment = vehicleEnrollments + .Single(ve => ve.Id == t.VehicleEnrollmentId); + } + + + // Send email + + if (ticketGroup.PassangerEmail != null) + { + var subject = + _localizer["PaymentProcessing.Ticket" + + ".Email.PaymentCompleted.Subject"]; + + var ticketDetails = GetTicketDetails(ticketGroup); + + var body = String.Format( + _localizer["PaymentProcessing.Ticket" + + ".Email.PaymentCompleted.Body"], + ticketDetails); + + await _emailSender.SendAsync( + new[] { ticketGroup.PassangerEmail }, subject, + body, cancellationToken); + } } + await _unitOfWork.SaveAsync(cancellationToken); _unitOfWork.Dispose(); } + + private string GetTicketDetails(TicketGroup ticketGroup) + { + var sb = new StringBuilder(); + + sb.AppendLine("General:"); + sb.AppendLine(); + sb.AppendLine($"Ticket uuid: {ticketGroup.Guid}"); + sb.AppendLine($"Purchase Time: {ticketGroup.PurchaseTime}"); + sb.AppendLine(); + + var departureRouteAddressId = + ticketGroup.Tickets.First().DepartureRouteAddressId; + var arrivalRouteAddressId = + ticketGroup.Tickets.Last().ArrivalRouteAddressId; + + var departureTime = + ticketGroup.Tickets.First() + .VehicleEnrollment.GetDepartureTime(departureRouteAddressId); + var arrivalTime = + ticketGroup.Tickets.Last() + .VehicleEnrollment.GetArrivalTime(arrivalRouteAddressId); + + var departureAddress = + ticketGroup.Tickets.First() + .VehicleEnrollment.Route.RouteAddresses + .Single(ra => ra.Id == departureRouteAddressId) + .Address; + var arrivalAddress = + ticketGroup.Tickets.Last() + .VehicleEnrollment.Route.RouteAddresses + .Single(ra => ra.Id == arrivalRouteAddressId) + .Address; + + var departureAddressName = + $"{departureAddress.City.Region.Country.Name}, " + + $"{departureAddress.City.Region.Name}, " + + $"{departureAddress.City.Name}, " + + $"{departureAddress.Name}"; + var arrivalAddressName = + $"{arrivalAddress.City.Region.Country.Name}, " + + $"{arrivalAddress.City.Region.Name}, " + + $"{arrivalAddress.City.Name}, " + + $"{arrivalAddress.Name}"; + + sb.AppendLine($"Departure: {departureAddressName} at {departureTime}."); + sb.AppendLine($"Arrival: {arrivalAddressName} at {arrivalTime}."); + sb.AppendLine(); + sb.AppendLine(); + + sb.AppendLine($"Passanger details:"); + sb.AppendLine(); + sb.AppendLine($"First Name: {ticketGroup.PassangerFirstName}."); + sb.AppendLine($"Last Name: {ticketGroup.PassangerLastName}."); + sb.AppendLine($"Patronymic: {ticketGroup.PassangerPatronymic}."); + sb.AppendLine($"Sex: {ticketGroup.PassangerSex}."); + sb.AppendLine($"Birth Date: {ticketGroup.PassangerBirthDate}."); + sb.AppendLine($"Email: {ticketGroup.PassangerEmail}."); + sb.AppendLine(); + sb.AppendLine(); + + + sb.AppendLine("Vehicle enrollments' details:"); + sb.AppendLine(); + + foreach (var t in ticketGroup.Tickets) + { + departureRouteAddressId = t.DepartureRouteAddressId; + arrivalRouteAddressId = t.ArrivalRouteAddressId; + + departureTime = + t.VehicleEnrollment.GetDepartureTime(departureRouteAddressId); + arrivalTime = + t.VehicleEnrollment.GetArrivalTime(arrivalRouteAddressId); + + departureAddress = + t.VehicleEnrollment.Route.RouteAddresses + .Single(ra => ra.Id == departureRouteAddressId) + .Address; + arrivalAddress = + t.VehicleEnrollment.Route.RouteAddresses + .Single(ra => ra.Id == arrivalRouteAddressId) + .Address; + + departureAddressName = + $"{departureAddress.City.Region.Country.Name}, " + + $"{departureAddress.City.Region.Name}, " + + $"{departureAddress.City.Name}, " + + $"{departureAddress.Name}"; + arrivalAddressName = + $"{arrivalAddress.City.Region.Country.Name}, " + + $"{arrivalAddress.City.Region.Name}, " + + $"{arrivalAddress.City.Name}, " + + $"{arrivalAddress.Name}"; + + var vehicle = t.VehicleEnrollment.Vehicle; + var company = vehicle.Company; + + sb.AppendLine($"Departure: {departureAddressName} at {departureTime}."); + sb.AppendLine($"Arrival: {arrivalAddressName} at {arrivalTime}."); + + if (vehicle is Bus) + { + sb.AppendLine($"Vehicle: Bus, {((Bus)vehicle).Model}, " + + $"{((Bus)vehicle).Number}."); + } + else if (vehicle is Aircraft) + { + sb.AppendLine($"Vehicle: Aircraft, {((Aircraft)vehicle).Model}, " + + $"{((Aircraft)vehicle).Number}."); + } + else if (vehicle is Train) + { + sb.AppendLine($"Vehicle: Train, {((Train)vehicle).Model}, " + + $"{((Train)vehicle).Number}."); + } + else + { + throw new NotImplementedException(); + } + + sb.AppendLine($"Company: {company.Name}, ({company.ContactEmail}, " + + $"{company.ContactPhoneNumber})."); + + var cost = t.Currency.Round( + t.VehicleEnrollment + .GetCost(departureRouteAddressId,arrivalRouteAddressId)); + sb.AppendLine($"Cost: {cost} {t.Currency.Name}"); + sb.AppendLine(); + } + + return sb.ToString(); + } } diff --git a/src/Application/Payments/LiqPay/TicketGroups/ViewModels/TicketGroupPaymentViewModel.cs b/src/Application/Payments/LiqPay/TicketGroups/ViewModels/TicketGroupPaymentViewModel.cs index 4d6d83b..0e4f447 100644 --- a/src/Application/Payments/LiqPay/TicketGroups/ViewModels/TicketGroupPaymentViewModel.cs +++ b/src/Application/Payments/LiqPay/TicketGroups/ViewModels/TicketGroupPaymentViewModel.cs @@ -13,6 +13,8 @@ public sealed class TicketGroupPaymentViewModel public DateOnly PassangerBirthDate { get; set; } + public string? PassangerEmail { get; set; } + public ICollection Tickets { get; set; } diff --git a/src/Application/Resources/Localization/en-US.json b/src/Application/Resources/Localization/en-US.json index 832562f..af06306 100644 --- a/src/Application/Resources/Localization/en-US.json +++ b/src/Application/Resources/Localization/en-US.json @@ -62,6 +62,18 @@ } }, "PaymentProcessing": { - "TicketPaymentDescription": "Ticket purchase." + "Ticket": { + "PaymentDescription": "Ticket purchase.", + "Email": { + "PaymentCreated": { + "Subject": "Ticket purchase payment link.", + "Body": "You have reserved a ticket. Payment amount is {0} {1} Payment link is valid until {2}.\n\nLink: {3}" + }, + "PaymentCompleted": { + "Subject": "Ticket purchase complete.", + "Body": "Payment is succeeded.\n\n\nTicket details:\n\n{0}" + } + } + } } } diff --git a/src/Application/VehicleEnrollmentSearch/Queries/SearchAll/SearchAllQueryHandler.cs b/src/Application/VehicleEnrollmentSearch/Queries/SearchAll/SearchAllQueryHandler.cs index ee286ee..02d83a3 100644 --- a/src/Application/VehicleEnrollmentSearch/Queries/SearchAll/SearchAllQueryHandler.cs +++ b/src/Application/VehicleEnrollmentSearch/Queries/SearchAll/SearchAllQueryHandler.cs @@ -357,16 +357,6 @@ public class SearchAllQueryHandler : tag = path.Select(e => e.Tag).Last(); - lastRouteAddressGuid = vehicleEnrollments - .Single(e => e.Id == tag.VehicleEnrollmentId) - .RouteAddressDetails - .Select(e => e.RouteAddress) - .OrderBy(e => e.Order) - .SkipWhile(e => e.Order != tag.RouteAddress.Order) - .Take(2) - .ElementAt(1) - .Guid; - costToNextAddress = await _currencyConverterService .ConvertAsync(tag.CostToNextAddress, tag.VehicleEnrollment.Currency, @@ -388,7 +378,7 @@ public class SearchAllQueryHandler : CostToNextAddress = 0, CurrentAddressStopTime = tag.CurrentAddressStopTime, Order = addressOrder, - RouteAddressUuid = lastRouteAddressGuid + RouteAddressUuid = tag.RouteAddress.Guid }); diff --git a/src/Configuration/Configuration/Configuration.cs b/src/Configuration/Configuration/Configuration.cs index d4b2ecc..7264d13 100644 --- a/src/Configuration/Configuration/Configuration.cs +++ b/src/Configuration/Configuration/Configuration.cs @@ -25,8 +25,9 @@ public static class Configuration .AddCommandLine(args) .Build(); - services.AddOptions() - .Bind(configuration); + services.AddOptions().Bind( + configuration.GetSection( + PersistenceConfigurationOptions.SectionName)); services.AddOptions() .Bind(configuration); diff --git a/src/Domain/Entities/TicketGroup.cs b/src/Domain/Entities/TicketGroup.cs index c40a2ae..b532b78 100644 --- a/src/Domain/Entities/TicketGroup.cs +++ b/src/Domain/Entities/TicketGroup.cs @@ -14,6 +14,8 @@ public sealed class TicketGroup : EntityBase public DateOnly PassangerBirthDate { get; set; } + public string? PassangerEmail { get; set; } + public DateTimeOffset PurchaseTime { get; set; } public TicketStatus Status { get; set; } diff --git a/src/Domain/Entities/VehicleEnrollment.cs b/src/Domain/Entities/VehicleEnrollment.cs index 7cfdc46..21470de 100644 --- a/src/Domain/Entities/VehicleEnrollment.cs +++ b/src/Domain/Entities/VehicleEnrollment.cs @@ -121,7 +121,7 @@ public class VehicleEnrollment : EntityBase .OrderBy(e => e.RouteAddress.Order); var departureRouteAddressDetail = orderedRouteAddressDetails - .Single(e => e.Id == DepartureRouteAddressId); + .Single(e => e.RouteAddressId == DepartureRouteAddressId); var timeInStops = TimeSpan.Zero; foreach (var routeAddressDetail in orderedRouteAddressDetails) @@ -159,8 +159,8 @@ public class VehicleEnrollment : EntityBase return RouteAddressDetails .OrderBy(e => e.RouteAddress.Order) - .SkipWhile(e => e.Id != DepartureRouteAddressId) - .TakeWhile(e => e.Id != ArrivalRouteAddressId) + .SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId) + .TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId) .Count() - 1; } @@ -180,8 +180,8 @@ public class VehicleEnrollment : EntityBase return RouteAddressDetails .OrderBy(e => e.RouteAddress.Order) - .SkipWhile(e => e.Id != DepartureRouteAddressId) - .TakeWhile(e => e.Id != ArrivalRouteAddressId) + .SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId) + .TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId) .Aggregate(TimeSpan.Zero, (sum, next) => sum += next.TimeToNextAddress); } @@ -202,8 +202,8 @@ public class VehicleEnrollment : EntityBase return RouteAddressDetails .OrderBy(e => e.RouteAddress.Order) - .SkipWhile(e => e.Id != DepartureRouteAddressId) - .TakeWhile(e => e.Id != ArrivalRouteAddressId) + .SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId) + .TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId) .Aggregate((decimal)0, (sum, next) => sum += next.CostToNextAddress); } diff --git a/src/Domain/Enums/Currency.cs b/src/Domain/Enums/Currency.cs index fb5f312..cf30549 100644 --- a/src/Domain/Enums/Currency.cs +++ b/src/Domain/Enums/Currency.cs @@ -14,24 +14,39 @@ public abstract class Currency : Enumeration protected Currency(int value, string name) : base(value, name) { } + protected virtual byte DecimalDigits { get; } = byte.MaxValue; + + public decimal Round(decimal amount) + { + return Math.Round(amount, DecimalDigits); + } + // When no currency is specified private sealed class DefaultCurrency : Currency { public DefaultCurrency() : base(Int32.MaxValue, "DEFAULT") { } + + protected override byte DecimalDigits => 2; } private sealed class USDCurrency : Currency { public USDCurrency() : base(840, "USD") { } + + protected override byte DecimalDigits => 2; } private sealed class EURCurrency : Currency { public EURCurrency() : base(978, "EUR") { } + + protected override byte DecimalDigits => 2; } private sealed class UAHCurrency : Currency { public UAHCurrency() : base(980, "UAH") { } + + protected override byte DecimalDigits => 2; } } diff --git a/src/HttpApi/Controllers/PaymentController.cs b/src/HttpApi/Controllers/PaymentController.cs index b7c2e70..04c28e8 100644 --- a/src/HttpApi/Controllers/PaymentController.cs +++ b/src/HttpApi/Controllers/PaymentController.cs @@ -48,6 +48,7 @@ public class PaymentController : ControllerBase PassangerPatronymic = viewModel.PassangerPatronymic, PassangerSex = Sex.FromName(viewModel.PassangerSex), PassangerBirthDate = viewModel.PassangerBirthDate, + PassangerEmail = viewModel.PassangerEmail, Tickets = viewModel.Tickets.Select(e => new TicketGroupPaymentTicketModel() { diff --git a/src/Persistence/PostgreSql/Configurations/TicketGroupConfiguration.cs b/src/Persistence/PostgreSql/Configurations/TicketGroupConfiguration.cs index 10e4a65..017bbd4 100644 --- a/src/Persistence/PostgreSql/Configurations/TicketGroupConfiguration.cs +++ b/src/Persistence/PostgreSql/Configurations/TicketGroupConfiguration.cs @@ -49,37 +49,43 @@ public class TicketGroupConfiguration : BaseConfiguration builder - .Property(a => a.PassangerFirstName) + .Property(tg => tg.PassangerFirstName) .HasColumnName("passanger_first_name") .HasColumnType("varchar(32)") .IsRequired(true); builder - .Property(a => a.PassangerLastName) + .Property(tg => tg.PassangerLastName) .HasColumnName("passanger_last_name") .HasColumnType("varchar(32)") .IsRequired(true); builder - .Property(a => a.PassangerPatronymic) + .Property(tg => tg.PassangerPatronymic) .HasColumnName("passanger_patronymic") .HasColumnType("varchar(32)") .IsRequired(true); builder - .Property(a => a.PassangerBirthDate) + .Property(tg => tg.PassangerBirthDate) .HasColumnName("passanger_birth_date") .HasColumnType("date") .IsRequired(true); builder - .Property(a => a.PurchaseTime) + .Property(tg => tg.PassangerEmail) + .HasColumnName("passanger_email") + .HasColumnType("varchar(256)") + .IsRequired(false); + + builder + .Property(tg => tg.PurchaseTime) .HasColumnName("purchase_time") .HasColumnType("timestamptz") .IsRequired(true); builder - .Property(a => a.TravelTime) + .Property(tg => tg.TravelTime) .HasColumnName("travel_time") .HasColumnType("interval") .IsRequired(true); diff --git a/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.Designer.cs b/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.Designer.cs new file mode 100644 index 0000000..9156d66 --- /dev/null +++ b/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.Designer.cs @@ -0,0 +1,1339 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using cuqmbr.TravelGuide.Persistence.PostgreSql; + +#nullable disable + +namespace Persistence.PostgreSql.Migrations +{ + [DbContext(typeof(PostgreSqlDbContext))] + [Migration("20250529131846_Add_email_to_Ticket_Group")] + partial class Add_email_to_Ticket_Group + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("application") + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.HasSequence("account_roles_id_sequence"); + + modelBuilder.HasSequence("accounts_id_sequence"); + + modelBuilder.HasSequence("addresses_id_sequence"); + + modelBuilder.HasSequence("cities_id_sequence"); + + modelBuilder.HasSequence("companies_id_sequence"); + + modelBuilder.HasSequence("countries_id_sequence"); + + modelBuilder.HasSequence("employee_documents_id_sequence"); + + modelBuilder.HasSequence("employees_id_sequence"); + + modelBuilder.HasSequence("refresh_tokens_id_sequence"); + + modelBuilder.HasSequence("regions_id_sequence"); + + modelBuilder.HasSequence("roles_id_sequence"); + + modelBuilder.HasSequence("route_address_details_id_sequence"); + + modelBuilder.HasSequence("route_addresses_id_sequence"); + + modelBuilder.HasSequence("routes_id_sequence"); + + modelBuilder.HasSequence("ticket_groups_id_sequence"); + + modelBuilder.HasSequence("tickets_id_sequence"); + + modelBuilder.HasSequence("vehicle_enrollment_employees_id_sequence"); + + modelBuilder.HasSequence("vehicle_enrollments_id_sequence"); + + modelBuilder.HasSequence("vehicles_id_sequence"); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.accounts_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "accounts_id_sequence"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(256)") + .HasColumnName("email"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("varchar(88)") + .HasColumnName("password_hash"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("varchar(24)") + .HasColumnName("password_salt"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_accounts"); + + b.HasAlternateKey("Guid") + .HasName("altk_accounts_uuid"); + + b.ToTable("accounts", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.AccountRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.account_roles_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "account_roles_id_sequence"); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("RoleId") + .HasColumnType("bigint") + .HasColumnName("role_id"); + + b.HasKey("Id") + .HasName("pk_account_roles"); + + b.HasAlternateKey("Guid") + .HasName("altk_account_roles_uuid"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_roles_account_id"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_account_roles_role_id"); + + b.ToTable("account_roles", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.addresses_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "addresses_id_sequence"); + + b.Property("CityId") + .HasColumnType("bigint") + .HasColumnName("city_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Latitude") + .HasColumnType("double precision"); + + b.Property("Longitude") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(128)") + .HasColumnName("name"); + + b.Property("VehicleType") + .IsRequired() + .HasColumnType("varchar(16)") + .HasColumnName("vehicle_type"); + + b.HasKey("Id") + .HasName("pk_addresses"); + + b.HasAlternateKey("Guid") + .HasName("altk_addresses_uuid"); + + b.HasIndex("CityId") + .HasDatabaseName("ix_addresses_city_id"); + + b.ToTable("addresses", "application", t => + { + t.HasCheckConstraint("ck_addresses_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.cities_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "cities_id_sequence"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.Property("RegionId") + .HasColumnType("bigint") + .HasColumnName("region_id"); + + b.HasKey("Id") + .HasName("pk_cities"); + + b.HasAlternateKey("Guid") + .HasName("altk_cities_uuid"); + + b.HasIndex("RegionId") + .HasDatabaseName("ix_cities_region_id"); + + b.ToTable("cities", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.companies_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "companies_id_sequence"); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("ContactEmail") + .IsRequired() + .HasColumnType("varchar(256)") + .HasColumnName("contact_email"); + + b.Property("ContactPhoneNumber") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("contact_phone_number"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("LegalAddress") + .IsRequired() + .HasColumnType("varchar(256)") + .HasColumnName("legal_address"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_companies"); + + b.HasAlternateKey("Guid") + .HasName("altk_companies_uuid"); + + b.HasIndex("AccountId") + .IsUnique() + .HasDatabaseName("ix_companies_account_id"); + + b.ToTable("companies", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.countries_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "countries_id_sequence"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_countries"); + + b.HasAlternateKey("Guid") + .HasName("altk_countries_uuid"); + + b.ToTable("countries", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.employees_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "employees_id_sequence"); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("BirthDate") + .HasColumnType("date") + .HasColumnName("birth_date"); + + b.Property("CompanyId") + .HasColumnType("bigint") + .HasColumnName("company_id"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("first_name"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("last_name"); + + b.Property("Patronymic") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("patronymic"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("sex"); + + b.HasKey("Id") + .HasName("pk_employees"); + + b.HasAlternateKey("Guid") + .HasName("altk_employees_uuid"); + + b.HasIndex("AccountId") + .IsUnique() + .HasDatabaseName("ix_employees_account_id"); + + b.HasIndex("CompanyId") + .HasDatabaseName("ix_employees_company_id"); + + b.ToTable("employees", "application", t => + { + t.HasCheckConstraint("ck_employees_sex", "sex IN ('male', 'female')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.employee_documents_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "employee_documents_id_sequence"); + + b.Property("DocumentType") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("document_type"); + + b.Property("EmployeeId") + .HasColumnType("bigint") + .HasColumnName("employee_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Information") + .IsRequired() + .HasColumnType("varchar(256)") + .HasColumnName("information"); + + b.HasKey("Id") + .HasName("pk_employee_documents"); + + b.HasAlternateKey("Guid") + .HasName("altk_employee_documents_uuid"); + + b.HasIndex("EmployeeId") + .HasDatabaseName("ix_employee_documents_employee_id"); + + b.ToTable("employee_documents", "application", t => + { + t.HasCheckConstraint("ck_employee_documents_document_type", "document_type IN ('passport')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.refresh_tokens_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "refresh_tokens_id_sequence"); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("CreationTime") + .HasColumnType("timestamptz") + .HasColumnName("creation_time"); + + b.Property("ExpirationTime") + .HasColumnType("timestamptz") + .HasColumnName("expiration_time"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("RevocationTime") + .HasColumnType("timestamptz") + .HasColumnName("revocation_time"); + + b.Property("Value") + .IsRequired() + .HasColumnType("varchar(24)") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_refresh_tokens"); + + b.HasAlternateKey("Guid") + .HasName("altk_refresh_tokens_uuid"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_refresh_tokens_account_id"); + + b.ToTable("refresh_tokens", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.regions_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "regions_id_sequence"); + + b.Property("CountryId") + .HasColumnType("bigint") + .HasColumnName("country_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_regions"); + + b.HasAlternateKey("Guid") + .HasName("altk_regions_uuid"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_regions_country_id"); + + b.ToTable("regions", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.roles_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "roles_id_sequence"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_roles"); + + b.HasAlternateKey("Guid") + .HasName("altk_roles_uuid"); + + b.ToTable("roles", "application", t => + { + t.HasCheckConstraint("ck_roles_name", "name IN ('administrator', 'user', 'company_owner', 'company_employee')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.routes_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "routes_id_sequence"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(64)") + .HasColumnName("name"); + + b.Property("VehicleType") + .IsRequired() + .HasColumnType("varchar(16)") + .HasColumnName("vehicle_type"); + + b.HasKey("Id") + .HasName("pk_routes"); + + b.HasAlternateKey("Guid") + .HasName("altk_routes_uuid"); + + b.ToTable("routes", "application", t => + { + t.HasCheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.route_addresses_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "route_addresses_id_sequence"); + + b.Property("AddressId") + .HasColumnType("bigint") + .HasColumnName("address_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Order") + .HasColumnType("smallint") + .HasColumnName("order"); + + b.Property("RouteId") + .HasColumnType("bigint") + .HasColumnName("route_id"); + + b.HasKey("Id") + .HasName("pk_route_addresses"); + + b.HasAlternateKey("Guid") + .HasName("altk_route_addresses_uuid"); + + b.HasAlternateKey("AddressId", "RouteId", "Order") + .HasName("altk_route_addresses_address_id_route_id_order"); + + b.HasIndex("AddressId") + .HasDatabaseName("ix_route_addresses_address_id"); + + b.HasIndex("RouteId") + .HasDatabaseName("ix_route_addresses_route_id"); + + b.ToTable("route_addresses", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.route_address_details_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "route_address_details_id_sequence"); + + b.Property("CostToNextAddress") + .HasColumnType("numeric(24,12)") + .HasColumnName("cost_to_next_address"); + + b.Property("CurrentAddressStopTime") + .HasColumnType("interval") + .HasColumnName("current_address_stop_time"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("RouteAddressId") + .HasColumnType("bigint") + .HasColumnName("route_address_id"); + + b.Property("TimeToNextAddress") + .HasColumnType("interval") + .HasColumnName("time_to_next_address"); + + b.Property("VehicleEnrollmentId") + .HasColumnType("bigint") + .HasColumnName("vehicle_enrollment_id"); + + b.HasKey("Id") + .HasName("pk_route_address_details"); + + b.HasAlternateKey("Guid") + .HasName("altk_route_address_details_uuid"); + + b.HasIndex("RouteAddressId") + .HasDatabaseName("ix_route_address_details_route_address_id"); + + b.HasIndex("VehicleEnrollmentId") + .HasDatabaseName("ix_route_address_details_vehicle_enrollment_id"); + + b.ToTable("route_address_details", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.tickets_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "tickets_id_sequence"); + + b.Property("ArrivalRouteAddressId") + .HasColumnType("bigint") + .HasColumnName("arrival_route_address_id"); + + b.Property("Cost") + .HasColumnType("numeric(24,12)") + .HasColumnName("cost"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("varchar(8)") + .HasColumnName("currency"); + + b.Property("DepartureRouteAddressId") + .HasColumnType("bigint") + .HasColumnName("departure_route_address_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("Order") + .HasColumnType("smallint") + .HasColumnName("order"); + + b.Property("TicketGroupId") + .HasColumnType("bigint") + .HasColumnName("ticket_group_id"); + + b.Property("VehicleEnrollmentId") + .HasColumnType("bigint") + .HasColumnName("vehicle_enrollment_id"); + + b.HasKey("Id") + .HasName("pk_tickets"); + + b.HasAlternateKey("Guid") + .HasName("altk_tickets_uuid"); + + b.HasIndex("ArrivalRouteAddressId"); + + b.HasIndex("DepartureRouteAddressId"); + + b.HasIndex("TicketGroupId") + .HasDatabaseName("ix_tickets_ticket_group_id"); + + b.HasIndex("VehicleEnrollmentId") + .HasDatabaseName("ix_tickets_vehicle_enrollment_id"); + + b.ToTable("tickets", "application", t => + { + t.HasCheckConstraint("ck_tickets_currency", "currency IN ('DEFAULT', 'USD', 'EUR', 'UAH')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.TicketGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.ticket_groups_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "ticket_groups_id_sequence"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("PassangerBirthDate") + .HasColumnType("date") + .HasColumnName("passanger_birth_date"); + + b.Property("PassangerEmail") + .HasColumnType("varchar(256)") + .HasColumnName("passanger_email"); + + b.Property("PassangerFirstName") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("passanger_first_name"); + + b.Property("PassangerLastName") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("passanger_last_name"); + + b.Property("PassangerPatronymic") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("passanger_patronymic"); + + b.Property("PassangerSex") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("passanger_sex"); + + b.Property("PurchaseTime") + .HasColumnType("timestamptz") + .HasColumnName("purchase_time"); + + b.Property("Status") + .IsRequired() + .HasColumnType("varchar(32)") + .HasColumnName("status"); + + b.Property("TravelTime") + .HasColumnType("interval") + .HasColumnName("travel_time"); + + b.HasKey("Id") + .HasName("pk_ticket_groups"); + + b.HasAlternateKey("Guid") + .HasName("altk_ticket_groups_uuid"); + + b.ToTable("ticket_groups", "application", t => + { + t.HasCheckConstraint("ck_ticket_groups_passanger_sex", "passanger_sex IN ('male', 'female')"); + + t.HasCheckConstraint("ck_ticket_groups_status", "status IN ('reserved', 'returned', 'purchased')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.vehicles_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "vehicles_id_sequence"); + + b.Property("CompanyId") + .HasColumnType("bigint") + .HasColumnName("company_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("VehicleType") + .IsRequired() + .HasColumnType("varchar(16)") + .HasColumnName("vehicle_type"); + + b.HasKey("Id") + .HasName("pk_vehicles"); + + b.HasAlternateKey("Guid") + .HasName("altk_vehicles_uuid"); + + b.HasIndex("CompanyId") + .HasDatabaseName("ix_vehicles_company_id"); + + b.ToTable("vehicles", "application", t => + { + t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + + b.HasDiscriminator("VehicleType"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.vehicle_enrollments_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "vehicle_enrollments_id_sequence"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("varchar(8)") + .HasColumnName("currency"); + + b.Property("DepartureTime") + .HasColumnType("timestamptz") + .HasColumnName("departure_time"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("RouteId") + .HasColumnType("bigint") + .HasColumnName("route_id"); + + b.Property("VehicleId") + .HasColumnType("bigint") + .HasColumnName("vehicle_id"); + + b.HasKey("Id") + .HasName("pk_vehicle_enrollments"); + + b.HasAlternateKey("Guid") + .HasName("altk_vehicle_enrollments_uuid"); + + b.HasIndex("RouteId") + .HasDatabaseName("ix_vehicle_enrollments_route_id"); + + b.HasIndex("VehicleId") + .HasDatabaseName("ix_vehicle_enrollments_vehicle_id"); + + b.ToTable("vehicle_enrollments", "application", t => + { + t.HasCheckConstraint("ck_vehicle_enrollments_currency", "currency IN ('DEFAULT', 'USD', 'EUR', 'UAH')"); + }); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollmentEmployee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id") + .HasDefaultValueSql("nextval('application.vehicle_enrollment_employees_id_sequence')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("Id"), "vehicle_enrollment_employees_id_sequence"); + + b.Property("EmployeeId") + .HasColumnType("bigint") + .HasColumnName("employee_id"); + + b.Property("Guid") + .HasColumnType("uuid") + .HasColumnName("uuid"); + + b.Property("VehicleEnrollmentId") + .HasColumnType("bigint") + .HasColumnName("vehicle_enrollment_id"); + + b.HasKey("Id") + .HasName("pk_vehicle_enrollment_employees"); + + b.HasAlternateKey("Guid") + .HasName("altk_vehicle_enrollment_employees_uuid"); + + b.HasIndex("EmployeeId") + .HasDatabaseName("ix_vehicle_enrollment_employees_employee_id"); + + b.HasIndex("VehicleEnrollmentId") + .HasDatabaseName("ix_vehicle_enrollment_employees_vehicle_enrollment_id"); + + b.ToTable("vehicle_enrollment_employees", "application"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Aircraft", b => + { + b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle"); + + b.Property("Capacity") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("smallint") + .HasColumnName("capacity"); + + b.Property("Model") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(64)") + .HasColumnName("model"); + + b.Property("Number") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(32)") + .HasColumnName("number"); + + b.ToTable(t => + { + t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + + b.HasDiscriminator().HasValue("aircraft"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Bus", b => + { + b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle"); + + b.Property("Capacity") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("smallint") + .HasColumnName("capacity"); + + b.Property("Model") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(64)") + .HasColumnName("model"); + + b.Property("Number") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(32)") + .HasColumnName("number"); + + b.ToTable(t => + { + t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + + b.HasDiscriminator().HasValue("bus"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Train", b => + { + b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle"); + + b.Property("Capacity") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("smallint") + .HasColumnName("capacity"); + + b.Property("Model") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(64)") + .HasColumnName("model"); + + b.Property("Number") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("varchar(32)") + .HasColumnName("number"); + + b.ToTable(t => + { + t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')"); + }); + + b.HasDiscriminator().HasValue("train"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.AccountRole", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account") + .WithMany("AccountRoles") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_roles_account_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Role", "Role") + .WithMany("AccountRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_roles_role_id"); + + b.Navigation("Account"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.City", "City") + .WithMany("Addresses") + .HasForeignKey("CityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_addresses_city_id"); + + b.Navigation("City"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Region", "Region") + .WithMany("Cities") + .HasForeignKey("RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_cities_region_id"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account") + .WithOne("Company") + .HasForeignKey("cuqmbr.TravelGuide.Domain.Entities.Company", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_companies_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account") + .WithOne("Employee") + .HasForeignKey("cuqmbr.TravelGuide.Domain.Entities.Employee", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_employees_account_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company") + .WithMany("Employees") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_employees_company_id"); + + b.Navigation("Account"); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Employee", "Employee") + .WithMany("Documents") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_employee_documents_employee_id"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RefreshToken", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account") + .WithMany("RefreshTokens") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_refresh_tokens_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country") + .WithMany("Regions") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_regions_country_id"); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Address", "Address") + .WithMany("AddressRoutes") + .HasForeignKey("AddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_route_addresses_address_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route") + .WithMany("RouteAddresses") + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_route_addresses_route_id"); + + b.Navigation("Address"); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "RouteAddress") + .WithMany("Details") + .HasForeignKey("RouteAddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_route_address_details_route_address_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment") + .WithMany("RouteAddressDetails") + .HasForeignKey("VehicleEnrollmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_route_address_details_vehicle_enrollment_id"); + + b.Navigation("RouteAddress"); + + b.Navigation("VehicleEnrollment"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Ticket", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "ArrivalRouteAddress") + .WithMany() + .HasForeignKey("ArrivalRouteAddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "DepartureRouteAddress") + .WithMany() + .HasForeignKey("DepartureRouteAddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.TicketGroup", "TicketGroup") + .WithMany("Tickets") + .HasForeignKey("TicketGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tickets_ticket_group_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment") + .WithMany("Tickets") + .HasForeignKey("VehicleEnrollmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tickets_vehicle_enrollment_id"); + + b.Navigation("ArrivalRouteAddress"); + + b.Navigation("DepartureRouteAddress"); + + b.Navigation("TicketGroup"); + + b.Navigation("VehicleEnrollment"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company") + .WithMany("Vehicles") + .HasForeignKey("CompanyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_vehicles_company_id"); + + b.Navigation("Company"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route") + .WithMany("VehicleEnrollments") + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_vehicle_enrollments_route_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Vehicle", "Vehicle") + .WithMany("Enrollments") + .HasForeignKey("VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_vehicle_enrollments_vehicle_id"); + + b.Navigation("Route"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollmentEmployee", b => + { + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Employee", "Employee") + .WithMany("VehicleEnrollmentEmployees") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_vehicle_enrollment_employees_employee_id"); + + b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment") + .WithMany("VehicleEnrollmentEmployees") + .HasForeignKey("VehicleEnrollmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_vehicle_enrollment_employees_vehicle_enrollment_id"); + + b.Navigation("Employee"); + + b.Navigation("VehicleEnrollment"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Account", b => + { + b.Navigation("AccountRoles"); + + b.Navigation("Company"); + + b.Navigation("Employee"); + + b.Navigation("RefreshTokens"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b => + { + b.Navigation("AddressRoutes"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b => + { + b.Navigation("Addresses"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b => + { + b.Navigation("Employees"); + + b.Navigation("Vehicles"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b => + { + b.Navigation("Regions"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b => + { + b.Navigation("Documents"); + + b.Navigation("VehicleEnrollmentEmployees"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Role", b => + { + b.Navigation("AccountRoles"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b => + { + b.Navigation("RouteAddresses"); + + b.Navigation("VehicleEnrollments"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b => + { + b.Navigation("Details"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.TicketGroup", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b => + { + b.Navigation("Enrollments"); + }); + + modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b => + { + b.Navigation("RouteAddressDetails"); + + b.Navigation("Tickets"); + + b.Navigation("VehicleEnrollmentEmployees"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.cs b/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.cs new file mode 100644 index 0000000..836bffb --- /dev/null +++ b/src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.PostgreSql.Migrations +{ + /// + public partial class Add_email_to_Ticket_Group : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "passanger_email", + schema: "application", + table: "ticket_groups", + type: "varchar(256)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "passanger_email", + schema: "application", + table: "ticket_groups"); + } + } +} diff --git a/src/Persistence/PostgreSql/Migrations/PostgreSqlDbContextModelSnapshot.cs b/src/Persistence/PostgreSql/Migrations/PostgreSqlDbContextModelSnapshot.cs index 93002e6..99901fb 100644 --- a/src/Persistence/PostgreSql/Migrations/PostgreSqlDbContextModelSnapshot.cs +++ b/src/Persistence/PostgreSql/Migrations/PostgreSqlDbContextModelSnapshot.cs @@ -737,6 +737,10 @@ namespace Persistence.PostgreSql.Migrations .HasColumnType("date") .HasColumnName("passanger_birth_date"); + b.Property("PassangerEmail") + .HasColumnType("varchar(256)") + .HasColumnName("passanger_email"); + b.Property("PassangerFirstName") .IsRequired() .HasColumnType("varchar(32)")