diff --git a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs
index a6d4fd56..e8404c7f 100644
--- a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs
+++ b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs
@@ -19,6 +19,11 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat
[Range(1, int.MaxValue, ErrorMessage = "Id секции скважины не может быть меньше 1")]
public int IdWellSectionType { get; set; }
+ ///
+ /// Название секции
+ ///
+ public string? Section { get; set; }
+
///
/// Глубина по стволу от, м
///
@@ -41,6 +46,6 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat
public virtual IEnumerable Validate(ValidationContext validationContext)
{
if(DepthEnd <= DepthStart)
- yield return new ("глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) });
+ yield return new ("Глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) });
}
}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs
index 15f30300..7ab7db5d 100644
--- a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs
+++ b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs
@@ -12,6 +12,11 @@ public class ProcessMapPlanDrillingDto : ProcessMapPlanBaseDto
///
[Range(1, 2, ErrorMessage = "Id режима должен быть либо 1-ротор либо 2-слайд")]
public int IdMode { get; set; }
+
+ ///
+ /// Название режима бурения
+ ///
+ public string? Mode { get; set; }
///
/// Осевая нагрузка, т план
diff --git a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs
index e47a66e8..237c20b2 100644
--- a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs
+++ b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs
@@ -1,11 +1,14 @@
using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
namespace AsbCloudApp.Data.Trajectory
{
///
/// Базовая географическая траектория
///
- public abstract class TrajectoryGeoDto : IId
+ public abstract class TrajectoryGeoDto : IId, IValidatableObject
{
///
/// ИД строки с координатами
@@ -49,5 +52,11 @@ namespace AsbCloudApp.Data.Trajectory
/// ИД пользователя
///
public int IdUser { get; set; }
+
+ ///
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ return Enumerable.Empty();
+ }
}
}
diff --git a/AsbCloudApp/Services/IParserService.cs b/AsbCloudApp/Services/IParserService.cs
index 2cfd8eb7..8c01af5a 100644
--- a/AsbCloudApp/Services/IParserService.cs
+++ b/AsbCloudApp/Services/IParserService.cs
@@ -1,6 +1,4 @@
using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests.ParserOptions;
@@ -10,10 +8,8 @@ namespace AsbCloudApp.Services;
/// Сервис парсинга
///
///
-///
-public interface IParserService : IParserService
+public interface IParserService : IParserService
where TDto : class, IId
- where TOptions : IParserOptionsRequest
{
///
/// Распарсить файл
@@ -21,7 +17,8 @@ public interface IParserService : IParserService
///
///
///
- ParserResultDto Parse(Stream file, TOptions options);
+ ParserResultDto Parse(Stream file, TOptions options)
+ where TOptions : IParserOptionsRequest;
///
/// Получение шаблона для заполнения
diff --git a/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs b/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs
index 7b30b80d..a0c3d4ec 100644
--- a/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs
+++ b/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Threading.Tasks;
using System.Threading;
@@ -7,6 +8,7 @@ namespace AsbCloudApp.Services.ProcessMaps;
///
/// Сервис импорта РТК
///
+[Obsolete]
public interface IProcessMapPlanImportService
{
///
diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
index cdb57ae5..ba012eb5 100644
--- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
+++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
@@ -36,6 +36,7 @@
+
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index 5e1ea903..7cfc86f9 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using AsbCloudApp.Data;
using AsbCloudApp.Data.DrillTestReport;
using AsbCloudApp.Data.Manuals;
@@ -45,6 +46,8 @@ using AsbCloudDb.Model.WellSections;
using AsbCloudInfrastructure.Services.ProcessMaps;
using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudApp.Requests;
+using AsbCloudInfrastructure.Services.Parser;
+using AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
using AsbCloudInfrastructure.Services.Trajectory.Parser;
namespace AsbCloudInfrastructure
@@ -205,8 +208,6 @@ namespace AsbCloudInfrastructure
services.AddTransient();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
- services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
@@ -334,8 +335,10 @@ namespace AsbCloudInfrastructure
services.AddTransient();
services.AddTransient();
- services.AddSingleton();
-
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
return services;
}
}
diff --git a/AsbCloudInfrastructure/ParserServiceBase.cs b/AsbCloudInfrastructure/ParserServiceBase.cs
deleted file mode 100644
index d98404e7..00000000
--- a/AsbCloudInfrastructure/ParserServiceBase.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.IO;
-using System.Linq;
-using AsbCloudApp.Data;
-using AsbCloudApp.Requests.ParserOptions;
-using AsbCloudApp.Services;
-using ClosedXML.Excel;
-
-namespace AsbCloudInfrastructure;
-
-public abstract class ParserServiceBase : IParserService
- where TDto : class, IId
- where TOptions : IParserOptionsRequest
-{
- protected readonly IServiceProvider serviceProvider;
-
- protected ParserServiceBase(IServiceProvider serviceProvider)
- {
- this.serviceProvider = serviceProvider;
- }
-
- public abstract ParserResultDto Parse(Stream file, TOptions options);
- public abstract Stream GetTemplateFile();
-
- protected virtual ParserResultDto ParseExcelSheet(IXLWorksheet sheet,
- Func> parseRow,
- int columnCount,
- int headerRowsCount = 0)
- {
- if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount)
- throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
-
- var count = sheet.RowsUsed().Count() - headerRowsCount;
-
- if (count > 1024)
- throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк.");
-
- if (count <= 0)
- return new ParserResultDto();
-
- var dtos = new List>(count);
- var warnings = new List();
-
- for (var i = 0; i < count; i++)
- {
- var row = sheet.Row(1 + i + headerRowsCount);
-
- try
- {
- var dto = parseRow.Invoke(row);
- dtos.Add(dto);
- }
- catch (FileFormatException ex)
- {
- var warning = new ValidationResult(ex.Message);
- warnings.Add(warning);
- }
- }
-
- var parserResult = new ParserResultDto
- {
- Item = dtos
- };
-
- if (warnings.Any())
- parserResult.Warnings = warnings;
-
- return parserResult;
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
index f9265ba8..7a01f7df 100644
--- a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
+++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
@@ -30,7 +30,6 @@ public abstract class ChangeLogRepositoryAbstract : ICh
var result = 0;
if (dtos.Any())
{
- using var transaction = await db.Database.BeginTransactionAsync(token);
var entities = dtos.Select(Convert);
var creation = DateTimeOffset.UtcNow;
var dbSet = db.Set();
@@ -47,7 +46,6 @@ public abstract class ChangeLogRepositoryAbstract : ICh
}
result += await SaveChangesWithExceptionHandling(token);
- await transaction.CommitAsync(token);
}
return result;
}
@@ -82,30 +80,39 @@ public abstract class ChangeLogRepositoryAbstract : ICh
}
using var transaction = db.Database.BeginTransaction();
- foreach (var entity in entitiesToDelete)
+ try
{
- entity.IdState = ChangeLogAbstract.IdStateReplaced;
- entity.Obsolete = updateTime;
- entity.IdEditor = idUser;
- }
- result += await db.SaveChangesAsync(token);
+ foreach (var entity in entitiesToDelete)
+ {
+ entity.IdState = ChangeLogAbstract.IdStateReplaced;
+ entity.Obsolete = updateTime;
+ entity.IdEditor = idUser;
+ }
+ result += await db.SaveChangesAsync(token);
- var entitiesNew = dtos.Select(Convert);
- foreach (var entity in entitiesNew)
+ var entitiesNew = dtos.Select(Convert);
+ foreach (var entity in entitiesNew)
+ {
+ entity.IdPrevious = entity.Id;
+ entity.Id = default;
+ entity.Creation = updateTime;
+ entity.IdAuthor = idUser;
+ entity.Obsolete = null;
+ entity.IdEditor = null;
+ entity.IdState = ChangeLogAbstract.IdStateActual;
+ dbSet.Add(entity);
+ }
+
+ result += await SaveChangesWithExceptionHandling(token);
+
+ await transaction.CommitAsync(token);
+ return result;
+ }
+ catch
{
- entity.IdPrevious = entity.Id;
- entity.Id = default;
- entity.Creation = updateTime;
- entity.IdAuthor = idUser;
- entity.Obsolete = null;
- entity.IdEditor = null;
- entity.IdState = ChangeLogAbstract.IdStateActual;
- dbSet.Add(entity);
+ await transaction.RollbackAsync(token);
+ throw;
}
-
- result += await SaveChangesWithExceptionHandling(token);
- await transaction.CommitAsync(token);
- return result;
}
public async Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token)
@@ -146,10 +153,19 @@ public abstract class ChangeLogRepositoryAbstract : ICh
{
var result = 0;
using var transaction = await db.Database.BeginTransactionAsync(token);
- result += await Clear(idUser, request, token);
- result += await InsertRange(idUser, dtos, token);
- await transaction.CommitAsync(token);
- return result;
+ try
+ {
+ result += await Clear(idUser, request, token);
+ result += await InsertRange(idUser, dtos, token);
+
+ await transaction.CommitAsync(token);
+ return result;
+ }
+ catch
+ {
+ await transaction.RollbackAsync(token);
+ throw;
+ }
}
public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token)
diff --git a/AsbCloudInfrastructure/Services/Parser/Cell.cs b/AsbCloudInfrastructure/Services/Parser/Cell.cs
new file mode 100644
index 00000000..381ca57b
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/Parser/Cell.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using ClosedXML.Excel;
+
+namespace AsbCloudInfrastructure.Services.Parser;
+
+public class Cell
+{
+ private static IDictionary> converters = new Dictionary>()
+ {
+ { typeof(bool), cell => cell.GetBoolean() },
+ { typeof(double), cell => cell.GetValue() },
+ { typeof(float), cell => cell.GetValue() },
+ { typeof(long), cell => cell.GetValue() },
+ { typeof(ulong), cell => cell.GetValue() },
+ { typeof(int), cell => cell.GetValue() },
+ { typeof(uint), cell => cell.GetValue() },
+ { typeof(short), cell => cell.GetValue() },
+ { typeof(ushort), cell => cell.GetValue() },
+ { typeof(string), cell => cell.GetString() },
+
+ {
+ typeof(DateTime), cell =>
+ {
+ if (cell.DataType == XLDataType.DateTime)
+ return cell.GetDateTime();
+
+ var stringValue = cell.GetString();
+ return DateTime.Parse(stringValue);
+ }
+ },
+
+ {
+ typeof(DateTimeOffset), cell =>
+ {
+ var stringValue = cell.GetString();
+ return DateTimeOffset.Parse(stringValue);
+ }
+ },
+
+ {
+ typeof(DateOnly), cell =>
+ {
+ var stringValue = cell.GetString();
+ return DateOnly.Parse(stringValue);
+ }
+ },
+
+ {
+ typeof(TimeOnly), cell =>
+ {
+ var stringValue = cell.GetString();
+ return TimeOnly.Parse(stringValue);
+ }
+ },
+
+ {
+ typeof(TimeSpan), cell =>
+ {
+ if (cell.DataType == XLDataType.TimeSpan)
+ return cell.GetTimeSpan();
+
+ var stringValue = cell.GetString();
+ return TimeSpan.Parse(stringValue);
+ }
+ },
+ };
+
+ private readonly Type type;
+
+ public Cell(int columnNumber,
+ Type type)
+ {
+ ColumnNumber = columnNumber;
+ this.type = type;
+ }
+
+ public int ColumnNumber { get; }
+
+ public object? GetValueFromCell(IXLCell cell)
+ {
+ try
+ {
+ return converters[type].Invoke(cell);
+ }
+ catch
+ {
+ var message = string.Format(XLExtentions.InvalidValueTemplate, cell.Worksheet.Name, cell.Address.RowNumber,
+ cell.Address.ColumnNumber);
+ throw new FileFormatException(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Parser/ParserExcelService.cs b/AsbCloudInfrastructure/Services/Parser/ParserExcelService.cs
new file mode 100644
index 00000000..661c729f
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/Parser/ParserExcelService.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using AsbCloudApp.Data;
+using AsbCloudApp.Requests.ParserOptions;
+using AsbCloudApp.Services;
+using ClosedXML.Excel;
+using Mapster;
+
+namespace AsbCloudInfrastructure.Services.Parser;
+
+public abstract class ParserExcelService : IParserService
+ where TDto : class, IValidatableObject, IId
+{
+ protected abstract string SheetName { get; }
+
+ protected virtual int HeaderRowsCount => 0;
+
+ protected abstract string TemplateFileName { get; }
+
+ protected abstract IDictionary Cells { get; }
+
+ public virtual ParserResultDto Parse(Stream file, TOptions options)
+ where TOptions : IParserOptionsRequest
+ {
+ using var workbook = new XLWorkbook(file);
+ var sheet = workbook.GetWorksheet(SheetName);
+ var dtos = ParseExcelSheet(sheet);
+ return dtos;
+ }
+
+ public virtual Stream GetTemplateFile() =>
+ Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName)
+ ?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден");
+
+
+ protected virtual IDictionary ParseRow(IXLRow xlRow)
+ {
+ var cells = Cells.ToDictionary(x => x.Key, x =>
+ {
+ var columnNumber = x.Value.ColumnNumber;
+ var xlCell = xlRow.Cell(columnNumber);
+ var cellValue = x.Value.GetValueFromCell(xlCell);
+ return cellValue;
+ });
+
+ return cells;
+ }
+
+ protected virtual TDto BuildDto(IDictionary row, int rowNumber)
+ {
+ var dto = row.Adapt();
+ return dto;
+ }
+
+ private ValidationResultDto Validate(TDto dto, int rowNumber)
+ {
+ var validationResults = new List();
+
+ var isValid = dto.Validate(validationResults);
+
+ if (isValid)
+ {
+ var validDto = new ValidationResultDto
+ {
+ Item = dto
+ };
+
+ return validDto;
+ }
+
+ var columnsDict = Cells.ToDictionary(x => x.Key, x => x.Value.ColumnNumber);
+
+ var invalidDto = new ValidationResultDto
+ {
+ Item = dto,
+ Warnings = validationResults
+ .SelectMany(v => v.MemberNames
+ .Where(columnsDict.ContainsKey)
+ .Select(m =>
+ {
+ var columnNumber = columnsDict[m];
+ var errorMessage = v.ErrorMessage;
+ var warningMessage = string.Format(XLExtentions.ProblemDetailsTemplate, SheetName, rowNumber, columnNumber,
+ errorMessage);
+ var warning = new ValidationResult(warningMessage, new[] { m });
+ return warning;
+ }))
+ };
+
+ return invalidDto;
+ }
+
+ protected virtual ParserResultDto ParseExcelSheet(IXLWorksheet sheet)
+ {
+ var count = sheet.RowsUsed().Count() - HeaderRowsCount;
+ if (count <= 0)
+ return new ParserResultDto();
+
+ var valiationResults = new List>(count);
+ var warnings = new List();
+
+ for (var i = 0; i < count; i++)
+ {
+ var xlRow = sheet.Row(1 + i + HeaderRowsCount);
+ var rowNumber = xlRow.RowNumber();
+
+ try
+ {
+ var row = ParseRow(xlRow);
+ var dto = BuildDto(row, rowNumber);
+ var validationResult = Validate(dto, rowNumber);
+ valiationResults.Add(validationResult);
+ }
+ catch (FileFormatException ex)
+ {
+ var warning = new ValidationResult(ex.Message);
+ warnings.Add(warning);
+ }
+ }
+
+ var parserResult = new ParserResultDto
+ {
+ Item = valiationResults
+ };
+
+ if (warnings.Any())
+ parserResult.Warnings = warnings;
+
+ return parserResult;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs
deleted file mode 100644
index 5b852a8e..00000000
--- a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using AsbCloudApp.Data;
-using AsbCloudApp.Requests.ParserOptions;
-using AsbCloudApp.Services;
-using AsbCloudInfrastructure.Services.Trajectory.Parser;
-
-namespace AsbCloudInfrastructure.Services;
-
-public class ParserServiceFactory
-{
- public const int IdTrajectoryFactManualParserService = 1;
- public const int IdTrajectoryPlanParserService = 2;
-
- private readonly IDictionary> parsers;
-
- public ParserServiceFactory(IServiceProvider serviceProvider)
- {
- parsers = new Dictionary>
- {
- { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) },
- { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) }
- };
- }
-
- public IParserService Create(int idParserService)
- where TDto : class, IId
- where TOptions : IParserOptionsRequest
- {
- if (!parsers.TryGetValue(idParserService, out var parserService))
- throw new ArgumentNullException(nameof(idParserService), "Не правильный идентификатор парсера");
-
- return parserService.Invoke() as IParserService
- ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs
new file mode 100644
index 00000000..2268b821
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudApp.Repositories;
+using AsbCloudInfrastructure.Services.Parser;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
+
+public class ProcessMapPlanDrillingParser : ProcessMapPlanParser
+{
+ private readonly IEnumerable sections;
+
+ public ProcessMapPlanDrillingParser(IWellOperationRepository wellOperationRepository)
+ {
+ sections = wellOperationRepository.GetSectionTypes();
+ }
+
+ protected override string SheetName => "План";
+ protected override string TemplateFileName => "ProcessMapPlanDrillingTemplate.xlsx";
+
+ private const int ColumnSection = 1;
+ private const int ColumnMode = 2;
+
+ protected override IDictionary Cells => new Dictionary
+ {
+ { nameof(ProcessMapPlanDrillingDto.Section), new Cell(ColumnSection, typeof(string)) },
+ { nameof(ProcessMapPlanDrillingDto.Mode), new Cell(ColumnMode, typeof(string)) },
+ { nameof(ProcessMapPlanDrillingDto.DepthStart), new Cell(3, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.DepthEnd), new Cell(4, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.DeltaPressurePlan), new Cell(5, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.DeltaPressureLimitMax), new Cell(6, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.AxialLoadPlan), new Cell(7, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.AxialLoadLimitMax), new Cell(8, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.TopDriveTorquePlan), new Cell(9, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.TopDriveTorqueLimitMax), new Cell(10, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.TopDriveSpeedPlan), new Cell(11, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.TopDriveSpeedLimitMax), new Cell(12, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.FlowPlan), new Cell(13, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.FlowLimitMax), new Cell(14, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.RopPlan), new Cell(15, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.UsageSaub), new Cell(16, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.UsageSpin), new Cell(17, typeof(double)) },
+ { nameof(ProcessMapPlanDrillingDto.Comment), new Cell(18, typeof(string)) }
+ };
+
+ protected override ProcessMapPlanDrillingDto BuildDto(IDictionary row, int rowNumber)
+ {
+ var dto = base.BuildDto(row, rowNumber);
+
+ var section = sections.FirstOrDefault(s =>
+ string.Equals(s.Caption.Trim(), dto.Section?.Trim(), StringComparison.CurrentCultureIgnoreCase));
+
+ if (section is null)
+ {
+ var message = string.Format(XLExtentions.ProblemDetailsTemplate, SheetName, rowNumber, ColumnSection,
+ "Указана некорректная секция");
+ throw new FileFormatException(message);
+ }
+
+ var idMode = GetIdMode(dto.Mode);
+
+ if (idMode is null)
+ {
+ var message = string.Format(XLExtentions.ProblemDetailsTemplate, SheetName, rowNumber, ColumnSection,
+ "Указан некорректный режим бурения");
+ throw new FileFormatException(message);
+ }
+
+ dto.IdWellSectionType = section.Id;
+ dto.IdMode = idMode.Value;
+
+ return dto;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs
new file mode 100644
index 00000000..496010c4
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs
@@ -0,0 +1,19 @@
+using System;
+using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudInfrastructure.Services.Parser;
+
+namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
+
+public abstract class ProcessMapPlanParser : ParserExcelService
+ where TDto : ProcessMapPlanBaseDto
+{
+ protected override int HeaderRowsCount => 2;
+
+ protected static int? GetIdMode(string? modeName) =>
+ modeName?.Trim().ToLower() switch
+ {
+ "ротор" => 1,
+ "слайд" => 2,
+ _ => null
+ };
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx b/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx
new file mode 100644
index 00000000..bdf13143
Binary files /dev/null and b/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx differ
diff --git a/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs b/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs
index f322c798..454daa7a 100644
--- a/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs
+++ b/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs
@@ -18,6 +18,8 @@ namespace AsbCloudInfrastructure.Services.ProcessMaps.WellDrilling;
/*
* password for ProcessMapImportTemplate.xlsx is ASB2020!
*/
+
+[Obsolete]
public class ProcessMapPlanImportWellDrillingService : IProcessMapPlanImportService
{
private readonly IProcessMapPlanRepository processMapPlanWellDrillingRepository;
diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParser.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParser.cs
new file mode 100644
index 00000000..5de4e2c5
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParser.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using AsbCloudApp.Data.Trajectory;
+using AsbCloudInfrastructure.Services.Parser;
+
+namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
+
+public class TrajectoryFactManualParser : ParserExcelService
+{
+ protected override string SheetName => "Фактическая траектория";
+
+ protected override int HeaderRowsCount => 2;
+
+ protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
+
+ protected override IDictionary Cells => new Dictionary
+ {
+ { nameof(TrajectoryGeoFactDto.WellboreDepth), new Cell(1, typeof(double)) },
+ { nameof(TrajectoryGeoFactDto.ZenithAngle), new Cell(2, typeof(double)) },
+ { nameof(TrajectoryGeoFactDto.AzimuthGeo), new Cell(3, typeof(double)) },
+ { nameof(TrajectoryGeoFactDto.AzimuthMagnetic), new Cell(4, typeof(double)) },
+ { nameof(TrajectoryGeoFactDto.VerticalDepth), new Cell(5, typeof(double)) },
+ { nameof(TrajectoryGeoFactDto.Comment), new Cell(6, typeof(string)) }
+ };
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs
deleted file mode 100644
index 6e4034a5..00000000
--- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using AsbCloudApp.Data;
-using AsbCloudApp.Data.Trajectory;
-using ClosedXML.Excel;
-
-namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
-
-public class TrajectoryFactManualParserService : TrajectoryParserService
-{
- protected override string SheetName => "Фактическая траектория";
- protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
-
- public TrajectoryFactManualParserService(IServiceProvider serviceProvider)
- : base(serviceProvider)
- {
- }
-
- protected override ValidationResultDto ParseRow(IXLRow row)
- {
- var trajectoryRow = new TrajectoryGeoFactDto
- {
- WellboreDepth = row.Cell(1).GetCellValue(),
- ZenithAngle = row.Cell(2).GetCellValue(),
- AzimuthGeo = row.Cell(3).GetCellValue(),
- AzimuthMagnetic = row.Cell(4).GetCellValue(),
- VerticalDepth = row.Cell(5).GetCellValue(),
- Comment = row.Cell(6).GetCellValue()
- };
-
- //TODO: Добавить валидацию модели
-
- var validationResult = new ValidationResultDto
- {
- Item = trajectoryRow
- };
-
- return validationResult;
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs
deleted file mode 100644
index b306e602..00000000
--- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using AsbCloudApp.Data.Trajectory;
-using ClosedXML.Excel;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using AsbCloudApp.Data;
-using AsbCloudApp.Requests.ParserOptions;
-
-namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
-
-public abstract class TrajectoryParserService : ParserServiceBase
- where T : TrajectoryGeoDto
-{
- private const int HeaderRowsCount = 2;
- private const int ColumnCount = 6;
-
- protected TrajectoryParserService(IServiceProvider serviceProvider)
- : base(serviceProvider)
- {
- }
-
- protected abstract string SheetName { get; }
-
- protected abstract string TemplateFileName { get; }
-
- protected abstract ValidationResultDto ParseRow(IXLRow row);
-
- public override Stream GetTemplateFile() =>
- Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName)
- ?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден");
-
- public override ParserResultDto Parse(Stream file, IParserOptionsRequest options)
- {
- using var workbook = new XLWorkbook(file);
-
- var sheet = workbook.GetWorksheet(SheetName);
-
- var trajectoryRows = ParseExcelSheet(sheet, ParseRow, ColumnCount, HeaderRowsCount);
- return trajectoryRows;
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParser.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParser.cs
new file mode 100644
index 00000000..4c100766
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParser.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using AsbCloudApp.Data.Trajectory;
+using AsbCloudInfrastructure.Services.Parser;
+
+namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
+
+public class TrajectoryPlanParser : ParserExcelService
+{
+ protected override string SheetName => "Плановая траектория";
+
+ protected override int HeaderRowsCount => 2;
+
+ protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
+
+ protected override IDictionary Cells => new Dictionary
+ {
+ { nameof(TrajectoryGeoPlanDto.WellboreDepth), new Cell(1, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.ZenithAngle), new Cell(2, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.AzimuthGeo), new Cell(3, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.AzimuthMagnetic), new Cell(4, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.VerticalDepth), new Cell(5, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.Radius), new Cell(6, typeof(double)) },
+ { nameof(TrajectoryGeoPlanDto.Comment), new Cell(7, typeof(string)) }
+ };
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs
deleted file mode 100644
index fbf5a537..00000000
--- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using AsbCloudApp.Data;
-using AsbCloudApp.Data.Trajectory;
-using ClosedXML.Excel;
-
-namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
-
-public class TrajectoryPlanParserService : TrajectoryParserService
-{
- protected override string SheetName => "Плановая траектория";
- protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
-
- public TrajectoryPlanParserService(IServiceProvider serviceProvider)
- : base(serviceProvider)
- {
- }
-
- protected override ValidationResultDto ParseRow(IXLRow row)
- {
- var trajectoryRow = new TrajectoryGeoPlanDto
- {
- WellboreDepth = row.Cell(1).GetCellValue(),
- ZenithAngle = row.Cell(2).GetCellValue(),
- AzimuthGeo = row.Cell(3).GetCellValue(),
- AzimuthMagnetic = row.Cell(4).GetCellValue(),
- VerticalDepth = row.Cell(5).GetCellValue(),
- Radius = row.Cell(6).GetCellValue(),
- Comment = row.Cell(7).GetCellValue()
- };
-
- //TODO: Добавить валидацию модели
-
- var validationResult = new ValidationResultDto
- {
- Item = trajectoryRow
- };
-
- return validationResult;
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/ValidationExtensions.cs b/AsbCloudInfrastructure/ValidationExtensions.cs
new file mode 100644
index 00000000..feb9c06b
--- /dev/null
+++ b/AsbCloudInfrastructure/ValidationExtensions.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace AsbCloudInfrastructure;
+
+public static class ValidationExtensions
+{
+ public static bool Validate(this IValidatableObject validatableObject, ICollection validationResults)
+ {
+ var validationContext = new ValidationContext(validatableObject, serviceProvider: null, items: null);
+
+ foreach (var validationResult in validatableObject.Validate(validationContext))
+ validationResults.Add(validationResult);
+
+ return Validator.TryValidateObject(validatableObject, validationContext, validationResults, true);
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/XLExtentions.cs b/AsbCloudInfrastructure/XLExtentions.cs
index 0aeeca00..927c90a8 100644
--- a/AsbCloudInfrastructure/XLExtentions.cs
+++ b/AsbCloudInfrastructure/XLExtentions.cs
@@ -7,9 +7,13 @@ namespace AsbCloudInfrastructure;
public static class XLExtentions
{
+ public const string ProblemDetailsTemplate = "Лист: {0}, Строка: {1}, Столбец: {2}. {3}";
+ public const string NotFoundSheetTemplate = "Книга excel не содержит листа {0}";
+ public const string InvalidValueTemplate = "Лист: {0}, Строка: {1}, Столбец: {2}. Содержит некорректное значение";
+
public static IXLWorksheet GetWorksheet(this IXLWorkbook workbook, string sheetName) =>
workbook.Worksheets.FirstOrDefault(ws => string.Equals(ws.Name.Trim(), sheetName.Trim(), StringComparison.CurrentCultureIgnoreCase))
- ?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
+ ?? throw new FileFormatException(string.Format(NotFoundSheetTemplate, sheetName));
public static IXLCell SetCellValue(this IXLCell cell, T value)
{
@@ -39,8 +43,8 @@ public static class XLExtentions
}
catch
{
- throw new FileFormatException(
- $"Лист '{cell.Worksheet.Name}'. {cell.Address.RowNumber} строка содержит некорректное значение в {cell.Address.ColumnNumber} столбце");
+ var message = string.Format(InvalidValueTemplate, cell.Worksheet.Name, cell.Address.RowNumber, cell.Address.ColumnNumber);
+ throw new FileFormatException(message);
}
}
}
\ No newline at end of file
diff --git a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj
index f20fc316..a9d7713a 100644
--- a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj
+++ b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj
@@ -22,4 +22,9 @@
+
+
+
+
+
diff --git a/AsbCloudWebApi.IntegrationTests/AssemblyExtensions.cs b/AsbCloudWebApi.IntegrationTests/AssemblyExtensions.cs
new file mode 100644
index 00000000..d4013aa1
--- /dev/null
+++ b/AsbCloudWebApi.IntegrationTests/AssemblyExtensions.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+
+namespace AsbCloudWebApi.IntegrationTests;
+
+internal static class AssemblyExtensions
+{
+ internal static Stream GetFileCopyStream(this Assembly assembly, string templateName)
+ {
+ var resourceName = assembly
+ .GetManifestResourceNames()
+ .FirstOrDefault(n => n.EndsWith(templateName));
+
+ if (string.IsNullOrWhiteSpace(resourceName))
+ throw new ArgumentNullException(nameof(resourceName));
+
+ using var stream = Assembly.GetExecutingAssembly()
+ .GetManifestResourceStream(resourceName);
+
+ var memoryStream = new MemoryStream();
+ stream?.CopyTo(memoryStream);
+ memoryStream.Position = 0;
+
+ return memoryStream;
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
index 7c8c65e3..e60f5e90 100644
--- a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
+++ b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
@@ -1,6 +1,6 @@
-using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudApp.Requests;
-using Microsoft.AspNetCore.Mvc;
using Refit;
namespace AsbCloudWebApi.IntegrationTests.Clients;
@@ -32,4 +32,8 @@ public interface IProcessMapPlanDrillingClient
[Put(BaseRoute)]
Task> UpdateOrInsertRange(int idWell, IEnumerable dtos);
+
+ [Multipart]
+ [Post(BaseRoute + "/parse")]
+ Task>> Parse(int idWell, [AliasAs("files")] IEnumerable streams);
}
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingInvalid.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingInvalid.xlsx
new file mode 100644
index 00000000..09865489
Binary files /dev/null and b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingInvalid.xlsx differ
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingValid.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingValid.xlsx
new file mode 100644
index 00000000..b2bdd446
Binary files /dev/null and b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/Files/ProcessMapPlanDrillingValid.xlsx differ
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/ProcessMapPlanDrillingControllerTest.cs
similarity index 89%
rename from AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs
rename to AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/ProcessMapPlanDrillingControllerTest.cs
index c0ba7093..f06e2133 100644
--- a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs
+++ b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/ProcessMapPlanDrillingControllerTest.cs
@@ -1,31 +1,35 @@
using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudApp.Requests;
using AsbCloudDb.Model.ProcessMapPlan;
-using AsbCloudDb.Model.ProcessMaps;
using AsbCloudWebApi.IntegrationTests.Clients;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System.Net;
+using System.Reflection;
+using AsbCloudDb.Model.ProcessMaps;
+using AsbCloudWebApi.IntegrationTests.Data;
+using Refit;
using Xunit;
-namespace AsbCloudWebApi.IntegrationTests.Controllers;
+namespace AsbCloudWebApi.IntegrationTests.Controllers.ProcessMapPlan;
public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
{
- private IProcessMapPlanDrillingClient client;
private readonly ProcessMapPlanDrillingDto dto = new (){
Id = 0,
Creation = new(),
Obsolete = null,
IdState = 0,
IdPrevious = null,
-
+
IdWell = 1,
- IdWellSectionType = 1,
+ Section = "Кондуктор",
+ IdWellSectionType = 3,
DepthStart = 0.5,
DepthEnd = 1.5,
IdMode = 1,
+ Mode = "Ротор",
AxialLoadPlan = 2.718281,
AxialLoadLimitMax = 3.1415926,
DeltaPressurePlan = 4,
@@ -73,6 +77,8 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
Comment = "это тестовая запись",
};
+ private IProcessMapPlanDrillingClient client;
+
public ProcessMapPlanDrillingControllerTest(WebAppFactoryFixture factory) : base(factory)
{
dbContext.CleanupDbSet();
@@ -109,6 +115,8 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
nameof(ProcessMapPlanDrillingDto.IdState),
nameof(ProcessMapPlanDrillingDto.Author),
nameof(ProcessMapPlanDrillingDto.Creation),
+ nameof(ProcessMapPlanDrillingDto.Mode),
+ nameof(ProcessMapPlanDrillingDto.Section)
};
MatchHelper.Match(expected, actual, excludeProps);
}
@@ -556,4 +564,59 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
};
MatchHelper.Match(expected, actual, excludeProps);
}
+
+ [Fact]
+ public async Task Parse_returns_success()
+ {
+ //arrange
+ const string fileName = "ProcessMapPlanDrillingValid.xlsx";
+ var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName);
+
+ //act
+ var streamPart = new StreamPart(stream, fileName, "application/octet-stream");
+ var response = await client.Parse(Defaults.Wells[0].Id, new[] { streamPart });
+
+ //assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var parserResult = response.Content;
+
+ Assert.NotNull(parserResult);
+ Assert.Single(parserResult.Item);
+ Assert.True(parserResult.IsValid);
+
+ var row = parserResult.Item.First();
+ var dtoActual = row.Item;
+
+ Assert.True(row.IsValid);
+
+ var excludeProps = new[] { nameof(ProcessMapPlanDrillingDto.IdWell) };
+ MatchHelper.Match(dto, dtoActual, excludeProps);
+ }
+
+ [Fact]
+ public async Task Parse_returns_success_for_result_with_warnings()
+ {
+ //arrange
+ const string fileName = "ProcessMapPlanDrillingInvalid.xlsx";
+ var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName);
+
+ //act
+ var streamPart = new StreamPart(stream, fileName, "application/octet-stream");
+ var response = await client.Parse(Defaults.Wells[0].Id, new[] { streamPart });
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var parserResult = response.Content;
+
+ Assert.NotNull(parserResult);
+ Assert.False(parserResult.IsValid);
+ Assert.Single(parserResult.Warnings);
+ Assert.Single(parserResult.Item);
+
+ var row = parserResult.Item.First();
+
+ Assert.False(row.IsValid);
+ Assert.Equal(2, row.Warnings.Count());
+ }
}
diff --git a/AsbCloudWebApi.IntegrationTests/Converters/ValidationResultConverter.cs b/AsbCloudWebApi.IntegrationTests/Converters/ValidationResultConverter.cs
new file mode 100644
index 00000000..15423bc0
--- /dev/null
+++ b/AsbCloudWebApi.IntegrationTests/Converters/ValidationResultConverter.cs
@@ -0,0 +1,68 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace AsbCloudWebApi.IntegrationTests.Converters;
+
+public class ValidationResultConverter : JsonConverter
+{
+ public override ValidationResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("Expected the start of an object.");
+ }
+
+ string? errorMessage = null;
+ List? memberNames = null;
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException($"Unexpected token type: {reader.TokenType}");
+ }
+
+ var propertyName = reader.GetString();
+ reader.Read();
+
+ switch (propertyName)
+ {
+ case "errorMessage":
+ errorMessage = reader.GetString();
+ break;
+ case "memberNames":
+ if (reader.TokenType != JsonTokenType.StartArray)
+ {
+ throw new JsonException("Expected the start of an array for 'memberNames'.");
+ }
+ memberNames = new List();
+ while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
+ {
+ memberNames.Add(reader.GetString() ?? string.Empty);
+ }
+ break;
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+
+ if (errorMessage == null)
+ {
+ throw new JsonException("Missing 'errorMessage' property.");
+ }
+
+ return new ValidationResult(errorMessage, memberNames ?? Enumerable.Empty());
+ }
+
+ public override void Write(Utf8JsonWriter writer, ValidationResult value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs b/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs
index af7303b6..aa8bd27e 100644
--- a/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs
+++ b/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs
@@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
using Refit;
using System.Net.Http.Headers;
using System.Text.Json;
+using AsbCloudWebApi.IntegrationTests.Converters;
using Xunit;
namespace AsbCloudWebApi.IntegrationTests;
@@ -18,7 +19,8 @@ public class WebAppFactoryFixture : WebApplicationFactory,
private static readonly JsonSerializerOptions jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- PropertyNameCaseInsensitive = true
+ PropertyNameCaseInsensitive = true,
+ Converters = { new ValidationResultConverter() }
};
private static readonly RefitSettings refitSettings = new RefitSettings(new SystemTextJsonContentSerializer(jsonSerializerOptions));
diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs
index 1fddd56d..8921d982 100644
--- a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs
+++ b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs
@@ -1,10 +1,8 @@
-using System;
-using System.Linq;
+using System.Linq;
using AsbCloudApp.Data.Trajectory;
using AsbCloudApp.Requests.ParserOptions;
-using AsbCloudInfrastructure.Services;
-using Microsoft.Extensions.DependencyInjection;
-using NSubstitute;
+using AsbCloudInfrastructure.Services.Parser;
+using AsbCloudInfrastructure.Services.Trajectory.Parser;
using Xunit;
namespace AsbCloudWebApi.Tests.Services.Trajectory;
@@ -12,21 +10,10 @@ namespace AsbCloudWebApi.Tests.Services.Trajectory;
public class TrajectoryParserTest
{
private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates";
+
+ private readonly TrajectoryPlanParser trajectoryPlanParser = new();
+ private readonly TrajectoryFactManualParser trajectoryFactManualParser = new();
- private readonly IServiceProvider serviceProviderMock = Substitute.For();
- private readonly IServiceScope serviceScopeMock = Substitute.For();
- private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For();
-
- private readonly ParserServiceFactory parserServiceFactory;
-
- public TrajectoryParserTest()
- {
- serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock);
- ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock);
-
- parserServiceFactory = new ParserServiceFactory(serviceProviderMock);
- }
-
[Fact]
public void Parse_trajectory_plan()
{
@@ -35,11 +22,8 @@ public class TrajectoryParserTest
if (stream is null)
Assert.Fail("Файла для импорта не существует");
-
- var parserService = parserServiceFactory.Create(
- ParserServiceFactory.IdTrajectoryPlanParserService);
- var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
+ var trajectoryRows = trajectoryPlanParser.Parse(stream, IParserOptionsRequest.Empty());
Assert.Equal(3, trajectoryRows.Item.Count());
}
@@ -53,10 +37,7 @@ public class TrajectoryParserTest
if (stream is null)
Assert.Fail("Файла для импорта не существует");
- var parserService = parserServiceFactory.Create(
- ParserServiceFactory.IdTrajectoryFactManualParserService);
-
- var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
+ var trajectoryRows = trajectoryFactManualParser.Parse(stream, IParserOptionsRequest.Empty());
Assert.Equal(4, trajectoryRows.Item.Count());
}
diff --git a/AsbCloudWebApi.Tests/XLExtensionsTests.cs b/AsbCloudWebApi.Tests/XLExtensionsTests.cs
index 7f5fd781..601e71e0 100644
--- a/AsbCloudWebApi.Tests/XLExtensionsTests.cs
+++ b/AsbCloudWebApi.Tests/XLExtensionsTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using AsbCloudInfrastructure;
using ClosedXML.Excel;
using Xunit;
@@ -112,6 +113,16 @@ public class XLExtensionsTests
Assert.Equal(DateTimeKind.Unspecified, actualValue.Kind);
}
+ [Fact]
+ public void GetCellValue_returns_exception()
+ {
+ //arrange
+ SetCellValue("test");
+
+ //assert
+ Assert.Throws(() => GetCell(cellUsed).GetCellValue());
+ }
+
[Fact]
public void GetCellValue_returns_nullable()
{
diff --git a/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs b/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs
deleted file mode 100644
index d11ac201..00000000
--- a/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.IO;
-using AsbCloudApp.Data;
-using Microsoft.AspNetCore.Mvc;
-
-namespace AsbCloudWebApi.Controllers.Interfaces;
-
-public interface IControllerWithParser
- where TDto : class, IId
-{
- ActionResult> Parse(Stream file, TOptions options);
-
- IActionResult GetTemplate();
-}
\ No newline at end of file
diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
index c3fd0c49..9c0c8594 100644
--- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
+++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
@@ -9,8 +9,12 @@ using Microsoft.AspNetCore.Http;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests;
using System;
+using System.IO;
using AsbCloudApp.Services;
using System.Linq;
+using AsbCloudApp.Data;
+using AsbCloudApp.Requests.ParserOptions;
+using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudWebApi.Controllers.ProcessMapPlan;
@@ -21,18 +25,24 @@ namespace AsbCloudWebApi.Controllers.ProcessMapPlan;
[Route("api/well/{idWell}/[controller]")]
[Authorize]
public abstract class ProcessMapPlanBaseController : ControllerBase
- where TDto : ProcessMapPlanBaseDto
+ where TDto : ProcessMapPlanBaseDto
{
- private readonly IChangeLogRepository repository;
- private readonly IWellService wellService;
+ private readonly IChangeLogRepository repository;
+ private readonly IWellService wellService;
+ private readonly ParserExcelService parserService;
+
+ protected ProcessMapPlanBaseController(IChangeLogRepository repository,
+ IWellService wellService,
+ ParserExcelService parserService)
+ {
+ this.repository = repository;
+ this.wellService = wellService;
+ this.parserService = parserService;
+ }
+
+ protected abstract string TemplateFileName { get; }
- public ProcessMapPlanBaseController(IChangeLogRepository repository, IWellService wellService)
- {
- this.repository = repository;
- this.wellService = wellService;
- }
-
- ///
+ ///
/// Добавление
///
///
@@ -190,6 +200,49 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
var result = await repository.UpdateOrInsertRange(idUser, dtos, token);
return Ok(result);
}
+
+ ///
+ /// Импорт РТК из excel (xlsx) файла
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("parse")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
+ public async Task>> Parse(int idWell,
+ [FromForm] IFormFileCollection files,
+ CancellationToken token)
+ {
+ await AssertUserHasAccessToWell(idWell, token);
+
+ var stream = files.GetExcelFile();
+
+ try
+ {
+ var dto = parserService.Parse(stream, IParserOptionsRequest.Empty());
+ return Ok(dto);
+ }
+ catch (FileFormatException ex)
+ {
+ return this.ValidationBadRequest(nameof(files), ex.Message);
+ }
+ }
+
+ ///
+ /// Получение шаблона для заполнения РТК
+ ///
+ ///
+ [HttpGet("template")]
+ [AllowAnonymous]
+ [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public IActionResult GetTemplate()
+ {
+ var stream = parserService.GetTemplateFile();
+ return File(stream, "application/octet-stream", TemplateFileName);
+ }
///
/// returns user id, if he has access to well
@@ -221,4 +274,4 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
var idUser = User.GetUserId() ?? throw new ForbidException("Неизвестный пользователь");
return idUser;
}
-}
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
index b0a20f1e..a22676d8 100644
--- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
+++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
@@ -2,15 +2,18 @@
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
+using AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
namespace AsbCloudWebApi.Controllers.ProcessMapPlan;
public class ProcessMapPlanDrillingController : ProcessMapPlanBaseController
{
- public ProcessMapPlanDrillingController(
- IChangeLogRepository repository,
- IWellService wellService)
- : base(repository, wellService)
- {
- }
-}
+ public ProcessMapPlanDrillingController(IChangeLogRepository repository,
+ IWellService wellService,
+ ProcessMapPlanDrillingParser parserService)
+ : base(repository, wellService, parserService)
+ {
+ }
+
+ protected override string TemplateFileName => "ЕЦП_шаблон_файла_РТК_план_бурение.xlsx";
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs
index 7a04e7fc..344ed707 100644
--- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs
+++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs
@@ -1,4 +1,5 @@
-using AsbCloudApp.Data.Trajectory;
+using System;
+using AsbCloudApp.Data.Trajectory;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Services.Trajectory.Export;
@@ -11,8 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests.ParserOptions;
-using AsbCloudInfrastructure.Services;
-using AsbCloudWebApi.Controllers.Interfaces;
+using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudWebApi.Controllers.Trajectory
{
@@ -22,42 +22,23 @@ namespace AsbCloudWebApi.Controllers.Trajectory
///
[ApiController]
[Authorize]
- public abstract class TrajectoryEditableController : TrajectoryController,
- IControllerWithParser
- where TDto : TrajectoryGeoDto
+ public abstract class TrajectoryEditableController : TrajectoryController
+ where TDto : TrajectoryGeoDto
{
- private readonly IParserService parserService;
+ private readonly ParserExcelService parserService;
private readonly ITrajectoryEditableRepository trajectoryRepository;
protected TrajectoryEditableController(IWellService wellService,
- ParserServiceFactory parserServiceFactory,
+ ParserExcelService parserService,
TrajectoryExportService trajectoryExportService,
- ITrajectoryEditableRepository trajectoryRepository,
- int idParserService)
- : base(
- wellService,
- trajectoryExportService,
- trajectoryRepository)
- {
- parserService = parserServiceFactory.Create(idParserService);
+ ITrajectoryEditableRepository trajectoryRepository)
+ : base(wellService, trajectoryExportService, trajectoryRepository)
+ {
+ this.parserService = parserService;
this.trajectoryRepository = trajectoryRepository;
}
-
- ActionResult> IControllerWithParser.Parse(Stream file,
- IParserOptionsRequest options)
- {
- try
- {
- var parserResult = parserService.Parse(file, options);
- return Ok(parserResult);
- }
- catch (FileFormatException ex)
- {
- return this.ValidationBadRequest("files", ex.Message);
- }
- }
- ///
+ ///
/// Возвращает excel шаблон для заполнения строк траектории
///
/// Запрашиваемый файл
@@ -93,7 +74,17 @@ namespace AsbCloudWebApi.Controllers.Trajectory
if (!await CanUserAccessToWellAsync(idWell, token))
return Forbid();
- return this.ParseExcelFile(files, IParserOptionsRequest.Empty());
+ var stream = files.GetExcelFile();
+
+ try
+ {
+ var dto = parserService.Parse(stream, IParserOptionsRequest.Empty());
+ return Ok(dto);
+ }
+ catch (FileFormatException ex)
+ {
+ return this.ValidationBadRequest(nameof(files), ex.Message);
+ }
}
///
diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs
index 97b84b07..aa1a60dc 100644
--- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs
+++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs
@@ -1,8 +1,8 @@
using AsbCloudApp.Data.Trajectory;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
-using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Trajectory.Export;
+using AsbCloudInfrastructure.Services.Trajectory.Parser;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers.Trajectory;
@@ -18,13 +18,9 @@ public class TrajectoryFactManualController : TrajectoryEditableController trajectoryRepository)
- : base(wellService,
- parserServiceFactory,
- trajectoryExportService,
- trajectoryRepository,
- ParserServiceFactory.IdTrajectoryFactManualParserService)
+ : base(wellService, parserService, trajectoryExportService, trajectoryRepository)
{
}
}
\ No newline at end of file
diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs
index cf3f648d..bffd7d15 100644
--- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs
+++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using AsbCloudInfrastructure.Services;
+using AsbCloudInfrastructure.Services.Trajectory.Parser;
namespace AsbCloudWebApi.Controllers.Trajectory
{
@@ -23,15 +23,11 @@ namespace AsbCloudWebApi.Controllers.Trajectory
protected override string fileName => "ЕЦП_шаблон_файла_плановая_траектория.xlsx";
public TrajectoryPlanController(IWellService wellService,
+ TrajectoryPlanParser parserService,
TrajectoryPlanExportService trajectoryExportService,
- ParserServiceFactory parserServiceFactory,
ITrajectoryEditableRepository trajectoryRepository,
TrajectoryService trajectoryVisualizationService)
- : base(wellService,
- parserServiceFactory,
- trajectoryExportService,
- trajectoryRepository,
- ParserServiceFactory.IdTrajectoryPlanParserService)
+ : base(wellService, parserService, trajectoryExportService, trajectoryRepository)
{
this.trajectoryVisualizationService = trajectoryVisualizationService;
}
diff --git a/AsbCloudWebApi/Extensions.cs b/AsbCloudWebApi/Extensions.cs
index 489d6149..59c72e37 100644
--- a/AsbCloudWebApi/Extensions.cs
+++ b/AsbCloudWebApi/Extensions.cs
@@ -8,8 +8,9 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using AsbCloudApp.Data;
+using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests.ParserOptions;
-using AsbCloudWebApi.Controllers.Interfaces;
+using AsbCloudInfrastructure.Services.Parser;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Mvc;
@@ -96,30 +97,20 @@ public static class Extensions
}
///
- /// Вызов парсера со стандартной валидацией входного файла
+ /// Получение Excel
///
- ///
- ///
- ///
///
- ///
///
- public static ActionResult> ParseExcelFile(
- this IControllerWithParser controller,
- IFormFileCollection files,
- TOptions options)
- where TDto : class, IId
- where TOptions : class, IParserOptionsRequest
+ ///
+ public static Stream GetExcelFile(this IFormFileCollection files)
{
if (files.Count < 1)
- return MakeBadRequestObjectResult(nameof(files), "Нет файла");
+ throw new ArgumentInvalidException(nameof(files), "Нет файла");
var file = files[0];
if (Path.GetExtension(file.FileName).ToLower() != ".xlsx")
- return MakeBadRequestObjectResult(nameof(files), "Требуется .xlsx файл.");
+ throw new ArgumentInvalidException(nameof(files), "Требуется .xlsx файл.");
- var stream = file.OpenReadStream();
-
- return controller.Parse(stream, options);
+ return file.OpenReadStream();
}
}
\ No newline at end of file