diff --git a/AsbCloudApp/Services/IScheduleReportService.cs b/AsbCloudApp/Services/IScheduleReportService.cs new file mode 100644 index 00000000..0b0180d5 --- /dev/null +++ b/AsbCloudApp/Services/IScheduleReportService.cs @@ -0,0 +1,11 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ + public interface IScheduleReportService + { + Task MakeReportAsync(int idWell, CancellationToken token = default); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 768c3c40..377bd348 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -9,10 +9,12 @@ + + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 15ecdc2b..f7e83474 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -81,6 +81,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // admin crud services: services.AddTransient, CrudServiceBase>(); // может быть включен в сервис TelemetryService diff --git a/AsbCloudInfrastructure/Services/WellOperationService/ReportTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperationService/ReportTemplate.xlsx deleted file mode 100644 index bd0220d3..00000000 Binary files a/AsbCloudInfrastructure/Services/WellOperationService/ReportTemplate.xlsx and /dev/null differ diff --git a/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportService.cs b/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportService.cs new file mode 100644 index 00000000..25e8ecb7 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportService.cs @@ -0,0 +1,312 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Services; +using ClosedXML.Excel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.WellOperationService +{ + public class ScheduleReportService: IScheduleReportService + { + private readonly IOperationsStatService operationsStatService; + private readonly IWellService wellService; + const string sheetNameSchedule = "Сетевой график"; + const string sheetNameTvd = "ГГД"; + const int maxChartsToWrap = 89; + + public ScheduleReportService(IOperationsStatService operationsStatService, IWellService wellService) + { + this.operationsStatService = operationsStatService; + this.wellService = wellService; + } + + public async Task MakeReportAsync(int idWell, CancellationToken token = default) + { + var tvd = await operationsStatService.GetTvdAsync(idWell, token); + + if (!tvd.Any()) + return null; + + var well = await wellService.GetAsync(idWell, token); + + var ecxelTemplateStream = GetExcelTemplateStream(); + using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled); + FillScheduleSheetToWorkbook(workbook, tvd, well); + FillTvdSheetToWorkbook(workbook, tvd, well); + MemoryStream memoryStream = new MemoryStream(); + workbook.SaveAs(memoryStream, new SaveOptions { }); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + private void FillScheduleSheetToWorkbook(XLWorkbook workbook, IEnumerable> tvd, WellDto well) + { + var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameSchedule); + if (sheet is null) + return; + + const int headerRowsCount = 6; + const int rowTitle = 3; + + const int columnRowNumber = 2; + const int columnCaption = 3; + const int columnWellDepthPlan = 4; + const int columnWellDepthFact = 5; + const int columnWellDepthPredict = 6; + const int columnDeltaWellDepthPerDay = 7; + const int columnDurationPlan = 8; + const int columnDurationFact = 9; + const int columnDurationPredict = 10; + const int columnDateEndPlan = 11; + const int columnDateEndFact = 12; + const int columnDateEndPredict = 13; + const int columnGuilty = 14; + const int columnNpt = 15; + + var subTitle = $"на строительство скважины №{well.Caption}, куст: {well.Cluster}, м/р: {well.Deposit}"; + sheet.Row(rowTitle).Cell(3).Value = subTitle; + + var tvdList = tvd.ToList(); + double dayStartDepth = 0d; + double dayNum = 0; + + int i = 0; + for (; i < tvdList.Count; i++) + { + var tvdItem = tvdList[i]; + var operation = tvdItem.Fact ?? tvdItem.Plan; + if (operation is null) + continue; + + var row = sheet.Row(1 + i + headerRowsCount); + + SetCell(row, columnRowNumber, $"{1 + i}"); + SetCell(row, columnCaption, $"{operation.CategoryName} {operation.CategoryInfo}".Trim()); + SetCell(row, columnWellDepthPlan, tvdItem.Plan?.DepthEnd); + SetCell(row, columnWellDepthFact, tvdItem.Fact?.DepthEnd); + SetCell(row, columnWellDepthPredict, tvdItem.Predict?.DepthEnd); + + SetCell(row, columnDeltaWellDepthPerDay, null); + if (tvdItem.Fact is not null) + { + if (dayStartDepth == 0d) + { + dayStartDepth = tvdItem.Fact.DepthStart; + dayNum = tvdItem.Fact.DateStart.DayOfYear; + } + + if (i > 0 && tvdItem.Fact.DateStart.DayOfYear > dayNum) + { + double? delta = tvdItem.Fact.DepthStart - dayStartDepth; + delta = delta > 0 ? delta : null; + SetCell(sheet.Row(0 + i + headerRowsCount), columnDeltaWellDepthPerDay, delta); + dayStartDepth = tvdItem.Fact.DepthStart; + dayNum = tvdItem.Fact.DateStart.DayOfYear; + } + } + + SetCell(row, columnDurationPlan, tvdItem.Plan?.DurationHours); + SetCell(row, columnDurationFact, tvdItem.Fact?.DurationHours); + SetCell(row, columnDurationPredict, tvdItem.Predict?.DurationHours); + + SetCell(row, columnDateEndPlan, tvdItem.Plan?.DateStart.AddHours(tvdItem.Plan?.DurationHours ?? 0)); + SetCell(row, columnDateEndFact, tvdItem.Fact?.DateStart.AddHours(tvdItem.Fact?.DurationHours ?? 0)); + SetCell(row, columnDateEndPredict, tvdItem.Predict?.DateStart.AddHours(tvdItem.Predict?.DurationHours ?? 0)); + + if (tvdItem.Fact?.IdCategory == WellOperationService.idOperationNonProductiveTime) + { + SetCell(row, columnGuilty, tvdItem.Fact.Comment); + SetCell(row, columnNpt, tvdItem.Fact.DurationHours); + row.Row(columnRowNumber, columnNpt).Style.Fill.BackgroundColor = XLColor.Red; + } + else + { + SetCell(row, columnGuilty, null); + SetCell(row, columnNpt, null); + } + } + + var rowNumSummary = 1 + i + headerRowsCount; + var rowNumStart = 1 + headerRowsCount; + var rowNumEnd = i + headerRowsCount; + + string MakeRangeFunction(string funcName, int column) + => $"={funcName}({GetColunmLetter(column)}{rowNumStart}:{GetColunmLetter(column)}{rowNumEnd})"; + + IXLCell AddRangeFormula(IXLRow row, string funcName, int column) + { + var cell = row.Cell(column); + cell.FormulaA1 = MakeRangeFunction(funcName, column); + return cell; + } + + var rowSummary = sheet.Row(rowNumSummary); + rowSummary.Style.Font.Bold = true; + rowSummary.Cell(columnCaption).Value = "Итого:"; + + AddRangeFormula(rowSummary, "sum", columnDeltaWellDepthPerDay); + AddRangeFormula(rowSummary, "sum", columnDurationPlan); + AddRangeFormula(rowSummary, "sum", columnDurationFact); + var cell = AddRangeFormula(rowSummary, "max", columnDateEndPlan); + SetDateTime(cell); + cell = AddRangeFormula(rowSummary, "max", columnDateEndFact); + SetDateTime(cell); + AddRangeFormula(rowSummary, "sum", columnNpt); + SetBorder(rowSummary.Cells(true).Style); + + var rowSummary2 = sheet.Row(rowNumSummary + 1); + rowSummary2.DataType = XLDataType.Number; + rowSummary2.Style.NumberFormat.Format = "0,00"; + rowSummary2.Cell(columnCaption).Value = "в сутках:"; + rowSummary2.Cell(columnDurationPlan).FormulaA1 = $"={GetColunmLetter(columnDurationPlan)}{rowNumSummary}/24"; + SetNumber(rowSummary2.Cell(columnDurationPlan)); + rowSummary2.Cell(columnDurationFact).FormulaA1 = $"={GetColunmLetter(columnDurationFact)}{rowNumSummary}/24"; + SetNumber(rowSummary2.Cell(columnDurationFact)); + rowSummary2.Cell(columnNpt).FormulaA1 = $"={GetColunmLetter(columnNpt)}{rowNumSummary}/24"; + SetNumber(rowSummary2.Cell(columnNpt)); + SetBorder(rowSummary2.Cells(true).Style); + } + + private void FillTvdSheetToWorkbook(XLWorkbook workbook, IEnumerable> tvd, WellDto well) + { + var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameTvd); + if (sheet is null) + return; + + const int rowTitle = 2; + const int rowSubtitle = 3; + const int colTitle = 5; + + const int rowTopStatTitle = 2; + const int colTopStatvalue = 10; + + const int colBottomStatvalue = 3; + const int rowStartDateFact = 43; + const int rowEndDatePlan = 44; + const int rowEndDateFact = 45; + + sheet.Row(rowSubtitle).Cell(colTitle).Value + = $"скважины №{well.Caption}, куст: {well.Cluster}, м/р: {well.Deposit}"; + + SetCell(sheet.Row(rowTitle), colTopStatvalue, DateTime.Now); + + var Plan = tvd.Where(t => t.Plan is not null) + .Select(t => t.Plan); + var Fact = tvd.Where(t => t.Fact is not null) + .Select(t => t.Fact); + var Predict = tvd.Where(t => t.Predict is not null) + .Select(t => t.Predict); + + var startDateFact = Fact.FirstOrDefault()?.DateStart; + var planLast = Plan.LastOrDefault(); + var factLast = Fact.LastOrDefault(); + var predictLast = Predict.LastOrDefault(); + + DateTime GetEndDate(WellOperationDto operation) + => operation is not null + ? operation.DateStart.AddHours(operation.DurationHours) + : default; + + var endDatePlan = GetEndDate(planLast); + var endDateFact = GetEndDate(factLast); + var endDatePredict = GetEndDate(predictLast); + + var endDate = endDatePredict > endDateFact + ? endDatePredict + : endDateFact; + + if(startDateFact is not null) + { + SetCell(sheet.Row(rowStartDateFact), colBottomStatvalue, startDateFact); + SetCell(sheet.Row(rowEndDatePlan), colBottomStatvalue, endDatePlan); + SetCell(sheet.Row(rowEndDateFact), colBottomStatvalue, endDate); + if(endDate != default) + { + var deltaEndDate = (endDatePlan - endDate).TotalDays; + SetCell(sheet.Row(rowTopStatTitle + 1), colTopStatvalue, Math.Abs(deltaEndDate)); + if(deltaEndDate >= 0) + SetCell(sheet.Row(rowTopStatTitle + 1), colTopStatvalue - 1, "+") + .Style.Font.SetFontColor(XLColor.Green); + else + SetCell(sheet.Row(rowTopStatTitle + 1), colTopStatvalue - 1, "-") + .Style.Font.SetFontColor(XLColor.Red); + } + } + } + + private static string GetColunmLetter(int columnNumber) + { + string letter = ""; + + while (columnNumber > 0) + { + int modulo = (columnNumber - 1) % 26; + letter = Convert.ToChar('A' + modulo) + letter; + columnNumber = (columnNumber - modulo) / 26; + } + + return letter; + } + + private static IXLStyle SetBorder(IXLStyle style) + { + style.Border.RightBorder = XLBorderStyleValues.Thin; + style.Border.LeftBorder = XLBorderStyleValues.Thin; + style.Border.TopBorder = XLBorderStyleValues.Thin; + style.Border.BottomBorder = XLBorderStyleValues.Thin; + style.Border.InsideBorder = XLBorderStyleValues.Thin; + return style; + } + + private static IXLCell SetDateTime(IXLCell cell) + { + cell.DataType = XLDataType.DateTime; + cell.Style.DateFormat.Format = "DD.MM.YYYY HH:MM:SS"; + return cell; + } + + private static IXLCell SetNumber(IXLCell cell) + { + cell.DataType = XLDataType.Number; + cell.Style.NumberFormat.Format = "0.00"; + return cell; + } + + private static IXLCell SetCell(IXLRow row, int colunm, object value) + { + var cell = row.Cell(colunm); + cell.Value = value; + + SetBorder(cell.Style); + cell.Style.Alignment.WrapText = true; + + if (value is string valueString && valueString.Length > maxChartsToWrap) + { + var baseHeight = row.Height; + row.Height = Math.Ceiling(0.85d * valueString.Length / maxChartsToWrap) * baseHeight; + } + + if (value is DateTime) + { + SetDateTime(cell); + } + else if (value is IFormattable) + { + SetNumber(cell); + } + + return cell; + } + + private static Stream GetExcelTemplateStream() + { + var stream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream("AsbCloudInfrastructure.Services.WellOperationService.ScheduleReportTemplate.xlsx"); + return stream; + } + } +} diff --git a/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportTemplate.xlsx new file mode 100644 index 00000000..d7ce54ed Binary files /dev/null and b/AsbCloudInfrastructure/Services/WellOperationService/ScheduleReportTemplate.xlsx differ diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index d5c3aee8..d6b20260 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -268,6 +268,34 @@ namespace AsbCloudWebApi.Controllers return File(stream, "application/octet-stream", fileName); } + + /// + /// Создает excel файл с "сетевым графиком" + /// + /// id скважины + /// + /// Токен отмены задачи + /// Запрашиваемый файл + [HttpGet] + [Route("scheduleReport")] + [Permission] + [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)] + public async Task ScheduleReportAsync([FromRoute] int idWell, [FromServices]IScheduleReportService scheduleReportService, CancellationToken token = default) + { + int? idCompany = User.GetCompanyId(); + + if (idCompany is null) + return Forbid(); + + if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, + idWell, token).ConfigureAwait(false)) + return Forbid(); + + var stream = await scheduleReportService.MakeReportAsync(idWell, token); + var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx"; + return File(stream, "application/octet-stream", fileName); + } + /// /// Возвращает шаблон файла импорта /// diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 48a4eb54..3e180ff9 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -9,6 +9,11 @@ using Google.Apis.Drive.v3.Data; using Microsoft.EntityFrameworkCore; using System.Net.Mail; using System.Text.RegularExpressions; +using AsbCloudInfrastructure.Services.WellOperationService; +using AsbCloudInfrastructure.Services.Cache; +using AsbCloudInfrastructure.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; namespace ConsoleApp1 { @@ -17,15 +22,57 @@ namespace ConsoleApp1 // .Options; //var context = new AsbCloudDbContext(options); + class ConfigurationService : IConfigurationSection + { + public string this[string key] { get => "Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"; + set{} } + + public string Key => ""; + + public string Path => ""; + + public string Value { get; set; } = "Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"; + + public IEnumerable GetChildren() + { + return null; + } + + public IChangeToken GetReloadToken() + { + return null; + } + + public IConfigurationSection GetSection(string key) => this; + + } + class Program { - + static void Main(/*string[] args*/) { - var regex = new Regex(@"<[a-zA-Z0-9]+.*>.*<[a-zA-Z]+/*>"); - var testHtml = "aa

AAA

asdasd"; - var t = regex.IsMatch(testHtml); + var options = new DbContextOptionsBuilder() + .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") + .Options; + var db = new AsbCloudDbContext(options); + var cacheDb = new CacheDb(); + + AsbCloudInfrastructure.DependencyInjection.MapsterSetup(); + var configService = new ConfigurationService(); + var telemetryTracker = new TelemetryTracker(cacheDb, configService); + var timeZoneService = new TimezoneService(); + var telemetryService = new TelemetryService(db, telemetryTracker, timeZoneService, cacheDb); + var wellService = new WellService(db, cacheDb, telemetryService, timeZoneService); + var operationService = new OperationsStatService(db, cacheDb, wellService); + var scheduleReportService = new ScheduleReportService(operationService, wellService); + var stream = scheduleReportService.MakeReportAsync(4).Result; + var outStream = System.IO.File.OpenWrite(@"c:\temp\1.xlsx"); + stream.CopyTo(outStream); + outStream.Flush(); + outStream.Close(); + return; //SendMail.Main(); //DbDemoDataService.AddDemoData();