add optional ticket group binding to account
All checks were successful
/ tests (push) Successful in 26s
/ build (push) Successful in 10m28s
/ build-docker (push) Successful in 5m49s

This commit is contained in:
cuqmbr 2025-05-30 16:40:28 +03:00
parent 4d1f6edc2e
commit 120963f3cc
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
18 changed files with 1597 additions and 31 deletions

View File

@ -1,6 +1,4 @@
using cuqmbr.TravelGuide.Application.Common.Authorization; using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization; using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Payments.LiqPay namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
@ -9,24 +7,8 @@ namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
public class GetPaymentLinkCommandAuthorizer : public class GetPaymentLinkCommandAuthorizer :
AbstractRequestAuthorizer<GetPaymentLinkCommand> AbstractRequestAuthorizer<GetPaymentLinkCommand>
{ {
private readonly SessionUserService _sessionUserService;
public GetPaymentLinkCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetPaymentLinkCommand request) public override void BuildPolicy(GetPaymentLinkCommand request)
{ {
UseRequirement(new MustBeAuthenticatedRequirement UseRequirement(new AllowAllRequirement());
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
} }
} }

View File

@ -25,6 +25,7 @@ public class GetPaymentLinkCommandHandler :
private readonly SessionTimeZoneService _sessionTimeZoneService; private readonly SessionTimeZoneService _sessionTimeZoneService;
private readonly SessionCultureService _sessionCultureService; private readonly SessionCultureService _sessionCultureService;
private readonly SessionUserService _sessionUserService;
public GetPaymentLinkCommandHandler( public GetPaymentLinkCommandHandler(
UnitOfWork unitOfWork, UnitOfWork unitOfWork,
@ -33,7 +34,8 @@ public class GetPaymentLinkCommandHandler :
IStringLocalizer localizer, IStringLocalizer localizer,
EmailSenderService emailSender, EmailSenderService emailSender,
SessionTimeZoneService SessionTimeZoneService, SessionTimeZoneService SessionTimeZoneService,
SessionCultureService sessionCultureService) SessionCultureService sessionCultureService,
SessionUserService sessionUserService)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_currencyConverterService = currencyConverterService; _currencyConverterService = currencyConverterService;
@ -42,6 +44,7 @@ public class GetPaymentLinkCommandHandler :
_emailSender = emailSender; _emailSender = emailSender;
_sessionTimeZoneService = SessionTimeZoneService; _sessionTimeZoneService = SessionTimeZoneService;
_sessionCultureService = sessionCultureService; _sessionCultureService = sessionCultureService;
_sessionUserService = sessionUserService;
} }
public async Task<PaymentLinkDto> Handle( public async Task<PaymentLinkDto> Handle(
@ -409,6 +412,10 @@ public class GetPaymentLinkCommandHandler :
.Items; .Items;
var account = await _unitOfWork.AccountRepository.GetOneAsync(
e => e.Guid == _sessionUserService.Guid, cancellationToken);
var travelTime = var travelTime =
ticketsDetails.OrderBy(td => td.order).Last().arrivalTime - ticketsDetails.OrderBy(td => td.order).Last().arrivalTime -
ticketsDetails.OrderBy(td => td.order).First().departureTime; ticketsDetails.OrderBy(td => td.order).First().departureTime;
@ -451,8 +458,8 @@ public class GetPaymentLinkCommandHandler :
Currency = detail.currency, Currency = detail.currency,
VehicleEnrollmentId = ve.Id VehicleEnrollmentId = ve.Id
}; };
}) }).ToArray(),
.ToArray() AccountId = account?.Id
}; };
entity = await _unitOfWork.TicketGroupRepository.AddOneAsync( entity = await _unitOfWork.TicketGroupRepository.AddOneAsync(

View File

@ -148,7 +148,7 @@ public class AddTicketGroupCommandHandler :
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
.GetPageAsync( .GetPageAsync(
e => vehicleEnrollmentGuids.Contains(e.Guid), e => vehicleEnrollmentGuids.Contains(e.Guid),
e => e.Vehicle, e => e.Vehicle.Company,
1, vehicleEnrollmentGuids.Count(), cancellationToken)) 1, vehicleEnrollmentGuids.Count(), cancellationToken))
.Items; .Items;
@ -416,7 +416,6 @@ public class AddTicketGroupCommandHandler :
1, vehicleEnrollmentGuids.Count(), cancellationToken)) 1, vehicleEnrollmentGuids.Count(), cancellationToken))
.Items; .Items;
var routeAddressGuids = var routeAddressGuids =
request.Tickets.Select(t => t.DepartureRouteAddressGuid).Concat( request.Tickets.Select(t => t.DepartureRouteAddressGuid).Concat(
request.Tickets.Select(t => t.ArrivalRouteAddressGuid)); request.Tickets.Select(t => t.ArrivalRouteAddressGuid));

View File

@ -85,6 +85,14 @@ public class GetTicketGroupQueryHandler :
1, vehicleIds.Count(), cancellationToken)) 1, vehicleIds.Count(), cancellationToken))
.Items; .Items;
var account = await _unitOfWork.AccountRepository.GetOneAsync(
a => a.Id == ticketGroup.AccountId, cancellationToken);
if (ticketGroup.AccountId != null)
{
ticketGroup.Account = account;
}
foreach (var ve in vehicleEnrollments) foreach (var ve in vehicleEnrollments)
{ {
ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId); ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId);

View File

@ -32,6 +32,8 @@ public record GetTicketGroupsPageQuery : IRequest<PaginatedList<TicketGroupDto>>
public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; } public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; }
public Guid? AccountGuid { get; set; }
// TODO: Add filtering parametetrs listed below. It is hard to // TODO: Add filtering parametetrs listed below. It is hard to
// be done because of pagination. // be done because of pagination.

View File

@ -76,6 +76,9 @@ public class GetTicketGroupsPageQueryHandler :
: true) && : true) &&
(request.TravelTimeLessThanOrEqualTo != null (request.TravelTimeLessThanOrEqualTo != null
? e.TravelTime <= request.TravelTimeLessThanOrEqualTo ? e.TravelTime <= request.TravelTimeLessThanOrEqualTo
: true) &&
(request.AccountGuid != null
? e.Account.Guid == request.AccountGuid
: true), : true),
e => e.Tickets, e => e.Tickets,
request.PageNumber, request.PageSize, cancellationToken); request.PageNumber, request.PageSize, cancellationToken);
@ -123,6 +126,22 @@ public class GetTicketGroupsPageQueryHandler :
1, vehicleIds.Count(), cancellationToken)) 1, vehicleIds.Count(), cancellationToken))
.Items; .Items;
var accountIds =
ticketGroups.Select(tg => tg.AccountId);
var accounts = (await _unitOfWork.AccountRepository
.GetPageAsync(
a => accountIds.Contains(a.Id),
1, accountIds.Count(), cancellationToken))
.Items;
foreach (var tg in ticketGroups)
{
if (tg.AccountId != null)
{
tg.Account = accounts.Single(a => a.Id == tg.AccountId);
}
}
foreach (var ve in vehicleEnrollments) foreach (var ve in vehicleEnrollments)
{ {
ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId); ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId);
@ -137,8 +156,8 @@ public class GetTicketGroupsPageQueryHandler :
} }
// TODO: Replace with AutoMapper resolvers // TODO: Replace with AutoMapper resolvers.
// Convert currency and apply session time zone // Convert currency and apply session time zone.
var convertTasks = new List<Task>(); var convertTasks = new List<Task>();
var processedRouteAddressDetailIds = new HashSet<long>(); var processedRouteAddressDetailIds = new HashSet<long>();

View File

@ -0,0 +1,21 @@
using cuqmbr.TravelGuide.Application.Common.Mappings;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.TicketGroups;
public sealed class TicketGroupAccountDto : IMapFrom<Account>
{
public Guid Uuid { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<Account, TicketGroupAccountDto>()
.ForMember(
d => d.Uuid,
opt => opt.MapFrom(s => s.Guid));
}
}

View File

@ -39,6 +39,7 @@ public sealed class TicketGroupDto : IMapFrom<TicketGroup>
public decimal Cost { get; set; } public decimal Cost { get; set; }
public TicketGroupAccountDto? Account { get; set; }
public ICollection<TicketGroupVehicleEnrollmentDto> Enrollments { get; set; } public ICollection<TicketGroupVehicleEnrollmentDto> Enrollments { get; set; }

View File

@ -19,4 +19,6 @@ public sealed class GetTicketGroupsPageFilterViewModel
public TimeSpan? TravelTimeGreaterThanOrEqualTo { get; set; } public TimeSpan? TravelTimeGreaterThanOrEqualTo { get; set; }
public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; } public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; }
public Guid? AccountUuid { get; set; }
} }

View File

@ -18,4 +18,6 @@ public sealed class Account : EntityBase
public Employee? Employee { get; set; } public Employee? Employee { get; set; }
public Company? Company { get; set; } public Company? Company { get; set; }
public ICollection<TicketGroup> TicketGroups { get; set; }
} }

View File

@ -24,4 +24,9 @@ public sealed class TicketGroup : EntityBase
public ICollection<Ticket> Tickets { get; set; } public ICollection<Ticket> Tickets { get; set; }
public long? AccountId { get; set; }
public Account? Account { get; set; }
} }

View File

@ -119,7 +119,8 @@ public class TicketGroupsController : ControllerBase
TravelTimeGreaterThanOrEqualTo = TravelTimeGreaterThanOrEqualTo =
filterQuery.TravelTimeGreaterThanOrEqualTo, filterQuery.TravelTimeGreaterThanOrEqualTo,
TravelTimeLessThanOrEqualTo = TravelTimeLessThanOrEqualTo =
filterQuery.TravelTimeLessThanOrEqualTo filterQuery.TravelTimeLessThanOrEqualTo,
AccountGuid = filterQuery.AccountUuid
}, },
cancellationToken); cancellationToken);
} }

View File

@ -54,7 +54,7 @@ public class CompanyConfiguration : BaseConfiguration<Company>
"fk_" + "fk_" +
$"{builder.Metadata.GetTableName()}_" + $"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}") $"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.ClientNoAction);
builder builder
.HasIndex(c => c.AccountId) .HasIndex(c => c.AccountId)

View File

@ -69,7 +69,7 @@ public class EmployeeConfiguration : BaseConfiguration<Employee>
"fk_" + "fk_" +
$"{builder.Metadata.GetTableName()}_" + $"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}") $"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.ClientNoAction);
builder builder
.HasIndex(e => e.CompanyId) .HasIndex(e => e.CompanyId)

View File

@ -89,5 +89,29 @@ public class TicketGroupConfiguration : BaseConfiguration<TicketGroup>
.HasColumnName("travel_time") .HasColumnName("travel_time")
.HasColumnType("interval") .HasColumnType("interval")
.IsRequired(true); .IsRequired(true);
builder
.Property(tg => tg.AccountId)
.HasColumnName("account_id")
.HasColumnType("bigint")
.IsRequired(false);
builder
.HasOne(tg => tg.Account)
.WithMany(a => a.TicketGroups)
.HasForeignKey(tg => tg.AccountId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.SetNull);
builder
.HasIndex(c => c.AccountId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}");
} }
} }

View File

@ -0,0 +1,114 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.PostgreSql.Migrations
{
/// <inheritdoc />
public partial class Add_navigation_from_Ticket_Group_to_Account : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_companies_account_id",
schema: "application",
table: "companies");
migrationBuilder.DropForeignKey(
name: "fk_employees_company_id",
schema: "application",
table: "employees");
migrationBuilder.AddColumn<long>(
name: "account_id",
schema: "application",
table: "ticket_groups",
type: "bigint",
nullable: true);
migrationBuilder.CreateIndex(
name: "ix_ticket_groups_account_id",
schema: "application",
table: "ticket_groups",
column: "account_id");
migrationBuilder.AddForeignKey(
name: "fk_companies_account_id",
schema: "application",
table: "companies",
column: "account_id",
principalSchema: "application",
principalTable: "accounts",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_employees_company_id",
schema: "application",
table: "employees",
column: "company_id",
principalSchema: "application",
principalTable: "companies",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_ticket_groups_account_id",
schema: "application",
table: "ticket_groups",
column: "account_id",
principalSchema: "application",
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_companies_account_id",
schema: "application",
table: "companies");
migrationBuilder.DropForeignKey(
name: "fk_employees_company_id",
schema: "application",
table: "employees");
migrationBuilder.DropForeignKey(
name: "fk_ticket_groups_account_id",
schema: "application",
table: "ticket_groups");
migrationBuilder.DropIndex(
name: "ix_ticket_groups_account_id",
schema: "application",
table: "ticket_groups");
migrationBuilder.DropColumn(
name: "account_id",
schema: "application",
table: "ticket_groups");
migrationBuilder.AddForeignKey(
name: "fk_companies_account_id",
schema: "application",
table: "companies",
column: "account_id",
principalSchema: "application",
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_employees_company_id",
schema: "application",
table: "employees",
column: "company_id",
principalSchema: "application",
principalTable: "companies",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -729,6 +729,10 @@ namespace Persistence.PostgreSql.Migrations
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "ticket_groups_id_sequence"); NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "ticket_groups_id_sequence");
b.Property<long?>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Guid>("Guid") b.Property<Guid>("Guid")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("uuid"); .HasColumnName("uuid");
@ -780,6 +784,9 @@ namespace Persistence.PostgreSql.Migrations
b.HasAlternateKey("Guid") b.HasAlternateKey("Guid")
.HasName("altk_ticket_groups_uuid"); .HasName("altk_ticket_groups_uuid");
b.HasIndex("AccountId")
.HasDatabaseName("ix_ticket_groups_account_id");
b.ToTable("ticket_groups", "application", t => b.ToTable("ticket_groups", "application", t =>
{ {
t.HasCheckConstraint("ck_ticket_groups_passanger_sex", "passanger_sex IN ('male', 'female')"); t.HasCheckConstraint("ck_ticket_groups_passanger_sex", "passanger_sex IN ('male', 'female')");
@ -1053,7 +1060,7 @@ namespace Persistence.PostgreSql.Migrations
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account") b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account")
.WithOne("Company") .WithOne("Company")
.HasForeignKey("cuqmbr.TravelGuide.Domain.Entities.Company", "AccountId") .HasForeignKey("cuqmbr.TravelGuide.Domain.Entities.Company", "AccountId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired() .IsRequired()
.HasConstraintName("fk_companies_account_id"); .HasConstraintName("fk_companies_account_id");
@ -1072,7 +1079,7 @@ namespace Persistence.PostgreSql.Migrations
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company") b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company")
.WithMany("Employees") .WithMany("Employees")
.HasForeignKey("CompanyId") .HasForeignKey("CompanyId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired() .IsRequired()
.HasConstraintName("fk_employees_company_id"); .HasConstraintName("fk_employees_company_id");
@ -1196,6 +1203,17 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("VehicleEnrollment"); b.Navigation("VehicleEnrollment");
}); });
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.TicketGroup", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Account", "Account")
.WithMany("TicketGroups")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_ticket_groups_account_id");
b.Navigation("Account");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
{ {
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company") b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company")
@ -1259,6 +1277,8 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("Employee"); b.Navigation("Employee");
b.Navigation("RefreshTokens"); b.Navigation("RefreshTokens");
b.Navigation("TicketGroups");
}); });
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>