diff --git a/AsbCloudApp/Data/WellOperationImport/RowDto.cs b/AsbCloudApp/Data/WellOperationImport/RowDto.cs
new file mode 100644
index 00000000..5b6691f4
--- /dev/null
+++ b/AsbCloudApp/Data/WellOperationImport/RowDto.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace AsbCloudApp.Data.WellOperationImport;
+
+///
+/// Объект строки полученный из файла excel
+///
+public class RowDto
+{
+ ///
+ /// Номер строки
+ ///
+ public int Number { get; set; }
+
+ ///
+ /// Название секции
+ ///
+ public string? Section { get; set; }
+
+ ///
+ /// Категория
+ ///
+ public string? Category { get; set; }
+
+ ///
+ /// Описание категории
+ ///
+ public string CategoryInfo { get; set; } = null!;
+
+ ///
+ /// Начальная глубина операции
+ ///
+ public double DepthStart { get; set; }
+
+ ///
+ /// Конечная глубина операции
+ ///
+ public double DepthEnd { get; set; }
+
+ ///
+ /// Дата начала операции
+ ///
+ public DateTime Date { get; set; }
+
+ ///
+ /// Длительность операции
+ ///
+ public double Duration { get; set; }
+
+ ///
+ /// Комментарий
+ ///
+ public string? Comment { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/WellOperationImport/WellOperationImportOptionsDto.cs b/AsbCloudApp/Data/WellOperationImport/WellOperationImportOptionsDto.cs
new file mode 100644
index 00000000..5a1efc22
--- /dev/null
+++ b/AsbCloudApp/Data/WellOperationImport/WellOperationImportOptionsDto.cs
@@ -0,0 +1,29 @@
+namespace AsbCloudApp.Data.WellOperationImport;
+
+///
+/// Опции для настройки парсинга документа
+///
+public class WellOperationParserOptionsDto
+{
+ ///
+ /// Название листа
+ ///
+ public string? SheetName { get; set; }
+
+ ///
+ /// Id шаблона
+ /// 0 - Дефолтный шаблон
+ /// 1 - Газпром хантос
+ ///
+ public int IdTemplate { get; set; }
+
+ ///
+ /// Начальная строка
+ ///
+ public int? StartRow { get; set; }
+
+ ///
+ /// Конечная строка
+ ///
+ public int? EndRow { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/IWellOperationImportService.cs b/AsbCloudApp/Services/IWellOperationImportService.cs
deleted file mode 100644
index f8c5fac9..00000000
--- a/AsbCloudApp/Services/IWellOperationImportService.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.IO;
-
-namespace AsbCloudApp.Services
-{
- ///
- /// сервис импорта/экспорта операций по скважине вводимых вручную
- ///
- public interface IWellOperationImportService
- {
- ///
- /// скачать в excel
- ///
- ///
- ///
- Stream Export(int idWell);
-
- ///
- /// скачать шаблон для заполнения
- ///
- ///
- Stream GetExcelTemplateStream();
-
- ///
- /// закгрузить из excel список операций
- ///
- ///
- ///
- ///
- /// Очистить старые перед импортом (если файл проходит валидацию)
- void Import(int idWell, Stream stream, int idUser, bool deleteWellOperationsBeforeImport = false);
- }
-}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/WellOperationImport/IWellOperationExcelParser.cs b/AsbCloudApp/Services/WellOperationImport/IWellOperationExcelParser.cs
new file mode 100644
index 00000000..c8c8042a
--- /dev/null
+++ b/AsbCloudApp/Services/WellOperationImport/IWellOperationExcelParser.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.IO;
+using AsbCloudApp.Data.WellOperationImport;
+
+namespace AsbCloudApp.Services.WellOperationImport;
+
+///
+/// Парсинг операций из excel файла
+///
+public interface IWellOperationExcelParser
+{
+ ///
+ /// Id шаблона
+ ///
+ int IdTemplate { get; }
+
+ ///
+ /// Типы операций, которые можно получить из файла
+ ///
+ IEnumerable IdTypes { get; }
+
+ ///
+ /// Метод парсинга документа
+ ///
+ ///
+ ///
+ ///
+ IEnumerable Parse(Stream stream, WellOperationParserOptionsDto options);
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/WellOperationImport/IWellOperationExportService.cs b/AsbCloudApp/Services/WellOperationImport/IWellOperationExportService.cs
new file mode 100644
index 00000000..9b0a96ac
--- /dev/null
+++ b/AsbCloudApp/Services/WellOperationImport/IWellOperationExportService.cs
@@ -0,0 +1,19 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudApp.Services.WellOperationImport;
+
+///
+/// Экспорт ГГД
+///
+public interface IWellOperationExportService
+{
+ ///
+ /// Скачать в excel
+ ///
+ ///
+ ///
+ ///
+ Task ExportAsync(int idWell, CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs
new file mode 100644
index 00000000..1da68cf8
--- /dev/null
+++ b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs
@@ -0,0 +1,26 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data.WellOperationImport;
+
+namespace AsbCloudApp.Services.WellOperationImport;
+
+///
+/// Импорт ГГД
+///
+public interface IWellOperationImportService
+{
+ ///
+ /// Загрузить из excel список операций
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task ImportAsync(int idWell, int idUser, int idType, Stream stream, WellOperationParserOptionsDto options,
+ bool deleteWellOperationsBeforeImport,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Services/WellOperationImport/IWellOperationImportTemplateService.cs b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportTemplateService.cs
new file mode 100644
index 00000000..817f3ae5
--- /dev/null
+++ b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportTemplateService.cs
@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace AsbCloudApp.Services.WellOperationImport;
+
+///
+/// Сервис для получения шаблонов ГГД
+///
+public interface IWellOperationImportTemplateService
+{
+ ///
+ /// Скачать шаблон для заполнения
+ ///
+ ///
+ Stream GetExcelTemplateStream();
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
index 72ed00c1..e14fb012 100644
--- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
+++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
@@ -34,9 +34,12 @@
-
+
+
+
+
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index 3bff8ee2..9f845fff 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -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();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
@@ -233,6 +235,13 @@ namespace AsbCloudInfrastructure
services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+ services.AddTransient();
+
return services;
}
diff --git a/AsbCloudInfrastructure/Services/ProcessMap/ProcessMapPlanImportService.cs b/AsbCloudInfrastructure/Services/ProcessMap/ProcessMapPlanImportService.cs
index 21f118a8..e8185a34 100644
--- a/AsbCloudInfrastructure/Services/ProcessMap/ProcessMapPlanImportService.cs
+++ b/AsbCloudInfrastructure/Services/ProcessMap/ProcessMapPlanImportService.cs
@@ -342,7 +342,8 @@ public class ProcessMapPlanImportService : IProcessMapPlanImportService
2 => "Слайд",
_ => "Ручной",
};
-
+
+ //TODO: вынести в метод расширения
private static T GetCellValue(IXLRow row, int columnNumber)
{
try
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Constants/DefaultTemplateInfo.cs b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/DefaultTemplateInfo.cs
new file mode 100644
index 00000000..96f3272b
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/DefaultTemplateInfo.cs
@@ -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;
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Constants/OperationAttributes.cs b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/OperationAttributes.cs
new file mode 100644
index 00000000..c4049d03
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/OperationAttributes.cs
@@ -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 = "Дата окончания операции";
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Constants/Templates.cs b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/Templates.cs
new file mode 100644
index 00000000..631dc170
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Constants/Templates.cs
@@ -0,0 +1,7 @@
+namespace AsbCloudInfrastructure.Services.WellOperationImport.Constants;
+
+public static class Templates
+{
+ public const int IdDefaultTemplate = 0;
+ public const int IdGazpromKhantosTemplate = 1;
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/StringSimilarity/CosineSimilarity.cs b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/StringSimilarity/CosineSimilarity.cs
new file mode 100644
index 00000000..ae69f7f9
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/StringSimilarity/CosineSimilarity.cs
@@ -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 profile1, IDictionary profile2)
+ => DotProduct(profile1, profile2)
+ / (Norm(profile1) * Norm(profile2));
+
+ public Dictionary GetProfile(string s)
+ {
+ var shingles = new Dictionary();
+
+ 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 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 profile1, IDictionary 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;
+ }
+}
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationDefaultExcelParser.cs b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationDefaultExcelParser.cs
new file mode 100644
index 00000000..7d79e243
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationDefaultExcelParser.cs
@@ -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 IdTypes => new[] { WellOperation.IdOperationTypePlan, WellOperation.IdOperationTypeFact };
+
+ public IEnumerable Parse(Stream stream, WellOperationParserOptionsDto options)
+ {
+ using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
+
+ return ParseWorkbook(workbook, options);
+ }
+
+ private static IEnumerable 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 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();
+ }
+
+ var rows = new RowDto[count];
+
+ var cellValuesErrors = new List();
+
+ 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(xlRow.Cell(DefaultTemplateInfo.ColumnSection)),
+ Category = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnCategory)),
+ CategoryInfo = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnCategoryInfo)),
+ DepthStart = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnDepthStart)),
+ DepthEnd = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnDepthEnd)),
+ Date = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnDate)),
+ Duration = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnDuration)),
+ Comment = GetCellValue(xlRow.Cell(DefaultTemplateInfo.ColumnComment))
+ };
+ }
+
+ //TODO: вынести в метод расширения
+ private static T GetCellValue(IXLCell cell)
+ {
+ try
+ {
+ return (T)Convert.ChangeType(cell.Value, typeof(T));
+ }
+ catch
+ {
+ throw new FileFormatException(
+ $"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationGazpromKhantosExcelParser.cs b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationGazpromKhantosExcelParser.cs
new file mode 100644
index 00000000..4de99d77
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/FileParser/WellOperationGazpromKhantosExcelParser.cs
@@ -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 operationDict = InitDict("Operations.txt", '=');
+ private readonly Dictionary sectionDict = InitDict("Sections.txt", '=');
+ private readonly Dictionary operationAttributesDict = InitDict("OperationAttributes.txt", '=');
+
+
+ public WellOperationGazpromKhantosExcelParser()
+ {
+ cosineSimilarity = new CosineSimilarity();
+ }
+
+ public int IdTemplate => Templates.IdGazpromKhantosTemplate;
+
+ public IEnumerable IdTypes => new[] { WellOperation.IdOperationTypePlan };
+
+ public IEnumerable Parse(Stream stream, WellOperationParserOptionsDto options)
+ {
+ using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
+
+ return ParseWorkBook(workbook, options);
+ }
+
+ private IEnumerable 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 ParseSheet(IXLWorksheet sheet, int startRow, int endRow)
+ {
+ var operationAttributes = GetOperationAttributes(sheet.RowsUsed());
+
+ if (operationAttributes is null)
+ return Enumerable.Empty();
+
+ var rowsCount = endRow - startRow + 1;
+
+ var operations = new List();
+
+ var cellValuesErrors = new List();
+
+ for (int i = 0; i < rowsCount; i++)
+ {
+ var xlRow = sheet.Row(startRow + i);
+
+ try
+ {
+ operations.Add(new Operation
+ {
+ RowNumber = xlRow.RowNumber(),
+ CategoryInfo = GetCellValue(xlRow.Cell(operationAttributes[OperationAttributes.CategoryInfo])),
+ SectionDiameter = GetCellValue(xlRow.Cell(operationAttributes[OperationAttributes.SectionDiameter])),
+ Depth = GetCellValue(xlRow.Cell(operationAttributes[OperationAttributes.Depth])),
+ Duration = GetCellValue(xlRow.Cell(operationAttributes[OperationAttributes.Duration])),
+ Date = GetCellValue(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(sections.Select(s => s.section));
+
+ sections.Add(new ValueTuple(groupedOperation.Diameter, sectionDict.FirstOrDefault(item =>
+ groupedOperation.CategoryInfo.Contains(item.Key) &&
+ (!sectionNamesSet.Contains(item.Value) || repeatedSections.Contains(item.Value.ToLowerInvariant()))).Value));
+ }
+
+ return sections;
+ }
+
+ IEnumerable BuildRows()
+ {
+ if (!operations.Any())
+ return Enumerable.Empty();
+
+ var rows = new List();
+
+ 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? GetOperationAttributes(IXLRows xlRows)
+ {
+ const int countOperationAttributes = 5;
+
+ IDictionary? operationAttributes = null;
+
+ foreach (var xlRow in xlRows)
+ {
+ operationAttributes = new Dictionary();
+
+ var cells = xlRow.CellsUsed().ToArray();
+
+ foreach (var cell in cells)
+ {
+ var operationAttribute = GetValueDictionary(operationAttributesDict, GetCellValue(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 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 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(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}) содержит некорректное значение");
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/OperationAttributes.txt b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/OperationAttributes.txt
new file mode 100644
index 00000000..7127ce99
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/OperationAttributes.txt
@@ -0,0 +1,8 @@
+Описание=Описание
+ОК=ОК
+Секция=ОК
+Забой, м=Забой
+Время=Время операции
+Плановое время бурения, сут=Время операции
+Окончание=Дата окончания операции
+Дата окончания План РГ=Дата окончания операции
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Operations.txt b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Operations.txt
new file mode 100644
index 00000000..7bad4814
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Operations.txt
@@ -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 ч)=Сборка и спуск ТБТ
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Sections.txt b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Sections.txt
new file mode 100644
index 00000000..b0b2df54
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/Files/Dictionaries/Sections.txt
@@ -0,0 +1,7 @@
+направ=Направление
+конд=Кондуктор
+техн=Техническая колонна
+экспл=Эксплуатационная колонна
+транс=Транспортный ствол
+пилот=Пилотный ствол
+хвост=Хвостовик
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx
similarity index 100%
rename from AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xlsx
rename to AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationExportService.cs b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationExportService.cs
new file mode 100644
index 00000000..df49b7aa
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationExportService.cs
@@ -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 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 MakeExcelFileStreamAsync(IEnumerable 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 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 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 sections,
+ IEnumerable 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;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs
new file mode 100644
index 00000000..c57a3531
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs
@@ -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 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 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();
+
+ 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();
+
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportTemplateService.cs b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportTemplateService.cs
new file mode 100644
index 00000000..edb71c14
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportTemplateService.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs
deleted file mode 100644
index 949aaf71..00000000
--- a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs
+++ /dev/null
@@ -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 categories = null!;
- public List Categories
- {
- get
- {
- if (categories is null)
- {
- categories = db.WellOperationCategories
- .Where(c => c.Id >= 5000)
- .AsNoTracking()
- .ToList();
- }
-
- return categories;
- }
- }
-
- private List sections = null!;
- public List 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 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 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 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 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();
- 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 ParseFileStream(Stream stream)
- {
- using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
- return ParseWorkbook(workbook);
- }
-
- private IEnumerable 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();
-
- var wellOperationsPlan = ParseSheet(sheetPlan, 0);
- wellOperations.AddRange(wellOperationsPlan);
-
- var wellOperationsFact = ParseSheet(sheetFact, 1);
- wellOperations.AddRange(wellOperationsFact);
-
- return wellOperations;
- }
-
- private IEnumerable 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();
-
- var operations = new List(count);
- var parseErrors = new List();
- 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;
- }
- }
-
-}
diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs
index 335981df..82bc6bf6 100644
--- a/AsbCloudWebApi/Controllers/WellOperationController.cs
+++ b/AsbCloudWebApi/Controllers/WellOperationController.cs
@@ -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 operationService,
+ 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
///
- /// Импортирует операции из excel (xlsx) файла
+ /// Импорт плановых операций из excel (xlsx) файла
///
/// id скважины
+ /// Тип операции
+ /// Начальная строка
+ /// Конечная строка
/// Коллекция из одного файла xlsx
- /// Удалить операции перед импортом = 1, если фал валидный
- /// Токен отмены задачи
+ /// Удалить операции перед импортом = 1, если файл валидный
+ /// Название листа
+ /// Токен отмены задачи
+ /// Шаблон файла. 0 - стандартный, 1 - Газпромнефть Хантос
///
[HttpPost("import/{options}")]
[Permission]
public async Task 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);
}