merge dev to rtk-report-export

This commit is contained in:
ngfrolov 2024-02-16 15:42:20 +05:00
commit 8f271eac0a
Signed by untrusted user who does not match committer: ng.frolov
GPG Key ID: E99907A0357B29A7
40 changed files with 791 additions and 408 deletions

View File

@ -19,6 +19,11 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat
[Range(1, int.MaxValue, ErrorMessage = "Id секции скважины не может быть меньше 1")]
public int IdWellSectionType { get; set; }
/// <summary>
/// Название секции
/// </summary>
public string? Section { get; set; }
/// <summary>
/// Глубина по стволу от, м
/// <para>
@ -41,6 +46,6 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(DepthEnd <= DepthStart)
yield return new ("глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) });
yield return new ("Глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) });
}
}

View File

@ -12,6 +12,11 @@ public class ProcessMapPlanDrillingDto : ProcessMapPlanBaseDto
/// </summary>
[Range(1, 2, ErrorMessage = "Id режима должен быть либо 1-ротор либо 2-слайд")]
public int IdMode { get; set; }
/// <summary>
/// Название режима бурения
/// </summary>
public string? Mode { get; set; }
/// <summary>
/// Осевая нагрузка, т план

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace AsbCloudApp.Data.Trajectory
{
/// <summary>
/// Базовая географическая траектория
/// </summary>
public abstract class TrajectoryGeoDto : IId
public abstract class TrajectoryGeoDto : IId, IValidatableObject
{
/// <summary>
/// ИД строки с координатами
@ -49,5 +52,11 @@ namespace AsbCloudApp.Data.Trajectory
/// ИД пользователя
/// </summary>
public int IdUser { get; set; }
/// <inheritdoc />
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Enumerable.Empty<ValidationResult>();
}
}
}

View File

@ -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;
/// Сервис парсинга
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TOptions"></typeparam>
public interface IParserService<TDto, in TOptions> : IParserService
public interface IParserService<TDto> : IParserService
where TDto : class, IId
where TOptions : IParserOptionsRequest
{
/// <summary>
/// Распарсить файл
@ -21,7 +17,8 @@ public interface IParserService<TDto, in TOptions> : IParserService
/// <param name="file"></param>
/// <param name="options"></param>
/// <returns></returns>
ParserResultDto<TDto> Parse(Stream file, TOptions options);
ParserResultDto<TDto> Parse<TOptions>(Stream file, TOptions options)
where TOptions : IParserOptionsRequest;
/// <summary>
/// Получение шаблона для заполнения

View File

@ -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;
/// <summary>
/// Сервис импорта РТК
/// </summary>
[Obsolete]
public interface IProcessMapPlanImportService
{
/// <summary>

View File

@ -37,6 +37,7 @@
<EmbeddedResource Include="Services\DetectOperations\DetectOperations.xlsx" />
<EmbeddedResource Include="Services\DailyReport\DailyReportTemplate.xlsx" />
<EmbeddedResource Include="Services\DrillTestReport\DrillTestReportTemplate.xlsx" />
<EmbeddedResource Include="Services\ProcessMapPlan\Templates\ProcessMapPlanDrillingTemplate.xlsx" />
<EmbeddedResource Include="Services\ProcessMaps\Report\ProcessMapReportDataSaubStatTemplate.xlsx" />
<EmbeddedResource Include="Services\Trajectory\Templates\TrajectoryFactNnbTemplate.xlsx" />
<EmbeddedResource Include="Services\Trajectory\Templates\TrajectoryFactManualTemplate.xlsx" />

View File

@ -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
@ -206,8 +209,6 @@ namespace AsbCloudInfrastructure
services.AddTransient<TrajectoryPlanExportService>();
services.AddTransient<TrajectoryFactManualExportService>();
services.AddTransient<TrajectoryFactNnbExportService>();
services.AddTransient<TrajectoryPlanParserService>();
services.AddTransient<TrajectoryFactManualParserService>();
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
services.AddTransient<IDailyReportService, DailyReportService>();
services.AddTransient<IDetectedOperationService, DetectedOperationService>();
@ -335,8 +336,10 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellOperationCategoryRepository, WellOperationCategoryRepository>();
services.AddTransient<IDetectedOperationRepository, DetectedOperationRepository>();
services.AddSingleton<ParserServiceFactory>();
services.AddTransient<TrajectoryPlanParser>();
services.AddTransient<TrajectoryFactManualParser>();
services.AddTransient<ProcessMapPlanDrillingParser>();
return services;
}
}

View File

@ -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<TDto, TOptions> : IParserService<TDto, TOptions>
where TDto : class, IId
where TOptions : IParserOptionsRequest
{
protected readonly IServiceProvider serviceProvider;
protected ParserServiceBase(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public abstract ParserResultDto<TDto> Parse(Stream file, TOptions options);
public abstract Stream GetTemplateFile();
protected virtual ParserResultDto<TDto> ParseExcelSheet(IXLWorksheet sheet,
Func<IXLRow, ValidationResultDto<TDto>> 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<TDto>();
var dtos = new List<ValidationResultDto<TDto>>(count);
var warnings = new List<ValidationResult>();
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<TDto>
{
Item = dtos
};
if (warnings.Any())
parserResult.Warnings = warnings;
return parserResult;
}
}

View File

@ -30,7 +30,6 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : 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<TEntity>();
@ -47,7 +46,6 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
}
result += await SaveChangesWithExceptionHandling(token);
await transaction.CommitAsync(token);
}
return result;
}
@ -82,30 +80,39 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : 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<int> UpdateOrInsertRange(int idUser, IEnumerable<TDto> dtos, CancellationToken token)
@ -146,10 +153,19 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : 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<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)

View File

@ -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<Type, Func<IXLCell, object?>> converters = new Dictionary<Type, Func<IXLCell, object?>>()
{
{ typeof(bool), cell => cell.GetBoolean() },
{ typeof(double), cell => cell.GetValue<double>() },
{ typeof(float), cell => cell.GetValue<float>() },
{ typeof(long), cell => cell.GetValue<long>() },
{ typeof(ulong), cell => cell.GetValue<ulong>() },
{ typeof(int), cell => cell.GetValue<int>() },
{ typeof(uint), cell => cell.GetValue<uint>() },
{ typeof(short), cell => cell.GetValue<short>() },
{ typeof(ushort), cell => cell.GetValue<ushort>() },
{ 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);
}
}
}

View File

@ -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<TDto> : IParserService<TDto>
where TDto : class, IValidatableObject, IId
{
protected abstract string SheetName { get; }
protected virtual int HeaderRowsCount => 0;
protected abstract string TemplateFileName { get; }
protected abstract IDictionary<string, Cell> Cells { get; }
public virtual ParserResultDto<TDto> Parse<TOptions>(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<string, object?> 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<string, object?> row, int rowNumber)
{
var dto = row.Adapt<TDto>();
return dto;
}
private ValidationResultDto<TDto> Validate(TDto dto, int rowNumber)
{
var validationResults = new List<ValidationResult>();
var isValid = dto.Validate(validationResults);
if (isValid)
{
var validDto = new ValidationResultDto<TDto>
{
Item = dto
};
return validDto;
}
var columnsDict = Cells.ToDictionary(x => x.Key, x => x.Value.ColumnNumber);
var invalidDto = new ValidationResultDto<TDto>
{
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<TDto> ParseExcelSheet(IXLWorksheet sheet)
{
var count = sheet.RowsUsed().Count() - HeaderRowsCount;
if (count <= 0)
return new ParserResultDto<TDto>();
var valiationResults = new List<ValidationResultDto<TDto>>(count);
var warnings = new List<ValidationResult>();
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<TDto>
{
Item = valiationResults
};
if (warnings.Any())
parserResult.Warnings = warnings;
return parserResult;
}
}

View File

@ -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<int, Func<IParserService>> parsers;
public ParserServiceFactory(IServiceProvider serviceProvider)
{
parsers = new Dictionary<int, Func<IParserService>>
{
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) },
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) }
};
}
public IParserService<TDto, TOptions> Create<TDto, TOptions>(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<TDto, TOptions>
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
}
}

View File

@ -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<ProcessMapPlanDrillingDto>
{
private readonly IEnumerable<WellSectionTypeDto> 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<string, Cell> Cells => new Dictionary<string, Cell>
{
{ 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<string, object?> 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;
}
}

View File

@ -0,0 +1,19 @@
using System;
using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
public abstract class ProcessMapPlanParser<TDto> : ParserExcelService<TDto>
where TDto : ProcessMapPlanBaseDto
{
protected override int HeaderRowsCount => 2;
protected static int? GetIdMode(string? modeName) =>
modeName?.Trim().ToLower() switch
{
"ротор" => 1,
"слайд" => 2,
_ => null
};
}

View File

@ -18,6 +18,8 @@ namespace AsbCloudInfrastructure.Services.ProcessMaps.WellDrilling;
/*
* password for ProcessMapImportTemplate.xlsx is ASB2020!
*/
[Obsolete]
public class ProcessMapPlanImportWellDrillingService : IProcessMapPlanImportService
{
private readonly IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillingRepository;

View File

@ -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<TrajectoryGeoFactDto>
{
protected override string SheetName => "Фактическая траектория";
protected override int HeaderRowsCount => 2;
protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
protected override IDictionary<string, Cell> Cells => new Dictionary<string, Cell>
{
{ 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)) }
};
}

View File

@ -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<TrajectoryGeoFactDto>
{
protected override string SheetName => "Фактическая траектория";
protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
public TrajectoryFactManualParserService(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override ValidationResultDto<TrajectoryGeoFactDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoFactDto
{
WellboreDepth = row.Cell(1).GetCellValue<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Comment = row.Cell(6).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели
var validationResult = new ValidationResultDto<TrajectoryGeoFactDto>
{
Item = trajectoryRow
};
return validationResult;
}
}

View File

@ -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<T> : ParserServiceBase<T, IParserOptionsRequest>
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<T> ParseRow(IXLRow row);
public override Stream GetTemplateFile() =>
Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName)
?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден");
public override ParserResultDto<T> 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;
}
}

View File

@ -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<TrajectoryGeoPlanDto>
{
protected override string SheetName => "Плановая траектория";
protected override int HeaderRowsCount => 2;
protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
protected override IDictionary<string, Cell> Cells => new Dictionary<string, Cell>
{
{ 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)) }
};
}

View File

@ -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<TrajectoryGeoPlanDto>
{
protected override string SheetName => "Плановая траектория";
protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
public TrajectoryPlanParserService(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override ValidationResultDto<TrajectoryGeoPlanDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoPlanDto
{
WellboreDepth = row.Cell(1).GetCellValue<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Radius = row.Cell(6).GetCellValue<double>(),
Comment = row.Cell(7).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели
var validationResult = new ValidationResultDto<TrajectoryGeoPlanDto>
{
Item = trajectoryRow
};
return validationResult;
}
}

View File

@ -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<ValidationResult> 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);
}
}

View File

@ -7,9 +7,13 @@ namespace AsbCloudInfrastructure;
public static class XLExtentions
{
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}.");
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(string.Format(NotFoundSheetTemplate, sheetName));
public static IXLCell SetCellValue<T>(this IXLCell cell, T value, string? format = null)
{
@ -37,12 +41,12 @@ public static class XLExtentions
if (cell.IsEmpty() && default(T) == null)
return default;
return cell.GetValue<T>();
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. {cell.Address.RowNumber} строка содержит некорректное значение в {cell.Address.ColumnNumber} столбце");
}
}
return cell.GetValue<T>();
}
catch
{
var message = string.Format(InvalidValueTemplate, cell.Worksheet.Name, cell.Address.RowNumber, cell.Address.ColumnNumber);
throw new FileFormatException(message);
}
}
}

View File

@ -22,4 +22,9 @@
<ProjectReference Include="..\AsbCloudWebApi\AsbCloudWebApi.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Controllers\ProcessMapPlan\Files\ProcessMapPlanDrillingInvalid.xlsx" />
<EmbeddedResource Include="Controllers\ProcessMapPlan\Files\ProcessMapPlanDrillingValid.xlsx" />
</ItemGroup>
</Project>

View File

@ -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;
}
}

View File

@ -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<IApiResponse<int>> UpdateOrInsertRange(int idWell, IEnumerable<ProcessMapPlanDrillingDto> dtos);
[Multipart]
[Post(BaseRoute + "/parse")]
Task<IApiResponse<ParserResultDto<ProcessMapPlanDrillingDto>>> Parse(int idWell, [AliasAs("files")] IEnumerable<StreamPart> streams);
}

View File

@ -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<ProcessMapPlanDrilling>();
@ -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());
}
}

View File

@ -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<ValidationResult>
{
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<string>? 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<string>();
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<string>());
}
public override void Write(Utf8JsonWriter writer, ValidationResult value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}

View File

@ -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<Startup>,
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));

View File

@ -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<IServiceProvider, ISupportRequiredService>();
private readonly IServiceScope serviceScopeMock = Substitute.For<IServiceScope>();
private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For<IServiceScopeFactory>();
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<TrajectoryGeoPlanDto, IParserOptionsRequest>(
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<TrajectoryGeoFactDto, IParserOptionsRequest>(
ParserServiceFactory.IdTrajectoryFactManualParserService);
var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
var trajectoryRows = trajectoryFactManualParser.Parse(stream, IParserOptionsRequest.Empty());
Assert.Equal(4, trajectoryRows.Item.Count());
}

View File

@ -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<FileFormatException>(() => GetCell(cellUsed).GetCellValue<double>());
}
[Fact]
public void GetCellValue_returns_nullable()
{

View File

@ -1,13 +0,0 @@
using System.IO;
using AsbCloudApp.Data;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers.Interfaces;
public interface IControllerWithParser<TDto, in TOptions>
where TDto : class, IId
{
ActionResult<ParserResultDto<TDto>> Parse(Stream file, TOptions options);
IActionResult GetTemplate();
}

View File

@ -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<TDto> : ControllerBase
where TDto : ProcessMapPlanBaseDto
where TDto : ProcessMapPlanBaseDto
{
private readonly IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository;
private readonly IWellService wellService;
private readonly IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository;
private readonly IWellService wellService;
private readonly ParserExcelService<TDto> parserService;
protected ProcessMapPlanBaseController(IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository,
IWellService wellService,
ParserExcelService<TDto> parserService)
{
this.repository = repository;
this.wellService = wellService;
this.parserService = parserService;
}
protected abstract string TemplateFileName { get; }
public ProcessMapPlanBaseController(IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository, IWellService wellService)
{
this.repository = repository;
this.wellService = wellService;
}
/// <summary>
/// <summary>
/// Добавление
/// </summary>
/// <param name="idWell"></param>
@ -190,6 +200,49 @@ public abstract class ProcessMapPlanBaseController<TDto> : ControllerBase
var result = await repository.UpdateOrInsertRange(idUser, dtos, token);
return Ok(result);
}
/// <summary>
/// Импорт РТК из excel (xlsx) файла
/// </summary>
/// <param name="idWell"></param>
/// <param name="files"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("parse")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<ParserResultDto<TDto>>> 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);
}
}
/// <summary>
/// Получение шаблона для заполнения РТК
/// </summary>
/// <returns></returns>
[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);
}
/// <summary>
/// returns user id, if he has access to well
@ -221,4 +274,4 @@ public abstract class ProcessMapPlanBaseController<TDto> : ControllerBase
var idUser = User.GetUserId() ?? throw new ForbidException("Неизвестный пользователь");
return idUser;
}
}
}

View File

@ -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<ProcessMapPlanDrillingDto>
{
public ProcessMapPlanDrillingController(
IChangeLogRepository<ProcessMapPlanDrillingDto, ProcessMapPlanBaseRequestWithWell> repository,
IWellService wellService)
: base(repository, wellService)
{
}
}
public ProcessMapPlanDrillingController(IChangeLogRepository<ProcessMapPlanDrillingDto, ProcessMapPlanBaseRequestWithWell> repository,
IWellService wellService,
ProcessMapPlanDrillingParser parserService)
: base(repository, wellService, parserService)
{
}
protected override string TemplateFileName => "ЕЦП_шаблон_файла_РТК_план_бурение.xlsx";
}

View File

@ -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
/// </summary>
[ApiController]
[Authorize]
public abstract class TrajectoryEditableController<TDto> : TrajectoryController<TDto>,
IControllerWithParser<TDto, IParserOptionsRequest>
where TDto : TrajectoryGeoDto
public abstract class TrajectoryEditableController<TDto> : TrajectoryController<TDto>
where TDto : TrajectoryGeoDto
{
private readonly IParserService<TDto, IParserOptionsRequest> parserService;
private readonly ParserExcelService<TDto> parserService;
private readonly ITrajectoryEditableRepository<TDto> trajectoryRepository;
protected TrajectoryEditableController(IWellService wellService,
ParserServiceFactory parserServiceFactory,
ParserExcelService<TDto> parserService,
TrajectoryExportService<TDto> trajectoryExportService,
ITrajectoryEditableRepository<TDto> trajectoryRepository,
int idParserService)
: base(
wellService,
trajectoryExportService,
trajectoryRepository)
{
parserService = parserServiceFactory.Create<TDto, IParserOptionsRequest>(idParserService);
ITrajectoryEditableRepository<TDto> trajectoryRepository)
: base(wellService, trajectoryExportService, trajectoryRepository)
{
this.parserService = parserService;
this.trajectoryRepository = trajectoryRepository;
}
ActionResult<ParserResultDto<TDto>> IControllerWithParser<TDto, IParserOptionsRequest>.Parse(Stream file,
IParserOptionsRequest options)
{
try
{
var parserResult = parserService.Parse(file, options);
return Ok(parserResult);
}
catch (FileFormatException ex)
{
return this.ValidationBadRequest("files", ex.Message);
}
}
/// <summary>
/// <summary>
/// Возвращает excel шаблон для заполнения строк траектории
/// </summary>
/// <returns>Запрашиваемый файл</returns>
@ -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);
}
}
/// <summary>

View File

@ -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<Traje
public TrajectoryFactManualController(IWellService wellService,
TrajectoryFactManualExportService trajectoryExportService,
ParserServiceFactory parserServiceFactory,
TrajectoryFactManualParser parserService,
ITrajectoryEditableRepository<TrajectoryGeoFactDto> trajectoryRepository)
: base(wellService,
parserServiceFactory,
trajectoryExportService,
trajectoryRepository,
ParserServiceFactory.IdTrajectoryFactManualParserService)
: base(wellService, parserService, trajectoryExportService, trajectoryRepository)
{
}
}

View File

@ -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<TrajectoryGeoPlanDto> trajectoryRepository,
TrajectoryService trajectoryVisualizationService)
: base(wellService,
parserServiceFactory,
trajectoryExportService,
trajectoryRepository,
ParserServiceFactory.IdTrajectoryPlanParserService)
: base(wellService, parserService, trajectoryExportService, trajectoryRepository)
{
this.trajectoryVisualizationService = trajectoryVisualizationService;
}

View File

@ -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
}
/// <summary>
/// Вызов парсера со стандартной валидацией входного файла
/// Получение Excel
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TOptions"></typeparam>
/// <param name="controller"></param>
/// <param name="files"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ActionResult<ParserResultDto<TDto>> ParseExcelFile<TDto, TOptions>(
this IControllerWithParser<TDto, TOptions> controller,
IFormFileCollection files,
TOptions options)
where TDto : class, IId
where TOptions : class, IParserOptionsRequest
/// <exception cref="ArgumentInvalidException"></exception>
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();
}
}