Merge branch 'dev' into feature/validation

This commit is contained in:
ngfrolov 2023-09-28 16:37:58 +05:00
commit 8d1920c6e5
Signed by untrusted user who does not match committer: ng.frolov
GPG Key ID: E99907A0357B29A7
36 changed files with 9947 additions and 451 deletions

View File

@ -53,8 +53,13 @@ namespace AsbCloudApp.Data
public IEnumerable<CompanyDto> Companies { get; set; } = Enumerable.Empty<CompanyDto>();
/// <summary>
/// Отставание от ГГД, проценты
/// Отставание от ГГД, дни
/// </summary>
public double? TvdLagPercent { get; set; }
public double? TvdLagDays { get; set; }
/// <summary>
/// Кол-во дней бурения по ГГД
/// </summary>
public double? TvdDrillingDays { get; set; }
}
}

View File

@ -125,8 +125,13 @@ namespace AsbCloudApp.Data
public PlanFactDto<double?> WellDepth { get; set; } = null!;
/// <summary>
/// Отставание от ГГД, проценты
/// Отставание от ГГД, дни
/// </summary>
public double TvdLagPercent { get; set; }
public double? TvdLagDays { get; set; }
/// <summary>
/// Кол-во дней бурения по ГГД
/// </summary>
public double? TvdDrillingDays { get; set; }
}
}

View File

@ -0,0 +1,54 @@
using System;
namespace AsbCloudApp.Data.WellOperationImport;
/// <summary>
/// Объект строки полученный из файла excel
/// </summary>
public class RowDto
{
/// <summary>
/// Номер строки
/// </summary>
public int Number { get; set; }
/// <summary>
/// Название секции
/// </summary>
public string? Section { get; set; }
/// <summary>
/// Категория
/// </summary>
public string? Category { get; set; }
/// <summary>
/// Описание категории
/// </summary>
public string CategoryInfo { get; set; } = null!;
/// <summary>
/// Начальная глубина операции
/// </summary>
public double DepthStart { get; set; }
/// <summary>
/// Конечная глубина операции
/// </summary>
public double DepthEnd { get; set; }
/// <summary>
/// Дата начала операции
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Длительность операции
/// </summary>
public double Duration { get; set; }
/// <summary>
/// Комментарий
/// </summary>
public string? Comment { get; set; }
}

View File

@ -0,0 +1,29 @@
namespace AsbCloudApp.Data.WellOperationImport;
/// <summary>
/// Опции для настройки парсинга документа
/// </summary>
public class WellOperationParserOptionsDto
{
/// <summary>
/// Название листа
/// </summary>
public string? SheetName { get; set; }
/// <summary>
/// Id шаблона
/// 0 - Дефолтный шаблон
/// 1 - Газпром хантос
/// </summary>
public int IdTemplate { get; set; }
/// <summary>
/// Начальная строка
/// </summary>
public int? StartRow { get; set; }
/// <summary>
/// Конечная строка
/// </summary>
public int? EndRow { get; set; }
}

View File

@ -1,32 +0,0 @@
using System.IO;
namespace AsbCloudApp.Services
{
/// <summary>
/// сервис импорта/экспорта операций по скважине вводимых вручную
/// </summary>
public interface IWellOperationImportService
{
/// <summary>
/// скачать в excel
/// </summary>
/// <param name="idWell"></param>
/// <returns></returns>
Stream Export(int idWell);
/// <summary>
/// скачать шаблон для заполнения
/// </summary>
/// <returns></returns>
Stream GetExcelTemplateStream();
/// <summary>
/// закгрузить из excel список операций
/// </summary>
/// <param name="idWell"></param>
/// <param name="stream"></param>
/// <param name="idUser"></param>
/// <param name="deleteWellOperationsBeforeImport">Очистить старые перед импортом (если файл проходит валидацию)</param>
void Import(int idWell, Stream stream, int idUser, bool deleteWellOperationsBeforeImport = false);
}
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.IO;
using AsbCloudApp.Data.WellOperationImport;
namespace AsbCloudApp.Services.WellOperationImport;
/// <summary>
/// Парсинг операций из excel файла
/// </summary>
public interface IWellOperationExcelParser
{
/// <summary>
/// Id шаблона
/// </summary>
int IdTemplate { get; }
/// <summary>
/// Типы операций, которые можно получить из файла
/// </summary>
IEnumerable<int> IdTypes { get; }
/// <summary>
/// Метод парсинга документа
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <returns></returns>
IEnumerable<RowDto> Parse(Stream stream, WellOperationParserOptionsDto options);
}

View File

@ -0,0 +1,19 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services.WellOperationImport;
/// <summary>
/// Экспорт ГГД
/// </summary>
public interface IWellOperationExportService
{
/// <summary>
/// Скачать в excel
/// </summary>
/// <param name="idWell"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<Stream> ExportAsync(int idWell, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,26 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperationImport;
namespace AsbCloudApp.Services.WellOperationImport;
/// <summary>
/// Импорт ГГД
/// </summary>
public interface IWellOperationImportService
{
/// <summary>
/// Загрузить из excel список операций
/// </summary>
/// <param name="idWell"></param>
/// <param name="idType"></param>
/// <param name="stream"></param>
/// <param name="idUser"></param>
/// <param name="deleteWellOperationsBeforeImport"></param>
/// <param name="cancellationToken"></param>
/// <param name="options"></param>
Task ImportAsync(int idWell, int idUser, int idType, Stream stream, WellOperationParserOptionsDto options,
bool deleteWellOperationsBeforeImport,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,15 @@
using System.IO;
namespace AsbCloudApp.Services.WellOperationImport;
/// <summary>
/// Сервис для получения шаблонов ГГД
/// </summary>
public interface IWellOperationImportTemplateService
{
/// <summary>
/// Скачать шаблон для заполнения
/// </summary>
/// <returns></returns>
Stream GetExcelTemplateStream();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class WellOperations_Add_OnDelete_SetNull_Behavior : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_t_well_operation_t_well_operation_id_plan",
table: "t_well_operation");
migrationBuilder.AddForeignKey(
name: "FK_t_well_operation_t_well_operation_id_plan",
table: "t_well_operation",
column: "id_plan",
principalTable: "t_well_operation",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_t_well_operation_t_well_operation_id_plan",
table: "t_well_operation");
migrationBuilder.AddForeignKey(
name: "FK_t_well_operation_t_well_operation_id_plan",
table: "t_well_operation",
column: "id_plan",
principalTable: "t_well_operation",
principalColumn: "id");
}
}
}

View File

@ -8408,7 +8408,8 @@ namespace AsbCloudDb.Migrations
b.HasOne("AsbCloudDb.Model.WellOperation", "OperationPlan")
.WithMany()
.HasForeignKey("IdPlan");
.HasForeignKey("IdPlan")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("AsbCloudDb.Model.Well", "Well")
.WithMany("WellOperations")

View File

@ -277,6 +277,11 @@ namespace AsbCloudDb.Model
{
entity.HasIndex(d => d.DepthEnd);
entity.HasIndex(d => d.DateStart);
entity.HasOne(o => o.OperationPlan)
.WithMany()
.HasForeignKey(o => o.IdPlan)
.OnDelete(DeleteBehavior.SetNull);
});
modelBuilder.Entity<DrillingProgramPart>(entity =>

View File

@ -66,7 +66,7 @@ namespace AsbCloudDb.Model
[JsonIgnore]
[ForeignKey(nameof(IdPlan))]
public virtual WellOperation OperationPlan { get; set; } = null!;
public virtual WellOperation? OperationPlan { get; set; } = null!;
}
}

View File

@ -34,9 +34,12 @@
<EmbeddedResource Include="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
<EmbeddedResource Include="Services\ProcessMap\ProcessMapReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationService\WellOperationImportTemplate.xlsx" />
<EmbeddedResource Include="Services\ProcessMap\ProcessMapPlanTemplate.xlsx" />
<EmbeddedResource Include="Services\AutoGeneratedDailyReports\AutogeneratedDailyReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationImport\Files\WellOperationImportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\Operations.txt" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\Sections.txt" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\OperationAttributes.txt" />
</ItemGroup>
<ItemGroup>

View File

@ -26,8 +26,11 @@ using System;
using AsbCloudApp.Data.Manuals;
using AsbCloudApp.Services.AutoGeneratedDailyReports;
using AsbCloudApp.Services.Notifications;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model.Manuals;
using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
using AsbCloudInfrastructure.Services.WellOperationImport;
using AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
using AsbCloudInfrastructure.Services.ProcessMap.ProcessMapWellboreDevelopment;
namespace AsbCloudInfrastructure
@ -133,7 +136,6 @@ namespace AsbCloudInfrastructure
services.AddTransient<ITelemetryUserService, TelemetryUserService>();
services.AddTransient<ITimezoneService, TimezoneService>();
services.AddTransient<IWellService, WellService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IProcessMapPlanImportService, ProcessMapPlanImportService>();
services.AddTransient<IPlannedTrajectoryImportService, PlannedTrajectoryImportService>();
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
@ -233,6 +235,13 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellboreService, WellboreService>();
services.AddTransient<IWellOperationExportService, WellOperationExportService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationImportTemplateService, WellOperationImportTemplateService>();
services.AddTransient<IWellOperationExcelParser, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser, WellOperationGazpromKhantosExcelParser>();
return services;
}

View File

@ -343,6 +343,7 @@ public class ProcessMapPlanImportService : IProcessMapPlanImportService
_ => "Ручной",
};
//TODO: вынести в метод расширения
private static T GetCellValue<T>(IXLRow row, int columnNumber)
{
try

View File

@ -188,7 +188,8 @@ namespace AsbCloudInfrastructure.Services
wellMapInfo.SaubUsage = wellSubsystemStat?.SubsystemAKB?.KUsage ?? 0d;
wellMapInfo.SpinUsage = wellSubsystemStat?.SubsystemSpinMaster?.KUsage ?? 0d;
wellMapInfo.TorqueKUsage = wellSubsystemStat?.SubsystemTorqueMaster?.KUsage ?? 0d;
wellMapInfo.TvdLagPercent = wellOperationsStat?.TvdLagPercent ?? 0d;
wellMapInfo.TvdLagDays = wellOperationsStat?.TvdLagDays;
wellMapInfo.TvdDrillingDays = wellOperationsStat?.TvdDrillingDays;
wellMapInfo.IdsCompanies = well.Companies.Select(c => c.Id);
return wellMapInfo;

View File

@ -0,0 +1,17 @@
namespace AsbCloudInfrastructure.Services.WellOperationImport.Constants;
public static class DefaultTemplateInfo
{
public const string SheetNamePlan = "План";
public const string SheetNameFact = "Факт";
public const int HeaderRowsCount = 1;
public const int ColumnSection = 1;
public const int ColumnCategory = 2;
public const int ColumnCategoryInfo = 3;
public const int ColumnDepthStart = 4;
public const int ColumnDepthEnd = 5;
public const int ColumnDate = 6;
public const int ColumnDuration = 7;
public const int ColumnComment = 8;
}

View File

@ -0,0 +1,10 @@
namespace AsbCloudInfrastructure.Services.WellOperationImport.Constants;
public static class OperationAttributes
{
public const string CategoryInfo = "Описание";
public const string SectionDiameter = "ОК";
public const string Depth = "Забой";
public const string Duration = "Время операции";
public const string Date = "Дата окончания операции";
}

View File

@ -0,0 +1,7 @@
namespace AsbCloudInfrastructure.Services.WellOperationImport.Constants;
public static class Templates
{
public const int IdDefaultTemplate = 0;
public const int IdGazpromKhantosTemplate = 1;
}

View File

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace AsbCloudInfrastructure.Services.WellOperationImport.FileParser.StringSimilarity;
public class CosineSimilarity
{
private const int DefaultK = 2;
protected int K { get; }
public CosineSimilarity(int k)
{
if (k <= 0)
{
throw new ArgumentOutOfRangeException(nameof(k), "k should be positive!");
}
K = k;
}
public CosineSimilarity() : this(DefaultK) { }
public double Similarity(IDictionary<string, int> profile1, IDictionary<string, int> profile2)
=> DotProduct(profile1, profile2)
/ (Norm(profile1) * Norm(profile2));
public Dictionary<string, int> GetProfile(string s)
{
var shingles = new Dictionary<string, int>();
if (string.IsNullOrWhiteSpace(s))
return shingles;
var cleanString = Stemming(s);
for (int i = 0; i < (cleanString.Length - K + 1); i++)
{
var shingle = cleanString.Substring(i, K);
if (shingles.TryGetValue(shingle, out var old))
{
shingles[shingle] = old + 1;
}
else
{
shingles[shingle] = 1;
}
}
return shingles;
}
private static string Stemming(string s)
{
var cleaned = Regex.Replace(s.ToLower(), "[^a-zа-я0-9]", "");
var words = cleaned.Split(' ');
var filteredWords = words.Where(word => word.Length > 1).ToArray();
return string.Concat(filteredWords);
}
private static double Norm(IDictionary<string, int> profile)
{
double agg = 0;
foreach (var entry in profile)
{
agg += 1.0 * entry.Value * entry.Value;
}
return Math.Sqrt(agg);
}
private static double DotProduct(IDictionary<string, int> profile1, IDictionary<string, int> profile2)
{
var smallProfile = profile2;
var largeProfile = profile1;
if (profile1.Count < profile2.Count)
{
smallProfile = profile1;
largeProfile = profile2;
}
double agg = 0;
foreach (var entry in smallProfile)
{
if (!largeProfile.TryGetValue(entry.Key, out var i))
continue;
agg += 1.0 * entry.Value * i;
}
return agg;
}
}

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperationImport.Constants;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
public class WellOperationDefaultExcelParser : IWellOperationExcelParser
{
public int IdTemplate => Templates.IdDefaultTemplate;
public IEnumerable<int> IdTypes => new[] { WellOperation.IdOperationTypePlan, WellOperation.IdOperationTypeFact };
public IEnumerable<RowDto> Parse(Stream stream, WellOperationParserOptionsDto options)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook, options);
}
private static IEnumerable<RowDto> ParseWorkbook(IXLWorkbook workbook, WellOperationParserOptionsDto options)
{
if (string.IsNullOrWhiteSpace(options.SheetName))
throw new ArgumentInvalidException("Не указано название листа", nameof(options.SheetName));
var sheet = workbook.Worksheets.FirstOrDefault(ws =>
string.Equals(ws.Name, options.SheetName, StringComparison.CurrentCultureIgnoreCase))
?? throw new FileFormatException($"Книга excel не содержит листа '{options.SheetName}'");
return ParseSheet(sheet);
}
private static IEnumerable<RowDto> ParseSheet(IXLWorksheet sheet)
{
if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 7)
throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
var count = sheet.RowsUsed().Count() - DefaultTemplateInfo.HeaderRowsCount;
switch (count)
{
case > 1024:
throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество операций.");
case <= 0:
return Enumerable.Empty<RowDto>();
}
var rows = new RowDto[count];
var cellValuesErrors = new List<string>();
for (int i = 0; i < rows.Length; i++)
{
try
{
var xlRow = sheet.Row(1 + i + DefaultTemplateInfo.HeaderRowsCount);
rows[i] = ParseRow(xlRow);
}
catch (FileFormatException ex)
{
cellValuesErrors.Add(ex.Message);
}
}
if (cellValuesErrors.Any())
throw new FileFormatException(string.Join("\r\n", cellValuesErrors));
return rows;
}
private static RowDto ParseRow(IXLRow xlRow)
{
return new RowDto
{
Number = xlRow.RowNumber(),
Section = GetCellValue<string>(xlRow.Cell(DefaultTemplateInfo.ColumnSection)),
Category = GetCellValue<string>(xlRow.Cell(DefaultTemplateInfo.ColumnCategory)),
CategoryInfo = GetCellValue<string>(xlRow.Cell(DefaultTemplateInfo.ColumnCategoryInfo)),
DepthStart = GetCellValue<double>(xlRow.Cell(DefaultTemplateInfo.ColumnDepthStart)),
DepthEnd = GetCellValue<double>(xlRow.Cell(DefaultTemplateInfo.ColumnDepthEnd)),
Date = GetCellValue<DateTime>(xlRow.Cell(DefaultTemplateInfo.ColumnDate)),
Duration = GetCellValue<double>(xlRow.Cell(DefaultTemplateInfo.ColumnDuration)),
Comment = GetCellValue<string>(xlRow.Cell(DefaultTemplateInfo.ColumnComment))
};
}
//TODO: вынести в метод расширения
private static T GetCellValue<T>(IXLCell cell)
{
try
{
return (T)Convert.ChangeType(cell.Value, typeof(T));
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
}
}
}

View File

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperationImport.Constants;
using AsbCloudInfrastructure.Services.WellOperationImport.FileParser.StringSimilarity;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
{
private class Operation
{
public int RowNumber { get; set; }
public string CategoryInfo { get; set; } = null!;
public double SectionDiameter { get; set; }
public double Depth { get; set; }
public double Duration { get; set; }
public DateTime Date { get; set; }
}
private readonly CosineSimilarity cosineSimilarity;
private readonly Dictionary<string, string> operationDict = InitDict("Operations.txt", '=');
private readonly Dictionary<string, string> sectionDict = InitDict("Sections.txt", '=');
private readonly Dictionary<string, string> operationAttributesDict = InitDict("OperationAttributes.txt", '=');
public WellOperationGazpromKhantosExcelParser()
{
cosineSimilarity = new CosineSimilarity();
}
public int IdTemplate => Templates.IdGazpromKhantosTemplate;
public IEnumerable<int> IdTypes => new[] { WellOperation.IdOperationTypePlan };
public IEnumerable<RowDto> Parse(Stream stream, WellOperationParserOptionsDto options)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkBook(workbook, options);
}
private IEnumerable<RowDto> ParseWorkBook(IXLWorkbook workbook, WellOperationParserOptionsDto options)
{
if (string.IsNullOrWhiteSpace(options.SheetName))
throw new ArgumentInvalidException("Не указано название листа", nameof(options.SheetName));
if (options.StartRow is null or < 1 or > 1048576)
throw new ArgumentInvalidException("Некорректное значение начальной строки", nameof(options.StartRow));
if (options.EndRow is null or < 1 or > 1048576)
throw new ArgumentInvalidException("Некорректное значение конечной строки", nameof(options.EndRow));
if (options.EndRow < options.StartRow)
throw new ArgumentInvalidException("Конечный номер строки не может быть больше начального", nameof(options.EndRow));
var sheet = workbook.Worksheets.FirstOrDefault(ws =>
string.Equals(ws.Name, options.SheetName, StringComparison.CurrentCultureIgnoreCase))
?? throw new FileFormatException($"Книга excel не содержит листа '{options.SheetName}'");
return ParseSheet(sheet, options.StartRow.Value, options.EndRow.Value);
}
private IEnumerable<RowDto> ParseSheet(IXLWorksheet sheet, int startRow, int endRow)
{
var operationAttributes = GetOperationAttributes(sheet.RowsUsed());
if (operationAttributes is null)
return Enumerable.Empty<RowDto>();
var rowsCount = endRow - startRow + 1;
var operations = new List<Operation>();
var cellValuesErrors = new List<string>();
for (int i = 0; i < rowsCount; i++)
{
var xlRow = sheet.Row(startRow + i);
try
{
operations.Add(new Operation
{
RowNumber = xlRow.RowNumber(),
CategoryInfo = GetCellValue<string>(xlRow.Cell(operationAttributes[OperationAttributes.CategoryInfo])),
SectionDiameter = GetCellValue<double>(xlRow.Cell(operationAttributes[OperationAttributes.SectionDiameter])),
Depth = GetCellValue<double>(xlRow.Cell(operationAttributes[OperationAttributes.Depth])),
Duration = GetCellValue<double>(xlRow.Cell(operationAttributes[OperationAttributes.Duration])),
Date = GetCellValue<DateTime>(xlRow.Cell(operationAttributes[OperationAttributes.Date]))
});
}
catch (FileFormatException ex)
{
cellValuesErrors.Add(ex.Message);
}
}
if (cellValuesErrors.Any())
throw new FileFormatException(string.Join("\r\n", cellValuesErrors));
return BuildRows();
IEnumerable<(double Diameter, string Name)> BuildSections()
{
var groupedOperations = operations.GroupBy(o => o.SectionDiameter)
.Select(s => new
{
Diameter = s.Key,
CategoryInfo = string.Concat(s.Select(o => o.CategoryInfo))
});
var repeatedSections = new[] { "xвостовик" };
var sections = new List<(double diameter, string section)>();
foreach (var groupedOperation in groupedOperations)
{
var sectionNamesSet = new HashSet<string>(sections.Select(s => s.section));
sections.Add(new ValueTuple<double, string>(groupedOperation.Diameter, sectionDict.FirstOrDefault(item =>
groupedOperation.CategoryInfo.Contains(item.Key) &&
(!sectionNamesSet.Contains(item.Value) || repeatedSections.Contains(item.Value.ToLowerInvariant()))).Value));
}
return sections;
}
IEnumerable<RowDto> BuildRows()
{
if (!operations.Any())
return Enumerable.Empty<RowDto>();
var rows = new List<RowDto>();
for (int i = 0; i < operations.Count; i++)
{
var currentOperation = operations[i];
var nextOperation = i + 1 < operations.Count ? operations[i + 1] : currentOperation;
rows.Add(new RowDto
{
Number = currentOperation.RowNumber,
Section = BuildSections().FirstOrDefault(s => Math.Abs(s.Diameter - currentOperation.SectionDiameter) < 0.1).Name,
Category = GetValueDictionary(operationDict, currentOperation.CategoryInfo, 0.3),
CategoryInfo = currentOperation.CategoryInfo,
DepthStart = currentOperation.Depth,
DepthEnd = nextOperation.Depth,
Duration = currentOperation.Duration,
Date = currentOperation.Date.AddHours(-currentOperation.Duration)
});
}
return rows;
}
}
private IDictionary<string, int>? GetOperationAttributes(IXLRows xlRows)
{
const int countOperationAttributes = 5;
IDictionary<string, int>? operationAttributes = null;
foreach (var xlRow in xlRows)
{
operationAttributes = new Dictionary<string, int>();
var cells = xlRow.CellsUsed().ToArray();
foreach (var cell in cells)
{
var operationAttribute = GetValueDictionary(operationAttributesDict, GetCellValue<string>(cell), 0.7);
if (operationAttribute is null || operationAttributes.Any(a => a.Key == operationAttribute))
continue;
operationAttributes.Add(operationAttribute, cell.Address.ColumnNumber);
}
if (operationAttributes.Count >= countOperationAttributes)
break;
}
return operationAttributes is not null && operationAttributes.Count == countOperationAttributes ? operationAttributes : null;
}
private string? GetValueDictionary(IDictionary<string, string> dict, string cellValue, double? minSimilarity)
{
var similarValues = new List<(double similarity, string value)>();
var profile1 = cosineSimilarity.GetProfile(cellValue);
foreach (var item in dict)
{
var profile2 = cosineSimilarity.GetProfile(item.Key);
var similarity = cosineSimilarity.Similarity(profile1, profile2);
similarValues.Add((similarity, item.Value));
}
var mostSimilarValue = similarValues.MaxBy(v => v.similarity);
return minSimilarity.HasValue && mostSimilarValue.similarity >= minSimilarity ? mostSimilarValue.value : null;
}
private static Dictionary<string, string> InitDict(string fileName, char separator)
{
var resourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith(fileName))!;
var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
using var reader = new StreamReader(stream);
return reader.ReadToEnd().Split('\r')
.Where(s => !string.IsNullOrWhiteSpace(s))
.Select(line => line.Split(separator))
.ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
}
//TODO: вынести в метод расширения
private static T GetCellValue<T>(IXLCell cell)
{
try
{
if (typeof(T) != typeof(DateTime))
return (T)Convert.ChangeType(cell.GetFormattedString(), typeof(T), CultureInfo.InvariantCulture);
return (T)(object)DateTime.FromOADate((double)cell.Value);
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
}
}
}

View File

@ -0,0 +1,8 @@
Описание=Описание
ОК=ОК
Секция=ОК
Забой, м=Забой
Время=Время операции
Плановое время бурения, сут=Время операции
Окончание=Дата окончания операции
Дата окончания План РГ=Дата окончания операции

View File

@ -0,0 +1,190 @@
Сборка КНБК=Сборка КНБК
Сборка роторной КНБК=Сборка КНБК
Шаблонирование спуск КНБК=Шаблонирование перед спуском
Бурение под направлением=Бурение ротором
Шаблонирование перед спуском=Шаблонирование перед спуском
Шаблонировка пробуренного интервала + промывка на забое+ подъем КНБК=Шаблонирование перед спуском
Разборка КНБК=Разборка КНБК
ПР к спуску направления 324мм=ПЗР при спуске ОК
Спуск направления=Спуск ОК
Спуск направления 324мм=Спуск ОК
Цементаж направления 324мм=Цементирование
ОЗЦ. Оборудование устья.=ОЗЦ
ОЗЦ. Чистка забурочной ямы. Чистка ВШН. Отворот доп. патрубка. ЗГР=ОЗЦ
Перетяжка талевого каната / замена.=Перетяжка талевого каната
Шаблонирование подъём КНБК=Шаблонировка подъем БИ, продувка
Сборка СБТ 127мм-300м=Сборка БИ с мостков на подсвечник
Сборка КНБК для бурения кондуктора=Сборка КНБК
Сборка КНБК для бурения. Компоновка БК согласно собранного БИ в п.10=Сборка КНБК
Cпуск КНБК=Спуск КНБК
Cпуск КНБК со сборкой БИ с мостков=Спуск бурильного инструмента со сборкой с мостков
Разбурка оснастки (ЦКОД, цем.стакан, БК), замена раствора=Разбуривание тех.оснастки
Бурение под кондуктор. Наращивание св.=Бурение ротором
Промывка, ОБР, МBТ БР<70 кг/м3=Промывка
Промывка на забое=Промывка
Шаблонирование (подъем)=Шаблонировка во время бурения
Шаблонирование (спуск)=Шаблонировка во время бурения
Промывка на забое. Прокачка ВУС, ОБР, МBТ БР <70 кг/м3=Промывка
Подъем=Подъем КНБК
Разборка КНБК с телесистемой=Разборка КНБК
ПЗР к спуску ОК 245мм=ПЗР при спуске ОК
Спуск ОК 245мм с промежуточными промывками (500 м, 1000м). Вывоз БР с БДЕ=Спуск ОК
Промывка перед цементажем=Промывка при спуске ОК
Цементаж кондуктора 245мм=Цементирование
Монтаж ОУС. Вывоз БР, Чистка емкостей=Чистка ЦСГО/емкостного блока
Монтаж ОУС=Монтаж ПВО
Заготовка бурового раствора, чистка емкостей.=Опрессовка ПВО
Монтаж ПВО, монтаж разрезной воронки и устьевого желоба. Вывоз БР, заготовка БР=Монтаж ПВО
Опрессовка глухих плашек ППГ, БГ, БД , выкидных линий, крестовины с коренными задвижками. ЗБР=Опрессовка ПВО
Сборка КНБК на бурение=Сборка КНБК
Сборка СБТ 127мм-465м=Сборка БИ с мостков на подсвечник
Спуск КНБК со сборкой с мостков СБТ -127 (1700м)=Спуск КНБК
Сборка КНБК на бурение транспортного ствола=Сборка КНБК
Опрессовка трубных плашек, ПУГ=Опрессовка ПВО
Разбурка оснастки (ЦКОД, цем.стакан, БК, углубление на 2 метра ниже БК, опрессовка цементного кольца)=Разбуривание тех.оснастки
Разбурка БК, ЦКОДа и цем.стакана=Разбуривание тех.оснастки
Перевод скважины на новый раствор, чистка ЦСГО=Промывка - перевод скважины на новый раствор
Перевод скважины на новый буровой раствор=Промывка - перевод скважины на новый раствор
Бурение транспортного ствола наращ.св. (прокачка укрепляющих пачек ч/з каждые 150-200м)=Бурение ротором
Промывка после ХМ св TVD - 1660 м (ниже на 50 м)=Промывка
Чистка ЦСГО (опрессовка цем. кольца кондуктора во время чистки ЦСГО)=Чистка ЦСГО/емкостного блока
Промывка после Алымской св TVD - 2140 м (ниже на 50 м)=Промывка
Бурение транспортного ствола наращ. cв. (прокачка укрепляющих пачек ч/з каждые 150-200м).=Бурение ротором
Бурение транспортного ствола (1000м первые сутки бурения)=Бурение ротором
Подъем КНБК шаблонировка ствола скважины=Шаблонировка подъем БИ, продувка
Промывка (по согласованию с ЦУСС)=Промывка
Шаблонировка. Подъем КНБК (по согласованию с ЦУСС)=Шаблонировка во время бурения
Шаблонировка.Спуск КНБК со сборкой БИ 300м (по согласованию с ЦУСС)=Шаблонировка во время бурения
Промывка=Промывка
Шаблонировка. Подъем КНБК=Шаблонировка во время бурения
Шаблонировка.Спуск КНБК=Шаблонировка во время бурения
Разборка КНБК с т/с=Разборка КНБК
Промывка на забое, прокачка кольмат. пачки=Помывка
ПЗР к спуску ОК-178мм.=ПЗР при спуске ОК
Спуск ОК 178 мм (до устья, не потайная) с промежуточными промывками=Спуск ОК
Цементирование ОК-178мм=Цементирование
Отворот и выброс допускной трубы, демонтаж ПВО, замыв шурфа для выброса СБТ-127мм, чистка емкостей, приготовление БР=Демонтаж ПВО
Промывка, установка смазывающей пачки=Промывка
Выброс СБТ-127мм на мостки, чистка емкостей, приготовление БР=Подъем БИ с выбросом на мостки
Подъем КНБК с выбросом БИ - 500м (выброс согласовать с куратором ЦУСС)=Подъем КНБК
Монтаж ПВО, замена трубных плашек 127мм на 102мм, замена рабочего переводника на СВП, приготовление БР=Перетяжка талевого каната
ПЗР к спуску ОК 178мм=ПЗР при спуске ОК
Спуск ОК 178мм с промывками. Вывоз БР с БДЕ=Спуск ОК
Цементирование 178мм ОК. Вывоз БР с БДЕ=Цементирование
Частичный демонтаж ПВО=Демонтаж ПВО
Выброс БИ 127 на мостки - 1600м (Оставляем БИ 127 1400 м на бурение под кондуктор). Вывоз БР, чистка емкостей=Подъем БИ с выбросом на мостки
Частичный монтаж ПВО=Монтаж ПВО
Опрессовка (200 атм) глухих плашек ППГ, БГ, БД, выкидных линий, крестовины с коренными задвижками, ЗБР. Сборка БИ-102мм - 1000м для бурения ГС свечами.=Опрессовка ПВО
Сборка КНБК на бурение секции под хвостовик 114мм=Сборка КНБК
Спуск КНБК со сборкой БИ 102 и промежуточными промывками.=Промывка - перевод скважины на новый раствор
Опрессовка трубных плашек ППГ, ПУГ. Промывка, перезапись гаммы=Опрессовка ПВО
Разбурка оснастки (ЦКОД, цем.стакан, БК)=Разбуривание тех.оснастки
Перевод на новый раствор=Промывка - перевод скважины на новый раствор
Чистка ЦСГО=Чистка ЦСГО/емкостного блока
Бурение горизонтального участка скважины (прокачка укрепляющих пачек ч/з каждые 100 м)=Бурение ротором
Подъем БИ в БК Ø178мм.=Подъем КНБК
Спуск БИ со сборкой ТБТ 88,9мм на опрессовку (20м до БК 178)=Спуск КНБК
Опрессовка БИ, установка на подсвечник ТБТ=Опрессовка БИ
Проработка в 2 этапа:1 этап - прямая принудительная проработка; 2 этап - спуск на "сухую"(имитация спуска хвостовика)=Проработка принудительная
Cборка хвостовика=Сборка хвостовика 114мм (согласно схеме)
Промывка, прокачка ВУС=Промывка
Подъем КНБК=Подъем КНБК
ПЗР к спуску хвостовика=ПЗР при спуске ОК
Сборка хвостовика 114мм (согласно схеме)=Сборка хвостовика 114мм (согласно схеме)
Спуск хвостовика 114мм на БИ. В БК 178 перевод на тех.воду (по согл.с ЦУСС)=Спуск ОК
Активация подвески (4ч). Перевод на жидкость заканчивания (2ч).=Активация подвески, опрессовка
Подъем БИ с выбросом на мостки. Оставляем ТБТ 89 (800 м) на следующую скв=Подъем БИ с выбросом на мостки
Демонтаж ПВО=Демонтаж ПВО
Монтаж, опрессовка ФА=Монтаж, опрессовка ФА
5% времени на ТО БУ=Ремонт
Монтаж ФА=Монтаж, опрессовка ФА
Подъем разъединителя с выбросом СБТ-102мм на мостки=Подъем инструмента
Активация подвески. Перевод на жидкость заканчивания. Опрессовка пакера подвески хвостовика.=Активация подвески (потайной колонны, хвостовика)
ПР к спуску хвостовика=ПЗР при спуске ОК
Подъем КНБК с частичным выбросом СБТ-102мм на приемные мостки=Подъем БИ с выбросом на мостки
Бурение горизонтального участка скважины (прокачка укрепляющих пачек ч/з каждые 100м)=Бурение ротором
Промывка перезапись ГК=Промывка
Спуск КНБК со сборкой СБТ-102мм с приемных мостков, с промежуточными промывками каждые 500м=Спуск бурильного инструмента со сборкой с мостков
Сборка КНБК для бурения горизонтального участка скважины=Сборка БИ с мостков на подсвечник
Опрессовка глухих плашек ППГ, БГ, БД, выкидных линий, крестовины с коренными задвижками, приготовление бур.раствора=Опрессовка ПВО
ВМР=ВМР
Долив затруба при подъёме=Долив затруба при подъёме
Закачка/прокачка пачки=Закачка/прокачка пачки
Комплекс ГИС на жестком кабеле=Комплекс ГИС на жестком кабеле
Комплекс ГИС на кабеле=Комплекс ГИС на кабеле
Комплекс ГИС на трубах=Комплекс ГИС на трубах
Контролируемое ГНВП=Контролируемое ГНВП
Ловильные работы=Ловильные работы
Наработка жёлоба=Наработка жёлоба
Наращивание=Наращивание
НПВ / прочее=НПВ / прочее
Обвязка устья с циркуляционной системой=Обвязка устья с циркуляционной системой
Оборудование устья=Оборудование устья
Обработка БР=Обработка БР
Обработка раствора (несоответствие параметров)=Обработка раствора (несоответствие параметров)
Ожидание=Ожидание
Определение места прихвата и ЛМ=Определение места прихвата и ЛМ
Опрессовка ОК=Опрессовка ОК
Ориентирование ТС при бурении=Ориентирование ТС при бурении
Отворот допускной трубы=Отворот допускной трубы
Перезапись гаммы-каротажа=Перезапись гаммы-каротажа
Перемонтаж ПВО=Перемонтаж ПВО
ПЗР к спуску УЭЦН=ПЗР к спуску УЭЦН
ПЗР при сборке КНБК=ПЗР при сборке КНБК
ПЗР при цементировании=ПЗР при цементировании
Поглощение=Поглощение
Подготовка ствола скважины. Перезапись ГК в интервале установки КО.=Подготовка ствола скважины. Перезапись ГК в интервале установки КО.
Подъем БИ с выбросом на мостки=Подъем БИ с выбросом на мостки
подъем ОК=подъем ОК
Подъем приборов ГИС (на трубах)=Подъем приборов ГИС (на трубах)
Полная замена талевого каната=Полная замена талевого каната
ПР перед забуркой направления=ПР перед забуркой направления
Приготовление БР=Приготовление БР
Продувка манифольда=Продувка манифольда
Промывка перед наращиванием=Промывка перед наращиванием
Проработка во время бурения=Проработка во время бурения
Проработка перед наращиванием=Проработка перед наращиванием
Работа яссом=Работа яссом
Разборка комплекса приборов ГИС=Разборка комплекса приборов ГИС
Разбуривание тех.оснастк=Разбуривание тех.оснастки
Расхаживани=Расхаживание
Ревизия КНБК/инструмента/ЗТС=Ревизия КНБК/инструмента/ЗТС
Ремонт бурового оборудования=Ремонт бурового оборудования
Сальникообразование=Сальникообразование
Сборка и спуск ТБТ=Сборка и спуск ТБТ
Сборка комплекса приборов ГИС=Сборка комплекса приборов ГИС
Сборка устройства ориентирования КО=Сборка устройства ориентирования КО
Смена рабочего переводника ВСП=Смена рабочего переводника ВСП
СПО - колокол=СПО - колокол
СПО - метчик=СПО - метчик
СПО - овершот=СПО - овершот
СПО - труболовка=СПО - труболовка
Спуск БИ со сборкой с мостков=Спуск БИ со сборкой с мостков
Спуск инструмента=Спуск инструмента
Спуск инструмента с проработкой=Спуск инструмента с проработкой
Спуск КО на транспотрной колонне=Спуск КО на транспотрной колонне
Спуск приборов ГИС (на трубах)=Спуск приборов ГИС (на трубах)
Срезка=Срезка
Тайм-дриллинг=Тайм-дриллинг
Тех.отстой=Тех.отстой
Торпедирование (встряхивание)=Торпедирование (встряхивание)
Торпедирование (отстрел)=Торпедирование (отстрел)
Удержание в клиньях=Удержание в клиньях
Установка ванн=Установка ванн
Утяжеление БР=Утяжеление БР
Учебная тревога "Выброс"=Учебная тревога "Выброс"
Фрезеровка=Фрезеровка
Шаблонировка подъем БИ, продувка=Шаблонировка подъем БИ, продувка
Шаблонировка перед наращиванием=Шаблонировка перед наращиванием
Демонтаж ПВО ( переоборудование устья скважины). Вывоз БР=Демонтаж ПВО
Сборка БИ 127/147с мостков установкой на подсвечник=Сборка БИ с мостков на подсвечник
Спуск приборов комплекса АМАК.=Спуск приборов ГИС (на трубах)
Подъем с записью=Подъем приборов ГИС (на трубах)
ОЗЦ под давлением (по согласованию с ЦУСС)=ОЗЦ
"Демонтаж ПВО ( переоборудование устья скважины). Вывоз БР=Демонтаж ПВО
Сборка CБТ-127 (0м) с мостков установкой на подсвечник (оставлено СБТ-127 (1500м) с пердыдущей скв). Заготовка БР=Сборка БИ с мостков на подсвечник
ПЗР к спуску ОК=ПЗР при спуске ОК
Выброс СБТ 127 (2100м), оставляется СБТ-127 (700 м) на след скв. ЗБР, чистка емкостей, вывоз БР.=Подъем БИ с выбросом на мостки
Монтаж ПВО повторный (смена плашек ПВО). ЗБР, чистка емкостей, вывоз БР=Монтаж ПВО
Опрессовка ПВО (200 атм), глухие=Опрессовка ПВО
Сборка ТБТ на 2 этапе (кол-во по согласованию с ЦУСС). Подъем/спуск БИ со сборкой ТБТ 102 мм. Опрессовка БИ (1.5 ч)=Сборка и спуск ТБТ

View File

@ -0,0 +1,7 @@
направ=Направление
конд=Кондуктор
техн=Техническая колонна
экспл=Эксплуатационная колонна
транс=Транспортный ствол
пилот=Пилотный ствол
хвост=Хвостовик

View File

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudInfrastructure.Services.WellOperationImport.Constants;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.WellOperationImport;
public class WellOperationExportService : IWellOperationExportService
{
private readonly IWellOperationRepository wellOperationRepository;
private readonly IWellService wellService;
private readonly IWellOperationImportTemplateService wellOperationImportTemplateService;
public WellOperationExportService(IWellOperationRepository wellOperationRepository,
IWellService wellService,
IWellOperationImportTemplateService wellOperationImportTemplateService)
{
this.wellOperationRepository = wellOperationRepository;
this.wellService = wellService;
this.wellOperationImportTemplateService = wellOperationImportTemplateService;
}
public async Task<Stream> ExportAsync(int idWell, CancellationToken cancellationToken)
{
var operations = await wellOperationRepository.GetAsync(new WellOperationRequest()
{
IdWell = idWell
}, cancellationToken);
var timezone = wellService.GetTimezone(idWell);
return await MakeExcelFileStreamAsync(operations, timezone.Hours, cancellationToken);
}
private async Task<Stream> MakeExcelFileStreamAsync(IEnumerable<WellOperationDto> operations, double timezoneOffset,
CancellationToken cancellationToken)
{
using Stream ecxelTemplateStream = wellOperationImportTemplateService.GetExcelTemplateStream();
using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled);
await AddOperationsToWorkbook(workbook, operations, timezoneOffset, cancellationToken);
var memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private async Task AddOperationsToWorkbook(XLWorkbook workbook, IEnumerable<WellOperationDto> operations, double timezoneOffset,
CancellationToken cancellationToken)
{
var planOperations = operations.Where(o => o.IdType == 0);
if (planOperations.Any())
{
var sheetPlan = workbook.Worksheets.FirstOrDefault(ws => ws.Name == DefaultTemplateInfo.SheetNamePlan);
if (sheetPlan is not null)
await AddOperationsToSheetAsync(sheetPlan, planOperations, timezoneOffset, cancellationToken);
}
var factOperations = operations.Where(o => o.IdType == 1);
if (factOperations.Any())
{
var sheetFact = workbook.Worksheets.FirstOrDefault(ws => ws.Name == DefaultTemplateInfo.SheetNameFact);
if (sheetFact is not null)
await AddOperationsToSheetAsync(sheetFact, factOperations, timezoneOffset, cancellationToken);
}
}
private async Task AddOperationsToSheetAsync(IXLWorksheet sheet, IEnumerable<WellOperationDto> operations, double timezoneOffset,
CancellationToken cancellationToken)
{
var operationsToArray = operations.ToArray();
var sections = wellOperationRepository.GetSectionTypes();
var categories = wellOperationRepository.GetCategories(false);
for (int i = 0; i < operationsToArray.Length; i++)
{
var row = sheet.Row(1 + i + DefaultTemplateInfo.HeaderRowsCount);
AddOperationToRow(row, operationsToArray[i], sections, categories, timezoneOffset);
}
}
private static void AddOperationToRow(IXLRow row, WellOperationDto operation, IEnumerable<WellSectionTypeDto> sections,
IEnumerable<WellOperationCategoryDto> categories, double timezoneOffset)
{
row.Cell(DefaultTemplateInfo.ColumnSection).Value = sections.First(s => s.Id == operation.IdWellSectionType).Caption;
row.Cell(DefaultTemplateInfo.ColumnCategory).Value = categories.First(o => o.Id == operation.IdCategory).Name;
row.Cell(DefaultTemplateInfo.ColumnCategoryInfo).Value = operation.CategoryInfo;
row.Cell(DefaultTemplateInfo.ColumnDepthStart).Value = operation.DepthStart;
row.Cell(DefaultTemplateInfo.ColumnDepthEnd).Value = operation.DepthEnd;
row.Cell(DefaultTemplateInfo.ColumnDate).Value = new DateTimeOffset(operation.DateStart).ToRemoteDateTime(timezoneOffset);
row.Cell(DefaultTemplateInfo.ColumnDuration).Value = operation.DurationHours;
row.Cell(DefaultTemplateInfo.ColumnComment).Value = operation.Comment;
}
}

View File

@ -0,0 +1,141 @@
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.WellOperationImport;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperationImport.Constants;
namespace AsbCloudInfrastructure.Services.WellOperationImport;
public class WellOperationImportService : IWellOperationImportService
{
private readonly IEnumerable<IWellOperationExcelParser> excelParsers;
private readonly IWellOperationRepository wellOperationRepository;
private static readonly DateTime dateLimitMin = new(2001, 1, 1, 0, 0, 0);
private static readonly DateTime dateLimitMax = new(2099, 1, 1, 0, 0, 0);
private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366);
public WellOperationImportService(IEnumerable<IWellOperationExcelParser> excelParsers,
IWellOperationRepository wellOperationRepository)
{
this.excelParsers = excelParsers;
this.wellOperationRepository = wellOperationRepository;
}
public async Task ImportAsync(int idWell, int idUser, int idType, Stream stream, WellOperationParserOptionsDto options,
bool deleteWellOperationsBeforeImport, CancellationToken cancellationToken)
{
var excelParser = excelParsers.FirstOrDefault(p => p.IdTemplate == options.IdTemplate &&
p.IdTypes.Contains(idType));
if (excelParser is null)
throw new ArgumentInvalidException("Невозможно импортировать файл", nameof(options.IdTemplate));
if (idType != WellOperation.IdOperationTypePlan && idType != WellOperation.IdOperationTypeFact)
throw new ArgumentInvalidException("Операции не существует", nameof(idType));
RowDto[] rows;
var validationErrors = new List<string>();
var sections = wellOperationRepository.GetSectionTypes();
var categories = wellOperationRepository.GetCategories(false);
switch (options.IdTemplate)
{
case 0:
options.SheetName = idType == WellOperation.IdOperationTypePlan
? DefaultTemplateInfo.SheetNamePlan
: DefaultTemplateInfo.SheetNameFact;
rows = excelParser.Parse(stream, options).ToArray();
break;
default:
if (string.IsNullOrWhiteSpace(options.SheetName))
throw new FileFormatException("Не указано название листа");
rows = excelParser.Parse(stream, options).ToArray();
break;
}
var operations = new List<WellOperationDto>();
foreach (var row in rows)
{
try
{
var section = sections.FirstOrDefault(s =>
string.Equals(s.Caption, row.Section, StringComparison.CurrentCultureIgnoreCase));
if (section is null)
throw new FileFormatException($"Лист '{options.SheetName}'. В строке '{row.Number}' не удалось определить секцию");
var category = categories.FirstOrDefault(c =>
string.Equals(c.Name, row.Category, StringComparison.CurrentCultureIgnoreCase));
if (category is null)
throw new FileFormatException($"Лист '{options.SheetName}'. В строке '{row.Number}' не удалось определить операцию");
if (row.DepthStart is not (>= 0d and <= 20_000d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная глубина на начало операции");
if (row.DepthEnd is not (>= 0d and <= 20_000d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная глубина на конец операции");
if (row.Date < dateLimitMin && row.Date > dateLimitMax)
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' неправильно получена дата начала операции");
if (operations.LastOrDefault()?.DateStart > row.Date)
throw new FileFormatException($"Лист '{options.SheetName}' строка '{row.Number}' дата позднее даты предыдущей операции");
if (row.Duration is not (>= 0d and <= 240d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная длительность операции");
operations.Add(new WellOperationDto
{
IdWell = idWell,
IdUser = idUser,
IdType = idType,
IdWellSectionType = section.Id,
IdCategory = category.Id,
CategoryInfo = row.CategoryInfo,
DepthStart = row.DepthStart,
DepthEnd = row.DepthEnd,
DateStart = row.Date,
DurationHours = row.Duration
});
}
catch (FileFormatException ex)
{
validationErrors.Add(ex.Message);
}
}
if (operations.Any() && operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax)
validationErrors.Add($"Лист {options.SheetName} содержит диапазон дат больше {drillingDurationLimitMax}");
if (validationErrors.Any())
throw new FileFormatException(string.Join("\r\n", validationErrors));
if(!operations.Any())
return;
if (deleteWellOperationsBeforeImport)
{
var existingOperations = await wellOperationRepository.GetAsync(new WellOperationRequest
{
IdWell = idWell
}, cancellationToken);
await wellOperationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken);
}
await wellOperationRepository.InsertRangeAsync(operations, cancellationToken);
}
}

View File

@ -0,0 +1,21 @@
using System.IO;
using System.Linq;
using System.Reflection;
using AsbCloudApp.Services.WellOperationImport;
namespace AsbCloudInfrastructure.Services.WellOperationImport;
public class WellOperationImportTemplateService : IWellOperationImportTemplateService
{
public Stream GetExcelTemplateStream()
{
var resourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("WellOperationImportTemplate.xlsx"))!;
var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
return stream;
}
}

View File

@ -172,80 +172,56 @@ public class OperationsStatService : IOperationsStatService
var timezoneOffsetH = wellService.GetTimezone(well.Id).Hours;
statWellDto.Sections = CalcSectionsStats(wellOperations, timezoneOffsetH);
statWellDto.Total = GetStatTotal(wellOperations, well.IdState, timezoneOffsetH);
statWellDto.TvdLagPercent = CalcTvdLagPercent(well.IdTelemetry, wellOperations);
statWellDto.TvdLagDays = CalcTvdLagDays(wellOperations);
statWellDto.TvdDrillingDays = CalcDrillingDays(wellOperations);
return statWellDto;
}
private double? CalcTvdLagPercent(int? idTelemetry, IOrderedEnumerable<WellOperation> wellOperations)
private static double? CalcDrillingDays(IEnumerable<WellOperation> wellOperations)
{
var currentDate = DateTimeOffset.UtcNow;
var operationsOrdered = wellOperations
.OrderBy(o => o.DateStart);
var wellDepth = wellOperations
.LastOrDefault(o => o.IdType == WellOperation.IdOperationTypeFact)?.DepthEnd;
var factOperations = operationsOrdered
.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
if (idTelemetry.HasValue)
wellDepth = telemetryDataCache.GetLastOrDefault(idTelemetry.Value)?.WellDepth;
if (wellDepth is null)
if (!factOperations.Any())
return null;
var planOperations = wellOperations
.Where(o => o.IdType == WellOperation.IdOperationTypePlan)
.OrderBy(o => o.DateStart.AddHours(o.DurationHours));
var operationFrom = factOperations.First();
if (!planOperations.Any())
return null;
var operationTo = factOperations.Last();
var planDepth = CalcPlanDepth(planOperations, currentDate);
if (planDepth is null)
return null;
if (planDepth == 0d)
return 0d;
return (1 - wellDepth / planDepth) * 100;
return (operationTo.DateStart.AddHours(operationTo.DurationHours) - operationFrom.DateStart).TotalDays;
}
private static double? CalcPlanDepth(IOrderedEnumerable<WellOperation> planOperations, DateTimeOffset currentDate)
private static double? CalcTvdLagDays(IEnumerable<WellOperation> wellOperations)
{
var operationIn = planOperations
.FirstOrDefault(o => o.DateStart <= currentDate && o.DateStart.AddHours(o.DurationHours) >= currentDate);
var operationsOrdered = wellOperations
.OrderBy(o => o.DateStart);
if (operationIn is not null)
return Interpolate(
operationIn.DepthStart,
operationIn.DepthEnd,
operationIn.DateStart,
operationIn.DateStart.AddHours(operationIn.DurationHours),
currentDate);
var factOperations = operationsOrdered
.Where(o => o.IdType == WellOperation.IdOperationTypeFact);
var operationFrom = planOperations
.LastOrDefault(o => o.DateStart.AddHours(o.DurationHours) <= currentDate);
var operationTo = planOperations
.FirstOrDefault(o => o.DateStart >= currentDate);
if (operationFrom is null && operationTo is not null)
return 0d;
else if (operationFrom is not null && operationTo is not null)
{
return Interpolate(
operationFrom.DepthEnd,
operationTo.DepthStart,
operationFrom.DateStart.AddHours(operationTo.DurationHours),
operationTo.DateStart,
currentDate);
}
else if (operationFrom is not null && operationTo is null)
return operationFrom.DepthEnd;
var lastCorrespondingFactOperation = factOperations
.LastOrDefault(o => o.IdPlan is not null);
if (lastCorrespondingFactOperation is null)
return null;
}
private static double Interpolate(double y0, double y1, DateTimeOffset x0, DateTimeOffset x1, DateTimeOffset x)
=> y0 + (y1 - y0) * (x - x0).TotalMinutes / (x1 - x0).TotalMinutes;
var lastCorrespondingPlanOperation = wellOperations
.FirstOrDefault(o => o.Id == lastCorrespondingFactOperation.IdPlan);
if (lastCorrespondingPlanOperation is null)
return null;
var factEnd = lastCorrespondingFactOperation.DateStart.AddHours(lastCorrespondingFactOperation.DurationHours);
var planEnd = lastCorrespondingPlanOperation.DateStart.AddHours(lastCorrespondingPlanOperation.DurationHours);
var lagDays = (planEnd - factEnd).TotalDays;
return lagDays;
}
private IEnumerable<StatSectionDto> CalcSectionsStats(IEnumerable<WellOperation> operations, double timezoneOffsetH)
{

View File

@ -1,341 +0,0 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using ClosedXML.Excel;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace AsbCloudInfrastructure.Services.WellOperationService
{
/*
* password for WellOperationImportTemplate.xlsx is ASB2020!
*/
public class WellOperationImportService : IWellOperationImportService
{
private const string sheetNamePlan = "План";
private const string sheetNameFact = "Факт";
private const int headerRowsCount = 1;
private const int columnSection = 1;
private const int columnCategory = 2;
private const int columnCategoryInfo = 3;
private const int columnDepthStart = 4;
private const int columnDepthEnd = 5;
private const int columnDate = 6;
private const int columnDuration = 7;
private const int columnComment = 8;
private static readonly DateTime dateLimitMin = new DateTime(2001, 1, 1, 0, 0, 0);
private static readonly DateTime dateLimitMax = new DateTime(2099, 1, 1, 0, 0, 0);
private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366);
private readonly IAsbCloudDbContext db;
private readonly IWellService wellService;
private List<WellOperationCategory> categories = null!;
public List<WellOperationCategory> Categories
{
get
{
if (categories is null)
{
categories = db.WellOperationCategories
.Where(c => c.Id >= 5000)
.AsNoTracking()
.ToList();
}
return categories;
}
}
private List<WellSectionType> sections = null!;
public List<WellSectionType> Sections
{
get
{
if (sections is null)
sections = db.WellSectionTypes
.AsNoTracking()
.ToList();
return sections;
}
}
// TODO: use WellOperationRepository instead of DB
public WellOperationImportService(IAsbCloudDbContext db, IWellService wellService)
{
this.db = db;
this.wellService = wellService;
}
public void Import(int idWell, Stream stream, int idUser, bool deleteWellOperationsBeforeImport = false)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
var operations = ParseFileStream(stream);
foreach (var operation in operations)
{
operation.IdWell = idWell;
operation.IdUser = idUser;
}
SaveOperations(idWell, operations, deleteWellOperationsBeforeImport);
}
public Stream Export(int idWell)
{
var operations = db.WellOperations
.Include(o => o.WellSectionType)
.Include(o => o.OperationCategory)
.Where(o => o.IdWell == idWell)
.OrderBy(o => o.DateStart)
.AsNoTracking()
.ToList();
var timezone = wellService.GetTimezone(idWell);
return MakeExelFileStream(operations, timezone.Hours);
}
public Stream GetExcelTemplateStream()
{
var resourceName = System.Reflection.Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("WellOperationImportTemplate.xlsx"))!;
var stream = System.Reflection.Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
return stream;
}
private Stream MakeExelFileStream(IEnumerable<WellOperation> operations, double timezoneOffset)
{
using Stream ecxelTemplateStream = GetExcelTemplateStream();
using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled);
AddOperationsToWorkbook(workbook, operations, timezoneOffset);
MemoryStream memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private static void AddOperationsToWorkbook(XLWorkbook workbook, IEnumerable<WellOperation> operations, double timezoneOffset)
{
var planOperations = operations.Where(o => o.IdType == 0);
if (planOperations.Any())
{
var sheetPlan = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan);
if (sheetPlan is not null)
AddOperationsToSheet(sheetPlan, planOperations, timezoneOffset);
}
var factOperations = operations.Where(o => o.IdType == 1);
if (factOperations.Any())
{
var sheetFact = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact);
if (sheetFact is not null)
AddOperationsToSheet(sheetFact, factOperations, timezoneOffset);
}
}
private static void AddOperationsToSheet(IXLWorksheet sheet, IEnumerable<WellOperation> operations, double timezoneOffset)
{
var operationsList = operations.ToList();
for (int i = 0; i < operationsList.Count; i++)
{
var row = sheet.Row(1 + i + headerRowsCount);
AddOperationToRow(row, operationsList[i], timezoneOffset);
}
}
private static void AddOperationToRow(IXLRow row, WellOperation operation, double timezoneOffset)
{
row.Cell(columnSection).Value = operation.WellSectionType?.Caption;
row.Cell(columnCategory).Value = operation.OperationCategory?.Name;
row.Cell(columnCategoryInfo).Value = operation.CategoryInfo;
row.Cell(columnDepthStart).Value = operation.DepthStart;
row.Cell(columnDepthEnd).Value = operation.DepthEnd;
row.Cell(columnDate).Value = operation.DateStart.ToRemoteDateTime(timezoneOffset);
row.Cell(columnDuration).Value = operation.DurationHours;
row.Cell(columnComment).Value = operation.Comment;
}
private void SaveOperations(int idWell, IEnumerable<WellOperationDto> operations, bool deleteWellOperationsBeforeImport = false)
{
var timezone = wellService.GetTimezone(idWell);
var transaction = db.Database.BeginTransaction();
try
{
if (deleteWellOperationsBeforeImport)
db.WellOperations.RemoveRange(db.WellOperations.Where(o => o.IdWell == idWell));
var entities = operations.Select(o =>
{
var entity = o.Adapt<WellOperation>();
entity.IdWell = idWell;
entity.DateStart = o.DateStart.ToUtcDateTimeOffset(timezone.Hours);
entity.LastUpdateDate = DateTimeOffset.UtcNow;
return entity;
});
db.WellOperations.AddRange(entities);
db.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
private IEnumerable<WellOperationDto> ParseFileStream(Stream stream)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook);
}
private IEnumerable<WellOperationDto> ParseWorkbook(IXLWorkbook workbook)
{
var sheetPlan = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan);
if (sheetPlan is null)
throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}.");
var sheetFact = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact);
if (sheetFact is null)
throw new FileFormatException($"Книга excel не содержит листа {sheetNameFact}.");
//sheetPlan.RangeUsed().RangeAddress.LastAddress.ColumnNumber
var wellOperations = new List<WellOperationDto>();
var wellOperationsPlan = ParseSheet(sheetPlan, 0);
wellOperations.AddRange(wellOperationsPlan);
var wellOperationsFact = ParseSheet(sheetFact, 1);
wellOperations.AddRange(wellOperationsFact);
return wellOperations;
}
private IEnumerable<WellOperationDto> ParseSheet(IXLWorksheet sheet, int idType)
{
if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 7)
throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
var count = sheet.RowsUsed().Count() - headerRowsCount;
if (count > 1024)
throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество операций.");
if (count <= 0)
return new List<WellOperationDto>();
var operations = new List<WellOperationDto>(count);
var parseErrors = new List<string>();
DateTime lastOperationDateStart = new DateTime();
for (int i = 0; i < count; i++)
{
var row = sheet.Row(1 + i + headerRowsCount);
try
{
var operation = ParseRow(row, idType);
operations.Add(operation);
if (lastOperationDateStart > operation.DateStart)
parseErrors.Add($"Лист {sheet.Name} строка {row.RowNumber()} дата позднее даты предыдущей операции.");
lastOperationDateStart = operation.DateStart;
}
catch (FileFormatException ex)
{
parseErrors.Add(ex.Message);
}
};
if (parseErrors.Any())
throw new FileFormatException(string.Join("\r\n", parseErrors));
else
{
if (operations.Any())
if (operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax)
parseErrors.Add($"Лист {sheet.Name} содержит диапазон дат больше {drillingDurationLimitMax}");
}
return operations;
}
private WellOperationDto ParseRow(IXLRow row, int idType)
{
var vSection = row.Cell(columnSection).Value;
var vCategory = row.Cell(columnCategory).Value;
var vCategoryInfo = row.Cell(columnCategoryInfo).Value;
var vDepthStart = row.Cell(columnDepthStart).Value;
var vDepthEnd = row.Cell(columnDepthEnd).Value;
var vDate = row.Cell(columnDate).Value;
var vDuration = row.Cell(columnDuration).Value;
var vComment = row.Cell(columnComment).Value;
var operation = new WellOperationDto { IdType = idType };
if (vSection is string sectionName)
{
var section = Sections.Find(c => c.Caption.ToLower() == sectionName.ToLower());
if (section is null)
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} указана некорректная секция");
operation.IdWellSectionType = section.Id;
operation.WellSectionTypeName = section.Caption;
}
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана секция");
if (vCategory is string categoryName)
{
var category = Categories.Find(c => c.Name.ToLower() == categoryName.ToLower());
if (category is null)
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} указана некорректная операция ({categoryName})");
operation.IdCategory = category.Id;
operation.CategoryName = category.Name;
}
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана операция");
if (vCategoryInfo is not null)
operation.CategoryInfo = vCategoryInfo.ToString();
if (vDepthStart is double depthStart && depthStart >= 0d && depthStart <= 20_000d)
operation.DepthStart = depthStart;
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана глубина на начало операции");
if (vDepthEnd is double depthEnd && depthEnd >= 0d && depthEnd <= 20_000d)
operation.DepthEnd = depthEnd;
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана глубина при завершении операции");
if (vDate is DateTime date && date > dateLimitMin && date < dateLimitMax)
operation.DateStart = date;
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} неправильно указана дата/время начала операции");
if (vDuration is double duration && duration >= 0d && duration <= 240d)
operation.DurationHours = duration;
else
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана длительность операции");
if (vComment is not null)
operation.Comment = vComment.ToString();
return operation;
}
}
}

View File

@ -258,6 +258,10 @@ namespace AsbCloudWebApi.Controllers
if (!idUser.HasValue)
return false;
var idCompany = User.GetCompanyId();
if (!idCompany.HasValue || !await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token))
return false;
var well = await wellService.GetOrDefaultAsync(idWell, token);
if (well is null)

View File

@ -87,7 +87,7 @@ public class ProcessMapWellboreDevelopmentController : CrudWellRelatedController
?? throw new ForbidException($"Скважины с {idWell} не существует");
var idCompany = User.GetCompanyId();
if (idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken))
if (!idCompany.HasValue || !await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken))
throw new ForbidException("Нет доступа к скважине");
if (well.IdState == 2 && !userRepository.HasPermission(idUser, "ProcessMap.editCompletedWell"))

View File

@ -11,6 +11,8 @@ using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Services.WellOperationImport;
namespace AsbCloudWebApi.Controllers
{
@ -25,15 +27,22 @@ namespace AsbCloudWebApi.Controllers
{
private readonly IWellOperationRepository operationRepository;
private readonly IWellService wellService;
private readonly IWellOperationExportService wellOperationExportService;
private readonly IWellOperationImportTemplateService wellOperationImportTemplateService;
private readonly IWellOperationImportService wellOperationImportService;
private readonly IUserRepository userRepository;
public WellOperationController(IWellOperationRepository operationRepository, IWellService wellService,
public WellOperationController(IWellOperationRepository operationRepository,
IWellService wellService,
IWellOperationImportTemplateService wellOperationImportTemplateService,
IWellOperationExportService wellOperationExportService,
IWellOperationImportService wellOperationImportService,
IUserRepository userRepository)
{
this.operationRepository = operationRepository;
this.wellService = wellService;
this.wellOperationImportTemplateService = wellOperationImportTemplateService;
this.wellOperationExportService = wellOperationExportService;
this.wellOperationImportService = wellOperationImportService;
this.userRepository = userRepository;
}
@ -278,22 +287,32 @@ namespace AsbCloudWebApi.Controllers
/// <summary>
/// Импортирует операции из excel (xlsx) файла
/// Импорт плановых операций из excel (xlsx) файла
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="idType">Тип операции</param>
/// <param name="startRow">Начальная строка</param>
/// <param name="endRow">Конечная строка</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="options">Удалить операции перед импортом = 1, если фал валидный</param>
/// <param name="token"> Токен отмены задачи </param>
/// <param name="options">Удалить операции перед импортом = 1, если файл валидный</param>
/// <param name="sheetName">Название листа</param>
/// <param name="token">Токен отмены задачи </param>
/// <param name="idTemplate">Шаблон файла. 0 - стандартный, 1 - Газпромнефть Хантос</param>
/// <returns></returns>
[HttpPost("import/{options}")]
[Permission]
public async Task<IActionResult> ImportAsync(int idWell,
[Required] int idType,
string? sheetName,
[Required] int idTemplate,
int? startRow,
int? endRow,
[FromForm] IFormFileCollection files,
int options,
CancellationToken token)
{
int? idCompany = User.GetCompanyId();
int? idUser = User.GetUserId();
var idCompany = User.GetCompanyId();
var idUser = User.GetUserId();
if (idCompany is null || idUser is null)
return Forbid();
@ -309,16 +328,23 @@ namespace AsbCloudWebApi.Controllers
return Forbid();
if (files.Count < 1)
return BadRequest("нет файла");
return BadRequest("Нет файла");
var file = files[0];
if (Path.GetExtension(file.FileName).ToLower() != ".xlsx")
return BadRequest("Требуется xlsx файл.");
using Stream stream = file.OpenReadStream();
try
{
wellOperationImportService.Import(idWell, stream, idUser.Value, (options & 1) > 0);
await wellOperationImportService.ImportAsync(idWell, idUser.Value, idType, stream, new WellOperationParserOptionsDto
{
SheetName = sheetName,
IdTemplate = idTemplate,
StartRow = startRow,
EndRow = endRow
}, (options & 1) > 0, token);
}
catch (FileFormatException ex)
{
@ -349,7 +375,7 @@ namespace AsbCloudWebApi.Controllers
idWell, token).ConfigureAwait(false))
return Forbid();
var stream = wellOperationImportService.Export(idWell);
var stream = await wellOperationExportService.ExportAsync(idWell, token);
var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_operations.xlsx";
return File(stream, "application/octet-stream", fileName);
}
@ -389,7 +415,7 @@ namespace AsbCloudWebApi.Controllers
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
public IActionResult GetTemplate()
{
var stream = wellOperationImportService.GetExcelTemplateStream();
var stream = wellOperationImportTemplateService.GetExcelTemplateStream();
var fileName = "ЕЦП_шаблон_файла_операций.xlsx";
return File(stream, "application/octet-stream", fileName);
}