add payment email notifications
This commit is contained in:
parent
68a9e06eeb
commit
6a9504d6ff
@ -17,6 +17,8 @@ public record GetPaymentLinkCommand : IRequest<PaymentLinkDto>
|
||||
|
||||
public DateOnly PassangerBirthDate { get; set; }
|
||||
|
||||
public string? PassangerEmail { get; set; }
|
||||
|
||||
|
||||
public ICollection<TicketGroupPaymentTicketModel> Tickets { get; set; }
|
||||
|
||||
|
@ -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<PaymentLinkDto> 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 };
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<ProcessCallbackCommand>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ public sealed class TicketGroupPaymentViewModel
|
||||
|
||||
public DateOnly PassangerBirthDate { get; set; }
|
||||
|
||||
public string? PassangerEmail { get; set; }
|
||||
|
||||
|
||||
public ICollection<TicketPaymentViewModel> Tickets { get; set; }
|
||||
|
||||
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
||||
|
@ -25,8 +25,9 @@ public static class Configuration
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
||||
services.AddOptions<PersistenceConfigurationOptions>()
|
||||
.Bind(configuration);
|
||||
services.AddOptions<PersistenceConfigurationOptions>().Bind(
|
||||
configuration.GetSection(
|
||||
PersistenceConfigurationOptions.SectionName));
|
||||
|
||||
services.AddOptions<ApplicationConfigurationOptions>()
|
||||
.Bind(configuration);
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -14,24 +14,39 @@ public abstract class Currency : Enumeration<Currency>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -49,37 +49,43 @@ public class TicketGroupConfiguration : BaseConfiguration<TicketGroup>
|
||||
|
||||
|
||||
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);
|
||||
|
1339
src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.Designer.cs
generated
Normal file
1339
src/Persistence/PostgreSql/Migrations/20250529131846_Add_email_to_Ticket_Group.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_email_to_Ticket_Group : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "passanger_email",
|
||||
schema: "application",
|
||||
table: "ticket_groups",
|
||||
type: "varchar(256)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "passanger_email",
|
||||
schema: "application",
|
||||
table: "ticket_groups");
|
||||
}
|
||||
}
|
||||
}
|
@ -737,6 +737,10 @@ namespace Persistence.PostgreSql.Migrations
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("passanger_birth_date");
|
||||
|
||||
b.Property<string>("PassangerEmail")
|
||||
.HasColumnType("varchar(256)")
|
||||
.HasColumnName("passanger_email");
|
||||
|
||||
b.Property<string>("PassangerFirstName")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(32)")
|
||||
|
Loading…
Reference in New Issue
Block a user