diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportDto.cs
new file mode 100644
index 00000000..ea3b3c53
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportDto.cs
@@ -0,0 +1,28 @@
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// DTO авто-сгенерированного суточного отчёта
+///
+public class AutoGeneratedDailyReportDto : AutoGeneratedDailyReportInfoDto
+{
+ ///
+ /// Блок заголовка
+ ///
+ public HeadBlockDto Head { get; set; } = null!;
+
+ //TODO: поля не должны быть массивами
+ ///
+ /// Блок подсистем
+ ///
+ public SubsystemRecordDto[] Subsystems { get; set; } = null!;
+
+ ///
+ /// Блок ограничивающих параметров
+ ///
+ public LimitingParameterRecordDto[] LimitingParameters { get; set; } = null!;
+
+ ///
+ /// Баланс времени
+ ///
+ public TimeBalanceRecordDto[] TimeBalance { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportInfoDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportInfoDto.cs
new file mode 100644
index 00000000..0034879a
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/AutoGeneratedDailyReportInfoDto.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// Базовая информация о суточном отчёте
+///
+public class AutoGeneratedDailyReportInfoDto
+{
+ ///
+ /// Дата формирования отчёта
+ ///
+ public DateOnly ReportDate { get; set; }
+
+ ///
+ /// Название файла
+ ///
+ public string FileName { get; set; } = null!;
+
+ ///
+ /// Размер файла
+ ///
+ public int FileSize { get; set; }
+
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/HeadBlockDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/HeadBlockDto.cs
new file mode 100644
index 00000000..43ff5246
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/HeadBlockDto.cs
@@ -0,0 +1,37 @@
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// Блок заголовка
+///
+public class HeadBlockDto
+{
+ ///
+ /// Название скважины
+ ///
+ public string Well { get; set; } = null!;
+
+ ///
+ /// Название куста
+ ///
+ public string Cluster { get; set; } = null!;
+
+ ///
+ /// Заказчик
+ ///
+ public string Customer { get; set; } = null!;
+
+ ///
+ /// Месторождение
+ ///
+ public string Deposit { get; set; } = null!;
+
+ ///
+ /// Глубина забоя на дату начала интервала
+ ///
+ public double DepthFrom { get; set; }
+
+ ///
+ /// Глубина забоя на дату окончания интервала
+ ///
+ public double DepthTo { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/LimitingParameterRecordDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/LimitingParameterRecordDto.cs
new file mode 100644
index 00000000..e62b32d9
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/LimitingParameterRecordDto.cs
@@ -0,0 +1,27 @@
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// Блок ограничивающих параметров
+///
+public class LimitingParameterRecordDto
+{
+ ///
+ /// Время использования, мин
+ ///
+ public double Hours { get; set; }
+
+ ///
+ /// Проходка, м
+ ///
+ public double Depth { get; set; }
+
+ ///
+ /// Название ограничивающего параметра
+ ///
+ public string NameFeedRegulator { get; set; } = null!;
+
+ ///
+ /// Процент по проходке, %
+ ///
+ public double PercentDepth { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/SubsystemRecordDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/SubsystemRecordDto.cs
new file mode 100644
index 00000000..34b2e4a7
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/SubsystemRecordDto.cs
@@ -0,0 +1,27 @@
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// Блок подсистем
+///
+public class SubsystemRecordDto
+{
+ ///
+ /// Название подсистемы
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Использование, %
+ ///
+ public double KUsage { get; set; }
+
+ ///
+ /// Время работы, ч
+ ///
+ public double UsedTimeHours { get; set; }
+
+ ///
+ /// Проходка
+ ///
+ public double Depth { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/AutogeneratedDailyReport/TimeBalanceRecordDto.cs b/AsbCloudApp/Data/AutogeneratedDailyReport/TimeBalanceRecordDto.cs
new file mode 100644
index 00000000..a6ac9845
--- /dev/null
+++ b/AsbCloudApp/Data/AutogeneratedDailyReport/TimeBalanceRecordDto.cs
@@ -0,0 +1,17 @@
+namespace AsbCloudApp.Data.AutogeneratedDailyReport;
+
+///
+/// Баланс времени
+///
+public class TimeBalanceRecordDto
+{
+ ///
+ /// Название операции
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Продолжительность, часы
+ ///
+ public double DurationHours { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Requests/AutoGeneratedDailyReportRequest.cs b/AsbCloudApp/Requests/AutoGeneratedDailyReportRequest.cs
new file mode 100644
index 00000000..5e7dae7c
--- /dev/null
+++ b/AsbCloudApp/Requests/AutoGeneratedDailyReportRequest.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace AsbCloudApp.Requests;
+
+///
+/// Параметры запроса для получения авто-генерируемых суточных отчётов
+///
+public class AutoGeneratedDailyReportRequest : RequestBase
+{
+ ///
+ /// Дата начала периода
+ ///
+ public DateOnly? StartDate { get; set; }
+
+ ///
+ /// Дата конца периода
+ ///
+ public DateOnly? FinishDate { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportMakerService.cs b/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportMakerService.cs
new file mode 100644
index 00000000..8571e356
--- /dev/null
+++ b/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportMakerService.cs
@@ -0,0 +1,20 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+
+namespace AsbCloudApp.Services.AutoGeneratedDailyReports;
+
+///
+/// Сервис для генерации файлов авто-генерируемых суточный отчётов
+///
+public interface IAutoGeneratedDailyReportMakerService
+{
+ ///
+ /// Генерация файла
+ ///
+ ///
+ ///
+ ///
+ Task MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportService.cs b/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportService.cs
new file mode 100644
index 00000000..16cdaed6
--- /dev/null
+++ b/AsbCloudApp/Services/AutoGeneratedDailyReports/IAutoGeneratedDailyReportService.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using AsbCloudApp.Requests;
+
+namespace AsbCloudApp.Services.AutoGeneratedDailyReports;
+
+///
+/// Сервис для работы с авто-генерируемыми суточными отчётами
+///
+public interface IAutoGeneratedDailyReportService
+{
+ ///
+ /// Список файлов суточных отчётов
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task> GetListAsync(int idWell,
+ AutoGeneratedDailyReportRequest request,
+ CancellationToken cancellationToken);
+
+ ///
+ /// Генерация файла с отчётом
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task<(string fileName, Stream stream)> GenerateReportAsync(int idWell, DateOnly reportDate,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
index e6a5edcb..d807a6c8 100644
--- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
+++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
@@ -36,6 +36,7 @@
+
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index ed88ec5f..601d05e7 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -23,7 +23,9 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
+using AsbCloudApp.Services.AutoGeneratedDailyReports;
using AsbCloudApp.Services.Notifications;
+using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
namespace AsbCloudInfrastructure
{
@@ -215,7 +217,10 @@ namespace AsbCloudInfrastructure
services.AddTransient, WitsRecordRepository>();
services.AddTransient, WitsRecordRepository>();
services.AddTransient, WitsRecordRepository>();
- services.AddTransient, WitsRecordRepository>();
+ services.AddTransient, WitsRecordRepository>();
+
+ services.AddTransient();
+ services.AddTransient();
return services;
}
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportMakerService.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportMakerService.cs
new file mode 100644
index 00000000..0f1c7076
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportMakerService.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using AsbCloudApp.Services.AutoGeneratedDailyReports;
+using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
+
+public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMakerService
+{
+ private readonly IEnumerable blockWriters = new List()
+ {
+ new HeadExcelBlockWriter(),
+ new SubsystemExcelBlockWriter(),
+ new LimitingParameterExcelBlockWriter(),
+ new TimeBalanceExcelBlockWriter()
+ };
+
+ public async Task MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken)
+ {
+ using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
+
+ using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
+
+ AddToWorkbook(workbook, report);
+
+ MemoryStream memoryStream = new MemoryStream();
+ workbook.SaveAs(memoryStream, new SaveOptions { });
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ return memoryStream;
+ }
+
+ private async Task GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
+ {
+ var resourceName = Assembly.GetExecutingAssembly()
+ .GetManifestResourceNames()
+ .FirstOrDefault(n => n.EndsWith("AutogeneratedDailyReportTemplate.xlsx"))!;
+
+ using var stream = Assembly.GetExecutingAssembly()
+ .GetManifestResourceStream(resourceName)!;
+
+ var memoryStream = new MemoryStream();
+ await stream.CopyToAsync(memoryStream, cancellationToken);
+ memoryStream.Position = 0;
+
+ return memoryStream;
+ }
+
+ private void AddToWorkbook(XLWorkbook workbook, AutoGeneratedDailyReportDto report)
+ {
+ const string sheetName = "Рапорт";
+
+ var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName)
+ ?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
+
+ foreach (var blockBuilder in blockWriters)
+ {
+ blockBuilder.Write(sheet, report);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs
new file mode 100644
index 00000000..4012ebdf
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using AsbCloudApp.Data.SAUB;
+using AsbCloudApp.Data.Subsystems;
+using AsbCloudApp.Exceptions;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudApp.Services.AutoGeneratedDailyReports;
+using AsbCloudApp.Services.Subsystems;
+using AsbCloudDb.Model;
+using AsbCloudInfrastructure.Services.SAUB;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
+
+public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
+{
+ private const string fileNameTemplate = "Суточный_отчёт_по_скважине_{0}_куст_{1}_от_{2}.xlsx";
+
+ private readonly IWellService wellService;
+ private readonly IWellOperationRepository wellOperationRepository;
+ private readonly TelemetryDataCache telemetryDataCache;
+ private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
+ private readonly ICrudRepository subsystemRepository;
+ private readonly ILimitingParameterService limitingParameterService;
+ private readonly IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService;
+
+ public AutoGeneratedDailyReportService(IWellService wellService,
+ IWellOperationRepository wellOperationRepository,
+ TelemetryDataCache telemetryDataCache,
+ ISubsystemOperationTimeService subsystemOperationTimeService,
+ ICrudRepository subsystemRepository,
+ ILimitingParameterService limitingParameterService,
+ IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService)
+ {
+ this.wellOperationRepository = wellOperationRepository;
+ this.wellService = wellService;
+ this.telemetryDataCache = telemetryDataCache;
+ this.subsystemOperationTimeService = subsystemOperationTimeService;
+ this.subsystemRepository = subsystemRepository;
+ this.limitingParameterService = limitingParameterService;
+ this.autoGeneratedDailyReportMakerService = autoGeneratedDailyReportMakerService;
+ }
+
+ public async Task> GetListAsync(int idWell,
+ AutoGeneratedDailyReportRequest request,
+ CancellationToken cancellationToken)
+ {
+ var result = new PaginationContainer
+ {
+ Skip = request.Skip ?? 0,
+ Take = request.Take ?? 10,
+ Items = Enumerable.Empty()
+ };
+
+ var reports = new List();
+
+ var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
+ ?? throw new ArgumentInvalidException("Скважина не найдена", nameof(idWell));
+
+ if (!well.IdTelemetry.HasValue)
+ throw new ArgumentInvalidException("Телеметрия для скважины отсутствует", nameof(idWell));
+
+ var datesRange = telemetryDataCache.GetOrDefaultDataDateRange(well.IdTelemetry.Value);
+
+ if (datesRange is null)
+ return result;
+
+ result.Count = (int)(Math.Ceiling((datesRange.To - DateTime.UnixEpoch).TotalDays) - Math.Floor((datesRange.From - DateTime.UnixEpoch).TotalDays));
+
+ if (request.StartDate.HasValue)
+ {
+ var startDate = new DateTime(request.StartDate.Value.Year, request.StartDate.Value.Month,
+ request.StartDate.Value.Day);
+
+ if(startDate.Date >= datesRange.From.Date)
+ datesRange.From = startDate;
+ }
+
+ if (request.FinishDate.HasValue)
+ {
+ var finishDate = new DateTime(request.FinishDate.Value.Year, request.FinishDate.Value.Month,
+ request.FinishDate.Value.Day);
+
+ if (finishDate.Date <= datesRange.To.Date)
+ datesRange.To = finishDate;
+ }
+
+ for (int day = result.Skip; (day - result.Skip) < result.Take && (datesRange.From.AddDays(day)) <= datesRange.To; day++)
+ {
+ var dateFrom = datesRange.From.AddDays(day);
+
+ reports.Add(new AutoGeneratedDailyReportDto
+ {
+ FileName = string.Format(fileNameTemplate, well.Caption, well.Cluster, DateOnly.FromDateTime(dateFrom)),
+ ReportDate = DateOnly.FromDateTime(dateFrom),
+ FileSize = GetFileSize() / 1024,
+ });
+ }
+
+ result.Items = reports;
+
+ return result;
+ }
+
+ public async Task<(string fileName, Stream stream)> GenerateReportAsync(int idWell, DateOnly reportDate,
+ CancellationToken cancellationToken)
+ {
+ var startDate = new DateTime(reportDate.Year, reportDate.Month, reportDate.Day);
+ var finishDate = startDate.AddDays(1);
+
+ var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
+ ?? throw new ArgumentInvalidException("Скважина не найдена", nameof(idWell));
+
+ if (!well.IdTelemetry.HasValue)
+ throw new ArgumentInvalidException("Телеметрия для скважины отсутствует", nameof(idWell));
+
+ var factOperations = (await GetFactOperationsAsync(well.Id, startDate, finishDate,
+ cancellationToken)).ToArray();
+
+ var report = new AutoGeneratedDailyReportDto
+ {
+ FileName = string.Format(fileNameTemplate, well.Caption, well.Cluster, reportDate),
+ FileSize = GetFileSize() / 1024,
+ ReportDate = reportDate,
+ Head = CreateHeadBlock(well, reportDate, factOperations),
+ Subsystems = (await CreateSubsystemBlockAsync(idWell, startDate, finishDate, cancellationToken)).ToArray(),
+ LimitingParameters = (await CreateLimitingParameterBlockAsync(idWell, startDate, finishDate, cancellationToken)).ToArray(),
+ TimeBalance = factOperations.GroupBy(w => w.CategoryName).Select(x => new TimeBalanceRecordDto
+ {
+ Name = x.Key ?? "Название операции отсутствует",
+ DurationHours = x.Sum(o => o.DurationHours)
+ }).ToArray(),
+ };
+
+ var stream = await autoGeneratedDailyReportMakerService.MakeReportAsync(report, cancellationToken);
+
+ return (report.FileName, stream);
+ }
+
+ private HeadBlockDto CreateHeadBlock(WellDto well, DateOnly reportDate, WellOperationDto[] factOperations)
+ {
+ var customer = well.Companies.FirstOrDefault(company => company.IdCompanyType == 1);
+
+ return new HeadBlockDto
+ {
+ Customer = customer?.Caption ?? string.Empty,
+ Deposit = well.Deposit ?? string.Empty,
+ Cluster = well.Cluster ?? string.Empty,
+ Well = well.Caption,
+ DepthFrom = factOperations.FirstOrDefault()?.DepthStart ?? 0.00,
+ DepthTo = factOperations.LastOrDefault()?.DepthEnd ?? 0.00
+ };
+ }
+
+ private async Task> CreateSubsystemBlockAsync(int idWell, DateTime startDate,
+ DateTime finishDate,
+ CancellationToken cancellationToken)
+ {
+ var subsystems = await subsystemRepository.GetAllAsync(cancellationToken);
+ var subsystemStats = await GetSubsystemStatsAsync(idWell, startDate, finishDate,
+ cancellationToken);
+
+ return subsystems.Select(subsystem =>
+ {
+ var subsytemStat = subsystemStats?.FirstOrDefault(s => s.IdSubsystem == subsystem.Id);
+
+ return new SubsystemRecordDto
+ {
+ Name = subsystem.Name,
+ KUsage = subsytemStat?.KUsage ?? 0.00,
+ UsedTimeHours = subsytemStat?.UsedTimeHours ?? 0.00,
+ Depth = subsytemStat?.SumDepthInterval ?? 0.00,
+ };
+ });
+ }
+
+ private async Task> CreateLimitingParameterBlockAsync(int idWell,
+ DateTime startDate, DateTime finishDate, CancellationToken cancellationToken)
+ {
+ var limitingParameterStats = (await GetLimitingParameterStatsAsync(idWell,
+ startDate, finishDate, cancellationToken)).ToArray();
+
+ var sumDepths = limitingParameterStats.Sum(x => x.Depth);
+
+ return limitingParameterStats.Select(l => new LimitingParameterRecordDto
+ {
+ NameFeedRegulator = l.NameFeedRegulator,
+ Hours = l.TotalMinutes,
+ PercentDepth = l.Depth / sumDepths,
+ Depth = l.Depth,
+ });
+ }
+
+ private Task> GetFactOperationsAsync(int idWell, DateTime startDate,
+ DateTime finishDate, CancellationToken cancellationToken)
+ {
+ var request = new WellOperationRequest
+ {
+ IdWell = idWell,
+ OperationType = WellOperation.IdOperationTypeFact,
+ GeDate = startDate,
+ LtDate = finishDate,
+ SortFields = new[] { "DateStart asc" },
+ };
+
+ return wellOperationRepository.GetAsync(request, cancellationToken);
+ }
+
+ private Task?> GetSubsystemStatsAsync(int idWell, DateTime startDate,
+ DateTime finishDate, CancellationToken cancellationToken)
+ {
+ var request = new SubsystemOperationTimeRequest
+ {
+ IdWell = idWell,
+ GtDate = startDate,
+ LtDate = finishDate,
+ };
+
+ return subsystemOperationTimeService.GetStatAsync(request, cancellationToken);
+ }
+
+ private Task> GetLimitingParameterStatsAsync(int idWell,
+ DateTime startDate, DateTime finishDate, CancellationToken cancellationToken)
+ {
+ var request = new LimitingParameterRequest
+ {
+ IdWell = idWell,
+ GtDate = startDate,
+ LtDate = finishDate,
+ };
+
+ return limitingParameterService.GetStatAsync(request, cancellationToken);
+ }
+
+ private int GetFileSize()
+ {
+ const int fileSizeTemplate = 10240;
+ // TODO: Добавку размера сделать более предсказуемой на основе даты рапорта. что то типа `(Date.Ticks * idWell) % (fileSizeTemplate / 10)`
+ return new Random().Next(1, 8193) + fileSizeTemplate;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/HeadExcelBlockWriter.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/HeadExcelBlockWriter.cs
new file mode 100644
index 00000000..dad6bf35
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/HeadExcelBlockWriter.cs
@@ -0,0 +1,34 @@
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+
+public class HeadExcelBlockWriter : IExcelBlockWriter
+{
+ private static readonly (int, int) customerCell = (2, 2);
+ private static readonly (int, int) depositCell = (4, 2);
+ private static readonly (int, int) clusterCell = (5, 2);
+ private static readonly (int, int) wellCell = (6, 2);
+
+ private const int dateRow = 9;
+ private const int dateFromColumn = 2;
+ private const int dateFromToColumn = 3;
+
+ private const int depthRow = 10;
+ private const int depthFromColumn = 2;
+ private const int depthToColumn = 3;
+
+ public void Write(IXLWorksheet sheet, AutoGeneratedDailyReportDto report)
+ {
+ sheet.Cell(customerCell.Item1, customerCell.Item2).Value = report.Head.Customer;
+ sheet.Cell(depositCell.Item1, depositCell.Item2).Value = report.Head.Deposit;
+ sheet.Cell(clusterCell.Item1, clusterCell.Item2).Value = report.Head.Cluster;
+ sheet.Cell(wellCell.Item1, wellCell.Item2).Value = report.Head.Well;
+
+ sheet.Cell(dateRow, dateFromColumn).Value = report.ReportDate;
+ sheet.Cell(dateRow, dateFromToColumn).Value = report.ReportDate.AddDays(1);
+
+ sheet.Cell(depthRow, depthFromColumn).Value = report.Head.DepthFrom;
+ sheet.Cell(depthRow, depthToColumn).Value = report.Head.DepthTo;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/IExcelBlockWriter.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/IExcelBlockWriter.cs
new file mode 100644
index 00000000..0f83fe05
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/IExcelBlockWriter.cs
@@ -0,0 +1,9 @@
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+
+public interface IExcelBlockWriter
+{
+ void Write(IXLWorksheet sheet, AutoGeneratedDailyReportDto report);
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/LimitingParameterExcelBlockWriter.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/LimitingParameterExcelBlockWriter.cs
new file mode 100644
index 00000000..8e99e755
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/LimitingParameterExcelBlockWriter.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+
+public class LimitingParameterExcelBlockWriter : IExcelBlockWriter
+{
+ private const int rowHeaderBlock = 20;
+
+ private const int columnNameFeedRegulator = 1;
+ private const int columnDepth = 2;
+ private const int columnTotalHours = 3;
+ private const int columnPercentDepth = 4;
+
+ public void Write(IXLWorksheet sheet, AutoGeneratedDailyReportDto report)
+ {
+ if(!report.LimitingParameters.Any())
+ return;
+
+ for (int i = 0; i < report.LimitingParameters.Length; i++)
+ {
+ var row = sheet.Row(1 + i + rowHeaderBlock);
+
+ row.Cell(columnNameFeedRegulator).Value = report.LimitingParameters[i].NameFeedRegulator;
+ row.Cell(columnDepth).Value = report.LimitingParameters[i].Depth;
+ row.Cell(columnTotalHours).Value = report.LimitingParameters[i].Hours;
+ row.Cell(columnPercentDepth).Value = report.LimitingParameters[i].PercentDepth;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/SubsystemExcelBlockWriter.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/SubsystemExcelBlockWriter.cs
new file mode 100644
index 00000000..ec32c60e
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/SubsystemExcelBlockWriter.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+
+public class SubsystemExcelBlockWriter : IExcelBlockWriter
+{
+ private const int rowHeaderBlock = 13;
+
+ private const int columnName = 1;
+ private const int columnKUsage = 2;
+ private const int columnDepth = 3;
+ private const int columnUsedTimeHours = 4;
+
+ public void Write(IXLWorksheet sheet, AutoGeneratedDailyReportDto report)
+ {
+ if(!report.Subsystems.Any())
+ return;
+
+ for (int i = 0; i < report.Subsystems.Length; i++)
+ {
+ var row = sheet.Row(1 + i + rowHeaderBlock);
+
+ row.Cell(columnName).Value = report.Subsystems[i].Name;
+ row.Cell(columnKUsage).Value = report.Subsystems[i].KUsage;
+ row.Cell(columnDepth).Value = report.Subsystems[i].Depth;
+ row.Cell(columnUsedTimeHours).Value = report.Subsystems[i].UsedTimeHours;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/TimeBalanceExcelBlockWriter.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/TimeBalanceExcelBlockWriter.cs
new file mode 100644
index 00000000..c61fed8f
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportBlocks/TimeBalanceExcelBlockWriter.cs
@@ -0,0 +1,38 @@
+using System.Linq;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
+
+public class TimeBalanceExcelBlockWriter : IExcelBlockWriter
+{
+ private const int rowHeaderBlock = 27;
+
+ private const int columnName = 1;
+ private const int columnDurationHours = 2;
+
+ public void Write(IXLWorksheet sheet, AutoGeneratedDailyReportDto report)
+ {
+ if(!report.TimeBalance.Any())
+ return;
+
+ for (int i = 0; i < report.TimeBalance.Length; i++)
+ {
+ var row = sheet.Row(1 + i + rowHeaderBlock);
+
+ row.Cell(columnName).Value = report.TimeBalance[i].Name;
+ row.Cell(columnDurationHours).Value = report.TimeBalance[i].DurationHours;
+
+ AddBorderToCell(row.Cell(columnName));
+ AddBorderToCell(row.Cell(columnDurationHours));
+ }
+ }
+
+ private void AddBorderToCell(IXLCell cell)
+ {
+ cell.Style.Border.TopBorder = XLBorderStyleValues.Thin;
+ cell.Style.Border.BottomBorder = XLBorderStyleValues.Thin;
+ cell.Style.Border.LeftBorder = XLBorderStyleValues.Thin;
+ cell.Style.Border.RightBorder = XLBorderStyleValues.Thin;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportTemplate.xlsx b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportTemplate.xlsx
new file mode 100644
index 00000000..0a887a4e
Binary files /dev/null and b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutogeneratedDailyReportTemplate.xlsx differ
diff --git a/AsbCloudWebApi/Controllers/AutoGeneratedDailyReportController.cs b/AsbCloudWebApi/Controllers/AutoGeneratedDailyReportController.cs
new file mode 100644
index 00000000..0ef6c6da
--- /dev/null
+++ b/AsbCloudWebApi/Controllers/AutoGeneratedDailyReportController.cs
@@ -0,0 +1,89 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.AutogeneratedDailyReport;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudApp.Services.AutoGeneratedDailyReports;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AsbCloudWebApi.Controllers;
+
+///
+/// Контроллер для авто-генерируемых суточных отчётов
+///
+[ApiController]
+[Route("api/well/{idWell}/[controller]")]
+[Authorize]
+public class AutoGeneratedDailyReportController : ControllerBase
+{
+ private readonly IAutoGeneratedDailyReportService autoGeneratedDailyReportService;
+ private readonly IWellService wellService;
+
+ public AutoGeneratedDailyReportController(IAutoGeneratedDailyReportService autoGeneratedDailyReportService,
+ IWellService wellService)
+ {
+ this.autoGeneratedDailyReportService = autoGeneratedDailyReportService;
+ this.wellService = wellService;
+ }
+
+ ///
+ /// Формирование отчёта
+ ///
+ /// Id скважины
+ /// Дата отчёта
+ ///
+ ///
+ [HttpGet]
+ [Route("generate")]
+ [ProducesResponseType(typeof(PhysicalFileResult), (int)HttpStatusCode.OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task GenerateAsync([FromRoute] int idWell,
+ [Required] DateOnly reportDate,
+ CancellationToken cancellationToken)
+ {
+ if (!await CanUserAccessToWellAsync(idWell, cancellationToken))
+ return Forbid();
+
+ var reportFile = await autoGeneratedDailyReportService.GenerateReportAsync(idWell,
+ reportDate,
+ cancellationToken);
+
+ return File(reportFile.stream, "application/octet-stream", reportFile.fileName);
+ }
+
+ ///
+ /// Список файлов суточных отчётов
+ ///
+ /// Id скважины
+ /// Параметры запроса
+ ///
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(PaginationContainer), (int)HttpStatusCode.OK)]
+ public async Task GetListAsync([FromRoute][Required] int idWell,
+ [FromQuery] AutoGeneratedDailyReportRequest request,
+ CancellationToken cancellationToken)
+ {
+ if (!await CanUserAccessToWellAsync(idWell, cancellationToken))
+ return Forbid();
+
+ var reports = await autoGeneratedDailyReportService.GetListAsync(idWell,
+ request,
+ cancellationToken);
+
+ return Ok(reports);
+ }
+
+ private async Task CanUserAccessToWellAsync(int idWell, CancellationToken cancellationToken)
+ {
+ int? idCompany = User.GetCompanyId();
+ return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
+ idWell, cancellationToken).ConfigureAwait(false);
+ }
+}
\ No newline at end of file