feat: add "Popular routes" statistics

feat: add financial report generation

feat: add automation task: indirect route search
This commit is contained in:
cuqmbr 2022-12-09 11:54:14 +02:00
parent 168b1fdec3
commit 20def5de44
23 changed files with 1763 additions and 78 deletions

View File

@ -49,6 +49,14 @@ public class MapperInitializer : Profile
CreateMap<Ticket, TicketDto>().ReverseMap();
CreateMap<Ticket, CreateTicketDto>().ReverseMap();
CreateMap<Ticket, UpdateTicketDto>().ReverseMap();
CreateMap<Ticket, CreateInTicketGroupTicketDto>().ReverseMap();
CreateMap<Ticket, InTicketGroupTicketDto>().ReverseMap();
CreateMap<TicketGroup, TicketGroupDto>().ReverseMap();
CreateMap<TicketGroup, CreateTicketGroupDto>().ReverseMap();
CreateMap<TicketGroup, UpdateTicketGroupDto>().ReverseMap();
CreateMap<TicketGroup, CreateTicketGroupWithTicketsDto>().ReverseMap();
CreateMap<TicketGroup, TicketGroupWithTicketsDto>().ReverseMap();
CreateMap<Review, ReviewDto>().ReverseMap();
CreateMap<Review, CreateReviewDto>().ReverseMap();

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Server.Services;
namespace Server.Controllers;
[Route("api/[controller]")]
[ApiController]
public class AutomationController : ControllerBase
{
private readonly AutomationService _automationService;
public AutomationController(AutomationService automationService)
{
_automationService = automationService;
}
[HttpGet]
public async Task<IActionResult> GetRoute(int from, int to, DateTime date)
{
var result = await _automationService.GetRoute(from, to, date);
if (!result.isSucceed)
{
return result.actionResult;
}
return Ok(result.result);
}
}

View File

@ -21,10 +21,24 @@ public class ReportController : ControllerBase
if (!result.IsSucceed)
{
return BadRequest(result.message);
return BadRequest(result.actionResult);
}
return File(result.ticketPdf, "application/pdf",
$"ticket.pdf");
}
[HttpGet("report")]
public async Task<IActionResult> GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate)
{
var result = await _reportService.GetCompanyReport(companyId, fromDate, toDate);
if (!result.isSucceed)
{
return BadRequest(result.actionResult);
}
return File(result.reportPdf, "application/pdf",
$"report.pdf");
}
}

View File

@ -17,9 +17,18 @@ public class StatisticsController : ControllerBase
}
[HttpGet("routes")]
public async Task<IActionResult> GetPopularRoutes([FromQuery] int amount = 10)
public async Task<IActionResult> GetPopularRoutes([FromQuery] PopularRoutesParameters parameters)
{
return Ok();
var result = await _statisticsService.GetPopularRoutes(parameters);
if (!result.IsSucceed)
{
return BadRequest(result.actionResult);
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
return Ok(result.route);
}
[HttpGet("users")]
@ -30,7 +39,7 @@ public class StatisticsController : ControllerBase
if (!result.IsSucceed)
{
return BadRequest(result.message);
return BadRequest(result.actionResult);
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
@ -45,7 +54,7 @@ public class StatisticsController : ControllerBase
if (!result.IsSucceed)
{
return BadRequest(result.message);
return BadRequest(result.actionResult);
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
@ -60,7 +69,7 @@ public class StatisticsController : ControllerBase
if (!result.IsSucceed)
{
return BadRequest(result.message);
return BadRequest(result.actionResult);
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));

View File

@ -0,0 +1,133 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Server.Services;
using SharedModels.DataTransferObjects;
using SharedModels.QueryParameters.Objects;
namespace Server.Controllers;
[Route("api/ticketGroups")]
[ApiController]
public class TicketGroupController : ControllerBase
{
private readonly ITicketGroupManagementService _ticketGroupManagementService;
public TicketGroupController(ITicketGroupManagementService ticketGroupManagementService)
{
_ticketGroupManagementService = ticketGroupManagementService;
}
[HttpPost]
public async Task<IActionResult> AddTicketGroup(CreateTicketGroupDto ticketGroup)
{
var result = await _ticketGroupManagementService.AddTicketGroup(ticketGroup);
if (!result.isSucceed)
{
return result.actionResult;
}
return CreatedAtAction(nameof(GetTicketGroup), new {id = result.ticketGroup.Id}, result.ticketGroup);
}
[HttpPost("withTickets")]
public async Task<IActionResult> AddTicketGroupWithTickets(CreateTicketGroupWithTicketsDto ticketGroup)
{
var result = await _ticketGroupManagementService.AddTicketGroupWithTickets(ticketGroup);
if (!result.isSucceed)
{
return result.actionResult;
}
return CreatedAtAction(nameof(GetTicketGroup), new {id = result.ticketGroup.Id}, result.ticketGroup);
}
[HttpGet]
public async Task<IActionResult> GetTicketGroups([FromQuery] TicketGroupParameters parameters)
{
var result = await _ticketGroupManagementService.GetTicketGroups(parameters);
if (!result.isSucceed)
{
return result.actionResult;
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
return Ok(result.ticketGroups);
}
[HttpGet("withTickets")]
public async Task<IActionResult> GetTicketGroupsWithTickets([FromQuery] TicketGroupWithTicketsParameters parameters)
{
var result = await _ticketGroupManagementService.GetTicketGroupsWithTickets(parameters);
if (!result.isSucceed)
{
return result.actionResult;
}
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
return Ok(result.ticketGroups);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetTicketGroup(int id, [FromQuery] string? fields)
{
var result = await _ticketGroupManagementService.GetTicketGroup(id, fields);
if (!result.isSucceed)
{
return result.actionResult;
}
return Ok(result.ticketGroup);
}
[HttpGet("withTickets/{id}")]
public async Task<IActionResult> GetTicketGroupWithTickets(int id, [FromQuery] string? fields)
{
var result = await _ticketGroupManagementService.GetTicketGroupWithTickets(id, fields);
if (!result.isSucceed)
{
return result.actionResult;
}
return Ok(result.ticketGroup);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateTicketGroup(int id, UpdateTicketGroupDto ticketGroup)
{
if (id != ticketGroup.Id)
{
return BadRequest();
}
var result = await _ticketGroupManagementService.UpdateTicketGroup(ticketGroup);
if (!result.isSucceed)
{
return result.actionResult;
}
return Ok(result.ticketGroup);
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTicketGroup(int id)
{
var result = await _ticketGroupManagementService.DeleteTicketGroup(id);
if (!result.isSucceed)
{
return result.actionResult;
}
return NoContent();
}
}

View File

@ -99,6 +99,7 @@ builder.Services.AddScoped<IStateManagementService, StateManagementService>();
builder.Services.AddScoped<ICityManagementService, CityManagementService>();
builder.Services.AddScoped<IAddressManagementService, AddressManagementService>();
builder.Services.AddScoped<ITicketManagementService, TicketManagementService>();
builder.Services.AddScoped<ITicketGroupManagementService, TicketGroupManagementService>();
builder.Services.AddScoped<IReviewManagementService, ReviewManagementService>();
builder.Services.AddScoped<ICompanyManagementService, CompanyManagementService>();
builder.Services.AddScoped<IVehicleManagementService, VehicleManagementService>();
@ -113,6 +114,8 @@ builder.Services.AddScoped<IDataShaper<StateDto>, DataShaper<StateDto>>();
builder.Services.AddScoped<IDataShaper<CityDto>, DataShaper<CityDto>>();
builder.Services.AddScoped<IDataShaper<AddressDto>, DataShaper<AddressDto>>();
builder.Services.AddScoped<IDataShaper<TicketDto>, DataShaper<TicketDto>>();
builder.Services.AddScoped<IDataShaper<TicketGroupDto>, DataShaper<TicketGroupDto>>();
builder.Services.AddScoped<IDataShaper<TicketGroupWithTicketsDto>, DataShaper<TicketGroupWithTicketsDto>>();
builder.Services.AddScoped<IDataShaper<ReviewDto>, DataShaper<ReviewDto>>();
builder.Services.AddScoped<IDataShaper<CompanyDto>, DataShaper<CompanyDto>>();
builder.Services.AddScoped<IDataShaper<VehicleDto>, DataShaper<VehicleDto>>();
@ -124,6 +127,13 @@ builder.Services.AddScoped<IDataShaper<RouteAddressDto>, DataShaper<RouteAddress
builder.Services.AddScoped<IPager<ExpandoObject>, Pager<ExpandoObject>>();
builder.Services.AddScoped<AutomationService>();
builder.Services.AddScoped<IReportService, ReportService>();
builder.Services.AddScoped<IStatisticsService, StatisticsService>();
builder.Services.AddScoped<IDataShaper<UserDto>, DataShaper<UserDto>>();
builder.Services.AddScoped<IDataShaper<ExpandoObject>, DataShaper<ExpandoObject>>();
// Adding DB Context with PostgreSQL
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>

View File

@ -81,8 +81,12 @@ public class AddressManagementService : IAddressManagementService
return;
}
addresses = addresses.Where(a =>
a.Name.ToLower().Contains(search.ToLower()));
// TODO: Remove client evaluation
addresses = addresses.ToArray().Where(a =>
a.Name.ToLower().Contains(search.ToLower()) ||
a.GetFullName().ToLower().Contains(search.ToLower()))
.AsQueryable();
}
void FilterByCityId(ref IQueryable<Address> addresses,

View File

@ -0,0 +1,315 @@
using System.Dynamic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Server.Data;
using Server.Helpers;
using Server.Models;
namespace Server.Services;
public class AutomationService
{
private readonly ApplicationDbContext _dbContext;
private readonly ISortHelper<ExpandoObject> _sortHelper;
public AutomationService(ApplicationDbContext dbContext,
ISortHelper<ExpandoObject> sortHelper)
{
_dbContext = dbContext;
_sortHelper = sortHelper;
}
public async Task<(bool isSucceed, IActionResult? actionResult, List<ExpandoObject> result)> GetRoute(
int from, int to, DateTime date)
{
var dbEnrollments = await _dbContext.VehicleEnrollments
.Include(ve => ve.Tickets)
.Include(ve => ve.Vehicle)
.ThenInclude(v => v.Company)
.Include(ve => ve.RouteAddressDetails)
.Include(ve => ve.Route).ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.Address).ThenInclude(a => a.City)
.ThenInclude(c => c.State).ThenInclude(s => s.Country)
.Where(ve => ve.DepartureDateTimeUtc.Date >= date.Date &&
ve.DepartureDateTimeUtc.Date <= date.AddDays(3).Date)
.ToListAsync();
var toBeRemovedEnrollmentsIds = new List<int>();
var directEnrollments = new List<VehicleEnrollment>();
foreach (var e in dbEnrollments)
{
if (e.Route.RouteAddresses.Count(ra => ra.AddressId == from) == 0 ||
e.Route.RouteAddresses.Count(ra => ra.AddressId == to) == 0)
{
continue;
}
var fromOrder = e.Route.RouteAddresses.FirstOrDefault(rad =>
rad.AddressId == from)?.Order;
var toOrder = e.Route.RouteAddresses.FirstOrDefault(rad =>
rad.AddressId == to)?.Order;
if (fromOrder < toOrder)
{
directEnrollments.Add(e);
toBeRemovedEnrollmentsIds.Add(e.Id);
}
}
dbEnrollments.RemoveAll(e => toBeRemovedEnrollmentsIds.Any(id => id == e.Id));
toBeRemovedEnrollmentsIds.Clear();
foreach (var de in directEnrollments)
{
var routeAddresses = de.Route.RouteAddresses;
var fromOrder =
routeAddresses.First(ra => ra.AddressId == from).Order;
var toOrder =
routeAddresses.First(ra => ra.AddressId == to).Order;
directEnrollments[directEnrollments.IndexOf(de)].Route.RouteAddresses = routeAddresses
.OrderBy(ra => ra.Order)
.SkipWhile(ra => ra.Order < fromOrder)
.TakeWhile(ra => ra.Order <= toOrder)
.ToList();
directEnrollments[directEnrollments.IndexOf(de)]
.DepartureDateTimeUtc = GetDepartureTime(de);
}
var enrollmentsWithFrom = new List<VehicleEnrollment>();
foreach (var e in dbEnrollments)
{
if (e.Route.RouteAddresses.Count(ra => ra.AddressId == from) == 0)
{
continue;
}
if (e.Route.RouteAddresses.Any(ra => ra.AddressId == from))
{
enrollmentsWithFrom.Add(e);
toBeRemovedEnrollmentsIds.Add(e.Id);
}
}
// dbEnrollments.RemoveAll(e => ToBeRemovedEnrollmentsIds.Any(id => id == e.Id));
toBeRemovedEnrollmentsIds.Clear();
foreach (var ef in enrollmentsWithFrom)
{
var routeAddresses = ef.Route.RouteAddresses;
var fromOrder =
routeAddresses.First(ra => ra.AddressId == from).Order;
enrollmentsWithFrom[enrollmentsWithFrom.IndexOf(ef)].Route.RouteAddresses = routeAddresses
.OrderBy(ra => ra.Order)
.SkipWhile(ra => ra.Order < fromOrder).ToList();
}
var enrollmentsWithTo = new List<VehicleEnrollment>();
foreach (var e in dbEnrollments)
{
if (e.Route.RouteAddresses.Count(ra => ra.AddressId == to) == 0)
{
continue;
}
if (e.Route.RouteAddresses.Any(ra => ra.AddressId == to))
{
enrollmentsWithTo.Add(e);
toBeRemovedEnrollmentsIds.Add(e.Id);
}
}
// dbEnrollments.RemoveAll(e => ToBeRemovedEnrollmentsIds.Any(id => id == e.Id));
toBeRemovedEnrollmentsIds.Clear();
foreach (var et in enrollmentsWithTo)
{
var routeAddresses = et.Route.RouteAddresses;
var toOrder =
routeAddresses.First(ra => ra.AddressId == to).Order;
enrollmentsWithTo[enrollmentsWithTo.IndexOf(et)].Route.RouteAddresses = routeAddresses
.OrderBy(ra => ra.Order)
.TakeWhile(ra => ra.Order <= toOrder).ToList();
}
var oneTransferPath = new List<List<VehicleEnrollment>>();
foreach (var ef in enrollmentsWithFrom)
{
foreach (var et in enrollmentsWithTo)
{
var efRouteAddresses = ef.Route.RouteAddresses;
var etRouteAddresses = et.Route.RouteAddresses;
var intersectionAddressId = efRouteAddresses.IntersectBy(
etRouteAddresses.Select(x => x.AddressId),
x => x.AddressId).FirstOrDefault()?.AddressId;
var toOrder = efRouteAddresses.First(ra =>
ra.AddressId == intersectionAddressId).Order;
var fromOrder = etRouteAddresses.First(ra =>
ra.AddressId == intersectionAddressId).Order;
enrollmentsWithFrom[enrollmentsWithFrom.IndexOf(ef)].Route.RouteAddresses =
efRouteAddresses.OrderBy(ra => ra.Order)
.TakeWhile(ra => ra.Order <= toOrder).ToList();
enrollmentsWithTo[enrollmentsWithTo.IndexOf(et)].Route.RouteAddresses =
etRouteAddresses.OrderBy(ra => ra.Order)
.SkipWhile(ra => ra.Order < fromOrder).ToList();
var fromArrivalTime = GetArrivalTime(ef);
var toDepartureTime = GetDepartureTime(et);
var doesIntersect = intersectionAddressId != null;
if (doesIntersect && (toDepartureTime - fromArrivalTime) >= TimeSpan.FromMinutes(5))
{
oneTransferPath.Add(new List<VehicleEnrollment> {ef, et});
}
}
}
foreach (var directEnrollment in directEnrollments)
{
oneTransferPath.Add(new List<VehicleEnrollment> {directEnrollment});
}
var result = new List<ExpandoObject>();
int i = 1;
foreach (var path in oneTransferPath)
{
var shapedPath = new ExpandoObject();
var enrollmentGroup = new ExpandoObject();
int j = 1;
foreach (var vehicleEnrollment in path)
{
var enrollment = new ExpandoObject();
enrollment.TryAdd("id", vehicleEnrollment.Id);
enrollment.TryAdd("departureTime", GetDepartureTime(vehicleEnrollment));
enrollment.TryAdd("arrivalTime", GetArrivalTime(vehicleEnrollment));
enrollment.TryAdd("departureAddressName", vehicleEnrollment.Route.RouteAddresses.First().Address.Name);
enrollment.TryAdd("departureAddressFullName", vehicleEnrollment.Route.RouteAddresses.First().Address.GetFullName());
enrollment.TryAdd("departureAddressId", vehicleEnrollment.Route.RouteAddresses.First().AddressId);
enrollment.TryAdd("arrivalAddressName", vehicleEnrollment.Route.RouteAddresses.Last().Address.Name);
enrollment.TryAdd("arrivalAddressFullName", vehicleEnrollment.Route.RouteAddresses.Last().Address.GetFullName());
enrollment.TryAdd("arrivalAddressId", vehicleEnrollment.Route.RouteAddresses.Last().AddressId);
enrollment.TryAdd("order", j);
var vehicle = new ExpandoObject();
vehicle.TryAdd("type", vehicleEnrollment.Vehicle.Type);
vehicle.TryAdd("number", vehicleEnrollment.Vehicle.Number);
enrollment.TryAdd("vehicle", vehicle);
var company = new ExpandoObject();
company.TryAdd("name", vehicleEnrollment.Vehicle.Company.Name);
enrollment.TryAdd("company", company);
enrollmentGroup.TryAdd($"enrollment{j}", enrollment);
j++;
}
enrollmentGroup.TryAdd("totalDuration", GetTotalDuration(path));
enrollmentGroup.TryAdd("totalCost", GetTotalCost(path));
shapedPath.TryAdd($"enrollmentGroup{i}", enrollmentGroup);
result.Add(shapedPath);
i++;
}
// result = _sortHelper.ApplySort(result[].AsQueryable(), "+cost").ToList();
return (true, null, result);
DateTime GetDepartureTime(VehicleEnrollment enrollment)
{
var departureDateTimeUtc = enrollment.DepartureDateTimeUtc;
var departureRouteAddressId = enrollment.Route.RouteAddresses.First().Id;
foreach (var detail in enrollment.RouteAddressDetails)
{
if (detail.RouteAddressId == departureRouteAddressId)
{
departureDateTimeUtc += detail.WaitTimeSpan;
break;
}
departureDateTimeUtc += detail.TimeSpanToNextCity + detail.WaitTimeSpan;
}
return departureDateTimeUtc;
}
DateTime GetArrivalTime(VehicleEnrollment enrollment)
{
var arrivalDateTimeUtc = enrollment.DepartureDateTimeUtc;
var arrivalRouteAddressId = enrollment.Route.RouteAddresses.Last().Id;
foreach (var detail in enrollment.RouteAddressDetails)
{
if (detail.RouteAddressId == arrivalRouteAddressId)
{
break;
}
arrivalDateTimeUtc += detail.TimeSpanToNextCity + detail.WaitTimeSpan;
}
return arrivalDateTimeUtc;
}
TimeSpan GetTotalDuration(List<VehicleEnrollment> vehicleEnrollments)
{
return GetArrivalTime(vehicleEnrollments.Last()) -
GetDepartureTime(vehicleEnrollments.First());
}
double GetTotalCost(List<VehicleEnrollment> vehicleEnrollments)
{
double result = 0;
foreach (var enrollment in vehicleEnrollments)
{
foreach (var routeAddresses in enrollment.Route.RouteAddresses)
{
if (enrollment.Route.RouteAddresses.Last().Id == routeAddresses.Id)
{
break;
}
result += routeAddresses.RouteAddressDetails.First(rad =>
rad.VehicleEnrollmentId == enrollment.Id).CostToNextCity;
}
}
return result;
}
}
}

View File

@ -1,8 +1,12 @@
using Microsoft.AspNetCore.Mvc;
namespace Server.Services;
public interface IReportService
{
Task<(bool IsSucceed, string? message, Stream ticketPdf)> GetTicket(int ticketGroupId);
Task<(bool IsSucceed, IActionResult? actionResult, Stream ticketPdf)>
GetTicket(int ticketGroupId);
Task<(bool isSucceed, string? message, Stream reportPdf)> GetCompanyReport();
Task<(bool isSucceed, IActionResult? actionResult, Stream reportPdf)>
GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate);
}

View File

@ -1,4 +1,5 @@
using System.Dynamic;
using Microsoft.AspNetCore.Mvc;
using Server.Models;
using SharedModels.QueryParameters;
using SharedModels.QueryParameters.Statistics;
@ -7,18 +8,19 @@ namespace Server.Services;
public interface IStatisticsService
{
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> route)>
GetPopularRoutes(int amount);
Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> route,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularRoutes(PopularRoutesParameters parameters);
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users,
Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> users,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetEngagedUsers(EngagedUserParameters parameters);
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies,
Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> companies,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularCompanies(PopularCompanyParameters parameters);
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations,
Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> stations,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularStations(PopularAddressesParameters parameters);
}

View File

@ -0,0 +1,22 @@
using System.Dynamic;
using Microsoft.AspNetCore.Mvc;
using SharedModels.DataTransferObjects;
using SharedModels.QueryParameters;
using SharedModels.QueryParameters.Objects;
namespace Server.Services;
public interface ITicketGroupManagementService
{
Task<(bool isSucceed, IActionResult? actionResult, TicketGroupDto ticketGroup)> AddTicketGroup(CreateTicketGroupDto createTicketGroupDto);
Task<(bool isSucceed, IActionResult? actionResult, TicketGroupWithTicketsDto ticketGroup)> AddTicketGroupWithTickets(CreateTicketGroupWithTicketsDto createTicketGroupWithTicketsDto);
Task<(bool isSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> ticketGroups,
PagingMetadata<ExpandoObject> pagingMetadata)> GetTicketGroups(TicketGroupParameters parameters);
Task<(bool isSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> ticketGroups,
PagingMetadata<ExpandoObject> pagingMetadata)> GetTicketGroupsWithTickets(TicketGroupWithTicketsParameters parameters);
Task<(bool isSucceed, IActionResult? actionResult, ExpandoObject ticketGroup)> GetTicketGroup(int id, string? fields);
Task<(bool isSucceed, IActionResult? actionResult, ExpandoObject ticketGroup)> GetTicketGroupWithTickets(int id, string? fields);
Task<(bool isSucceed, IActionResult? actionResult, UpdateTicketGroupDto ticketGroup)> UpdateTicketGroup(UpdateTicketGroupDto updateTicketGroupDto);
Task<(bool isSucceed, IActionResult? actionResult)> DeleteTicketGroup(int id);
Task<bool> IsTicketGroupExist(int id);
}

View File

@ -1,11 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MigraDocCore.DocumentObjectModel;
using MigraDocCore.DocumentObjectModel.Tables;
using MigraDocCore.Rendering;
using PdfSharpCore;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using Server.Data;
using Server.Models;
using Route = Server.Models.Route;
namespace Server.Services;
@ -18,8 +21,13 @@ public class ReportService : IReportService
_dbContext = dbContext;
}
public async Task<(bool IsSucceed, string? message, Stream ticketPdf)> GetTicket(int ticketGroupId)
public async Task<(bool IsSucceed, IActionResult? actionResult, Stream ticketPdf)> GetTicket(int ticketGroupId)
{
if (!await DoesTicketGroupExist(ticketGroupId))
{
return (false, new NotFoundResult(), null!);
}
var dbTicketGroup = await _dbContext.TicketGroups
.Include(tg => tg.User)
.Include(tg => tg.Tickets)
@ -42,7 +50,7 @@ public class ReportService : IReportService
.ThenInclude(t => t.VehicleEnrollment)
.ThenInclude(ve => ve.RouteAddressDetails)
.FirstOrDefaultAsync(tg => tg.Id == ticketGroupId);
.FirstAsync(tg => tg.Id == ticketGroupId);
// Define document
@ -53,8 +61,7 @@ public class ReportService : IReportService
// Craft document
var pdfPage = document.AddPage();
pdfPage.Width = XUnit.FromCentimeter(21.0);
pdfPage.Height = XUnit.FromCentimeter(29.7);
pdfPage.Size = PageSize.A4;
var gfx = XGraphics.FromPdfPage(pdfPage);
// HACK²
@ -248,7 +255,8 @@ public class ReportService : IReportService
row.Cells[4].MergeRight = 3;
row.Cells[4].MergeDown = 1;
row.Cells[4].AddParagraph($"{departureAddress}");
row.Cells[4].Format.Font.Size = 8;
row.Cells[4].AddParagraph($"{departureAddress.GetFullName()}");
row.Cells[8].MergeRight = 1;
row.Cells[8].MergeDown = 1;
@ -281,7 +289,8 @@ public class ReportService : IReportService
row.Cells[4].MergeRight = 3;
row.Cells[4].MergeDown = 1;
row.Cells[4].AddParagraph($"{arrivalAddress}");
row.Cells[4].Format.Font.Size = 8;
row.Cells[4].AddParagraph($"{arrivalAddress.GetFullName()}");
row.Cells[8].MergeRight = 1;
row.Cells[8].MergeDown = 1;
@ -341,14 +350,16 @@ public class ReportService : IReportService
row.Cells[1].MergeRight = 3;
row.Cells[1].MergeDown = 1;
row.Cells[1].AddParagraph($"{departureAddress}");
row.Cells[1].Format.Font.Size = 8;
row.Cells[1].AddParagraph($"{departureAddress.GetFullName()}");
row.Cells[5].MergeDown = 1;
row.Cells[5].AddParagraph("Куди");
row.Cells[6].MergeRight = 3;
row.Cells[6].MergeDown = 1;
row.Cells[6].AddParagraph($"{arrivalAddress}");
row.Cells[6].Format.Font.Size = 8;
row.Cells[6].AddParagraph($"{arrivalAddress.GetFullName()}");
row.Cells[10].MergeDown = 1;
row.Cells[10].AddParagraph("Ціна");
@ -386,6 +397,7 @@ public class ReportService : IReportService
if (routeAddress.AddressId == ticket.FirstRouteAddressId)
{
departureDateTimeUtc += details.WaitTimeSpan;
break;
}
@ -414,6 +426,7 @@ public class ReportService : IReportService
}
arrivalDateTimeUtc += details.TimeSpanToNextCity;
arrivalDateTimeUtc += details.WaitTimeSpan;
}
return arrivalDateTimeUtc;
@ -465,10 +478,706 @@ public class ReportService : IReportService
return cost;
}
async Task<bool> DoesTicketGroupExist(int id)
{
return await _dbContext.TicketGroups.AnyAsync(tg => tg.Id == id);
}
}
public async Task<(bool isSucceed, string? message, Stream reportPdf)> GetCompanyReport()
public async Task<(bool isSucceed, IActionResult? actionResult, Stream reportPdf)>
GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate)
{
throw new NotImplementedException();
if (!await DoesCompanyExist(companyId))
{
return (false, new NotFoundResult(), null!);
}
var dbCompany = await _dbContext.Companies
.Include(c => c.Vehicles)
.ThenInclude(v => v.VehicleEnrollments)
.ThenInclude(ve => ve.Reviews)
.Include(c => c.Vehicles)
.ThenInclude(v => v.VehicleEnrollments)
.ThenInclude(ve => ve.Tickets)
.ThenInclude(t => t.TicketGroup)
.Include(c => c.Vehicles)
.ThenInclude(v => v.VehicleEnrollments)
.ThenInclude(ve => ve.Route)
.ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.Address)
.ThenInclude(a => a.City)
.ThenInclude(c => c.State)
.ThenInclude(s => s.Country)
.Include(c => c.Vehicles)
.ThenInclude(v => v.VehicleEnrollments)
.ThenInclude(ve => ve.RouteAddressDetails)
.Select(c => new {
Company = c,
VehicleEnrollments = c.Vehicles
.Select(v => new {
Vehicle = v,
VehicleEnrollments = v.VehicleEnrollments.Where(ve =>
ve.DepartureDateTimeUtc.Date >= fromDate &&
ve.DepartureDateTimeUtc <= toDate)
})
})
.Select(o => o.Company)
.FirstAsync(c => c.Id == companyId);
var routesEnrolled = new List<Route>();
foreach (var vehicle in dbCompany.Vehicles)
{
foreach (var vehicleEnrollment in vehicle.VehicleEnrollments)
{
var route = vehicleEnrollment.Route;
if (!routesEnrolled.Contains(route))
{
routesEnrolled.Add(route);
}
}
}
var vehicleEnrolled = dbCompany.Vehicles;
// Define document
var document = new PdfDocument();
document.Info.Title = "ticket";
document.Info.Author = "auto.bus";
var doc = new Document();
doc.DefaultPageSetup.LeftMargin = Unit.FromCentimeter(1);
doc.DefaultPageSetup.RightMargin = Unit.FromCentimeter(1);
doc.DefaultPageSetup.MirrorMargins = true;
DefineStyles(doc);
CreatePage(doc);
FillPage(doc);
var docRender = new DocumentRenderer(doc);
docRender.PrepareDocument();
var pageCount = docRender.FormattedDocument.PageCount;
for (int i = 0; i < pageCount; i++)
{
var pdfPage = document.AddPage();
pdfPage.Size = PageSize.A4;
using var gfx = XGraphics.FromPdfPage(pdfPage);
// HACK²
gfx.MUH = PdfFontEncoding.Unicode;
docRender.RenderPage(gfx, i + 1);
}
// Save document
var memoryStream = new MemoryStream();
document.Save(memoryStream);
return (true, null, memoryStream);
void DefineStyles(Document doc)
{
var styles = doc.Styles["Normal"];
styles.Font.Name = "Courier New Cyr";
styles.ParagraphFormat.LineSpacingRule = LineSpacingRule.OnePtFive;
styles = doc.Styles.AddStyle("Table", "Normal");
styles.Font.Size = 10;
styles.ParagraphFormat.SpaceBefore = 2.5;
styles.ParagraphFormat.SpaceAfter = 2.5;
styles.ParagraphFormat.LineSpacingRule = LineSpacingRule.Single;
}
void CreatePage(Document doc)
{
var section = doc.AddSection();
// Create footer
// var paragraph = section.Footers.Primary.AddParagraph();
// paragraph.Format.Alignment = ParagraphAlignment.Center;
// paragraph.AddPageField();
// section.Footers.
// section.PageSetup.DifferentFirstPageHeaderFooter = true;
var paragraph = section.AddParagraph("auto.bus");
paragraph.Format.Font.Size = 20;
paragraph.Format.Font.Bold = true;
paragraph.Format.LineSpacingRule = LineSpacingRule.OnePtFive;
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.SpaceBefore = Unit.FromCentimeter(5);
paragraph.Format.SpaceAfter = Unit.FromCentimeter(5);
paragraph = section.AddParagraph("Фінансовий звіт");
paragraph.Format.Font.Size = 20;
paragraph.Format.LineSpacingRule = LineSpacingRule.OnePtFive;
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph = section.AddParagraph($"{dbCompany.Name}");
paragraph.Format.Font.Size = 20;
paragraph.Format.LineSpacingRule = LineSpacingRule.OnePtFive;
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.SpaceAfter = Unit.FromCentimeter(5);
paragraph = section.AddParagraph($"{fromDate:dd.MM.yyyy} {toDate:dd.MM.yyyy}");
paragraph.Format.Font.Size = 20;
paragraph.Format.LineSpacingRule = LineSpacingRule.OnePtFive;
paragraph.Format.Alignment = ParagraphAlignment.Center;
Table table;
// Section and table for each enrolled route
for (int i = 0; i < routesEnrolled.Count; i++)
{
section = doc.AddSection();
// Add table and define columns
table = section.AddTable();
table.Style = "Table";
table.Borders.Color = Colors.Black;
table.Borders.Width = 0.25;
table.Borders.Left.Width = 0.5;
table.Borders.Right.Width = 0.5;
table.Rows.LeftIndent = 0;
table.Rows.Height = Unit.FromPoint(15);
table.Rows.VerticalAlignment = VerticalAlignment.Center;
for (int j = 0; j < 12; j++)
{
var column = table.AddColumn(Unit.FromCentimeter(1.583));
column.Format.Alignment = ParagraphAlignment.Center;
}
}
// Section for total
section = doc.AddSection();
}
void FillPage(Document doc)
{
Section section;
Paragraph paragraph;
int i = 1;
foreach (var route in routesEnrolled)
{
section = doc.Sections[i];
paragraph = section.Footers.Primary.AddParagraph();
paragraph.AddPageField();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var table = section.LastTable;
var row = table.AddRow();
row.Cells[0].MergeRight = 11;
row.Cells[0].AddParagraph($"МАРШРУТ №{route.Id}");
row.Cells[0].Format.Font.Bold = true;
row = table.AddRow();
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph("Відправлення");
row.Cells[2].MergeRight = 3;
row.Cells[2].AddParagraph($"{route.RouteAddresses.First().Address.GetFullName()}");
row.Cells[6].MergeRight = 1;
row.Cells[6].AddParagraph("Прибуття");
row.Cells[8].MergeRight = 3;
row.Cells[8].AddParagraph($"{route.RouteAddresses.Last().Address.GetFullName()}");
row = table.AddRow();
row.Cells[0].MergeRight = 11;
row.Cells[0].AddParagraph("КОРОТКО");
row.Cells[0].Format.Font.Bold = true;
row = table.AddRow();
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph("Поїздок проведено");
row.Cells[2].MergeRight = 1;
row.Cells[2].AddParagraph("Поїздок скасовано");
row.Cells[4].MergeRight = 1;
row.Cells[4].AddParagraph("Квитків продано");
row.Cells[6].MergeRight = 1;
row.Cells[6].AddParagraph("Неповних квитків");
row.Cells[8].MergeRight = 1;
row.Cells[8].AddParagraph("Грошей зароблено");
row.Cells[10].MergeRight = 1;
row.Cells[10].AddParagraph("Середній рейтинг");
row = table.AddRow();
row.Shading.Color = Color.FromRgbColor(25, Colors.Black);
row.Cells[0].MergeRight = 1;
row.Cells[0].AddParagraph($"{GetRouteEnrollmentCount(route)}");
row.Cells[2].MergeRight = 1;
row.Cells[2].AddParagraph($"{GetRouteCanceledEnrollmentCount(route)}");
row.Cells[4].MergeRight = 1;
row.Cells[4].AddParagraph($"{GetRouteSelledTicketCount(route)}");
row.Cells[6].MergeRight = 1;
row.Cells[6].AddParagraph($"{GetRouteIndirectTicketCount(route)}");
row.Cells[8].MergeRight = 1;
row.Cells[8].AddParagraph($"{GetRouteTotalRevenu(route)}");
row.Cells[10].MergeRight = 1;
var routeAverageRating = GetRouteAvarageRating(route);
row.Cells[10].AddParagraph($"{(routeAverageRating == 0 ? "-" : routeAverageRating)}");
row = table.AddRow();
row.Cells[0].MergeRight = 11;
row.Cells[0].AddParagraph("ДОКЛАДНО");
row.Cells[0].Format.Font.Bold = true;
row = table.AddRow();
row.Cells[0].MergeRight = 2;
row.Cells[0].AddParagraph("Ідентифікатор, тип та номер транспорту");
row.Cells[3].MergeRight = 1;
row.Cells[3].AddParagraph("Поїздок заплановано, проведена та скасовано");
row.Cells[5].MergeRight = 2;
row.Cells[5].AddParagraph("Квитків продано та повернено, з яких неповні");
row.Cells[8].MergeRight = 1;
row.Cells[8].AddParagraph("Грошей зароблено");
row.Cells[10].MergeRight = 1;
row.Cells[10].AddParagraph("Середній рейтинг");
var isFilled = true;
foreach (var vehicle in vehicleEnrolled)
{
if (route.VehicleEnrollments.Count(ve =>
ve.VehicleId == vehicle.Id) == 0)
{
continue;
}
row = table.AddRow();
row.Shading.Color = isFilled ? Color.FromRgbColor(25, Colors.Black) : Colors.White;
isFilled = !isFilled;
row.Cells[0].MergeRight = 2;
row.Cells[0].AddParagraph($"{vehicle.Id}, {vehicle.Type}, {vehicle.Number}");
var executedEnrollmentCount = GetVehicleEnrollmentCount(vehicle, route.Id);
var canceledEnrollmentCount = GetVehicleCanceledEnrollmentCount(vehicle, route.Id);
row.Cells[3].MergeRight = 1;
row.Cells[3].AddParagraph($"{executedEnrollmentCount + canceledEnrollmentCount}, " +
$"{executedEnrollmentCount}, {canceledEnrollmentCount}");
row.Cells[5].MergeRight = 2;
row.Cells[5].AddParagraph($"{GetVehicleSelledTicketCount(vehicle, route.Id)}, {GetVehicleReturnedTicketCount(vehicle, route.Id)}; " +
$"{GetVehicleIndirectTicketCount(vehicle, route.Id)}, {GetVehicleReturnedIndirectTicketCount(vehicle, route.Id)}");
row.Cells[8].MergeRight = 1;
row.Cells[8].AddParagraph($"{GetVehicleTotalRevenue(vehicle, route.Id)}");
row.Cells[10].MergeRight = 1;
var vehicleAverageRating = GetVehicleAverageRating(vehicle, route.Id);
row.Cells[10].AddParagraph($"{(vehicleAverageRating == 0 ? "-" : vehicleAverageRating)}");
}
i++;
}
section = doc.Sections[doc.Sections.Count - 1];
paragraph = section.AddParagraph("ПІДСУМОК");
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Size = 14;
paragraph.Format.Font.Bold = true;
section.AddParagraph();
var totalEnrollmentCount = GetTotalEnrollmentCount(dbCompany);
var totalCanceledEnrollmentCount = GetTotalCanceledEnrollmentCount(dbCompany);
var totalSoldTicketCount = GetTotalSoldTickets(dbCompany);
var totalReturnedTicketCount = GetTotalReturnedTicketCount(dbCompany);
var totalRevenue = GetTotalRevenu(dbCompany);
var totalAverageRating = GetTotalAverageRating(dbCompany);
paragraph = section.AddParagraph(
$"У період з {fromDate:dd.MM.yyyy} по {toDate:dd.MM.yyyy} " +
$"({(toDate - fromDate).Days} днів) компанією {dbCompany.Name} " +
$"було заплановано {(totalEnrollmentCount)} поїздки, " +
$"з яких {totalCanceledEnrollmentCount} було скасовано, " +
$"продано {totalSoldTicketCount} квитків, " +
$"з яких {totalReturnedTicketCount} було повернено. " +
$"За цей час було зароблено {totalRevenue} гривень. " +
$"Середній рейтинг по всім поїздкам: {totalAverageRating}");
paragraph.Format.Alignment = ParagraphAlignment.Justify;
paragraph.Format.Font.Size = 14;
int GetRouteEnrollmentCount(Route route)
{
return route.VehicleEnrollments.Count(ve => !ve.IsCanceled);
}
int GetVehicleEnrollmentCount(Vehicle vehicle, int routeId)
{
return vehicle.VehicleEnrollments.Count(ve =>
!ve.IsCanceled && ve.RouteId == routeId);
}
int GetTotalEnrollmentCount(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleEnrollmentCount(vehicle, enrollment.RouteId);
}
}
return result;
}
int GetRouteCanceledEnrollmentCount(Route route)
{
return route.VehicleEnrollments.Count(ve => ve.IsCanceled);
}
int GetVehicleCanceledEnrollmentCount(Vehicle vehicle, int routeId)
{
return vehicle.VehicleEnrollments.Count(ve =>
ve.IsCanceled && ve.RouteId == routeId);
}
int GetTotalCanceledEnrollmentCount(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleCanceledEnrollmentCount(vehicle, enrollment.RouteId);
}
}
return result;
}
int GetRouteSelledTicketCount(Route route)
{
int result = 0;
foreach (var enrollment in route.VehicleEnrollments)
{
result += enrollment.Tickets.Count(t => !t.IsReturned);
}
return result;
}
int GetVehicleSelledTicketCount(Vehicle vehicle, int routeId)
{
int result = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
result += enrollment.Tickets.Count(t => !t.IsReturned);
}
return result;
}
int GetTotalSoldTickets(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleSelledTicketCount(vehicle, enrollment.RouteId);
}
}
return result;
}
int GetVehicleReturnedTicketCount(Vehicle vehicle, int routeId)
{
int result = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
result += enrollment.Tickets.Count(t => t.IsReturned);
}
return result;
}
int GetTotalReturnedTicketCount(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleReturnedTicketCount(vehicle, enrollment.RouteId);
}
}
return result;
}
int GetRouteIndirectTicketCount(Route route)
{
int result = 0;
int departureAddressId = route.RouteAddresses.First().AddressId;
int arrivalAddressId = route.RouteAddresses.Last().AddressId;
foreach (var enrollment in route.VehicleEnrollments)
{
result += enrollment.Tickets.Count(t => !t.IsReturned &&
t.FirstRouteAddressId != departureAddressId ||
t.LastRouteAddressId != arrivalAddressId);
}
return result;
}
int GetVehicleIndirectTicketCount(Vehicle vehicle, int routeId)
{
int result = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
var departureRouteAddressId = enrollment.Route.RouteAddresses.First().AddressId;
var arrivalRouteAddressId = enrollment.Route.RouteAddresses.Last().AddressId;
result += enrollment.Tickets.Count(t => !t.IsReturned &&
t.FirstRouteAddressId != departureRouteAddressId ||
t.LastRouteAddressId != arrivalRouteAddressId);
}
return result;
}
int GetTotalIndirectTicketCount(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleIndirectTicketCount(vehicle, enrollment.RouteId);
}
}
return result;
}
int GetVehicleReturnedIndirectTicketCount(Vehicle vehicle, int routeId)
{
int result = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
var departureRouteAddressId = enrollment.Route.RouteAddresses.First().AddressId;
var arrivalRouteAddressId = enrollment.Route.RouteAddresses.Last().AddressId;
result += enrollment.Tickets.Count(t => t.IsReturned &&
(t.FirstRouteAddressId != departureRouteAddressId ||
t.LastRouteAddressId != arrivalRouteAddressId));
}
return result;
}
int GetTotalReturnedIndirectTicketCount(Company company)
{
int result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleReturnedIndirectTicketCount(vehicle, enrollment.RouteId);
}
}
return result;
}
double GetRouteTotalRevenu(Route route)
{
double result = 0;
foreach (var enrollment in route.VehicleEnrollments)
{
foreach (var ticket in enrollment.Tickets)
{
result += GetTicketCost(ticket);
}
}
return result;
}
double GetVehicleTotalRevenue(Vehicle vehicle, int routeId)
{
double result = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
foreach (var ticket in enrollment.Tickets)
{
result += GetTicketCost(ticket);
}
}
return result;
}
double GetTotalRevenu(Company company)
{
double result = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
result += GetVehicleTotalRevenue(vehicle, enrollment.RouteId);
}
}
return result;
}
double GetRouteAvarageRating(Route route)
{
double result = 0;
int reviewCount = 0;
foreach (var enrollment in route.VehicleEnrollments)
{
if (enrollment.Reviews.Count == 0)
{
continue;
}
foreach (var review in enrollment.Reviews)
{
result += review.Rating;
reviewCount += enrollment.Reviews.Count;
}
}
result /= reviewCount;
result = !Double.IsNaN(result) ? Math.Round(result, 3) : 0;
return result;
}
double GetVehicleAverageRating(Vehicle vehicle, int routeId)
{
double result = 0;
int reviewCount = 0;
foreach (var enrollment in vehicle.VehicleEnrollments
.Where(ve => ve.RouteId == routeId))
{
reviewCount += enrollment.Reviews.Count;
foreach (var review in enrollment.Reviews)
{
result += review.Rating;
}
}
result /= reviewCount;
result = !Double.IsNaN(result) ? Math.Round(result, 3) : 0;
return Math.Round(result, 3);
}
double GetTotalAverageRating(Company company)
{
double result = 0;
int enrollmentCount = 0;
foreach (var vehicle in company.Vehicles)
{
foreach (var enrollment in vehicle.VehicleEnrollments)
{
if (enrollment.Reviews.Count == 0)
{
continue;
}
result += GetVehicleAverageRating(vehicle, enrollment.RouteId);
enrollmentCount++;
}
}
result /= enrollmentCount;
result = !Double.IsNaN(result) ? Math.Round(result, 3) : 0;
return result;
}
// TODO: repeated function, yoinked from ticket generation
double GetTicketCost(Ticket ticket)
{
double cost = 0;
var routeAddresses = ticket.VehicleEnrollment.Route.RouteAddresses
.OrderBy(ra => ra.Order)
.SkipWhile(ra => ra.AddressId != ticket.FirstRouteAddressId)
.TakeWhile(ra => ra.AddressId != ticket.LastRouteAddressId)
.ToArray();
foreach (var routeAddress in routeAddresses)
{
var details = routeAddress.RouteAddressDetails
.First(rad => rad.RouteAddressId == routeAddress.Id);
cost += details.CostToNextCity;
}
return cost;
}
}
async Task<bool> DoesCompanyExist(int id)
{
return await _dbContext.Companies.AnyAsync(c => c.Id == id);
}
}
}

View File

@ -49,8 +49,6 @@ public class RouteManagementService : IRouteManagementService
{
var route = _mapper.Map<Route>(createRouteWithAddressesDto);
await _dbContext.Routes.AddAsync(route);
await _dbContext.SaveChangesAsync();
@ -78,9 +76,9 @@ public class RouteManagementService : IRouteManagementService
{
shapedData = _routeSortHelper.ApplySort(shapedData, parameters.Sort);
}
catch (Exception e)
catch (Exception)
{
return (false, new BadRequestObjectResult("Invalid sorting string"), null, null)!;
return (false, new BadRequestObjectResult("Invalid sorting string"), null!, null!);
}
var pagingMetadata = _pager.ApplyPaging(ref shapedData, parameters.PageNumber,
@ -133,7 +131,7 @@ public class RouteManagementService : IRouteManagementService
{
shapedData = _routeSortHelper.ApplySort(shapedData, parameters.Sort);
}
catch (Exception e)
catch (Exception)
{
return (false, new BadRequestObjectResult("Invalid sorting string"), null, null)!;
}

View File

@ -1,9 +1,9 @@
using System.Dynamic;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Server.Data;
using Server.Helpers;
using Server.Models;
using SharedModels.DataTransferObjects;
using SharedModels.QueryParameters;
using SharedModels.QueryParameters.Statistics;
@ -17,11 +17,14 @@ public class StatisticsService : IStatisticsService
private readonly IDataShaper<UserDto> _userDataShaper;
private readonly IDataShaper<CompanyDto> _companyDataShaper;
private readonly IDataShaper<AddressDto> _addressDataShaper;
private readonly IDataShaper<ExpandoObject> _expandoDataShaper;
private readonly IPager<ExpandoObject> _pager;
private readonly ISortHelper<ExpandoObject> _sortHelper;
public StatisticsService(ApplicationDbContext dbContext, IMapper mapper,
IDataShaper<UserDto> userDataShaper, IDataShaper<CompanyDto> companyDataShaper,
IDataShaper<AddressDto> addressDataShaper, IPager<ExpandoObject> pager)
IDataShaper<AddressDto> addressDataShaper, IPager<ExpandoObject> pager,
IDataShaper<ExpandoObject> expandoDataShaper, ISortHelper<ExpandoObject> sortHelper)
{
_dbContext = dbContext;
_mapper = mapper;
@ -29,25 +32,131 @@ public class StatisticsService : IStatisticsService
_companyDataShaper = companyDataShaper;
_addressDataShaper = addressDataShaper;
_pager = pager;
_expandoDataShaper = expandoDataShaper;
_sortHelper = sortHelper;
}
// Popularity is measured in number of purchased tickets
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> route)>
GetPopularRoutes(int amount)
public async Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> route,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularRoutes(PopularRoutesParameters parameters)
{
throw new NotImplementedException();
parameters.Days ??= parameters.DefaultDays;
var fromDateUtc = DateTime.UtcNow.Date - TimeSpan.FromDays((double) parameters.Days);
var dbTicketGroupsArray = await _dbContext.TicketGroups
.Include(tg => tg.Tickets)
.Where(tg => tg.Tickets.First().PurchaseDateTimeUtc >= fromDateUtc)
.ToArrayAsync();
var depArrCombCountDict = new Dictionary<(int, int), int>();
foreach (var tg in dbTicketGroupsArray)
{
// TODO: implement ticket ordering
var departureAddress = tg.Tickets.OrderBy(t => t.Id).First().FirstRouteAddressId;
var arrivalAddress = tg.Tickets.OrderBy(t => t.Id).Last().LastRouteAddressId;
if (!depArrCombCountDict.ContainsKey((departureAddress, arrivalAddress)))
{
depArrCombCountDict.Add((departureAddress, arrivalAddress), 1);
}
else
{
depArrCombCountDict[(departureAddress, arrivalAddress)] += 1;
}
}
depArrCombCountDict = depArrCombCountDict
.OrderByDescending(a => a.Value)
.Take(parameters.Amount)
.ToDictionary(x => x.Key, x => x.Value);
var addressIds = new List<int>();
foreach (var depArrAddressIds in depArrCombCountDict.Keys)
{
var (departureAddressId, arrivalAddressId) = depArrAddressIds;
if (!addressIds.Contains(departureAddressId))
{
addressIds.Add(departureAddressId);
}
if (!addressIds.Contains(arrivalAddressId))
{
addressIds.Add(arrivalAddressId);
}
}
var dbAddressArray = await _dbContext.Addresses
.Include(a => a.City)
.ThenInclude(c => c.State)
.ThenInclude(s => s.Country)
.Where(a => addressIds.Contains(a.Id))
.ToArrayAsync();
var derArrCount = new List<ExpandoObject>();
foreach (var depArrCount in depArrCombCountDict)
{
var obj = new ExpandoObject();
var departureAddressId = depArrCount.Key.Item1;
var departureAddress = dbAddressArray
.First(a => a.Id == departureAddressId);
var arrivalAddressId = depArrCount.Key.Item2;
var arrivalAddress = dbAddressArray
.First(a => a.Id == arrivalAddressId);
var count = depArrCount.Value;
var fields = parameters.Fields!.Split(',');
if (fields.Any(f => f.ToLower() == "departureAddressId".ToLower()))
obj.TryAdd("departureAddressId", departureAddressId);
if (fields.Any(f => f.ToLower() == "departureAddress".ToLower()))
obj.TryAdd("departureAddress", departureAddress.GetFullName());
if (fields.Any(f => f.ToLower() == "arrivalAddressId".ToLower()))
obj.TryAdd("arrivalAddressId", arrivalAddressId);
if (fields.Any(f => f.ToLower() == "arrivalAddress".ToLower()))
obj.TryAdd("arrivalAddress", arrivalAddress.GetFullName());
if (fields.Any(f => f.ToLower() == "count".ToLower()))
obj.TryAdd("count", count);
derArrCount.Add(obj);
}
var n = derArrCount.Count;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if ((int) (derArrCount[j] as IDictionary<string, object>)["count"] > (int) (derArrCount[j + 1] as IDictionary<string, object>)["count"])
{
// swap temp and arr[i]
(derArrCount[j], derArrCount[j + 1]) = (derArrCount[j + 1], derArrCount[j]);
(derArrCount[j], derArrCount[j + 1]) = (derArrCount[j + 1], derArrCount[j]);
}
}
}
var result = derArrCount.AsQueryable();
var pagingMetadata = _pager.ApplyPaging(ref result,
parameters.PageNumber, parameters.PageSize);
return (true, null, result.AsEnumerable(), pagingMetadata);
}
// Engagement is measured in number of purchases made in past N days
// One purchase contains one (direct route) or more (route with transfers) tickets
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users,
public async Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> users,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetEngagedUsers(EngagedUserParameters parameters)
{
var fromDateUtc =
DateTime.UtcNow - TimeSpan.FromDays(parameters.Days ?? parameters.DefaultDays);
parameters.Days ??= parameters.DefaultDays;
var fromDateUtc = DateTime.UtcNow.Date - TimeSpan.FromDays((double) parameters.Days);
var resultObjects = _dbContext.Users
var dbUsers = _dbContext.Users
.Include(u => u.TicketGroups)
.ThenInclude(tg => tg.Tickets)
.Select(u => new
@ -57,10 +166,9 @@ public class StatisticsService : IStatisticsService
tg.Tickets.First().PurchaseDateTimeUtc >= fromDateUtc)
})
.OrderByDescending(o => o.TicketGroups.Count())
.Take(parameters.Amount);
.Take(parameters.Amount)
.Select(i => i.User);
var dbUsers = resultObjects.Select(i => i.User);
var userDtos = _mapper.ProjectTo<UserDto>(dbUsers).ToArray();
var shapedDataArray = _userDataShaper
.ShapeData(userDtos, parameters.Fields ?? parameters.DefaultFields)
@ -86,7 +194,7 @@ public class StatisticsService : IStatisticsService
}
// Popularity is measured in average rating of all VehicleEnrollments of a company
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies,
public async Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> companies,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularCompanies(PopularCompanyParameters parameters)
{
@ -105,28 +213,22 @@ public class StatisticsService : IStatisticsService
for (int i = 0; i < dbCompaniesArray.Length; i++)
{
double tempC = 0;
double result = 0;
int reviewCount = 0;
foreach (var v in dbCompaniesArray[i].Vehicles)
{
double tempV = 0;
foreach (var ve in v.VehicleEnrollments)
{
double tempVE = 0;
foreach (var r in ve.Reviews)
{
tempVE += r.Rating;
result += r.Rating;
reviewCount++;
}
tempV += tempVE / ve.Reviews.Count;
}
tempC += tempV / v.VehicleEnrollments.Count;
}
companiesAvgRatings[i] = tempC / dbCompaniesArray[i].Vehicles.Count;
companiesAvgRatings[i] = reviewCount != 0 ? Math.Round(result / reviewCount, 5) : 0;
}
// Sort companiesAvgRatings and apply the same sorting to dbCompaniesArray
@ -176,14 +278,12 @@ public class StatisticsService : IStatisticsService
}
// Popularity is measured in number tickets in which the address is the first or last station
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations,
public async Task<(bool IsSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> stations,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetPopularStations(PopularAddressesParameters parameters)
{
// throw new NotImplementedException();
var fromDateUtc =
DateTime.UtcNow - TimeSpan.FromDays(parameters.Days ?? parameters.DefaultDays);
parameters.Days ??= parameters.DefaultDays;
var fromDateUtc = DateTime.UtcNow.Date - TimeSpan.FromDays((double) parameters.Days);
var dbTicketGroupsArray = await _dbContext.TicketGroups
.Include(tg => tg.Tickets)
@ -195,22 +295,25 @@ public class StatisticsService : IStatisticsService
foreach (var tg in dbTicketGroupsArray)
{
if (!addressCountDict.ContainsKey(tg.Tickets.First().FirstRouteAddressId))
// TODO: implement ticket ordering
var tickets = tg.Tickets.OrderBy(t => t.Id).ToArray();
if (!addressCountDict.ContainsKey(tickets.First().FirstRouteAddressId))
{
addressCountDict.Add(tg.Tickets.First().FirstRouteAddressId, 1);
addressCountDict.Add(tickets.First().FirstRouteAddressId, 1);
}
else
{
addressCountDict[tg.Tickets.First().FirstRouteAddressId] += 1;
addressCountDict[tickets.First().FirstRouteAddressId] += 1;
}
if (!addressCountDict.ContainsKey(tg.Tickets.Last().LastRouteAddressId))
if (!addressCountDict.ContainsKey(tickets.Last().LastRouteAddressId))
{
addressCountDict.Add(tg.Tickets.Last().LastRouteAddressId, 1);
addressCountDict.Add(tickets.Last().LastRouteAddressId, 1);
}
else
{
addressCountDict[tg.Tickets.Last().LastRouteAddressId] += 1;
addressCountDict[tickets.Last().LastRouteAddressId] += 1;
}
}
@ -228,6 +331,9 @@ public class StatisticsService : IStatisticsService
// Get top addresses from database ordered by Id (same as
// addressIds addressCountDict and )
var dbAddressesArray = await _dbContext.Addresses
.Include(a => a.City)
.ThenInclude(c => c.State)
.ThenInclude(s => s.Country)
.Where(a => addressIds.Any(id => a.Id == id))
.OrderBy(a => a.Id).ToArrayAsync();
@ -238,11 +344,12 @@ public class StatisticsService : IStatisticsService
{
for (int j = 0; j < n - i - 1; j++)
{
if (addressCountArray[j] > addressCountArray[j + 1])
if (addressCountArray[j] > addressCountArray[j + 1])
{
// swap temp and arr[i]
(addressCountArray[j], addressCountArray[j + 1]) = (addressCountArray[j + 1], addressCountArray[j]);
(dbAddressesArray[j], dbAddressesArray[j + 1]) = (dbAddressesArray[j + 1], dbAddressesArray[j]);
(addressCountArray[j], addressCountArray[j + 1]) =
(addressCountArray[j + 1], addressCountArray[j]);
(dbAddressesArray[j], dbAddressesArray[j + 1]) =
(dbAddressesArray[j + 1], dbAddressesArray[j]);
}
}
}

View File

@ -0,0 +1,220 @@
using System.Dynamic;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Server.Data;
using Server.Helpers;
using Server.Models;
using SharedModels.DataTransferObjects;
using SharedModels.QueryParameters;
using SharedModels.QueryParameters.Objects;
namespace Server.Services;
public class TicketGroupManagementService : ITicketGroupManagementService
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly ISortHelper<ExpandoObject> _ticketGroupSortHelper;
private readonly IDataShaper<TicketGroupDto> _ticketGroupDataShaper;
private readonly IDataShaper<TicketGroupWithTicketsDto> _ticketGroupWithTicketsDataShaper;
private readonly IPager<ExpandoObject> _pager;
public TicketGroupManagementService(ApplicationDbContext dbContext, IMapper mapper,
ISortHelper<ExpandoObject> ticketGroupSortHelper, IDataShaper<TicketGroupDto> ticketGroupDataShaper,
IDataShaper<TicketGroupWithTicketsDto> ticketGroupWithTicketsDataShaper, IPager<ExpandoObject> pager)
{
_dbContext = dbContext;
_mapper = mapper;
_ticketGroupSortHelper = ticketGroupSortHelper;
_ticketGroupDataShaper = ticketGroupDataShaper;
_ticketGroupWithTicketsDataShaper = ticketGroupWithTicketsDataShaper;
_pager = pager;
}
public async Task<(bool isSucceed, IActionResult? actionResult, TicketGroupDto ticketGroup)> AddTicketGroup(CreateTicketGroupDto createTicketGroupDto)
{
var ticketGroup = _mapper.Map<TicketGroup>(createTicketGroupDto);
await _dbContext.TicketGroups.AddAsync(ticketGroup);
await _dbContext.SaveChangesAsync();
return (true, null, _mapper.Map<TicketGroupDto>(ticketGroup));
}
public async Task<(bool isSucceed, IActionResult? actionResult, TicketGroupWithTicketsDto ticketGroup)> AddTicketGroupWithTickets(
CreateTicketGroupWithTicketsDto createTicketGroupWithTicketsDto)
{
var ticketGroup = _mapper.Map<TicketGroup>(createTicketGroupWithTicketsDto);
await _dbContext.TicketGroups.AddAsync(ticketGroup);
await _dbContext.SaveChangesAsync();
ticketGroup = await _dbContext.TicketGroups
.Include(tg => tg.Tickets)
.FirstAsync(tg => tg.Id == ticketGroup.Id);
return (true, null, _mapper.Map<TicketGroupWithTicketsDto>(ticketGroup));
}
public async Task<(bool isSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> ticketGroups,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetTicketGroups(TicketGroupParameters parameters)
{
var dbTicketGroups = _dbContext.TicketGroups.AsQueryable();
FilterTicketGroupsByUserId(ref dbTicketGroups, parameters.UserId);
var ticketGroupDtos = _mapper.ProjectTo<TicketGroupDto>(dbTicketGroups);
var shapedData = _ticketGroupDataShaper.ShapeData(ticketGroupDtos, parameters.Fields).AsQueryable();
try
{
shapedData = _ticketGroupSortHelper.ApplySort(shapedData, parameters.Sort);
}
catch (Exception)
{
return (false, new BadRequestObjectResult("Invalid sorting string"), null!, null!);
}
var pagingMetadata = _pager.ApplyPaging(ref shapedData, parameters.PageNumber,
parameters.PageSize);
return (true, null, shapedData, pagingMetadata);
void FilterTicketGroupsByUserId(ref IQueryable<TicketGroup> ticketGroups,
string? userId)
{
if (!ticketGroups.Any() || String.IsNullOrWhiteSpace(userId))
{
return;
}
ticketGroups = ticketGroups.Where(tg => tg.UserId == userId);
}
}
public async Task<(bool isSucceed, IActionResult? actionResult, IEnumerable<ExpandoObject> ticketGroups,
PagingMetadata<ExpandoObject> pagingMetadata)>
GetTicketGroupsWithTickets(TicketGroupWithTicketsParameters parameters)
{
var dbTicketGroups = _dbContext.TicketGroups
.Include(tg => tg.Tickets)
.AsQueryable();
FilterTicketGroupsByUserId(ref dbTicketGroups, parameters.UserId);
var ticketGroupDtos = _mapper.ProjectTo<TicketGroupWithTicketsDto>(dbTicketGroups);
var shapedData = _ticketGroupWithTicketsDataShaper.ShapeData(ticketGroupDtos, parameters.Fields).AsQueryable();
try
{
shapedData = _ticketGroupSortHelper.ApplySort(shapedData, parameters.Sort);
}
catch (Exception)
{
return (false, new BadRequestObjectResult("Invalid sorting string"), null, null)!;
}
var pagingMetadata = _pager.ApplyPaging(ref shapedData, parameters.PageNumber,
parameters.PageSize);
return (true, null, shapedData, pagingMetadata);
void FilterTicketGroupsByUserId(ref IQueryable<TicketGroup> ticketGroups,
string? userId)
{
if (!ticketGroups.Any() || String.IsNullOrWhiteSpace(userId))
{
return;
}
ticketGroups = ticketGroups.Where(tg => tg.UserId == userId);
}
}
public async Task<(bool isSucceed, IActionResult? actionResult, ExpandoObject ticketGroup)> GetTicketGroup(int id, string? fields)
{
if (!await IsTicketGroupExist(id))
{
return (false, new NotFoundResult(), null)!;
}
var dbTicketGroup = await _dbContext.TicketGroups.Where(tg => tg.Id == id)
.FirstAsync();
if (String.IsNullOrWhiteSpace(fields))
{
fields = RouteParameters.DefaultFields;
}
var ticketGroupDto = _mapper.Map<TicketGroupDto>(dbTicketGroup);
var shapedRouteData = _ticketGroupDataShaper.ShapeData(ticketGroupDto, fields);
return (true, null, shapedRouteData);
}
public async Task<(bool isSucceed, IActionResult? actionResult, ExpandoObject ticketGroup)> GetTicketGroupWithTickets(int id, string? fields)
{
if (!await IsTicketGroupExist(id))
{
return (false, new NotFoundResult(), null)!;
}
var dbTicketGroup = await _dbContext.TicketGroups.Where(tg => tg.Id == id)
.Include(tg => tg.Tickets)
.FirstAsync();
if (String.IsNullOrWhiteSpace(fields))
{
fields = RouteParameters.DefaultFields;
}
var ticketGroupDto = _mapper.Map<TicketGroupDto>(dbTicketGroup);
var shapedRouteData = _ticketGroupDataShaper.ShapeData(ticketGroupDto, fields);
return (true, null, shapedRouteData);
}
public async Task<(bool isSucceed, IActionResult? actionResult, UpdateTicketGroupDto ticketGroup)> UpdateTicketGroup(UpdateTicketGroupDto updateTicketGroupDto)
{
var ticketGroup = _mapper.Map<TicketGroup>(updateTicketGroupDto);
_dbContext.Entry(ticketGroup).State = EntityState.Modified;
try
{
await _dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await IsTicketGroupExist(updateTicketGroupDto.Id))
{
return (false, new NotFoundResult(), null)!;
}
}
var dbTicketGroup = await _dbContext.TicketGroups.FirstAsync(r => r.Id == ticketGroup.Id);
return (true, null, _mapper.Map<UpdateTicketGroupDto>(dbTicketGroup));
}
public async Task<(bool isSucceed, IActionResult? actionResult)> DeleteTicketGroup(int id)
{
var dbTicketGroup = await _dbContext.TicketGroups.FirstOrDefaultAsync(tg => tg.Id == id);
if (dbTicketGroup == null)
{
return (false, new NotFoundResult());
}
_dbContext.TicketGroups.Remove(dbTicketGroup);
await _dbContext.SaveChangesAsync();
return (true, null);
}
public async Task<bool> IsTicketGroupExist(int id)
{
return await _dbContext.TicketGroups.AnyAsync(tg => tg.Id == id);
}
}

View File

@ -5,7 +5,7 @@ namespace SharedModels.DataTransferObjects;
public class AddressDto : CreateAddressDto
{
public int Id { get; set; }
public string FullName = null!;
public string FullName { get; set; } = null!;
}
public class CreateAddressDto

View File

@ -15,10 +15,16 @@ public class TicketDto : CreateTicketDto
public class CreateTicketDto
{
[Required]
public string UserId { get; set; } = null!;
public int TicketGroupId { get; set; }
[Required]
public int VehicleEnrollmentId { get; set; }
[Required]
public int FirstRouteAddressId { get; set; }
[Required]
public int LastRouteAddressId { get; set; }
}
public class UpdateTicketDto
@ -26,11 +32,36 @@ public class UpdateTicketDto
[Required]
public int Id { get; set; }
public string? UserId { get; set; } = null!;
public int? VehicleEnrollmentId { get; set; }
[Required]
public int TicketGroupId { get; set; }
[Required]
public int VehicleEnrollmentId { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime PurchaseDateTimeUtc { get; set; }
[Required]
public bool IsReturned { get; set; } = false;
[Required]
public bool IsMissed { get; set; } = false;
}
public class CreateInTicketGroupTicketDto
{
[Required]
public int VehicleEnrollmentId { get; set; }
[Required]
public int FirstRouteAddressId { get; set; }
[Required]
public int LastRouteAddressId { get; set; }
}
public class InTicketGroupTicketDto : CreateInTicketGroupTicketDto
{
public int Id { get; set; }
}

View File

@ -1,6 +1,35 @@
using System.ComponentModel.DataAnnotations;
namespace SharedModels.DataTransferObjects;
public class TicketGroupDto
public class TicketGroupDto : CreateTicketGroupDto
{
public int Id { get; set; }
}
public class CreateTicketGroupDto
{
[Required]
public string UserId { get; set; } = null!;
}
public class UpdateTicketGroupDto
{
[Required]
public int Id { get; set; }
[Required]
public string UserId { get; set; } = null!;
}
public class TicketGroupWithTicketsDto
{
public int Id { get; set; }
public IList<InTicketGroupTicketDto> Tickets { get; set; } = null!;
}
public class CreateTicketGroupWithTicketsDto : CreateTicketGroupDto
{
[Required]
public IList<CreateInTicketGroupTicketDto> Tickets { get; set; } = null!;
}

View File

@ -36,9 +36,7 @@ public class CreateVehicleEnrollmentWithDetailsDto : CreateVehicleEnrollmentDto
public IList<CreateRouteAddressDetailsInVehicleEnrollmentDto> RouteAddressDetails { get; set; } = null!;
}
public class VehicleEnrollmentWithDetailsDto
public class VehicleEnrollmentWithDetailsDto : VehicleEnrollmentDto
{
public int Id { get; set; }
public IList<RouteAddressDetailsInVehicleEnrollmentDto> RouteAddressDetails { get; set; } = null!;
}

View File

@ -0,0 +1,13 @@
namespace SharedModels.QueryParameters.Objects;
public class TicketGroupParameters : ParametersBase
{
public const string DefaultFields = "id,userId";
public TicketGroupParameters()
{
Fields = DefaultFields;
}
public string? UserId { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace SharedModels.QueryParameters.Objects;
public class TicketGroupWithTicketsParameters : ParametersBase
{
public const string DefaultFields = "id,userId,tickets";
public TicketGroupWithTicketsParameters()
{
Fields = DefaultFields;
}
public string? UserId { get; set; }
}

View File

@ -2,7 +2,7 @@ namespace SharedModels.QueryParameters.Statistics;
public class PopularAddressesParameters : ParametersBase
{
public readonly string DefaultFields = "id,name,purchaseCount";
public readonly string DefaultFields = "id,name,fullName,purchaseCount";
public readonly int DefaultDays = 60;
public PopularAddressesParameters()

View File

@ -0,0 +1,16 @@
namespace SharedModels.QueryParameters.Statistics;
public class PopularRoutesParameters : ParametersBase
{
public readonly string DefaultFields = "departureAddressId,departureAddress," +
"arrivalAddressId,arrivalAddress," +
"count";
public readonly int DefaultDays = 60;
public PopularRoutesParameters()
{
Fields = DefaultFields;
}
public int? Days { get; set; }
}