Add ScheduleReport

This commit is contained in:
ngfrolov 2022-03-17 16:56:13 +05:00
parent c2a691f224
commit 3f2a7406d2
8 changed files with 405 additions and 4 deletions

View File

@ -0,0 +1,11 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
public interface IScheduleReportService
{
Task<Stream> MakeReportAsync(int idWell, CancellationToken token = default);
}
}

View File

@ -9,10 +9,12 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<None Remove="Services\WellOperationService\WellOperationImportTemplate.xlsx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationService\WellOperationImportTemplate.xlsx" />
</ItemGroup>

View File

@ -81,6 +81,7 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellCompositeService, WellCompositeService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationService, WellOperationService>();
services.AddTransient<IScheduleReportService, ScheduleReportService>();
// admin crud services:
services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>(); // может быть включен в сервис TelemetryService

View File

@ -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<Stream> 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<PlanFactPredictBase<WellOperationDto>> 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<PlanFactPredictBase<WellOperationDto>> 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;
}
}
}

View File

@ -268,6 +268,34 @@ namespace AsbCloudWebApi.Controllers
return File(stream, "application/octet-stream", fileName);
}
/// <summary>
/// Создает excel файл с "сетевым графиком"
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="scheduleReportService"></param>
/// <param name="token"> Токен отмены задачи</param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet]
[Route("scheduleReport")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Возвращает шаблон файла импорта
/// </summary>

View File

@ -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<IConfigurationSection> 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<H1>AAA<h1/><b>asdasd<b/>";
var t = regex.IsMatch(testHtml);
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.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();