Merge pull request 'Рефакторинг импорта ГГД' (#119) from feature/well_operation_import into dev

Reviewed-on: http://test.digitaldrilling.ru:8080/DDrilling/AsbCloudServer/pulls/119
This commit is contained in:
Никита Фролов 2023-10-04 17:28:47 +05:00
commit 771ba06a6f
17 changed files with 479 additions and 455 deletions

View File

@ -0,0 +1,12 @@
namespace AsbCloudApp.Data.WellOperationImport.Options;
/// <summary>
/// Опции для парсинга
/// </summary>
public interface IWellOperationImportOptions
{
/// <summary>
/// Тип операции
/// </summary>
int IdType { get; }
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.WellOperationImport.Options;
/// <summary>
/// Опции для парсинга дефолтного шаблона
/// </summary>
public class WellOperationImportDefaultOptionsDto : IWellOperationImportOptions
{
/// <summary>
/// Тип операции
/// 0 - плановая операция
/// 1 - фактическая операция
/// </summary>
[Required]
[Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")]
public int IdType { get; set; }
}

View File

@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.WellOperationImport.Options;
/// <summary>
/// Опции для настройки парсинга документа ГПНХ(Хантос)
/// </summary>
public class WellOperationImportGazpromKhantosOptionsDto : IWellOperationImportOptions
{
/// <summary>
/// Название листа
/// </summary>
[Required]
[StringLength(250, MinimumLength = 1, ErrorMessage = "Название листа должно быть задано")]
public string SheetName { get; set; } = null!;
/// <summary>
/// Тип операции
/// 0 - плановая операция
/// </summary>
[Required]
[Range(0, 0, ErrorMessage = "Тип операции недопустим. Допустимый: 0")]
public int IdType { get; set; }
/// <summary>
/// Начальная строка
/// </summary>
[Required]
public int StartRow { get; set; }
/// <summary>
/// Конечная строка
/// </summary>
[Required]
public int EndRow { get; set; }
}

View File

@ -25,7 +25,7 @@ public class RowDto
/// <summary>
/// Описание категории
/// </summary>
public string CategoryInfo { get; set; } = null!;
public string? CategoryInfo { get; set; }
/// <summary>
/// Начальная глубина операции

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudApp.Data.WellOperationImport;
/// <summary>
/// Лист полученный из файла Excel
/// </summary>
public class SheetDto
{
/// <summary>
/// Название листа
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// Строки
/// </summary>
public IEnumerable<RowDto> Rows { get; set; } = Enumerable.Empty<RowDto>();
}

View File

@ -1,32 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.WellOperationImport;
/// <summary>
/// Опции для настройки парсинга документа
/// </summary>
public class WellOperationParserOptionsDto
{
/// <summary>
/// Название листа
/// </summary>
[StringLength(250, MinimumLength =1, ErrorMessage = "Название листа должно быть задано")]
public string SheetName { get; set; } = null!;
/// <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,29 +1,20 @@
using System.Collections.Generic;
using System.IO;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Data.WellOperationImport.Options;
namespace AsbCloudApp.Services.WellOperationImport;
/// <summary>
/// Парсинг операций из excel файла
/// </summary>
public interface IWellOperationExcelParser
public interface IWellOperationExcelParser<in TOptions>
where TOptions : IWellOperationImportOptions
{
/// <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);
/// <summary>
/// Метод парсинга документа
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <returns></returns>
SheetDto Parse(Stream stream, TOptions options);
}

View File

@ -1,4 +1,3 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperationImport;
@ -10,17 +9,14 @@ namespace AsbCloudApp.Services.WellOperationImport;
/// </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);
/// <summary>
/// Загрузить из excel список операций
/// </summary>
/// <param name="idWell"></param>
/// <param name="idUser"></param>
/// <param name="idType"></param>
/// <param name="sheet"></param>
/// <param name="deleteBeforeImport"></param>
/// <param name="cancellationToken"></param>
Task ImportAsync(int idWell, int idUser, int idType, SheetDto sheet, bool deleteBeforeImport, CancellationToken cancellationToken);
}

View File

@ -31,6 +31,7 @@ using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudInfrastructure.Services.WellOperationImport;
using AsbCloudInfrastructure.Services.ProcessMap.ProcessMapWellboreDevelopment;
using AsbCloudApp.Data.WellOperationImport.Options;
using AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
namespace AsbCloudInfrastructure
@ -241,8 +242,8 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationImportTemplateService, WellOperationImportTemplateService>();
services.AddTransient<IWellOperationExcelParser, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser, WellOperationGazpromKhantosExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>, WellOperationGazpromKhantosExcelParser>();
return services;
}

View File

@ -1,161 +0,0 @@
using ClosedXML.Excel;
using System;
namespace AsbCloudInfrastructure.Services.DailyReport
{
internal static class XLExtentions
{
public static IXLRange _SetValue(this IXLRange range, object value)
{
var mergedRange = range.Merge();
mergedRange.FirstCell()._SetValue(value);
var colWidth = mergedRange.FirstCell().WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize);
if (value is string valueString && valueString.Length > maxCharsToWrap)
{
var row = mergedRange.FirstCell().WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap);
}
mergedRange.Style.SetAllBorders()
.Alignment.SetWrapText(true);
return mergedRange;
}
public static IXLCell _SetValue(this IXLCell cell, object value)
{
switch (value)
{
case DateTime dateTime:
cell._SetValue(dateTime);
break;
case IFormattable formattable:
cell._SetValue(formattable);
break;
case string valueString:
cell._SetValue(valueString);
break;
default:
cell.Value = value;
break;
}
return cell;
}
public static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
public static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
public static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
cell.DataType = XLDataType.DateTime;
cell.Style.DateFormat.Format = "DD.MM.YYYY HH:MM:SS";
return cell;
}
public static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
cell.DataType = XLDataType.Number;
cell.Style.NumberFormat.Format = "0.00";
return cell;
}
public static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin)
{
style.Border.RightBorder = borderStyle;
style.Border.LeftBorder = borderStyle;
style.Border.TopBorder = borderStyle;
style.Border.BottomBorder = borderStyle;
style.Border.InsideBorder = borderStyle;
style.Border.OutsideBorder = borderStyle;
return style;
}
public static IXLStyle SetBaseFont(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 10;
return style;
}
public static IXLStyle SetH1(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 14;
return style;
}
/// <summary>
/// Костыль исправляющий проблему в библиотеке IXLRange Range(this IXLWorksheet, IXLAddress, IXLAddress) с кастингом IXLAddress к XLAddress.
/// </summary>
/// <param name="sheet"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
public static IXLRange _Range(this IXLWorksheet sheet, CellAddress begin, CellAddress end)
=> sheet.Range(begin.RowNumber, begin.ColumnNumber, end.RowNumber, end.ColumnNumber);
}
}

View File

@ -187,23 +187,23 @@ public class ProcessMapPlanImportService : IProcessMapPlanImportService
private ProcessMapPlanDto ParseRow(IXLRow row)
{
var wellSectionTypeCaption = GetCellValue<string>(row, columnWellSectionType).Trim().ToLower();
var modeName = GetCellValue<string>(row, columnMode).Trim().ToLower();
var depthStart = GetCellValue<double>(row, columnDepthStart);
var depthEnd = GetCellValue<double>(row, columnDepthEnd);
var pressurePlan = GetCellValue<double>(row, columnPressurePlan);
var pressureLimitMax = GetCellValue<double>(row, columnPressureLimitMax);
var axialLoadPlan = GetCellValue<double>(row, columnAxialLoadPlan);
var axialLoadLimitMax = GetCellValue<double>(row, columnAxialLoadLimitMax);
var topDriveTorquePlan = GetCellValue<double>(row, columnTopDriveTorquePlan);
var topDriveTorqueLimitMax = GetCellValue<double>(row, columnTopDriveTorqueLimitMax);
var topDriveSpeedPlan = GetCellValue<double>(row, columnTopDriveSpeedPlan);
var topDriveSpeedLimitMax = GetCellValue<double>(row, columnTopDriveSpeedLimitMax);
var flowPlan = GetCellValue<double>(row, columnFlowPlan);
var flowLimitMax = GetCellValue<double>(row, columnFlowLimitMax);
var ropPlan = GetCellValue<double>(row, columnRopPlan);
var usageSaub = GetCellValue<double>(row, columnUsageSaub);
var usageSpin = GetCellValue<double>(row, columnUsageSpin);
var wellSectionTypeCaption = row.Cell(columnWellSectionType).GetCellValue<string>().Trim().ToLower();
var modeName = row.Cell(columnMode).GetCellValue<string>().Trim().ToLower();
var depthStart = row.Cell(columnDepthStart).GetCellValue<double>();
var depthEnd = row.Cell(columnDepthEnd).GetCellValue<double>();
var pressurePlan = row.Cell(columnPressurePlan).GetCellValue<double>();
var pressureLimitMax = row.Cell(columnPressureLimitMax).GetCellValue<double>();
var axialLoadPlan = row.Cell(columnAxialLoadPlan).GetCellValue<double>();
var axialLoadLimitMax = row.Cell(columnAxialLoadLimitMax).GetCellValue<double>();
var topDriveTorquePlan = row.Cell(columnTopDriveTorquePlan).GetCellValue<double>();
var topDriveTorqueLimitMax = row.Cell(columnTopDriveTorqueLimitMax).GetCellValue<double>();
var topDriveSpeedPlan = row.Cell(columnTopDriveSpeedPlan).GetCellValue<double>();
var topDriveSpeedLimitMax = row.Cell(columnTopDriveSpeedLimitMax).GetCellValue<double>();
var flowPlan = row.Cell(columnFlowPlan).GetCellValue<double>();
var flowLimitMax = row.Cell(columnFlowLimitMax).GetCellValue<double>();
var ropPlan = row.Cell(columnRopPlan).GetCellValue<double>();
var usageSaub = row.Cell(columnUsageSaub).GetCellValue<double>();
var usageSpin = row.Cell(columnUsageSpin).GetCellValue<double>();
var wellSection = sections.FirstOrDefault(s => s.Caption.Trim().ToLower() == wellSectionTypeCaption)
?? throw new FileFormatException(
@ -342,19 +342,4 @@ public class ProcessMapPlanImportService : IProcessMapPlanImportService
2 => "Слайд",
_ => "Ручной",
};
//TODO: вынести в метод расширения
private static T GetCellValue<T>(IXLRow row, int columnNumber)
{
try
{
var cell = row.Cell(columnNumber);
return (T)Convert.ChangeType(cell.Value, typeof(T));
}
catch
{
throw new FileFormatException(
$"Лист {row.Worksheet.Name}. Ячейка: ({row.RowNumber()},{columnNumber}) содержит некорректное значение");
}
}
}

View File

@ -177,32 +177,17 @@ namespace AsbCloudInfrastructure.Services.Trajectory
private TrajectoryGeoPlanDto ParseRow(IXLRow row)
{
var _wellboreDepth = row.Cell(ColumnWellboreDepth).Value;
var _zenithAngle = row.Cell(ColumnZenithAngle).Value;
var _azimuthGeo = row.Cell(ColumnAzimuthGeo).Value;
var _azimuthMagnetic = row.Cell(ColumnAzimuthMagnetic).Value;
var _verticalDepth = row.Cell(ColumnVerticalDepth).Value;
var _radius = row.Cell(ColumnRadius).Value;
var _comment = row.Cell(ColumnComment).Value;
var trajectoryRow = new TrajectoryGeoPlanDto();
static double getDoubleValue(object value, string nameParam, IXLRow row)
var trajectoryRow = new TrajectoryGeoPlanDto
{
if (value is double _value)
return _value;
throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} - некорректные данные - {nameParam}");
}
trajectoryRow.WellboreDepth = getDoubleValue(_wellboreDepth, "Глубина по стволу", row);
trajectoryRow.ZenithAngle = getDoubleValue(_zenithAngle, "Зенитный угол", row);
trajectoryRow.AzimuthGeo = getDoubleValue(_azimuthGeo, "Азимут географический", row);
trajectoryRow.AzimuthMagnetic = getDoubleValue(_azimuthMagnetic, "Азимут магнитный", row);
trajectoryRow.VerticalDepth = getDoubleValue(_verticalDepth, "Глубина вертикальная", row);
trajectoryRow.Radius = getDoubleValue(_radius, "Радиус цели", row);
if (_comment is not null)
trajectoryRow.Comment = _comment.ToString();
WellboreDepth = row.Cell(ColumnWellboreDepth).GetCellValue<double>(),
ZenithAngle = row.Cell(ColumnZenithAngle).GetCellValue<double>(),
AzimuthGeo = row.Cell(ColumnAzimuthGeo).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(ColumnAzimuthMagnetic).GetCellValue<double>(),
VerticalDepth = row.Cell(ColumnVerticalDepth).GetCellValue<double>(),
Radius = row.Cell(ColumnRadius).GetCellValue<double>(),
Comment = row.Cell(ColumnComment).GetCellValue<string?>()
};
return trajectoryRow;
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Data.WellOperationImport.Options;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperationImport.Constants;
@ -11,31 +11,29 @@ using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
public class WellOperationDefaultExcelParser : IWellOperationExcelParser
public class WellOperationDefaultExcelParser : IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>
{
public int IdTemplate => Templates.IdDefaultTemplate;
public IEnumerable<int> IdTypes => new[] { WellOperation.IdOperationTypePlan, WellOperation.IdOperationTypeFact };
public IEnumerable<RowDto> Parse(Stream stream, WellOperationParserOptionsDto options)
public SheetDto Parse(Stream stream, WellOperationImportDefaultOptionsDto options)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook, options);
}
private static IEnumerable<RowDto> ParseWorkbook(IXLWorkbook workbook, WellOperationParserOptionsDto options)
private static SheetDto ParseWorkbook(IXLWorkbook workbook, WellOperationImportDefaultOptionsDto options)
{
if (string.IsNullOrWhiteSpace(options.SheetName))
throw new ArgumentInvalidException(nameof(options.SheetName), "Не указано название листа");
var sheetName = options.IdType == WellOperation.IdOperationTypePlan
? DefaultTemplateInfo.SheetNamePlan
: DefaultTemplateInfo.SheetNameFact;
var sheet = workbook.Worksheets.FirstOrDefault(ws =>
string.Equals(ws.Name, options.SheetName, StringComparison.CurrentCultureIgnoreCase))
?? throw new FileFormatException($"Книга excel не содержит листа '{options.SheetName}'");
string.Equals(ws.Name, sheetName, StringComparison.CurrentCultureIgnoreCase))
?? throw new FileFormatException($"Книга excel не содержит листа '{sheetName}'");
return ParseSheet(sheet);
}
private static IEnumerable<RowDto> ParseSheet(IXLWorksheet sheet)
private static SheetDto ParseSheet(IXLWorksheet sheet)
{
if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 7)
throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
@ -47,7 +45,7 @@ public class WellOperationDefaultExcelParser : IWellOperationExcelParser
case > 1024:
throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество операций.");
case <= 0:
return Enumerable.Empty<RowDto>();
return new SheetDto { Name = sheet.Name };
}
var rows = new RowDto[count];
@ -71,7 +69,11 @@ public class WellOperationDefaultExcelParser : IWellOperationExcelParser
if (cellValuesErrors.Any())
throw new FileFormatException(string.Join("\r\n", cellValuesErrors));
return rows;
return new SheetDto
{
Name = sheet.Name,
Rows = rows
};
}
private static RowDto ParseRow(IXLRow xlRow)
@ -79,28 +81,14 @@ public class WellOperationDefaultExcelParser : IWellOperationExcelParser
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))
Section = xlRow.Cell(DefaultTemplateInfo.ColumnSection).GetCellValue<string>(),
Category = xlRow.Cell(DefaultTemplateInfo.ColumnCategory).GetCellValue<string>(),
CategoryInfo = xlRow.Cell(DefaultTemplateInfo.ColumnCategoryInfo).GetCellValue<string?>(),
DepthStart = xlRow.Cell(DefaultTemplateInfo.ColumnDepthStart).GetCellValue<double>(),
DepthEnd = xlRow.Cell(DefaultTemplateInfo.ColumnDepthEnd).GetCellValue<double>(),
Date = xlRow.Cell(DefaultTemplateInfo.ColumnDate).GetCellValue<DateTime>(),
Duration = xlRow.Cell(DefaultTemplateInfo.ColumnDuration).GetCellValue<double>(),
Comment = xlRow.Cell(DefaultTemplateInfo.ColumnComment).GetCellValue<string?>()
};
}
//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

@ -1,26 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Data.WellOperationImport.Options;
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
public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>
{
private class Operation
{
public int RowNumber { get; set; }
public string CategoryInfo { get; set; } = null!;
public string? CategoryInfo { get; set; }
public double SectionDiameter { get; set; }
@ -31,38 +30,25 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
public DateTime Date { get; set; }
}
private readonly CosineSimilarity cosineSimilarity;
private readonly CosineSimilarity cosineSimilarity = new();
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)
public SheetDto Parse(Stream stream, WellOperationImportGazpromKhantosOptionsDto options)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkBook(workbook, options);
}
}
private IEnumerable<RowDto> ParseWorkBook(IXLWorkbook workbook, WellOperationParserOptionsDto options)
private SheetDto ParseWorkBook(IXLWorkbook workbook, WellOperationImportGazpromKhantosOptionsDto options)
{
if (string.IsNullOrWhiteSpace(options.SheetName))
throw new ArgumentInvalidException(nameof(options.SheetName), "Не указано название листа");
if (options.StartRow is null or < 1 or > 1048576)
if (options.StartRow is < 1 or > 1048576)
throw new ArgumentInvalidException(nameof(options.StartRow), "Некорректное значение начальной строки");
if (options.EndRow is null or < 1 or > 1048576)
if (options.EndRow is < 1 or > 1048576)
throw new ArgumentInvalidException(nameof(options.EndRow), "Некорректное значение конечной строки");
if (options.EndRow < options.StartRow)
@ -72,15 +58,15 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
string.Equals(ws.Name, options.SheetName, StringComparison.CurrentCultureIgnoreCase))
?? throw new FileFormatException($"Книга excel не содержит листа '{options.SheetName}'");
return ParseSheet(sheet, options.StartRow.Value, options.EndRow.Value);
return ParseSheet(sheet, options.StartRow, options.EndRow);
}
private IEnumerable<RowDto> ParseSheet(IXLWorksheet sheet, int startRow, int endRow)
private SheetDto ParseSheet(IXLWorksheet sheet, int startRow, int endRow)
{
var operationAttributes = GetOperationAttributes(sheet.RowsUsed());
var operationAttributes = GetOperationAttributes(sheet.RowsUsed());
if (operationAttributes is null)
return Enumerable.Empty<RowDto>();
return new SheetDto { Name = sheet.Name };
var rowsCount = endRow - startRow + 1;
@ -97,11 +83,11 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
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]))
CategoryInfo = xlRow.Cell(operationAttributes[OperationAttributes.CategoryInfo]).GetCellValue<string?>(),
SectionDiameter =xlRow.Cell(operationAttributes[OperationAttributes.SectionDiameter]).GetCellValue<double>(),
Depth = xlRow.Cell(operationAttributes[OperationAttributes.Depth]).GetCellValue<double>(),
Duration = xlRow.Cell(operationAttributes[OperationAttributes.Duration]).GetCellValue<double>(),
Date = xlRow.Cell(operationAttributes[OperationAttributes.Date]).GetCellValue<DateTime>()
});
}
catch (FileFormatException ex)
@ -113,7 +99,11 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
if (cellValuesErrors.Any())
throw new FileFormatException(string.Join("\r\n", cellValuesErrors));
return BuildRows();
return new SheetDto()
{
Name = sheet.Name,
Rows = BuildRows()
};
IEnumerable<(double Diameter, string Name)> BuildSections()
{
@ -183,7 +173,7 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
foreach (var cell in cells)
{
var operationAttribute = GetValueDictionary(operationAttributesDict, GetCellValue<string>(cell), 0.7);
var operationAttribute = GetValueDictionary(operationAttributesDict, cell.GetCellValue<string>(), 0.7);
if (operationAttribute is null || operationAttributes.Any(a => a.Key == operationAttribute))
continue;
@ -198,8 +188,11 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
return operationAttributes is not null && operationAttributes.Count == countOperationAttributes ? operationAttributes : null;
}
private string? GetValueDictionary(IDictionary<string, string> dict, string cellValue, double? minSimilarity)
private string? GetValueDictionary(IDictionary<string, string> dict, string? cellValue, double? minSimilarity)
{
if (string.IsNullOrWhiteSpace(cellValue))
return null;
var similarValues = new List<(double similarity, string value)>();
var profile1 = cosineSimilarity.GetProfile(cellValue);
@ -234,21 +227,4 @@ public class WellOperationGazpromKhantosExcelParser : IWellOperationExcelParser
.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

@ -6,64 +6,35 @@ 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)
public WellOperationImportService(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)
public async Task ImportAsync(int idWell, int idUser, int idType, SheetDto sheet, bool deleteBeforeImport, CancellationToken cancellationToken)
{
var excelParser = excelParsers.FirstOrDefault(p => p.IdTemplate == options.IdTemplate && p.IdTypes.Contains(idType))
?? 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)
foreach (var row in sheet.Rows)
{
try
{
@ -71,28 +42,32 @@ public class WellOperationImportService : IWellOperationImportService
string.Equals(s.Caption, row.Section, StringComparison.CurrentCultureIgnoreCase));
if (section is null)
throw new FileFormatException($"Лист '{options.SheetName}'. В строке '{row.Number}' не удалось определить секцию");
throw new FileFormatException($"Лист '{sheet.Name}'. В строке '{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}' не удалось определить операцию");
throw new FileFormatException($"Лист '{sheet.Name}'. В строке '{row.Number}' не удалось определить операцию");
if (row.DepthStart is not (>= 0d and <= 20_000d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная глубина на начало операции");
throw new FileFormatException(
$"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная глубина на начало операции");
if (row.DepthEnd is not (>= 0d and <= 20_000d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная глубина на конец операции");
throw new FileFormatException(
$"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная глубина на конец операции");
if (row.Date < dateLimitMin && row.Date > dateLimitMax)
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' неправильно получена дата начала операции");
throw new FileFormatException(
$"Лист '{sheet.Name}'. Строка '{row.Number}' неправильно получена дата начала операции");
if (operations.LastOrDefault()?.DateStart > row.Date)
throw new FileFormatException($"Лист '{options.SheetName}' строка '{row.Number}' дата позднее даты предыдущей операции");
throw new FileFormatException(
$"Лист '{sheet.Name}' строка '{row.Number}' дата позднее даты предыдущей операции");
if (row.Duration is not (>= 0d and <= 240d))
throw new FileFormatException($"Лист '{options.SheetName}'. Строка '{row.Number}' некорректная длительность операции");
throw new FileFormatException($"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная длительность операции");
operations.Add(new WellOperationDto
{
@ -115,24 +90,25 @@ public class WellOperationImportService : IWellOperationImportService
}
if (operations.Any() && operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax)
validationErrors.Add($"Лист {options.SheetName} содержит диапазон дат больше {drillingDurationLimitMax}");
validationErrors.Add($"Лист {sheet.Name} содержит диапазон дат больше {drillingDurationLimitMax}");
if (validationErrors.Any())
throw new FileFormatException(string.Join("\r\n", validationErrors));
if(!operations.Any())
if (!operations.Any())
return;
if (deleteWellOperationsBeforeImport)
if (deleteBeforeImport)
{
var existingOperations = await wellOperationRepository.GetAsync(new WellOperationRequest
{
IdWell = idWell
IdWell = idWell,
OperationType = idType
}, cancellationToken);
await wellOperationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken);
}
await wellOperationRepository.InsertRangeAsync(operations, cancellationToken);
await wellOperationRepository.InsertRangeAsync(operations, cancellationToken);
}
}

View File

@ -0,0 +1,182 @@
using ClosedXML.Excel;
using System;
using System.Globalization;
using System.IO;
using AsbCloudInfrastructure.Services.DailyReport;
namespace AsbCloudInfrastructure;
internal static class XLExtentions
{
internal static IXLRange _SetValue(this IXLRange range, object value)
{
var mergedRange = range.Merge();
mergedRange.FirstCell()._SetValue(value);
var colWidth = mergedRange.FirstCell().WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize);
if (value is string valueString && valueString.Length > maxCharsToWrap)
{
var row = mergedRange.FirstCell().WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap);
}
mergedRange.Style.SetAllBorders()
.Alignment.SetWrapText(true);
return mergedRange;
}
internal static IXLCell _SetValue(this IXLCell cell, object value)
{
switch (value)
{
case DateTime dateTime:
cell._SetValue(dateTime);
break;
case IFormattable formattable:
cell._SetValue(formattable);
break;
case string valueString:
cell._SetValue(valueString);
break;
default:
cell.Value = value;
break;
}
return cell;
}
internal static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
internal static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
cell.DataType = XLDataType.DateTime;
cell.Style.DateFormat.Format = "DD.MM.YYYY HH:MM:SS";
return cell;
}
internal static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
cell.DataType = XLDataType.Number;
cell.Style.NumberFormat.Format = "0.00";
return cell;
}
internal static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin)
{
style.Border.RightBorder = borderStyle;
style.Border.LeftBorder = borderStyle;
style.Border.TopBorder = borderStyle;
style.Border.BottomBorder = borderStyle;
style.Border.InsideBorder = borderStyle;
style.Border.OutsideBorder = borderStyle;
return style;
}
internal static IXLStyle SetBaseFont(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 10;
return style;
}
internal static IXLStyle SetH1(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 14;
return style;
}
/// <summary>
/// Костыль исправляющий проблему в библиотеке IXLRange Range(this IXLWorksheet, IXLAddress, IXLAddress) с кастингом IXLAddress к XLAddress.
/// </summary>
/// <param name="sheet"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
internal static IXLRange _Range(this IXLWorksheet sheet, CellAddress begin, CellAddress end)
=> sheet.Range(begin.RowNumber, begin.ColumnNumber, end.RowNumber, end.ColumnNumber);
internal static T? GetCellValue<T>(this IXLCell cell)
{
try
{
if (cell.IsEmpty() && default(T) == null)
return default;
if (typeof(T) != typeof(DateTime))
return (T)Convert.ChangeType(cell.GetFormattedString(), typeof(T), CultureInfo.InvariantCulture);
if (cell.Value is DateTime dateTime)
return (T)(object)dateTime;
return (T)(object)DateTime.FromOADate((double)cell.Value);
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
}
}
}

View File

@ -11,8 +11,9 @@ using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperationImport;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudApp.Data.WellOperationImport.Options;
using AsbCloudApp.Exceptions;
namespace AsbCloudWebApi.Controllers
{
@ -30,6 +31,8 @@ namespace AsbCloudWebApi.Controllers
private readonly IWellOperationExportService wellOperationExportService;
private readonly IWellOperationImportTemplateService wellOperationImportTemplateService;
private readonly IWellOperationImportService wellOperationImportService;
private readonly IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser;
private readonly IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser;
private readonly IUserRepository userRepository;
public WellOperationController(IWellOperationRepository operationRepository,
@ -37,6 +40,8 @@ namespace AsbCloudWebApi.Controllers
IWellOperationImportTemplateService wellOperationImportTemplateService,
IWellOperationExportService wellOperationExportService,
IWellOperationImportService wellOperationImportService,
IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser,
IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser,
IUserRepository userRepository)
{
this.operationRepository = operationRepository;
@ -44,6 +49,8 @@ namespace AsbCloudWebApi.Controllers
this.wellOperationImportTemplateService = wellOperationImportTemplateService;
this.wellOperationExportService = wellOperationExportService;
this.wellOperationImportService = wellOperationImportService;
this.wellOperationDefaultExcelParser = wellOperationDefaultExcelParser;
this.wellOperationGazpromKhantosExcelParser = wellOperationGazpromKhantosExcelParser;
this.userRepository = userRepository;
}
@ -287,45 +294,28 @@ 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="options">Параметры для парсинга файла</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="options">Удалить операции перед импортом = 1, если файл валидный</param>
/// <param name="sheetName">Название листа</param>
/// <param name="token">Токен отмены задачи </param>
/// <param name="idTemplate">Шаблон файла. 0 - стандартный, 1 - Газпромнефть Хантос</param>
/// <param name="deleteBeforeImport">Удалить операции перед импортом = 1, если файл валидный</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("import/{options}")]
[HttpPost("import/default/{deleteBeforeImport}")]
[Permission]
public async Task<IActionResult> ImportAsync(int idWell,
[Required] int idType,
string? sheetName,
[Required] int idTemplate,
int? startRow,
int? endRow,
public async Task<IActionResult> ImportDefaultExcelFileAsync(int idWell,
[FromQuery] WellOperationImportDefaultOptionsDto options,
[FromForm] IFormFileCollection files,
int options,
[Range(0, 1, ErrorMessage = "Недопустимое значение. Допустимые: 0, 1")] int deleteBeforeImport,
CancellationToken token)
{
var idCompany = User.GetCompanyId();
var idUser = User.GetUserId();
if (idCompany is null || idUser is null)
return Forbid();
if (!idUser.HasValue)
throw new ForbidException("Неизвестный пользователь");
if (!await CanUserAccessToWellAsync(idWell, token))
return Forbid();
if (!await CanUserEditWellOperationsAsync(idWell, token))
return Forbid();
if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
idWell, token).ConfigureAwait(false))
return Forbid();
await AssertUserHasAccessToImportWellOperationsAsync(idWell, token);
if (files.Count < 1)
return this.ValidationBadRequest(nameof(files), "Нет файла");
@ -338,13 +328,56 @@ namespace AsbCloudWebApi.Controllers
try
{
await wellOperationImportService.ImportAsync(idWell, idUser.Value, idType, stream, new WellOperationParserOptionsDto
{
SheetName = sheetName,
IdTemplate = idTemplate,
StartRow = startRow,
EndRow = endRow
}, (options & 1) > 0, token);
var sheet = wellOperationDefaultExcelParser.Parse(stream, options);
await wellOperationImportService.ImportAsync(idWell, idUser.Value, options.IdType, sheet, (deleteBeforeImport & 1) > 0, token);
}
catch (FileFormatException ex)
{
return this.ValidationBadRequest(nameof(files), ex.Message);
}
return Ok();
}
/// <summary>
/// Импорт операций из excel (xlsx) файла. ГПНХ (Хантос)
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="options">Параметры для парсинга файла</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="deleteBeforeImport">Удалить операции перед импортом = 1, если файл валидный</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("import/gazpromKhantos/{deleteBeforeImport}")]
[Permission]
public async Task<IActionResult> ImportGazpromKhantosExcelFileAsync(int idWell,
[FromQuery] WellOperationImportGazpromKhantosOptionsDto options,
[FromForm] IFormFileCollection files,
[Range(0, 1, ErrorMessage = "Недопустимое значение. Допустимые: 0, 1")] int deleteBeforeImport,
CancellationToken token)
{
var idUser = User.GetUserId();
if (!idUser.HasValue)
throw new ForbidException("Неизвестный пользователь");
await AssertUserHasAccessToImportWellOperationsAsync(idWell, token);
if (files.Count < 1)
return this.ValidationBadRequest(nameof(files), "Нет файла");
var file = files[0];
if (Path.GetExtension(file.FileName).ToLower() != ".xlsx")
return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл.");
using Stream stream = file.OpenReadStream();
try
{
var sheet = wellOperationGazpromKhantosExcelParser.Parse(stream, options);
await wellOperationImportService.ImportAsync(idWell, idUser.Value, options.IdType, sheet, (deleteBeforeImport & 1) > 0, token);
}
catch (FileFormatException ex)
{
@ -420,6 +453,24 @@ namespace AsbCloudWebApi.Controllers
return File(stream, "application/octet-stream", fileName);
}
private async Task AssertUserHasAccessToImportWellOperationsAsync(int idWell, CancellationToken token)
{
var idCompany = User.GetCompanyId();
var idUser = User.GetUserId();
if (!idCompany.HasValue || !idUser.HasValue)
throw new ForbidException("Неизвестный пользователь");
if (!await CanUserAccessToWellAsync(idWell, token))
throw new ForbidException("Нет доступа к скважине");
if (!await CanUserEditWellOperationsAsync(idWell, token))
throw new ForbidException("Недостаточно прав для редактирования ГГД на завершенной скважине");
if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token))
throw new ForbidException("Скважина недоступна для компании");
}
private async Task<bool> CanUserEditWellOperationsAsync(int idWell, CancellationToken token)
{
var idUser = User.GetUserId();