diff --git a/Server/Controllers/ReportController.cs b/Server/Controllers/ReportController.cs index 9347d45..ab3d511 100644 --- a/Server/Controllers/ReportController.cs +++ b/Server/Controllers/ReportController.cs @@ -14,8 +14,8 @@ public class ReportController : ControllerBase _reportService = reportService; } - [HttpGet("ticket")] - public async Task GetTicket(int ticketGroupId) + [HttpGet("pdf/ticket")] + public async Task GetTicketPdf(int ticketGroupId) { var result = await _reportService.GetTicket(ticketGroupId); @@ -24,21 +24,45 @@ public class ReportController : ControllerBase return BadRequest(result.actionResult); } - return File(result.ticketPdf, "application/pdf", - $"ticket.pdf"); + return File(result.ticketPdf, "application/pdf", $"ticket.pdf"); } - [HttpGet("report")] - public async Task GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate) + [HttpGet("pdf/company")] + public async Task GetCompanyReportPdf(int companyId, DateTime fromDate, DateTime toDate) { - var result = await _reportService.GetCompanyReport(companyId, fromDate, toDate); + var result = await _reportService.GetCompanyReportPdf(companyId, fromDate, toDate); if (!result.isSucceed) { return BadRequest(result.actionResult); } - return File(result.reportPdf, "application/pdf", - $"report.pdf"); + return File(result.reportPdf, "application/pdf", $"report.pdf"); + } + + [HttpGet("raw/company")] + public async Task GetCompanyReportRaw(int companyId, DateTime fromDate, DateTime toDate) + { + var result = await _reportService.GetCompanyReportRaw(companyId, fromDate, toDate); + + if (!result.isSucceed) + { + return BadRequest(result.actionResult); + } + + return Ok(result.statistics); + } + + [HttpGet("raw/admin")] + public async Task GetAdminReportRaw(DateTime fromDate, DateTime toDate) + { + var result = await _reportService.GetAdminReportRaw(fromDate, toDate); + + if (!result.isSucceed) + { + return BadRequest(result.actionResult); + } + + return Ok(result.statistics); } } diff --git a/Server/Models/Company.cs b/Server/Models/Company.cs index c09d55b..0a471d1 100644 --- a/Server/Models/Company.cs +++ b/Server/Models/Company.cs @@ -16,4 +16,134 @@ public class Company public virtual IList Vehicles { get; set; } = null!; public virtual IList CompanyDrivers { get; set; } = null!; + + public int GetTotalEnrollmentCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetEnrollmentCount(enrollment.RouteId); + } + } + + return result; + } + + public int GetTotalCanceledEnrollmentCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetCanceledEnrollmentCount(enrollment.RouteId); + } + } + + return result; + } + + public int GetTotalSoldTicketCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetSoldTicketCount(enrollment.RouteId); + } + } + + return result; + } + + public int GetTotalReturnedTicketCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetReturnedTicketCount(enrollment.RouteId); + } + } + + return result; + } + + public int GetTotalIndirectTicketCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetIndirectTicketCount(enrollment.RouteId); + } + } + + return result; + } + + public int GetTotalReturnedIndirectTicketCount() + { + int result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetReturnedIndirectTicketCount(enrollment.RouteId); + } + } + + return result; + } + + public double GetTotalRevenue() + { + double result = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + result += vehicle.GetTotalRevenue(enrollment.RouteId); + } + } + + return result; + } + + public double GetTotalAverageRating() + { + double result = 0; + int enrollmentCount = 0; + + foreach (var vehicle in Vehicles) + { + foreach (var enrollment in vehicle.VehicleEnrollments) + { + if (enrollment.Reviews.Count == 0) + { + continue; + } + + result += vehicle.GetAverageRating(enrollment.RouteId); + enrollmentCount++; + } + } + + result /= enrollmentCount; + result = !Double.IsNaN(result) ? Math.Round(result, 3) : 0; + + return result; + } } \ No newline at end of file diff --git a/Server/Models/Route.cs b/Server/Models/Route.cs index 141c088..00f67d5 100644 --- a/Server/Models/Route.cs +++ b/Server/Models/Route.cs @@ -11,4 +11,83 @@ public class Route public virtual IList RouteAddresses { get; set; } = null!; public virtual IList VehicleEnrollments { get; set; } = null!; + + public int GetEnrollmentCount() + { + return VehicleEnrollments.Count(ve => !ve.IsCanceled); + } + + public int GetCanceledEnrollmentCount() + { + return VehicleEnrollments.Count(ve => ve.IsCanceled); + } + + public int GetSoldTicketCount() + { + int result = 0; + + foreach (var enrollment in VehicleEnrollments) + { + result += enrollment.Tickets.Count(t => !t.IsReturned); + } + + return result; + } + + public int GetIndirectTicketCount() + { + int result = 0; + + int departureAddressId = RouteAddresses.First().AddressId; + int arrivalAddressId = RouteAddresses.Last().AddressId; + + foreach (var enrollment in VehicleEnrollments) + { + result += enrollment.Tickets.Count(t => !t.IsReturned && + t.FirstRouteAddressId != departureAddressId || + t.LastRouteAddressId != arrivalAddressId); + } + + return result; + } + + public double GetTotalRevenue() + { + double result = 0; + + foreach (var enrollment in VehicleEnrollments) + { + foreach (var ticket in enrollment.Tickets) + { + result += ticket.GetCost(); + } + } + + return result; + } + + public double GetAverageRating() + { + double result = 0; + int reviewCount = 0; + + foreach (var enrollment in 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; + } } diff --git a/Server/Models/Ticket.cs b/Server/Models/Ticket.cs index fbf8755..491f255 100644 --- a/Server/Models/Ticket.cs +++ b/Server/Models/Ticket.cs @@ -21,4 +21,88 @@ public class Ticket public int LastRouteAddressId { get; set; } public bool IsReturned { get; set; } = false; public bool IsMissed { get; set; } = false; + + public double GetCost() + { + double cost = 0; + + var routeAddresses = VehicleEnrollment.Route.RouteAddresses + .OrderBy(ra => ra.Order) + .SkipWhile(ra => ra.AddressId != FirstRouteAddressId) + .TakeWhile(ra => ra.AddressId != LastRouteAddressId) + .ToArray(); + + foreach (var routeAddress in routeAddresses) + { + var details = routeAddress.RouteAddressDetails + .First(rad => rad.RouteAddressId == routeAddress.Id); + + cost += details.CostToNextCity; + } + + return cost; + } + + public DateTime GetDepartureTime() + { + var departureDateTimeUtc = VehicleEnrollment.DepartureDateTimeUtc; + + var routeAddresses = VehicleEnrollment.Route.RouteAddresses + .OrderBy(ra => ra.Order).ToArray(); + + foreach (var routeAddress in routeAddresses) + { + var details = routeAddress.RouteAddressDetails + .First(rad => rad.RouteAddressId == routeAddress.Id); + + if (routeAddress.AddressId == FirstRouteAddressId) + { + departureDateTimeUtc += details.WaitTimeSpan; + break; + } + + departureDateTimeUtc += details.TimeSpanToNextCity; + departureDateTimeUtc += details.WaitTimeSpan; + } + + return departureDateTimeUtc; + } + + public DateTime GetArrivalTime() + { + var arrivalDateTimeUtc = VehicleEnrollment.DepartureDateTimeUtc; + + var routeAddresses = VehicleEnrollment.Route.RouteAddresses + .OrderBy(ra => ra.Order).ToArray(); + + foreach (var routeAddress in routeAddresses) + { + var details = routeAddress.RouteAddressDetails + .First(rad => rad.RouteAddressId == routeAddress.Id); + + if (routeAddress.AddressId == LastRouteAddressId) + { + break; + } + + arrivalDateTimeUtc += details.TimeSpanToNextCity; + arrivalDateTimeUtc += details.WaitTimeSpan; + } + + return arrivalDateTimeUtc; + } + + public Address GetDepartureAddress() + { + return VehicleEnrollment.Route.RouteAddresses + .First(ra => ra.AddressId == FirstRouteAddressId) + .Address; + } + + public Address GetArrivalAddress() + { + return VehicleEnrollment.Route.RouteAddresses + .First(ra => ra.AddressId == LastRouteAddressId) + .Address; + } } \ No newline at end of file diff --git a/Server/Models/TicketGroup.cs b/Server/Models/TicketGroup.cs index 38e53bb..a40aeb6 100644 --- a/Server/Models/TicketGroup.cs +++ b/Server/Models/TicketGroup.cs @@ -11,4 +11,16 @@ public class TicketGroup public User User { get; set; } = null!; public virtual IList Tickets { get; set; } + + public double GetCost() + { + double cost = 0; + + foreach (var ticket in Tickets) + { + cost += ticket.GetCost(); + } + + return cost; + } } \ No newline at end of file diff --git a/Server/Models/Vehicle.cs b/Server/Models/Vehicle.cs index dc96df8..91f1bc8 100644 --- a/Server/Models/Vehicle.cs +++ b/Server/Models/Vehicle.cs @@ -25,4 +25,108 @@ public class Vehicle public bool HasBelts { get; set; } public IList VehicleEnrollments { get; set; } = null!; + + public int GetEnrollmentCount(int routeId) + { + return VehicleEnrollments.Count(ve => !ve.IsCanceled && ve.RouteId == routeId); + } + + public int GetCanceledEnrollmentCount(int routeId) + { + return VehicleEnrollments.Count(ve => ve.IsCanceled && ve.RouteId == routeId); + } + + public int GetSoldTicketCount(int routeId) + { + int result = 0; + + foreach (var enrollment in VehicleEnrollments.Where(ve => ve.RouteId == routeId)) + { + result += enrollment.Tickets.Count(t => !t.IsReturned); + } + + return result; + } + + public int GetReturnedTicketCount(int routeId) + { + int result = 0; + + foreach (var enrollment in VehicleEnrollments.Where(ve => ve.RouteId == routeId)) + { + result += enrollment.Tickets.Count(t => t.IsReturned); + } + + return result; + } + + public int GetIndirectTicketCount(int routeId) + { + int result = 0; + + foreach (var enrollment in 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; + } + + public int GetReturnedIndirectTicketCount(int routeId) + { + int result = 0; + + foreach (var enrollment in 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; + } + + public double GetTotalRevenue(int routeId) + { + double result = 0; + + foreach (var enrollment in VehicleEnrollments.Where(ve => ve.RouteId == routeId)) + { + foreach (var ticket in enrollment.Tickets) + { + result += ticket.GetCost(); + } + } + + return result; + } + + public double GetAverageRating(int routeId) + { + double result = 0; + int reviewCount = 0; + + foreach (var enrollment in 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); + } } \ No newline at end of file diff --git a/Server/Services/EmailSenderService.cs b/Server/Services/EmailSenderService.cs index df7e5a1..3ae2020 100644 --- a/Server/Services/EmailSenderService.cs +++ b/Server/Services/EmailSenderService.cs @@ -20,7 +20,7 @@ public class EmailSenderService : IEmailSenderService _smtpCredentials = smtpCredentials.Value; _smtpClient = new SmtpClient(); - _smtpClient.SslProtocols = SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; + _smtpClient.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; } public async Task<(bool succeeded, string message)> SendMail(string toEmail, string subject, string message) @@ -34,10 +34,11 @@ public class EmailSenderService : IEmailSenderService mailMessage.Subject = $"{applicationName}. {subject}"; mailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Text) { Text = message}; - await _smtpClient.ConnectAsync(_smtpCredentials.Host, _smtpCredentials.Port, false); + await _smtpClient.ConnectAsync(_smtpCredentials.Host, _smtpCredentials.Port, SecureSocketOptions.StartTls); await _smtpClient.AuthenticateAsync(Encoding.ASCII, _smtpCredentials.User, _smtpCredentials.Password); await _smtpClient.SendAsync(mailMessage); await _smtpClient.DisconnectAsync(true); + _smtpClient.Dispose(); return (true, "Letter has been sent successfully"); } diff --git a/Server/Services/IReportService.cs b/Server/Services/IReportService.cs index 3bf7619..e7e2c11 100644 --- a/Server/Services/IReportService.cs +++ b/Server/Services/IReportService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using SharedModels.Responses; namespace Server.Services; @@ -8,5 +9,11 @@ public interface IReportService GetTicket(int ticketGroupId); Task<(bool isSucceed, IActionResult? actionResult, Stream reportPdf)> - GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate); + GetCompanyReportPdf(int companyId, DateTime fromDate, DateTime toDate); + + Task<(bool isSucceed, IActionResult? actionResult, StatisticsResponse statistics)> + GetCompanyReportRaw(int companyId, DateTime fromDate, DateTime toDate); + + Task<(bool isSucceed, IActionResult? actionResult, StatisticsResponse statistics)> + GetAdminReportRaw(DateTime fromDate, DateTime toDate); } \ No newline at end of file diff --git a/Server/Services/ReportService.cs b/Server/Services/ReportService.cs index 57b05e4..3b78fdd 100644 --- a/Server/Services/ReportService.cs +++ b/Server/Services/ReportService.cs @@ -8,6 +8,7 @@ using PdfSharpCore.Drawing; using PdfSharpCore.Pdf; using Server.Data; using Server.Models; +using SharedModels.Responses; using Route = Server.Models.Route; namespace Server.Services; @@ -223,15 +224,8 @@ public class ReportService : IReportService for (var i = 0; i < ticketGroup.Tickets.Count; i++) { var ticket = ticketGroup.Tickets[i]; - var vehicle = ticket.VehicleEnrollment.Vehicle; - var departureDateTimeUtc = GetDepartureTime(ticket); - var arrivalDateTimeUtc = GetArrivalTime(ticket); - - var departureAddress = GetDepartureAddress(ticket); - var arrivalAddress = GetArrivalAddress(ticket); - row = table.AddRow(); table.AddRow(); row.Shading.Color = isFilled ? Color.FromRgbColor(25, Colors.Black) : Colors.White; @@ -251,12 +245,12 @@ public class ReportService : IReportService row.Cells[2].MergeRight = 1; row.Cells[2].MergeDown = 1; - row.Cells[2].AddParagraph($"{departureDateTimeUtc:dd.MM.yyyy HH:mm}"); + row.Cells[2].AddParagraph($"{ticket.GetDepartureTime():dd.MM.yyyy HH:mm}"); row.Cells[4].MergeRight = 3; row.Cells[4].MergeDown = 1; row.Cells[4].Format.Font.Size = 8; - row.Cells[4].AddParagraph($"{departureAddress.GetFullName()}"); + row.Cells[4].AddParagraph($"{ticket.GetDepartureAddress().GetFullName()}"); row.Cells[8].MergeRight = 1; row.Cells[8].MergeDown = 1; @@ -285,12 +279,12 @@ public class ReportService : IReportService row.Cells[2].MergeRight = 1; row.Cells[2].MergeDown = 1; - row.Cells[2].AddParagraph($"{arrivalDateTimeUtc:dd.MM.yyyy HH:mm}"); + row.Cells[2].AddParagraph($"{ticket.GetArrivalTime():dd.MM.yyyy HH:mm}"); row.Cells[4].MergeRight = 3; row.Cells[4].MergeDown = 1; row.Cells[4].Format.Font.Size = 8; - row.Cells[4].AddParagraph($"{arrivalAddress.GetFullName()}"); + row.Cells[4].AddParagraph($"{ticket.GetArrivalAddress().GetFullName()}"); row.Cells[8].MergeRight = 1; row.Cells[8].MergeDown = 1; @@ -302,8 +296,8 @@ public class ReportService : IReportService if (!ticketGroup.Tickets.Last().Equals(ticket)) { - var nextDepartureTimeUtc = GetDepartureTime(ticketGroup.Tickets[i + 1]); - var freeTime = nextDepartureTimeUtc - arrivalDateTimeUtc; + var nextDepartureTime = ticketGroup.Tickets[i + 1].GetDepartureTime(); + var freeTime = nextDepartureTime - ticket.GetArrivalTime(); row = table.AddRow(); table.AddRow(); @@ -316,7 +310,7 @@ public class ReportService : IReportService row.Cells[3].MergeRight = 5; row.Cells[3].MergeDown = 1; - row.Cells[3].AddParagraph($"{arrivalDateTimeUtc:dd.MM.yyyy HH:mm} – {nextDepartureTimeUtc:dd.MM.yyyy HH:mm}"); + row.Cells[3].AddParagraph($"{ticket.GetArrivalTime():dd.MM.yyyy HH:mm} – {nextDepartureTime:dd.MM.yyyy HH:mm}"); row.Cells[9].MergeRight = 2; row.Cells[9].MergeDown = 1; @@ -336,10 +330,6 @@ public class ReportService : IReportService foreach (var ticket in ticketGroup.Tickets) { - var departureAddress = GetDepartureAddress(ticket); - var arrivalAddress = GetArrivalAddress(ticket); - var cost = GetTicketCost(ticket); - row = table.AddRow(); table.AddRow(); row.Shading.Color = isFilled ? Color.FromRgbColor(25, Colors.Black) : Colors.White; @@ -351,7 +341,7 @@ public class ReportService : IReportService row.Cells[1].MergeRight = 3; row.Cells[1].MergeDown = 1; row.Cells[1].Format.Font.Size = 8; - row.Cells[1].AddParagraph($"{departureAddress.GetFullName()}"); + row.Cells[1].AddParagraph($"{ticket.GetDepartureAddress().GetFullName()}"); row.Cells[5].MergeDown = 1; row.Cells[5].AddParagraph("Куди"); @@ -359,17 +349,15 @@ public class ReportService : IReportService row.Cells[6].MergeRight = 3; row.Cells[6].MergeDown = 1; row.Cells[6].Format.Font.Size = 8; - row.Cells[6].AddParagraph($"{arrivalAddress.GetFullName()}"); + row.Cells[6].AddParagraph($"{ticket.GetArrivalAddress().GetFullName()}"); row.Cells[10].MergeDown = 1; row.Cells[10].AddParagraph("Ціна"); row.Cells[11].MergeDown = 1; - row.Cells[11].AddParagraph($"{cost}"); + row.Cells[11].AddParagraph($"{ticket.GetCost()}"); } - var totalCost = GetTicketGroupCost(ticketGroup); - row = table.AddRow(); row.Shading.Color = isFilled ? Color.FromRgbColor(25, Colors.Black) : Colors.White; isFilled = !isFilled; @@ -380,113 +368,12 @@ public class ReportService : IReportService row.Cells[10].Borders.Right.Color = Colors.Transparent; - row.Cells[11].AddParagraph($"{totalCost}"); - } - - DateTime GetDepartureTime(Ticket ticket) - { - var departureDateTimeUtc = ticket.VehicleEnrollment.DepartureDateTimeUtc; - - var routeAddresses = ticket.VehicleEnrollment.Route.RouteAddresses - .OrderBy(ra => ra.Order).ToArray(); - - foreach (var routeAddress in routeAddresses) - { - var details = routeAddress.RouteAddressDetails - .First(rad => rad.RouteAddressId == routeAddress.Id); - - if (routeAddress.AddressId == ticket.FirstRouteAddressId) - { - departureDateTimeUtc += details.WaitTimeSpan; - break; - } - - departureDateTimeUtc += details.TimeSpanToNextCity; - departureDateTimeUtc += details.WaitTimeSpan; - } - - return departureDateTimeUtc; - } - - DateTime GetArrivalTime(Ticket ticket) - { - var arrivalDateTimeUtc = ticket.VehicleEnrollment.DepartureDateTimeUtc; - - var routeAddresses = ticket.VehicleEnrollment.Route.RouteAddresses - .OrderBy(ra => ra.Order).ToArray(); - - foreach (var routeAddress in routeAddresses) - { - var details = routeAddress.RouteAddressDetails - .First(rad => rad.RouteAddressId == routeAddress.Id); - - if (routeAddress.AddressId == ticket.LastRouteAddressId) - { - break; - } - - arrivalDateTimeUtc += details.TimeSpanToNextCity; - arrivalDateTimeUtc += details.WaitTimeSpan; - } - - return arrivalDateTimeUtc; - } - - Address GetDepartureAddress(Ticket ticket) - { - return ticket.VehicleEnrollment.Route.RouteAddresses - .First(ra => ra.AddressId == ticket.FirstRouteAddressId) - .Address; - } - - Address GetArrivalAddress(Ticket ticket) - { - return ticket.VehicleEnrollment.Route.RouteAddresses - .First(ra => ra.AddressId == ticket.LastRouteAddressId) - .Address; - } - - 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; - } - - double GetTicketGroupCost(TicketGroup ticketGroup) - { - double cost = 0; - - foreach (var ticket in ticketGroup.Tickets) - { - cost += GetTicketCost(ticket); - } - - return cost; - } - - async Task DoesTicketGroupExist(int id) - { - return await _dbContext.TicketGroups.AnyAsync(tg => tg.Id == id); + row.Cells[11].AddParagraph($"{ticketGroup.GetCost()}"); } } public async Task<(bool isSucceed, IActionResult? actionResult, Stream reportPdf)> - GetCompanyReport(int companyId, DateTime fromDate, DateTime toDate) + GetCompanyReportPdf(int companyId, DateTime fromDate, DateTime toDate) { if (!await DoesCompanyExist(companyId)) { @@ -726,22 +613,22 @@ public class ReportService : IReportService row.Shading.Color = Color.FromRgbColor(25, Colors.Black); row.Cells[0].MergeRight = 1; - row.Cells[0].AddParagraph($"{GetRouteEnrollmentCount(route)}"); + row.Cells[0].AddParagraph($"{route.GetEnrollmentCount()}"); row.Cells[2].MergeRight = 1; - row.Cells[2].AddParagraph($"{GetRouteCanceledEnrollmentCount(route)}"); + row.Cells[2].AddParagraph($"{route.GetCanceledEnrollmentCount()}"); row.Cells[4].MergeRight = 1; - row.Cells[4].AddParagraph($"{GetRouteSelledTicketCount(route)}"); + row.Cells[4].AddParagraph($"{route.GetSoldTicketCount()}"); row.Cells[6].MergeRight = 1; - row.Cells[6].AddParagraph($"{GetRouteIndirectTicketCount(route)}"); + row.Cells[6].AddParagraph($"{route.GetIndirectTicketCount()}"); row.Cells[8].MergeRight = 1; - row.Cells[8].AddParagraph($"{GetRouteTotalRevenu(route)}"); + row.Cells[8].AddParagraph($"{route.GetTotalRevenue()}"); row.Cells[10].MergeRight = 1; - var routeAverageRating = GetRouteAvarageRating(route); + var routeAverageRating = route.GetAverageRating(); row.Cells[10].AddParagraph($"{(routeAverageRating == 0 ? "-" : routeAverageRating)}"); row = table.AddRow(); @@ -783,21 +670,23 @@ public class ReportService : IReportService 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); + var executedEnrollmentCount = vehicle.GetEnrollmentCount(route.Id); + var canceledEnrollmentCount = vehicle.GetCanceledEnrollmentCount(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[5].AddParagraph($"{vehicle.GetSoldTicketCount(route.Id)}, " + + $"{vehicle.GetReturnedTicketCount(route.Id)}; " + + $"{vehicle.GetIndirectTicketCount(route.Id)}, " + + $"{vehicle.GetReturnedIndirectTicketCount(route.Id)}"); row.Cells[8].MergeRight = 1; - row.Cells[8].AddParagraph($"{GetVehicleTotalRevenue(vehicle, route.Id)}"); + row.Cells[8].AddParagraph($"{vehicle.GetTotalRevenue(route.Id)}"); row.Cells[10].MergeRight = 1; - var vehicleAverageRating = GetVehicleAverageRating(vehicle, route.Id); + var vehicleAverageRating = vehicle.GetAverageRating(route.Id); row.Cells[10].AddParagraph($"{(vehicleAverageRating == 0 ? "-" : vehicleAverageRating)}"); } @@ -812,372 +701,140 @@ public class ReportService : IReportService 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}"); + $"було заплановано {dbCompany.GetTotalEnrollmentCount()} поїздки, " + + $"з яких {dbCompany.GetTotalCanceledEnrollmentCount()} було скасовано, " + + $"продано {dbCompany.GetTotalSoldTicketCount()} квитків, " + + $"з яких {dbCompany.GetTotalReturnedTicketCount()} було повернено. " + + $"За цей час було зароблено {dbCompany.GetTotalRevenue()} гривень. " + + $"Середній рейтинг по всім поїздкам: {dbCompany.GetTotalAverageRating()}"); 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 DoesCompanyExist(int id) - { - return await _dbContext.Companies.AnyAsync(c => c.Id == id); } } + + public async Task<(bool isSucceed, IActionResult? actionResult, StatisticsResponse statistics)> + GetCompanyReportRaw(int companyId, DateTime fromDate, DateTime toDate) + { + 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 statistics = new StatisticsResponse + { + EnrollmentsPlanned = dbCompany.GetTotalEnrollmentCount(), + EnrollmentsCanceled = dbCompany.GetTotalCanceledEnrollmentCount(), + TicketsSold = dbCompany.GetTotalSoldTicketCount(), + TicketsReturned = dbCompany.GetTotalReturnedTicketCount(), + MoneyEarned = dbCompany.GetTotalRevenue(), + AverageRating = dbCompany.GetTotalAverageRating() + }; + + return (true, null, statistics); + } + + public async Task<(bool isSucceed, IActionResult? actionResult, StatisticsResponse statistics)> + GetAdminReportRaw(DateTime fromDate, DateTime toDate) + { + var dbCompanies = 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) + .ToArrayAsync(); + + var statistics = new StatisticsResponse(); + + foreach (var company in dbCompanies) + { + statistics.EnrollmentsPlanned += company.GetTotalEnrollmentCount(); + statistics.EnrollmentsCanceled += company.GetTotalCanceledEnrollmentCount(); + statistics.TicketsSold += company.GetTotalSoldTicketCount(); + statistics.TicketsReturned += company.GetTotalReturnedTicketCount(); + statistics.MoneyEarned += company.GetTotalRevenue(); + statistics.AverageRating += company.GetTotalAverageRating(); + } + + return (true, null, statistics); + } + + async Task DoesCompanyExist(int id) + { + return await _dbContext.Companies.AnyAsync(c => c.Id == id); + } + + async Task DoesTicketGroupExist(int id) + { + return await _dbContext.TicketGroups.AnyAsync(tg => tg.Id == id); + } } \ No newline at end of file diff --git a/SharedModels/Responses/StatisticsResponse.cs b/SharedModels/Responses/StatisticsResponse.cs new file mode 100644 index 0000000..278dd80 --- /dev/null +++ b/SharedModels/Responses/StatisticsResponse.cs @@ -0,0 +1,11 @@ +namespace SharedModels.Responses; + +public class StatisticsResponse +{ + public int EnrollmentsPlanned { get; set; } + public int EnrollmentsCanceled { get; set; } + public int TicketsSold { get; set; } + public int TicketsReturned { get; set; } + public double MoneyEarned { get; set; } + public double AverageRating { get; set; } +} \ No newline at end of file