diff --git a/AsbCloudApp/Data/DrillerDto.cs b/AsbCloudApp/Data/DrillerDto.cs index 1a11bd2a..01a4473d 100644 --- a/AsbCloudApp/Data/DrillerDto.cs +++ b/AsbCloudApp/Data/DrillerDto.cs @@ -29,4 +29,9 @@ public class DrillerDto : IId /// Отчество /// public string? Patronymic { get; set; } + + /// + /// Полное имя + /// + public string FullName => $"{Surname} {Name} {Patronymic}"; } diff --git a/AsbCloudApp/Data/WellReport/DrillerReportDto.cs b/AsbCloudApp/Data/WellReport/DrillerReportDto.cs new file mode 100644 index 00000000..fed9a917 --- /dev/null +++ b/AsbCloudApp/Data/WellReport/DrillerReportDto.cs @@ -0,0 +1,21 @@ +using AsbCloudApp.Data.Subsystems; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudApp.Data.WellReport; + +/// +/// Показатели бурильщиков +/// +public class DrillerReportDto +{ + /// + /// Расписание + /// + public ScheduleDto Shedule { get; set; } = null!; + + /// + /// Наработка подсистем + /// + public IEnumerable SubsystemsStat { get; set; } = Enumerable.Empty(); +} \ No newline at end of file diff --git a/AsbCloudApp/Data/WellReport/DrillingBySetpointsDto.cs b/AsbCloudApp/Data/WellReport/DrillingBySetpointsDto.cs new file mode 100644 index 00000000..a1c43036 --- /dev/null +++ b/AsbCloudApp/Data/WellReport/DrillingBySetpointsDto.cs @@ -0,0 +1,27 @@ +namespace AsbCloudApp.Data.WellReport; + +/// +/// Бурение по уставкам +/// +public class DrillingBySetpointsDto +{ + /// + /// Метры пробуренные по уставке давления + /// + public double? MetersByPressure { get; set; } + + /// + /// Метры пробуренные по уставке нагрузки + /// + public double? MetersByLoad { get; set; } + + /// + /// Метры пробуренные по уставке момента + /// + public double? MetersByTorque { get; set; } + + /// + /// Метры пробуренные по уставке скорости + /// + public double? MetersBySpeed { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/WellReport/OperatingModeDto.cs b/AsbCloudApp/Data/WellReport/OperatingModeDto.cs new file mode 100644 index 00000000..c355c623 --- /dev/null +++ b/AsbCloudApp/Data/WellReport/OperatingModeDto.cs @@ -0,0 +1,87 @@ +namespace AsbCloudApp.Data.WellReport; + +/// +/// Режим работы +/// +public class OperatingModeDto +{ + /// + /// Интервал от + /// + public double DepthStart { get; set; } + + /// + /// Интервал до + /// + public double DepthEnd { get; set; } + + /// + /// Скорость проходки мин, м/ч + /// + public double? RopMin { get; set; } + + /// + /// Скорость проходки максимум, м/ч + /// + public double? RopMax { get; set; } + + /// + /// Скорость проходки среднее, м/ч + /// + public double? RopAvg { get; set; } + + /// + /// Нагрузка на долото минимум, т + /// + public double? WeightOnBitMin { get; set; } + + /// + /// Нагрузка на долото максимум, т + /// + public double? WeightOnBitMax { get; set; } + + /// + /// Нагрузка на долото среднее, т + /// + public double? WeightOnBitAvg { get; set; } + + /// + /// Момент минимум, кН*м + /// + public double? DriveTorqueMin { get; set; } + + /// + /// Момент максимум, кН*м + /// + public double? DriveTorqueMax { get; set; } + + /// + /// Момент среднее, кН*м + /// + public double? DriveTorqueAvg { get; set; } + + /// + /// Перепад давления минимум, атм + /// + public double? DifferentialPressureMin { get; set; } + + /// + /// Перепад давления максимум, атм + /// + public double? DifferentialPressureMax { get; set; } + + /// + /// Перепад давления среднее, атм + /// + public double? DifferentialPressureAvg { get; set; } + + /// + /// Q насосов минимум л/с + /// + public double? FrowRateMin { get; set; } + + /// + /// Q насосов максимум л/с + /// + public double? FrowRateMax { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/WellReport/SectionReportDto.cs b/AsbCloudApp/Data/WellReport/SectionReportDto.cs new file mode 100644 index 00000000..0acf094d --- /dev/null +++ b/AsbCloudApp/Data/WellReport/SectionReportDto.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using AsbCloudApp.Data.Subsystems; + +namespace AsbCloudApp.Data.WellReport; + +/// +/// Показатели по секции +/// +public class SectionReportDto +{ + /// + /// Идентификатор секции + /// + public int IdSection { get; set; } + + /// + /// Наработка подсистем + /// + public IEnumerable SubsystemsStat { get; set; } = []; + + /// + /// Режимы бурения + /// + public PlanFactDto OperatingMode { get; set; } = null!; + + /// + /// Бурение по уставкам + /// + public DrillingBySetpointsDto? DrillingBySetpoints { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/WellReport/WellReportDto.cs b/AsbCloudApp/Data/WellReport/WellReportDto.cs new file mode 100644 index 00000000..ae8e1b9c --- /dev/null +++ b/AsbCloudApp/Data/WellReport/WellReportDto.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Data.User; + +namespace AsbCloudApp.Data.WellReport; + +/// +/// Отчёт по скважине +/// +public class WellReportDto +{ + /// + /// Информация о скважине + /// + public WellDto Well { get; set; } + + /// + /// Дата начала бурения + /// + public DateTimeOffset? DateFrom { get; set; } + + /// + /// Дата окончания бурения + /// + public DateTimeOffset? DateTo { get; set; } + + /// + /// Дни бурения + /// + public PlanFactDto Days { get; set; } = null!; + + /// + /// Проектная глубина + /// + public PlanFactDto WellBoreDepth { get; set; } = null!; + + /// + /// Вертикальная глубина + /// + public PlanFactDto VerticalDepth { get; set; } = null!; + + /// + /// Дни бурения без НПВ + /// + public double WithoutNtpDays { get; set; } + + /// + /// Контакты + /// + public IEnumerable Contacts { get; set; } = []; + + /// + /// Показатели по секциям + /// + public IEnumerable SectionReports { get; set; } = []; + + /// + /// Показатели по бурильщикам + /// + public IEnumerable DrillerReports { get; set; } = []; +} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index f80ad02d..c18056e2 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -68,13 +68,13 @@ public interface IWellOperationRepository /// (WellOperationBaseDto First, WellOperationBaseDto Last)? GetFirstAndLastFact(int idWell); - /// - /// Получить список операций по запросу - /// - /// - /// - /// - Task> GetAll(WellOperationRequest request, CancellationToken token); + /// + /// Получить список операций по запросу + /// + /// + /// + /// + Task> GetAll(WellOperationRequest request, CancellationToken token); /// /// Получить список операций по запросу diff --git a/AsbCloudApp/Services/WellReport/IWellReportExportService.cs b/AsbCloudApp/Services/WellReport/IWellReportExportService.cs new file mode 100644 index 00000000..a697e260 --- /dev/null +++ b/AsbCloudApp/Services/WellReport/IWellReportExportService.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Threading.Tasks; +using System.Threading; + +namespace AsbCloudApp.Services.WellReport; + +/// +/// Сервис экспорта отчёта +/// +public interface IWellReportExportService +{ + /// + /// Экспортировать + /// + /// + /// + /// + Task<(string Name, Stream File)?> ExportAsync(int idWell, CancellationToken token); +} \ No newline at end of file diff --git a/AsbCloudApp/Services/WellReport/IWellReportService.cs b/AsbCloudApp/Services/WellReport/IWellReportService.cs new file mode 100644 index 00000000..9bf61049 --- /dev/null +++ b/AsbCloudApp/Services/WellReport/IWellReportService.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.WellReport; + +namespace AsbCloudApp.Services.WellReport; + +/// +/// Сервис формирования отчёта +/// +public interface IWellReportService +{ + /// + /// Сформировать + /// + /// + /// + /// + Task GetAsync(int idWell, CancellationToken token); +} \ No newline at end of file diff --git a/AsbCloudInfrastructure.Tests/Services/WellReportServiceTest.cs b/AsbCloudInfrastructure.Tests/Services/WellReportServiceTest.cs new file mode 100644 index 00000000..2c3fe4c2 --- /dev/null +++ b/AsbCloudInfrastructure.Tests/Services/WellReportServiceTest.cs @@ -0,0 +1,335 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMaps.Operations; +using AsbCloudApp.Data.Trajectory; +using AsbCloudApp.Data.User; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudApp.Services.ProcessMaps.WellDrilling; +using AsbCloudInfrastructure.Services.WellReport; +using NSubstitute; +using System.Collections.Generic; +using System; +using System.Threading; +using Xunit; +using AsbCloudDb.Model; +using System.Linq; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Tests.Services; + +public class WellReportServiceTest +{ + private static readonly WellDto Well = new() + { + Caption = "Скважина №1", + Cluster = "Кластер A", + Deposit = "Месторождение Б", + Latitude = 55.7558, + Longitude = 37.6176, + Timezone = new SimpleTimezoneDto { Hours = 3 }, + WellType = "Разведочная", + IdWellType = 1, + IdCluster = 1001, + IdState = 1, + StartDate = DateTimeOffset.Now.AddMonths(-2), + LastTelemetryDate = DateTimeOffset.Now, + IdTelemetry = 12345, + }; + + private static readonly IEnumerable WellOperations = new[] + { + new WellOperationDto + { + Id = 1, + IdWell = 101, + IdWellSectionType = 1001, + IdType = 1, + IdCategory = 2001, + DepthStart = 1500, + DepthEnd = 1550, + DateStart = new DateTimeOffset(new DateTime(2024, 1, 13, 2, 0, 0)), + DurationHours = 48, + IdPlan = null, + IdParentCategory = 2001, + Day = 5 + }, + new WellOperationDto + { + Id = 4, + IdWell = 101, + IdWellSectionType = 1002, + IdType = 1, + IdCategory = 2001, + DepthStart = 1500, + DepthEnd = 1550, + DateStart = new DateTimeOffset(new DateTime(2024, 1, 10, 0, 0, 0)), + DurationHours = 48, + IdPlan = null, + IdParentCategory = 2001, + Day = 3 + }, + new WellOperationDto + { + Id = 2, + IdWell = 102, + IdWellSectionType = 1002, + IdType = 0, + IdCategory = 2002, + DepthStart = 2500, + DepthEnd = 2600, + DateStart = new DateTimeOffset(new DateTime(2024, 1, 10, 0, 0, 0)), + DurationHours = 72, + IdPlan = 1, + IdParentCategory = 3002, + Day = 3 + }, + new WellOperationDto + { + Id = 3, + IdWell = 103, + IdWellSectionType = 1003, + IdType = 0, + IdCategory = 2003, + DepthStart = 3500, + DepthEnd = 3600, + DateStart = new DateTimeOffset(new DateTime(2024, 1, 10, 1, 0, 0)), + DurationHours = 24, + IdPlan = 2, + IdParentCategory = 3003, + Day = 4 + } + }; + + private readonly IWellService wellService; + private readonly IWellOperationService wellOperationService; + private readonly IWellContactService wellContactService; + private readonly IProcessMapReportDrillingService processMapReportDrillingService; + private readonly ISubsystemService subsystemService; + + private readonly ITrajectoryRepository trajectoryPlanRepository; + private readonly ITrajectoryRepository trajectoryFactRepository; + + private readonly IChangeLogRepository + processMapPlanRotorRepository; + + private readonly IScheduleRepository scheduleRepository; + + private readonly WellReportService wellReportService; + + public WellReportServiceTest() + { + wellService = Substitute.For(); + wellOperationService = Substitute.For(); + wellContactService = Substitute.For(); + processMapReportDrillingService = Substitute.For(); + subsystemService = Substitute.For(); + trajectoryPlanRepository = Substitute.For>(); + trajectoryFactRepository = Substitute.For>(); + processMapPlanRotorRepository = + Substitute.For>(); + + scheduleRepository = Substitute.For(); + + wellService.GetOrDefaultAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(Well); + + wellOperationService + .GetAsync(Arg.Is(x => x.OperationType == WellOperation.IdOperationTypeFact), + Arg.Any()) + .Returns(WellOperations.Where(x => x.IdType == WellOperation.IdOperationTypeFact)); + + wellOperationService + .GetAsync(Arg.Is(x => x.OperationType == WellOperation.IdOperationTypePlan), + Arg.Any()) + .Returns(WellOperations.Where(x => x.IdType == WellOperation.IdOperationTypePlan)); + + wellReportService = new WellReportService(wellService, + wellOperationService, + wellContactService, + processMapReportDrillingService, + subsystemService, + trajectoryPlanRepository, + trajectoryFactRepository, + processMapPlanRotorRepository, + scheduleRepository); + } + + [Fact] + public async Task Returns_well_info_not_null() + { + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + Assert.NotNull(result.Well); + } + + [Fact] + public async Task Returns_contacts_not_empty() + { + //arrange + var contacts = new[] + { + new ContactDto() + { + Id = 1, + IdCompanyType = 2, + IdWell = 101, + FullName = "Ivan Petrov", + Email = "ivan.petrov@example.com", + Phone = "+7 (123) 456-78-90", + Position = "Chief Engineer", + Company = "test" + } + }; + + wellContactService.GetAllAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(contacts); + + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + Assert.Single(result.Contacts); + } + + [Fact] + public async Task Returns_valid_from_and_to_dates() + { + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + + var expectedDateFrom = new DateTimeOffset(new DateTime(2024, 1, 10, 0, 0, 0)); + var expectedDateTo = new DateTimeOffset(new DateTime(2024, 1, 11, 1, 0, 0)); + + Assert.Equal(expectedDateFrom, result.DateFrom); + Assert.Equal(expectedDateTo, result.DateTo); + } + + [Fact] + public async Task Returns_valid_days() + { + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + + Assert.Equal(4, result.Days.Plan); + Assert.Equal(5, result.Days.Fact); + } + + [Fact] + public async Task Returns_valid_vertical_depth() + { + //arrange + var planTrajectory = new TrajectoryGeoPlanDto + { + Id = 1, + IdWell = 123, + WellboreDepth = 1500.75, + ZenithAngle = 45.5, + AzimuthGeo = 120.0, + AzimuthMagnetic = 115.5, + VerticalDepth = 1480.3, + UpdateDate = DateTimeOffset.UtcNow, + IdUser = 42 + }; + + var factTrajectory = new TrajectoryGeoFactDto + { + Id = 1, + IdWell = 123, + WellboreDepth = 1500.75, + ZenithAngle = 45.5, + AzimuthGeo = 120.0, + AzimuthMagnetic = 115.5, + VerticalDepth = 1600, + UpdateDate = DateTimeOffset.UtcNow, + IdUser = 42 + }; + + trajectoryPlanRepository.GetAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(new[] { planTrajectory }); + + trajectoryFactRepository.GetAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(new[] { factTrajectory }); + + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + + Assert.Equal(result.VerticalDepth.Plan, 1480.3); + Assert.Equal(result.VerticalDepth.Fact, 1600); + } + + [Fact] + public async Task Returns_valid_without_ntp_days() + { + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + + Assert.Equal(4, result.WithoutNtpDays); + } + + + [Fact] + public async Task Returns_section_reports_not_empty() + { + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + Assert.Equal(2, result.SectionReports.Count()); + } + + [Fact] + public async Task Returns_driller_reports_not_empty() + { + //arrange + var schedules = new[] + { + new ScheduleDto + { + Id = 1, + IdDriller = 2001, + ShiftStart = new TimeDto(7), + ShiftEnd = new TimeDto(20), + DrillStart = new DateTimeOffset(2024, 9, 1, 7, 0, 0, TimeSpan.Zero), + DrillEnd = new DateTimeOffset(2024, 9, 1, 19, 0, 0, TimeSpan.Zero), + }, + new ScheduleDto + { + Id = 2, + IdDriller = 2002, + ShiftStart = new TimeDto(20), + ShiftEnd = new TimeDto(16), + DrillStart = new DateTimeOffset(2024, 9, 1, 7, 0, 0, TimeSpan.Zero), + DrillEnd = new DateTimeOffset(2024, 9, 1, 19, 0, 0, TimeSpan.Zero), + } + }; + + scheduleRepository.GetByIdWellAsync(Arg.Any(), Arg.Any()) + .ReturnsForAnyArgs(schedules); + + //act + var result = await wellReportService.GetAsync(1, CancellationToken.None); + + //assert + Assert.NotNull(result); + Assert.Equal(2, result.DrillerReports.Count()); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 2942a43a..327b70b7 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -53,6 +53,7 @@ + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 7e8d5867..ca66ebe8 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -47,6 +47,8 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; +using AsbCloudApp.Services.WellReport; +using AsbCloudInfrastructure.Services.WellReport; namespace AsbCloudInfrastructure; @@ -469,7 +471,9 @@ public static class DependencyInjection services.AddTransient, TrajectoryEditableRepository>(); services.AddTransient, TrajectoryEditableRepository>(); services.AddTransient(); - services.AddTransient(); + services.AddTransient, TrajectoryEditableRepository>(); + services.AddTransient, TrajectoryEditableRepository>(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient, CrudCacheRepositoryBase(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + return services; } } diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index f557d106..2fa7b639 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -197,34 +197,34 @@ public class WellOperationRepository : CrudRepositoryBase - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); - var query = dbContext.Set() - .Where(o => o.IdType == WellOperation.IdOperationTypeFact) - .GroupBy(o => o.IdWell) - .Select(group => new - { - IdWell = group.Key, - FirstFact = group.OrderBy(o => o.DateStart).First(), - LastFact = group.OrderBy(o => o.DateStart).Last(), - }); + var cachedDictionary = memoryCache.GetOrCreate(cacheKeyWellOperations, (entry) => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); + var query = dbContext.Set() + .Where(o => o.IdType == WellOperation.IdOperationTypeFact) + .GroupBy(o => o.IdWell) + .Select(group => new + { + IdWell = group.Key, + FirstFact = group.OrderBy(o => o.DateStart).First(), + LastFact = group.OrderBy(o => o.DateStart).Last(), + }); - var entities = query.ToArray(); + var entities = query.ToArray(); - var dictionary = entities.ToDictionary(s => s.IdWell, s => (Convert(s.FirstFact), Convert(s.LastFact))); - entry.Value = dictionary; + var dictionary = entities.ToDictionary(s => s.IdWell, s => (Convert(s.FirstFact), Convert(s.LastFact))); + entry.Value = dictionary; - return dictionary; + return dictionary; - })!; + })!; - var firstAndLast = cachedDictionary.GetValueOrDefault(idWell); - return firstAndLast; + var firstAndLast = cachedDictionary.GetValueOrDefault(idWell); + return firstAndLast; } - public override async Task DeleteAsync(int id, CancellationToken token) + public override async Task DeleteAsync(int id, CancellationToken token) { var result = await base.DeleteAsync(id, token); if (result > 0) diff --git a/AsbCloudInfrastructure/Services/WellReport/WellReport.xlsx b/AsbCloudInfrastructure/Services/WellReport/WellReport.xlsx new file mode 100644 index 00000000..9e3b5ffd Binary files /dev/null and b/AsbCloudInfrastructure/Services/WellReport/WellReport.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellReport/WellReportExportService.cs b/AsbCloudInfrastructure/Services/WellReport/WellReportExportService.cs new file mode 100644 index 00000000..55ec4fb0 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellReport/WellReportExportService.cs @@ -0,0 +1,383 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Data.Subsystems; +using AsbCloudApp.Data.User; +using AsbCloudApp.Data.WellReport; +using AsbCloudApp.Services.WellReport; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.WellReport; + +public class WellReportExportService : IWellReportExportService +{ + private static readonly IDictionary PlanOperatingModeRows = new Dictionary() + { + { 2, 79 }, // Направление план + { 3, 81 }, // Кондуктор план + { 9, 83 }, // Кондуктор 2 план + { 1, 85 }, // Пилотный ствол план + { 7, 87 }, // Пилотный ствол 2 план + { 13, 89 }, // Пилотный ствол 3 план + { 4, 91 }, // Эксплуатационная колонна план + { 10, 93 }, // Эксплуатационная колонна 2 план + { 6, 95 }, // Хвостовик план + { 12, 97 }, // Хвостовик 2 план + { 18, 99 }, // Хвостовик 3 план + { 24, 101 }, // Хвостовик 4 план + { 30, 103 }, // Хвостовик 5 план + { 34, 105 }, // Хвостовик 6 план + { 35, 107 }, // Хвостовик 7 план + { 36, 109 }, // Хвостовик 8 план + { 37, 111 }, // Хвостовик 9 план + { 38, 113 } // Хвостовик 10 план + }; + + private static readonly IDictionary FactOperatingModeRows = new Dictionary + { + { 2, 80 }, // Направление факт + { 3, 82 }, // Кондуктор факт + { 9, 84 }, // Кондуктор 2 факт + { 1, 86 }, // Пилотный ствол факт + { 7, 88 }, // Пилотный ствол 2 факт + { 13, 90 }, // Пилотный ствол 3 факт + { 4, 92 }, // Эксплуатационная колонна факт + { 10, 94 }, // Эксплуатационная колонна 2 факт + { 6, 96 }, // Хвостовик факт + { 12, 98 }, // Хвостовик 2 факт + { 18, 100 }, // Хвостовик 3 факт + { 24, 102 }, // Хвостовик 4 факт + { 30, 104 }, // Хвостовик 5 факт + { 34, 106 }, // Хвостовик 6 факт + { 35, 108 }, // Хвостовик 7 факт + { 36, 110 }, // Хвостовик 8 факт + { 37, 112 }, // Хвостовик 9 факт + { 38, 114 } // Хвостовик 10 факт + }; + + private static readonly IDictionary SubsystemRows = new Dictionary() + { + { 2, 140 }, // Направление + { 3, 141 }, // Кондуктор + { 9, 142 }, // Кондуктор 2 + { 1, 143 }, // Пилотный ствол + { 7, 144 }, // Пилотный ствол 2 + { 13, 145 }, // Пилотный ствол 3 + { 4, 146 }, // Эксплуатационная колонна + { 10, 147 }, // Эксплуатационная колонна 2 + { 6, 148 }, // Хвостовик + { 12, 149 }, // Хвостовик 2 + { 18, 150 }, // Хвостовик 3 + { 24, 151 }, // Хвостовик 4 + { 30, 152 }, // Хвостовик 5 + { 34, 153 }, // Хвостовик 6 + { 35, 154 }, // Хвостовик 7 + { 36, 155 }, // Хвостовик 8 + { 37, 156 }, // Хвостовик 9 + { 38, 157 } // Хвостовик 10 + }; + + private static readonly IDictionary SetpointsRows = new Dictionary() + { + { 2, 161 }, // Направление + { 3, 162 }, // Кондуктор + { 9, 163 }, // Кондуктор 2 + { 1, 164 }, // Пилотный ствол + { 7, 165 }, // Пилотный ствол 2 + { 13, 166 }, // Пилотный ствол 3 + { 4, 167 }, // Эксплуатационная колонна + { 10, 168 }, // Эксплуатационная колонна 2 + { 6, 169 }, // Хвостовик + { 12, 170 }, // Хвостовик 2 + { 18, 171 }, // Хвостовик 3 + { 24, 172 }, // Хвостовик 4 + { 30, 173 }, // Хвостовик 5 + { 34, 174 }, // Хвостовик 6 + { 35, 175 }, // Хвостовик 7 + { 36, 176 }, // Хвостовик 8 + { 37, 177 }, // Хвостовик 9 + { 38, 178 } // Хвостовик 10 + }; + + private const string TemplateName = "WellReport.xlsx"; + private const string SheetName = "Отчёт"; + + private readonly IWellReportService wellReportService; + + private const string DateFromCell = "D5"; + private const string DateToCell = "E5"; + private const string DaysPlanCell = "D6"; + private const string DaysFactCell = "E6"; + private const string WithoutNtpDaysCell = "D8"; + private const string WellBoreDepthPlanCell = "D12"; + private const string VerticalDepthPlanCell = "E12"; + private const string WellBoreDepthFactCell = "D13"; + private const string VerticalDepthFactCell = "E13"; + private const string WellCell = "I5"; + private const string ClusterCell = "I6"; + private const string DepositCell = "I7"; + private const string CustomerCell = "N5"; + + public WellReportExportService(IWellReportService wellReportService) + { + this.wellReportService = wellReportService; + } + + public async Task<(string Name, Stream File)?> ExportAsync(int idWell, CancellationToken token) + { + var report = await wellReportService.GetAsync(idWell, token); + + if (report == null) + return null; + + var stream = Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateName); + using var workbook = new XLWorkbook(stream); + + var sheet = workbook.GetWorksheet(SheetName); + + FillSheet(sheet, report); + + MemoryStream memoryStream = new(); + workbook.SaveAs(memoryStream, new SaveOptions { }); + memoryStream.Seek(0, SeekOrigin.Begin); + + var name = $"Отчёт по скважине {report.Well.Caption} куст {report.Well.Cluster}.xlsx"; + + return (name, memoryStream); + } + + private static void FillSheet(IXLWorksheet sheet, WellReportDto report) + { + sheet.Cell(DateFromCell).SetCellValue(report.DateFrom); + sheet.Cell(DateToCell).SetCellValue(report.DateTo); + sheet.Cell(DaysPlanCell).SetCellValue(report.Days.Plan); + sheet.Cell(DaysFactCell).SetCellValue(report.Days.Fact); + sheet.Cell(WithoutNtpDaysCell).SetCellValue(report.WithoutNtpDays); + sheet.Cell(WellBoreDepthPlanCell).SetCellValue(report.WellBoreDepth.Plan); + sheet.Cell(WellBoreDepthFactCell).SetCellValue(report.WellBoreDepth.Fact); + sheet.Cell(VerticalDepthPlanCell).SetCellValue(report.VerticalDepth.Plan); + sheet.Cell(VerticalDepthFactCell).SetCellValue(report.VerticalDepth.Fact); + sheet.Cell(WellCell).SetCellValue(report.Well.Caption); + sheet.Cell(ClusterCell).SetCellValue(report.Well.Cluster); + sheet.Cell(DepositCell).SetCellValue(report.Well.Deposit); + + var customer = report.Well.Companies.FirstOrDefault(x => x.IdCompanyType == 1); + sheet.Cell(CustomerCell).SetCellValue(customer?.Caption); + + FillContacts(sheet, report.Contacts); + FillSectionReports(sheet, report.SectionReports); + FillDrillerReports(sheet, report.DrillerReports); + } + + private static void FillContacts(IXLWorksheet sheet, IEnumerable contacts) + { + var positionsByCompanyType = new Dictionary() + { + { 7, "Супервайзер" }, + { 2, "Мастер" }, + { 3, "Инженер по автоматизации" }, + { 5, "Инженер по р-рам " }, + { 6, "Инженер ННБ" }, + { 14, "Инженер по долотам" }, + { 4, "Инженер ГТИ" }, + { 9, "Инженер по цементированию" } + }; + + const int positionColumn = 11; + const int fullNameColumn = 14; + const int companyColumn = 16; + const int phoneColumn = 18; + + contacts = contacts.OrderByDescending(x => x.Id) + .GroupBy(x => x.IdCompanyType) + .Select(x => x.First()); + + var row = 6; + + foreach (var contact in contacts) + { + if (!positionsByCompanyType.TryGetValue(contact.IdCompanyType, out var position)) + continue; + + sheet.Cell(row, positionColumn).SetCellValue(position); + sheet.Cell(row, fullNameColumn).SetCellValue(contact.FullName); + sheet.Cell(row, companyColumn).SetCellValue(contact.Company); + sheet.Cell(row, phoneColumn).SetCellValue(contact.Phone); + + row++; + } + } + + private static void FillDrillerReports(IXLWorksheet sheet, IEnumerable drillerReports) + { + drillerReports = drillerReports.OrderBy(x => x.Shedule.DrillStart); + + const int IdSubsystemAPDRotor = 11; + const int IdSubsystemAPDSlide = 12; + const int IdSubsystemOscillation = 65536; + + const int fullNameColumn = 1; + const int drillStartColumn = 5; + const int drillEndColumn = 6; + const int shiftStart = 7; + const int shiftEnd = 8; + const int kUsageApdRotorColumn = 9; + const int kUsageApdSlideColumn = 10; + const int kUsageOscillationColumn = 11; + + var row = 182; + + foreach (var drillingReport in drillerReports) + { + sheet.Cell(row, fullNameColumn).SetCellValue(drillingReport.Shedule.Driller?.FullName); + sheet.Cell(row, drillStartColumn).SetCellValue(drillingReport.Shedule.DrillStart); + sheet.Cell(row, drillEndColumn).SetCellValue(drillingReport.Shedule.DrillEnd); + sheet.Cell(row, shiftStart).SetCellValue(drillingReport.Shedule.ShiftStart.ToString()); + sheet.Cell(row, shiftEnd).SetCellValue(drillingReport.Shedule.ShiftEnd.ToString()); + + foreach (var subsystemStat in drillingReport.SubsystemsStat) + { + switch (subsystemStat.IdSubsystem) + { + case IdSubsystemAPDRotor: + sheet.Cell(row, kUsageApdRotorColumn).SetCellValue(subsystemStat.KUsage); + break; + case IdSubsystemAPDSlide: + sheet.Cell(row, kUsageApdSlideColumn).SetCellValue(subsystemStat.KUsage); + break; + case IdSubsystemOscillation: + sheet.Cell(row, kUsageOscillationColumn).SetCellValue(subsystemStat.KUsage); + break; + } + } + + row++; + } + } + + private static void FillSectionReports(IXLWorksheet sheet, IEnumerable sectionReports) + { + foreach (var sectionReport in sectionReports) + { + FillOperatingMode(sheet, sectionReport.IdSection, sectionReport.OperatingMode); + + var drillingBySetpoints = sectionReport.DrillingBySetpoints; + + if (drillingBySetpoints != null) + FillDrillingBySetpoints(sheet, sectionReport.IdSection, drillingBySetpoints); + + FillSubsystemsStat(sheet, sectionReport.IdSection, sectionReport.SubsystemsStat); + } + } + + private static void FillDrillingBySetpoints(IXLWorksheet sheet, int idSection, + DrillingBySetpointsDto drillingBySetpoints) + { + const int pressureColumn = 8; + const int axialLoadColumn = 9; + const int topDriveTorqueColumn = 10; + const int speedLimitColumn = 11; + + if (!SetpointsRows.TryGetValue(idSection, out var row)) + return; + + sheet.Cell(row, pressureColumn).SetCellValue(drillingBySetpoints.MetersByPressure); + sheet.Cell(row, axialLoadColumn).SetCellValue(drillingBySetpoints.MetersByLoad); + sheet.Cell(row, topDriveTorqueColumn).SetCellValue(drillingBySetpoints.MetersByTorque); + sheet.Cell(row, speedLimitColumn).SetCellValue(drillingBySetpoints.MetersBySpeed); + } + + private static void FillSubsystemsStat(IXLWorksheet sheet, int idSection, + IEnumerable subsystemsStat) + { + const int idSubsystemAPDRotor = 11; + const int idSubsystemAPDSlide = 12; + const int idSubsystemOscillation = 65536; + + const int kUsageApdRotorColumn = 3; + const int kUsageApdSlideColumn = 4; + const int kUsageOscillationColumn = 5; + + const int sumDepthIntervalApdRotorColumn = 14; + const int sumDepthIntervalApdSlideColumn = 15; + const int sumDepthIntervalApdOscillation = 17; + + if (!SubsystemRows.TryGetValue(idSection, out var row)) + return; + + foreach (var subsystemStat in subsystemsStat) + { + switch (subsystemStat.IdSubsystem) + { + case idSubsystemAPDRotor: + sheet.Cell(row, kUsageApdRotorColumn).SetCellValue(subsystemStat.KUsage); + sheet.Cell(row, sumDepthIntervalApdRotorColumn).SetCellValue(subsystemStat.SumDepthInterval); + break; + case idSubsystemAPDSlide: + sheet.Cell(row, kUsageApdSlideColumn).SetCellValue(subsystemStat.KUsage); + sheet.Cell(row, sumDepthIntervalApdSlideColumn).SetCellValue(subsystemStat.SumDepthInterval); + break; + case idSubsystemOscillation: + sheet.Cell(row, kUsageOscillationColumn).SetCellValue(subsystemStat.KUsage); + sheet.Cell(row, sumDepthIntervalApdOscillation).SetCellValue(subsystemStat.SumDepthInterval); + break; + } + } + } + + private static void FillOperatingMode(IXLWorksheet sheet, int idSection, + PlanFactDto operatingMode) + { + const int depthStartColumn = 3; + const int depthEndColumn = 4; + const int ropMinColumn = 6; + const int ropMaxColumn = 7; + const int ropAvgColumn = 8; + const int weightOnBitMinColumn = 9; + const int weightOnBitMaxColumn = 10; + const int weightOnBitAvgColumn = 11; + const int driveTorqueMinColumn = 12; + const int driveTorqueMaxColumn = 13; + const int driveTorqueAvgColumn = 14; + const int differentialPressureMinColumn = 15; + const int differentialPressureMaxColumn = 16; + const int differentialPressureAvgColumn = 17; + const int frowRateMinColumn = 18; + const int frowRateMaxColumn = 19; + + if (!PlanOperatingModeRows.TryGetValue(idSection, out var planRow)) + return; + + if (!FactOperatingModeRows.TryGetValue(idSection, out var factRow)) + return; + + sheet.Cell(planRow, depthStartColumn).SetCellValue(operatingMode.Plan?.DepthStart); + sheet.Cell(planRow, depthEndColumn).SetCellValue(operatingMode.Plan?.DepthEnd); + + sheet.Cell(planRow, ropMinColumn).SetCellValue(operatingMode.Plan?.RopMin); + sheet.Cell(planRow, ropMaxColumn).SetCellValue(operatingMode.Plan?.RopMax); + sheet.Cell(planRow, ropAvgColumn).SetCellValue(operatingMode.Plan?.RopAvg); + + sheet.Cell(planRow, weightOnBitMinColumn).SetCellValue(operatingMode.Plan?.WeightOnBitMin); + sheet.Cell(planRow, weightOnBitMaxColumn).SetCellValue(operatingMode.Plan?.WeightOnBitMax); + sheet.Cell(planRow, weightOnBitAvgColumn).SetCellValue(operatingMode.Plan?.WeightOnBitAvg); + + sheet.Cell(planRow, driveTorqueMinColumn).SetCellValue(operatingMode.Plan?.DriveTorqueMin); + sheet.Cell(planRow, driveTorqueMaxColumn).SetCellValue(operatingMode.Plan?.DriveTorqueMax); + sheet.Cell(planRow, driveTorqueAvgColumn).SetCellValue(operatingMode.Plan?.DriveTorqueAvg); + + sheet.Cell(planRow, differentialPressureMinColumn).SetCellValue(operatingMode.Plan?.DifferentialPressureMin); + sheet.Cell(planRow, differentialPressureMaxColumn).SetCellValue(operatingMode.Plan?.DifferentialPressureMax); + sheet.Cell(planRow, differentialPressureAvgColumn).SetCellValue(operatingMode.Plan?.DifferentialPressureAvg); + + sheet.Cell(planRow, frowRateMinColumn).SetCellValue(operatingMode.Plan?.FrowRateMin); + sheet.Cell(planRow, frowRateMaxColumn).SetCellValue(operatingMode.Plan?.FrowRateMax); + + sheet.Cell(factRow, depthStartColumn).SetCellValue(operatingMode.Fact?.DepthStart); + sheet.Cell(factRow, depthEndColumn).SetCellValue(operatingMode.Fact?.DepthEnd); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellReport/WellReportService.cs b/AsbCloudInfrastructure/Services/WellReport/WellReportService.cs new file mode 100644 index 00000000..cf9ef0fe --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellReport/WellReportService.cs @@ -0,0 +1,234 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMaps.Operations; +using AsbCloudApp.Data.Trajectory; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Data.WellReport; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudApp.Services.ProcessMaps.WellDrilling; +using AsbCloudApp.Services.WellReport; +using AsbCloudDb.Model; + +namespace AsbCloudInfrastructure.Services.WellReport; + +public class WellReportService : IWellReportService +{ + private readonly IWellService wellService; + private readonly IWellOperationService wellOperationService; + private readonly IWellContactService wellContactService; + private readonly IProcessMapReportDrillingService processMapReportDrillingService; + private readonly ISubsystemService subsystemService; + + private readonly ITrajectoryRepository trajectoryPlanRepository; + private readonly ITrajectoryRepository trajectoryFactRepository; + + private readonly IChangeLogRepository + processMapPlanRotorRepository; + + private readonly IScheduleRepository scheduleRepository; + + private IEnumerable factWellOperations; + private IEnumerable planWellOperations; + + public WellReportService(IWellService wellService, + IWellOperationService wellOperationService, + IWellContactService wellContactService, + IProcessMapReportDrillingService processMapReportDrillingService, + ISubsystemService subsystemService, + ITrajectoryRepository trajectoryPlanRepository, + ITrajectoryRepository trajectoryFactRepository, + IChangeLogRepository processMapPlanRotorRepository, + IScheduleRepository scheduleRepository) + { + this.wellService = wellService; + this.wellOperationService = wellOperationService; + this.wellContactService = wellContactService; + this.processMapReportDrillingService = processMapReportDrillingService; + this.subsystemService = subsystemService; + this.trajectoryPlanRepository = trajectoryPlanRepository; + this.trajectoryFactRepository = trajectoryFactRepository; + this.processMapPlanRotorRepository = processMapPlanRotorRepository; + this.scheduleRepository = scheduleRepository; + } + + public async Task GetAsync(int idWell, CancellationToken token) + { + var well = await wellService.GetOrDefaultAsync(idWell, token); + + if (well == null) + return null; + + await InitWellOperations(idWell, token); + + var wellContactRequest = new WellContactRequest + { + IdsWells = new[] { idWell }, + }; + + var contacts = await wellContactService.GetAllAsync(wellContactRequest, token); + + var sectionReports = await GetSectionReportsAsync(idWell, token); + var drillerReports = await GetDrillerReportsAsync(idWell, token); + + var firstFactOperation = factWellOperations.MinByOrDefault(x => x.DateStart); + var lastFactOperation = factWellOperations.MaxByOrDefault(x => x.DateStart); + + var lastPlanOperation = planWellOperations.MaxByOrDefault(x => x.DateStart); + + var planTrajectories = await trajectoryPlanRepository.GetAsync(idWell, token); + var factTrajectories = await trajectoryFactRepository.GetAsync(idWell, token); + + var factOperationsWithoutNpt = factWellOperations.Where(o => WellOperationCategory.NonProductiveTimeSubIds + .Any(x => x != o.IdCategory)); + + return new WellReportDto + { + Well = well, + DateFrom = firstFactOperation?.DateStart, + DateTo = lastPlanOperation?.DateStart.AddHours(lastPlanOperation.DurationHours), + Days = new PlanFactDto + { + Plan = lastPlanOperation?.Day, + Fact = lastFactOperation?.Day + }, + WellBoreDepth = new PlanFactDto + { + Plan = planWellOperations.MaxOrDefault(x => x.DepthEnd), + Fact = factWellOperations.MaxOrDefault(x => x.DepthEnd) + }, + VerticalDepth = new PlanFactDto + { + Plan = planTrajectories.Max(x => x.VerticalDepth), + Fact = factTrajectories.Max(x => x.VerticalDepth) + }, + WithoutNtpDays = factOperationsWithoutNpt.Sum(x => x.DurationHours) / 24, + Contacts = contacts, + SectionReports = sectionReports, + DrillerReports = drillerReports, + }; + } + + private async Task InitWellOperations(int idWell, CancellationToken token) + { + var request = new WellOperationRequest(new[] { idWell }) + { + OperationType = WellOperation.IdOperationTypeFact + }; + + factWellOperations = await wellOperationService.GetAsync(request, token); + + request.OperationType = WellOperation.IdOperationTypePlan; + + planWellOperations = await wellOperationService.GetAsync(request, token); + } + + private async Task> GetSectionReportsAsync(int idWell, CancellationToken token) + { + var factWellOperationsBySection = factWellOperations.GroupBy(x => x.IdWellSectionType); + + var processMapPlanRequest = new ProcessMapPlanBaseRequestWithWell(idWell); + + var processMapPlanRotorBySection = + (await processMapPlanRotorRepository.GetCurrent(processMapPlanRequest, token)) + .GroupBy(x => x.IdWellSectionType) + .ToDictionary(x => x.Key, x => x.AsEnumerable()); + + var dataSaubStatRequest = new DataSaubStatRequest(); + + var processMapReportBySection = + (await processMapReportDrillingService.GetAsync(idWell, dataSaubStatRequest, token)) + .GroupBy(x => x.IdWellSectionType) + .ToDictionary(x => x.Key, x => x.AsEnumerable()); + + var sectionReports = new List(); + + foreach (var group in factWellOperationsBySection) + { + var subsystemRequest = new SubsystemRequest + { + IdWell = idWell, + GeDepth = group.Min(y => y.DepthStart), + LeDepth = group.Max(y => y.DepthEnd) + }; + + var sectionReport = new SectionReportDto + { + IdSection = group.Key, + SubsystemsStat = await subsystemService.GetStatAsync(subsystemRequest, token), + OperatingMode = new PlanFactDto + { + Fact = new OperatingModeDto + { + DepthStart = group.Min(w => w.DepthStart), + DepthEnd = group.Max(w => w.DepthEnd) + } + } + }; + + if (processMapPlanRotorBySection.TryGetValue(group.Key, out var processMapPlanRotor)) + sectionReport.OperatingMode.Plan = new OperatingModeDto + { + DepthStart = processMapPlanRotor.Min(p => p.DepthStart), + DepthEnd = processMapPlanRotor.Max(p => p.DepthEnd), + RopMin = processMapPlanRotor.Min(p => p.RopMax), + RopMax = processMapPlanRotor.Max(p => p.RopMax), + RopAvg = processMapPlanRotor.Average(p => p.RopMax), + WeightOnBitMin = processMapPlanRotor.Min(p => p.WeightOnBit), + WeightOnBitMax = processMapPlanRotor.Max(p => p.WeightOnBitMax), + WeightOnBitAvg = processMapPlanRotor.Average(p => p.WeightOnBit), + DriveTorqueMin = processMapPlanRotor.Min(p => p.TopDriveTorque), + DriveTorqueMax = processMapPlanRotor.Max(p => p.TopDriveTorqueMax), + DriveTorqueAvg = processMapPlanRotor.Average(p => p.TopDriveTorque), + DifferentialPressureMin = processMapPlanRotor.Min(p => p.DifferentialPressure), + DifferentialPressureMax = processMapPlanRotor.Max(p => p.DifferentialPressureMax), + DifferentialPressureAvg = processMapPlanRotor.Average(p => p.DifferentialPressure), + FrowRateMin = processMapPlanRotor.Min(p => p.FlowRate), + FrowRateMax = processMapPlanRotor.Max(p => p.FlowRateMax) + }; + + if (processMapReportBySection.TryGetValue(group.Key, out var processMapReport)) + sectionReport.DrillingBySetpoints = new DrillingBySetpointsDto + { + MetersByPressure = processMapReport.Sum(x => x.DeltaDepth * x.PressureDiff.SetpointUsage / 100), + MetersByLoad = processMapReport.Sum(x => x.DeltaDepth * x.AxialLoad.SetpointUsage / 100), + MetersByTorque = processMapReport.Sum(x => x.DeltaDepth * x.TopDriveTorque.SetpointUsage / 100), + MetersBySpeed = processMapReport.Sum(x => x.DeltaDepth * x.SpeedLimit.SetpointUsage / 100) + }; + + sectionReports.Add(sectionReport); + } + + return sectionReports; + } + + private async Task> GetDrillerReportsAsync(int idWell, CancellationToken token) + { + var schedules = await scheduleRepository.GetByIdWellAsync(idWell, token); + + var result = new List(); + + foreach (var schedule in schedules) + { + var subsystemRequest = new SubsystemRequest + { + IdWell = idWell, + IdDriller = schedule.IdDriller + }; + + var drillerReport = new DrillerReportDto + { + Shedule = schedule, + SubsystemsStat = await subsystemService.GetStatAsync(subsystemRequest, token) + }; + + result.Add(drillerReport); + } + + return result; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/XLExtentions.cs b/AsbCloudInfrastructure/XLExtentions.cs index 7c85c0a6..62992ec2 100644 --- a/AsbCloudInfrastructure/XLExtentions.cs +++ b/AsbCloudInfrastructure/XLExtentions.cs @@ -15,22 +15,25 @@ public static class XLExtentions workbook.Worksheets.FirstOrDefault(ws => string.Equals(ws.Name.Trim(), sheetName.Trim(), StringComparison.CurrentCultureIgnoreCase)) ?? throw new FileFormatException(string.Format(NotFoundSheetTemplate, sheetName)); - public static IXLCell SetCellValue(this IXLCell cell, T value, string? format = null) + public static IXLCell SetCellValue(this IXLCell cell, T? value, string? format = null) { - if (value is DateTime || value is DateTimeOffset) - { - cell.Style.DateFormat.Format = format ?? "DD.MM.YYYY HH:MM:SS"; + if (value == null) + return cell; - if (value is DateTimeOffset dateTimeOffset) - { - cell.Value = XLCellValue.FromObject(dateTimeOffset.DateTime); - return cell; - } - } + if (value is DateTime || value is DateTimeOffset) + { + cell.Style.DateFormat.Format = format ?? "DD.MM.YYYY HH:MM:SS"; - cell.Value = XLCellValue.FromObject(value); + if (value is DateTimeOffset dateTimeOffset) + { + cell.Value = XLCellValue.FromObject(dateTimeOffset.DateTime); + return cell; + } + } - return cell; + cell.Value = XLCellValue.FromObject(value); + + return cell; } public static IXLCell SetHyperlink(this IXLCell cell, string link) diff --git a/AsbCloudWebApi/Controllers/WellController.cs b/AsbCloudWebApi/Controllers/WellController.cs index 98e44773..d6d97547 100644 --- a/AsbCloudWebApi/Controllers/WellController.cs +++ b/AsbCloudWebApi/Controllers/WellController.cs @@ -1,13 +1,13 @@ using AsbCloudApp.Data; -using AsbCloudApp.Exceptions; using AsbCloudApp.Services; -using AsbCloudDb.Model; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Services.WellReport; +using Microsoft.AspNetCore.Http; namespace AsbCloudWebApi.Controllers; @@ -20,11 +20,13 @@ namespace AsbCloudWebApi.Controllers; public class WellController : ControllerBase { private readonly IWellService wellService; + private readonly IWellReportExportService wellReportExportService; - public WellController(IWellService wellService) - { - this.wellService = wellService; - } + public WellController(IWellService wellService, IWellReportExportService wellReportExportService) + { + this.wellService = wellService; + this.wellReportExportService = wellReportExportService; + } /// /// Возвращает список доступных скважин @@ -149,4 +151,25 @@ public class WellController : ControllerBase return Ok(result); } + + //TODO: навзание пока такое. У нас в API уже есть метод с такой сигнатурой. + + /// + /// Получить отчёт по скважине + /// + /// + /// + /// + [HttpGet("{idWell}/report/export")] + [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task ExportAsync(int idWell, CancellationToken token) + { + var report = await wellReportExportService.ExportAsync(idWell, token); + + if (report is null) + return NoContent(); + + return File(report.Value.File, "application/octet-stream", report.Value.Name); + } }