Merge branch 'dev' into feature/integration_tests

# Conflicts:
#	AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlan/ProcessMapPlanDrillingControllerTest.cs
This commit is contained in:
Степанов Дмитрий 2024-03-13 14:37:46 +03:00
commit 91002da6ac
22 changed files with 866 additions and 53 deletions

View File

@ -0,0 +1,39 @@
namespace AsbCloudApp.Data
{
/// Операция на скважине
public class WellOperationDataDto : IWellRelated
{
/// <inheritdoc/>
public int IdWell { get; set; }
/// <summary>
/// id секции скважины
/// </summary>
public int IdWellSectionType { get; set; }
/// <summary>
/// id категории операции
/// </summary>
public int IdCategory { get; set; }
/// <summary>
/// Глубина на начало операции, м
/// </summary>
public double DepthStart { get; set; }
/// <summary>
/// Продолжительность, часы
/// </summary>
public double DurationHours { get; set; }
/// <summary>
/// Наименование секции
/// </summary>
public string WellSectionTypeCaption { get; set; } = string.Empty;
/// <summary>
/// Наименование категории
/// </summary>
public string OperationCategoryName { get; set; } = string.Empty;
}
}

View File

@ -42,6 +42,14 @@ namespace AsbCloudApp.Repositories
/// <returns></returns>
Task<IEnumerable<WellOperationDto>> GetAsync(WellOperationRequest request, CancellationToken token);
/// <summary>
/// Получить список операций по запросу
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<WellOperationDataDto>> GetAsync(WellsOperationRequest request, CancellationToken token);
/// <summary>
/// Получить страницу списка операций
/// </summary>

View File

@ -0,0 +1,21 @@
namespace AsbCloudApp.Requests.ParserOptions;
/// <summary>
/// Параметры парсинга
/// </summary>
public class WellRelatedParserRequest : IParserOptionsRequest
{
/// <summary>
/// Конструктор
/// </summary>
/// <param name="idWell">Id скважины</param>
public WellRelatedParserRequest(int idWell)
{
IdWell = idWell;
}
/// <summary>
/// Id скважины
/// </summary>
public int IdWell { get; }
}

View File

@ -6,7 +6,7 @@ namespace AsbCloudApp.Requests
/// <summary>
/// параметры для запроса списка операций
/// </summary>
public class WellOperationRequestBase: RequestBase
public class WellOperationRequestBase : RequestBase
{
/// <summary>
/// фильтр по дате начала операции
@ -42,12 +42,40 @@ namespace AsbCloudApp.Requests
/// фильтр по списку id конструкций секции
/// </summary>
public IEnumerable<int>? SectionTypeIds { get; set; }
/// <summary>
/// Параметры для запроса списка операций.
/// Базовый конструктор
/// </summary>
public WellOperationRequestBase()
{ }
/// <summary>
/// Параметры для запроса списка операций.
/// Копирующий конструктор
/// </summary>
/// <param name="request"></param>
public WellOperationRequestBase(WellOperationRequestBase request)
{
GeDepth = request.GeDepth;
LeDepth = request.LeDepth;
GeDate = request.GeDate;
LtDate = request.LtDate;
OperationCategoryIds = request.OperationCategoryIds;
OperationType = request.OperationType;
SectionTypeIds = request.SectionTypeIds;
Skip = request.Skip;
Take = request.Take;
SortFields = request.SortFields;
}
}
/// <summary>
/// Параметры для запроса списка операций (с id скважины)
/// </summary>
public class WellOperationRequest: WellOperationRequestBase
public class WellOperationRequest : WellOperationRequestBase
{
/// <summary>
/// id скважины
@ -57,7 +85,7 @@ namespace AsbCloudApp.Requests
/// <summary>
/// ctor
/// </summary>
public WellOperationRequest(){}
public WellOperationRequest() { }
/// <summary>
/// копирующий конструктор
@ -65,21 +93,20 @@ namespace AsbCloudApp.Requests
/// <param name="request"></param>
/// <param name="idWell"></param>
public WellOperationRequest(WellOperationRequestBase request, int idWell)
:base(request)
{
this.IdWell = idWell;
this.GeDepth = request.GeDepth;
this.LeDepth = request.LeDepth;
this.GeDate = request.GeDate;
this.LtDate = request.LtDate;
this.OperationCategoryIds = request.OperationCategoryIds;
this.OperationType = request.OperationType;
this.SectionTypeIds = request.SectionTypeIds;
this.Skip= request.Skip;
this.Take= request.Take;
this.SortFields = request.SortFields;
IdWell = idWell;
}
}
/// <summary>
/// Параметры для запроса списка операций (с массивом id скважин)
/// </summary>
public class WellsOperationRequest : WellOperationRequestBase
{
/// <summary>
/// ids скважин
/// </summary>
public IEnumerable<int> IdsWell { get; set; } = null!;
}
}

View File

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

View File

@ -0,0 +1,21 @@
using AsbCloudApp.Data;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
/// <summary>
/// Интерфейс для вычисления композитной скважины
/// </summary>
public interface IWellCompositeOperationService
{
/// <summary>
/// Получение данных для построения композитной скважины
/// </summary>
/// <param name="idsWells"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<List<Dictionary<int, WellOperationDataDto>>> GetAsync(IEnumerable<int> idsWells, CancellationToken token);
}
}

View File

@ -198,6 +198,7 @@ namespace AsbCloudInfrastructure
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
NotificationCategory>>();
services.AddTransient<IDrillTestRepository, DrillTestRepository>();
services.AddTransient<IWellCompositeOperationService, WellCompositeOperationService>();
// admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>

View File

@ -205,6 +205,17 @@ public class WellOperationRepository : IWellOperationRepository
return dtos.Select(Convert);
}
public async Task<IEnumerable<WellOperationDataDto>> GetAsync(
WellsOperationRequest request,
CancellationToken token)
{
var query = BuildQuery(request)
.AsNoTracking();
var dtos = await query.ToArrayAsync(token);
return dtos;
}
/// <inheritdoc/>
public async Task<PaginationContainer<WellOperationDto>> GetPageAsync(
WellOperationRequest request,
@ -385,7 +396,6 @@ public class WellOperationRepository : IWellOperationRepository
.Include(s => s.OperationCategory)
.Where(o => o.IdWell == request.IdWell);
if (request.OperationType.HasValue)
query = query.Where(e => e.IdType == request.OperationType.Value);
@ -420,6 +430,7 @@ public class WellOperationRepository : IWellOperationRepository
.Where(subOp => subOp.IdType == 1)
.Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory));
// TODO: Вынести query.Select из метода BuildQuery
var dtos = query.Select(o => new WellOperationDto
{
Id = o.Id,
@ -472,6 +483,50 @@ public class WellOperationRepository : IWellOperationRepository
return dtos.AsNoTracking();
}
/// <summary>
/// Получение данных по запросу
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
private IQueryable<WellOperationDataDto> BuildQuery(WellsOperationRequest request)
{
var query = db.WellOperations
.Where(o => request.IdsWell.Contains(o.IdWell))
.Where(o => request.OperationType == o.IdType);
if (request.SectionTypeIds?.Any() == true)
query = query.Where(o => request.SectionTypeIds.Contains(o.IdWellSectionType));
if (request.OperationCategoryIds?.Any() == true)
query = query.Where(o => request.OperationCategoryIds.Contains(o.IdCategory));
// TODO: Вынести query.Select из метода BuildQuery
var dtos = query.Select(o => new WellOperationDataDto
{
DepthStart = o.DepthStart,
DurationHours = o.DurationHours,
IdCategory = o.IdCategory,
IdWell = o.IdWell,
IdWellSectionType = o.IdWellSectionType,
OperationCategoryName = o.OperationCategory.Name,
WellSectionTypeCaption = o.WellSectionType.Caption,
});
if (request.SortFields?.Any() == true)
{
dtos = dtos.SortBy(request.SortFields);
}
if (request.Skip.HasValue)
dtos = dtos.Skip(request.Skip.Value);
if (request.Take.HasValue)
dtos = dtos.Take(request.Take.Value);
return dtos.AsNoTracking();
}
private WellOperationDto Convert(WellOperationDto dto)
{
var timezone = wellService.GetTimezone(dto.IdWell);

View File

@ -12,8 +12,9 @@ using Mapster;
namespace AsbCloudInfrastructure.Services.Parser;
public abstract class ParserExcelService<TDto> : IParserService<TDto>
public abstract class ParserExcelService<TDto, TOptions> : IParserService<TDto, TOptions>
where TDto : class, IValidatableObject, IId
where TOptions : IParserOptionsRequest
{
protected abstract string SheetName { get; }
@ -22,9 +23,8 @@ public abstract class ParserExcelService<TDto> : IParserService<TDto>
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
public virtual ParserResultDto<TDto> Parse(Stream file, TOptions options)
{
using var workbook = new XLWorkbook(file);
var sheet = workbook.GetWorksheet(SheetName);

View File

@ -6,7 +6,6 @@ using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Repositories;
using AsbCloudInfrastructure.Services.Parser;
using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;

View File

@ -1,14 +1,26 @@
using System;
using System.IO;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Requests.ParserOptions;
using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser;
public abstract class ProcessMapPlanParser<TDto> : ParserExcelService<TDto>
public abstract class ProcessMapPlanParser<TDto> : ParserExcelService<TDto, WellRelatedParserRequest>
where TDto : ProcessMapPlanBaseDto
{
protected override int HeaderRowsCount => 2;
public override ParserResultDto<TDto> Parse(Stream file, WellRelatedParserRequest options)
{
var result = base.Parse(file, options);
foreach (var item in result.Item)
item.Item.IdWell = options.IdWell;
return result;
}
protected static int? GetIdMode(string? modeName) =>
modeName?.Trim().ToLower() switch
{

View File

@ -22,14 +22,12 @@ public class ProcessMapPlanReamParser : ProcessMapPlanParser<ProcessMapPlanReamD
protected override string TemplateFileName => "ProcessMapPlanReamTemplate.xlsx";
private const int ColumnSection = 1;
private const int ColumnMode = 2;
protected override IDictionary<string, Cell> Cells => new Dictionary<string, Cell>
{
{ nameof(ProcessMapPlanReamDto.Section), new Cell(ColumnSection, typeof(string)) },
{ nameof(ProcessMapPlanReamDto.DepthStart), new Cell(2, typeof(double)) },
{ nameof(ProcessMapPlanReamDto.DepthEnd), new Cell(3, typeof(double)) },
{ nameof(ProcessMapPlanReamDto.Repeats), new Cell(4, typeof(double)) },
{ nameof(ProcessMapPlanReamDto.SpinUpward), new Cell(5, typeof(double)) },
{ nameof(ProcessMapPlanReamDto.SpinUpward), new Cell(6, typeof(double)) },

View File

@ -4,12 +4,10 @@ using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
public class TrajectoryFactManualParser : ParserExcelService<TrajectoryGeoFactDto>
public class TrajectoryFactManualParser : TrajectoryParser<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>

View File

@ -0,0 +1,23 @@
using System.IO;
using AsbCloudApp.Data;
using AsbCloudApp.Data.Trajectory;
using AsbCloudApp.Requests.ParserOptions;
using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
public abstract class TrajectoryParser<TDto> : ParserExcelService<TDto, WellRelatedParserRequest>
where TDto : TrajectoryGeoDto
{
protected override int HeaderRowsCount => 2;
public override ParserResultDto<TDto> Parse(Stream file, WellRelatedParserRequest options)
{
var result = base.Parse(file, options);
foreach (var item in result.Item)
item.Item.IdWell = options.IdWell;
return result;
}
}

View File

@ -4,12 +4,10 @@ using AsbCloudInfrastructure.Services.Parser;
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
public class TrajectoryPlanParser : ParserExcelService<TrajectoryGeoPlanDto>
public class TrajectoryPlanParser : TrajectoryParser<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>

View File

@ -0,0 +1,220 @@
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services
{
public class WellCompositeOperationService : IWellCompositeOperationService
{
private ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository;
private IWellOperationCategoryRepository wellOperationCategoryRepository;
private IWellOperationRepository wellOperationRepository;
/// <summary>
/// Тип секции "Транспортный стол"
/// </summary>
private const int wellSectionTransportTable = 5;
/// <summary>
/// Тип секции "Эксплуатационная колонна"
/// </summary>
private const int wellSectionProductionString = 4;
/// <summary>
/// набор настроек для замены одной категории секции на другую
/// </summary>
private static Dictionary<(int, int), int> SettingsForSectionCategoryChange = new Dictionary<(int, int), int>() {
{ (2, 5096), 5013 },
{ (2, 5008), 5013 },
{ (3, 5096), 5084 },
{ (3, 5008), 5084 },
{ (3, 5085), 5015 },
{ (3, 5014), 5015 },
{ (31, 5014), 5015 },
{ (31, 5012), 5013 },
{ (31, 5083), 5013 },
{ (4, 5085), 5015 },
{ (4, 5087), 5015 },
{ (4, 5014), 5015 },
{ (4, 5053), 5037 },
{ (4, 5084), 5096 },
{ (4, 5086), 5013 },
{ (6, 5085), 5015 },
{ (6, 5036), 5034 },
{ (6, 5035), 5097 }
};
private HashSet<(int IdSectionType, int IdCategory)> WellSectionTypesWithCategories = new HashSet<(int IdSectionType, int IdCategory)>()
{
{ (2, 5001) },
{ (2, 5003) },
{ (2, 5013) },
{ (2, 5000) },
{ (2, 5022) },
{ (2, 5017) },
{ (2, 5023) },
{ (2, 4007) },
{ (2, 5090) },
{ (3, 5001) },
{ (3, 5015) },
{ (3, 5037) },
{ (3, 5057) },
{ (3, 5003) },
{ (3, 5036) },
{ (3, 5084) },
{ (3, 5013) },
{ (3, 5000) },
{ (3, 5022) },
{ (3, 5017) },
{ (3, 4007) },
{ (3, 5090) },
{ (3, 5045) },
{ (3, 5042) },
{ (3, 5046) },
{ (31, 5001) },
{ (31, 5015) },
{ (31, 5037) },
{ (31, 5057) },
{ (31, 5003) },
{ (31, 5036) },
{ (31, 5013) },
{ (31, 5022) },
{ (31, 5017) },
{ (31, 5023) },
{ (31, 4007) },
{ (31, 5045) },
{ (31, 5042) },
{ (31, 5046) },
{ (4, 5001) },
{ (4, 5015) },
{ (4, 5046) },
{ (4, 5037) },
{ (4, 5097) },
{ (4, 5057) },
{ (4, 5003) },
{ (4, 5036) },
{ (4, 5008) },
{ (4, 5003) },
{ (4, 5036) },
{ (4, 5013) },
{ (4, 5000) },
{ (4, 5029) },
{ (4, 5022) },
{ (4, 5017) },
{ (4, 5019) },
{ (4, 5042) },
{ (4, 5046) },
{ (6, 5001) },
{ (6, 5015) },
{ (6, 5034) },
{ (6, 5037) },
{ (6, 5097) },
{ (6, 5057) },
{ (6, 5003) }
};
public WellCompositeOperationService(
ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository,
IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellOperationRepository wellOperationRepository)
{
this.wellSectionTypeRepository = wellSectionTypeRepository;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
this.wellOperationRepository = wellOperationRepository;
}
public async Task<List<Dictionary<int, WellOperationDataDto>>> GetAsync(IEnumerable<int> idsWells, CancellationToken token)
{
var sections = await wellSectionTypeRepository.GetAllAsync(token);
var sectionsDict = sections.ToDictionary(s => s.Id, s => s.Caption);
var categories = wellOperationCategoryRepository.Get(true);
var categoriesDict = categories.ToDictionary(s => s.Id, s => s.Name);
var idsWellSectionTypes = WellSectionTypesWithCategories.Select(t => t.IdSectionType).Distinct();
var usedCategories = WellSectionTypesWithCategories.Select(c => c.IdCategory).Distinct();
var wellOperationRequest = new WellsOperationRequest()
{
IdsWell = idsWells,
OperationCategoryIds = usedCategories,
SectionTypeIds = idsWellSectionTypes,
OperationType = WellOperation.IdOperationTypeFact
};
var operations = await wellOperationRepository.GetAsync(wellOperationRequest, token);
var renamedOperations = operations.Select(o => UpdateIdWellSectionAndIdCategory(o, sectionsDict, categoriesDict));
var wellOperationsWithComposite = new List<Dictionary<int, WellOperationDataDto>>();
var compositeDepth = 0d;
foreach ((int IdSection, int IdCategory) in WellSectionTypesWithCategories)
{
var filteredByTemplate = renamedOperations
.Where(o => o.IdWellSectionType == IdSection)
.Where(o => o.IdCategory == IdCategory);
if (!filteredByTemplate.Any())
continue;
var groupedByWell = filteredByTemplate.GroupBy(o => o.IdWell);
var aggreagtedByWell = groupedByWell.Select(g => new WellOperationDataDto
{
IdCategory = IdCategory,
IdWell = g.Key,
IdWellSectionType = IdSection,
DepthStart = g.Min(o => o.DepthStart),
DurationHours = g.Sum(o => o.DurationHours),
OperationCategoryName = g.First().OperationCategoryName,
WellSectionTypeCaption = g.First().WellSectionTypeCaption,
});
var composite = aggreagtedByWell.OrderBy(o => o.DurationHours).
ThenByDescending(o => o.DepthStart)
.First();
composite.IdWell = 0;
if (compositeDepth > composite.DepthStart)
composite.DepthStart = compositeDepth;
compositeDepth = composite.DepthStart;
var resultItem = aggreagtedByWell.ToDictionary(o => o.IdWell);
resultItem.Add(0, composite);
wellOperationsWithComposite.Add(resultItem);
}
return wellOperationsWithComposite;
}
private static WellOperationDataDto UpdateIdWellSectionAndIdCategory(
WellOperationDataDto dto,
Dictionary<int, string> sectionTypes,
Dictionary<int, string> operationCategories)
{
if (dto.IdWellSectionType == wellSectionTransportTable)
{
dto.IdWellSectionType = wellSectionProductionString;
dto.WellSectionTypeCaption = sectionTypes[dto.IdWellSectionType] ?? string.Empty;
}
if ((SettingsForSectionCategoryChange.TryGetValue((dto.IdWellSectionType, dto.IdCategory), out int newIdCategory)))
{
dto.IdCategory = newIdCategory;
dto.OperationCategoryName = operationCategories[dto.IdCategory] ?? string.Empty;
}
return dto;
}
}
}

View File

@ -35,5 +35,5 @@ public interface IProcessMapPlanDrillingClient
[Multipart]
[Post(BaseRoute + "/parse")]
Task<IApiResponse<ParserResultDto<ProcessMapPlanDrillingDto>>> Parse(int idWell, [AliasAs("files")] IEnumerable<StreamPart> streams);
Task<IApiResponse<ParserResultDto<ProcessMapPlanDrillingDto>>> Parse(int idWell, [AliasAs("file")] StreamPart stream);
}

View File

@ -11,9 +11,11 @@ public class TrajectoryParserTest
{
private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates";
private readonly WellRelatedParserRequest options = new(1);
private readonly TrajectoryPlanParser trajectoryPlanParser = new();
private readonly TrajectoryFactManualParser trajectoryFactManualParser = new();
[Fact]
public void Parse_trajectory_plan()
{
@ -22,8 +24,8 @@ public class TrajectoryParserTest
if (stream is null)
Assert.Fail("Файла для импорта не существует");
var trajectoryRows = trajectoryPlanParser.Parse(stream, IParserOptionsRequest.Empty());
var trajectoryRows = trajectoryPlanParser.Parse(stream, options);
Assert.Equal(3, trajectoryRows.Item.Count());
}
@ -37,7 +39,7 @@ public class TrajectoryParserTest
if (stream is null)
Assert.Fail("Файла для импорта не существует");
var trajectoryRows = trajectoryFactManualParser.Parse(stream, IParserOptionsRequest.Empty());
var trajectoryRows = trajectoryFactManualParser.Parse(stream, options);
Assert.Equal(4, trajectoryRows.Item.Count());
}

View File

@ -0,0 +1,337 @@
using AsbCloudApp.Requests;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services.ProcessMaps.Report;
using AsbCloudInfrastructure.Services;
using NSubstitute;
using ProtoBuf.Meta;
using SignalRSwaggerGen.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using AsbCloudApp.Repositories;
using AsbCloudApp.Data;
using AsbCloudApp.Services;
namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation
{
public class WellCompositeOperationServiceTest
{
private WellCompositeOperationService service;
private ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository
= Substitute.For<ICrudRepository<WellSectionTypeDto>>();
private IWellOperationCategoryRepository wellOperationCategoryRepository
= Substitute.For<IWellOperationCategoryRepository>();
private IWellOperationRepository wellOperationRepository
= Substitute.For<IWellOperationRepository>();
private readonly static IEnumerable<WellOperationCategoryDto> operationCategories = new List<WellOperationCategoryDto>()
{
new(){Id = 5096, Name = "Шаблонирование перед спуском"},
new(){Id = 5008, Name = "Шаблонировка во время бурения"},
new(){Id = 5013, Name = "Подъем КНБК"},
new(){Id = 5003, Name = "Бурение ротором"},
new(){Id = 5036, Name = "Промывка"},
new(){Id = 5012, Name = "Подъем инструмента"},
new(){Id = 5083, Name = "Проработка принудительная"},
};
private readonly static IEnumerable<WellSectionTypeDto> sectionTypes = new List<WellSectionTypeDto>()
{
new() {Id = 2, Caption = "Направление", Order = 0},
new() {Id = 3, Caption = "Кондуктор", Order = 1},
new() {Id = 31, Caption = "Техническая колонна", Order = 2}
};
private readonly static IEnumerable<WellOperationDataDto> wellOperations1 = new List<WellOperationDataDto>()
{
new WellOperationDataDto()
{
DepthStart = 50,
DurationHours = 1,
IdCategory = 5096,
IdWell = 55,
IdWellSectionType = 2,
OperationCategoryName = "Шаблонирование перед спуском",
WellSectionTypeCaption = "Направление"
},
new WellOperationDataDto()
{
DepthStart = 84,
DurationHours = 1,
IdCategory = 5008,
IdWell = 64,
IdWellSectionType = 2,
OperationCategoryName = "Шаблонировка во время бурения",
WellSectionTypeCaption = "Направление"
}
};
private readonly static IEnumerable<WellOperationDataDto> wellOperations2 = new List<WellOperationDataDto>()
{
new WellOperationDataDto()
{
DepthStart = 10,
DurationHours = 1.5,
IdCategory = 5003,
IdWell = 55,
IdWellSectionType = 2,
OperationCategoryName = "Бурение ротором",
WellSectionTypeCaption = "Направление"
},
new WellOperationDataDto()
{
DepthStart = 20,
DurationHours = 3.5,
IdCategory = 5003,
IdWell = 64,
IdWellSectionType = 2,
OperationCategoryName = "Бурение ротором",
WellSectionTypeCaption = "Направление"
}
};
private readonly static IEnumerable<WellOperationDataDto> wellOperations3 = new List<WellOperationDataDto>()
{
new WellOperationDataDto()
{
DepthStart = 1372,
DurationHours = 3,
IdCategory = 5036,
IdWell = 55,
IdWellSectionType = 3,
OperationCategoryName = "Промывка",
WellSectionTypeCaption = "Кондуктор"
},
new WellOperationDataDto()
{
DepthStart = 1435,
DurationHours = 4,
IdCategory = 5036,
IdWell = 64,
IdWellSectionType = 3,
OperationCategoryName = "Промывка",
WellSectionTypeCaption = "Кондуктор"
}
};
private readonly static IEnumerable<WellOperationDataDto> wellOperations4 = new List<WellOperationDataDto>()
{
new WellOperationDataDto()
{
DepthStart = 1000,
DurationHours = 10,
IdCategory = 5012,
IdWell = 55,
IdWellSectionType = 31,
OperationCategoryName = "Подъем инструмента",
WellSectionTypeCaption = "Техническая колонна"
},
new WellOperationDataDto()
{
DepthStart = 500,
DurationHours = 5,
IdCategory = 5083,
IdWell = 55,
IdWellSectionType = 31,
OperationCategoryName = "Проработка принудительная",
WellSectionTypeCaption = "Техническая колонна"
},
new WellOperationDataDto()
{
DepthStart = 600,
DurationHours = 5,
IdCategory = 5083,
IdWell = 64,
IdWellSectionType = 31,
OperationCategoryName = "Проработка принудительная",
WellSectionTypeCaption = "Техническая колонна"
}
};
public WellCompositeOperationServiceTest()
{
wellSectionTypeRepository.GetAllAsync(Arg.Any<CancellationToken>())
.Returns(sectionTypes);
wellOperationCategoryRepository.Get(Arg.Any<bool>())
.Returns(operationCategories);
service = new WellCompositeOperationService(
wellSectionTypeRepository,
wellOperationCategoryRepository,
wellOperationRepository);
}
/// <summary>
/// На вход подаются 2 операции с одинаковыми секциями (id = 2), но разными категориями (ids = 5096, 5008) и ключами скважин (ids = 55, 64)
/// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
/// Операция должна иметь категорию 5013 для всех трех скважин
/// </summary>
/// <returns></returns>
[Fact]
public async Task GetAsync_return_composite_and_others_with_category_5013()
{
// arrange
wellOperationRepository.GetAsync(Arg.Any<WellsOperationRequest>(), Arg.Any<CancellationToken>())
.Returns(wellOperations1);
var idsWell = new List<int>() { 55, 64 };
// act
var result = await service.GetAsync(idsWell, CancellationToken.None);
// assert
var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
Assert.NotNull(compositeWellOperation);
Assert.Equal(5013, compositeWellOperation.IdCategory);
Assert.Equal(1, compositeWellOperation.DurationHours);
Assert.Equal(84, compositeWellOperation.DepthStart);
var currentWellOperations = result.SelectMany(o => o.Values.Where(o => o.IdWell != 0));
var categories = currentWellOperations.Select(o => o.IdCategory).Distinct();
Assert.NotNull(categories);
Assert.Single(categories);
Assert.Equal(5013, categories.First());
var categoryName = currentWellOperations.Select(o => o.OperationCategoryName).First();
Assert.Equal("Подъем КНБК", categoryName);
}
/// <summary>
/// На вход подаются 2 операции с одинаковыми секциями (id = 2) и категориями (id = 5003), но разными ключами скважин (ids = 55, 64)
/// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
/// Операция композитной скважины должна содержать данные той операции, которая содержит минимальный duration_hours
/// </summary>
/// <returns></returns>
[Fact]
public async Task GetAsync_return_composite_with_minimum_depth_start()
{
// arrange
wellOperationRepository.GetAsync(Arg.Any<WellsOperationRequest>(), Arg.Any<CancellationToken>())
.Returns(wellOperations2);
var idsWell = new List<int>() { 55, 64 };
// act
var result = await service.GetAsync(idsWell, CancellationToken.None);
// assert
var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
Assert.NotNull(compositeWellOperation);
Assert.Equal(5003, compositeWellOperation.IdCategory);
Assert.Equal(1.5, compositeWellOperation.DurationHours);
Assert.Equal(10, compositeWellOperation.DepthStart);
}
/// <summary>
/// На вход подаются 2 операции с одинаковыми секциями (id = 3) и категориями (id = 5036), но разными ключами скважин (ids = 55, 64)
/// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
/// Операция композитной скважины должна содержать данные той операции, которая содержит минимальный duration_hours
/// </summary>
/// <returns></returns>
[Fact]
public async Task GetAsync_return_data3()
{
// arrange
wellOperationRepository.GetAsync(Arg.Any<WellsOperationRequest>(), Arg.Any<CancellationToken>())
.Returns(wellOperations3);
var idsWell = new List<int>() { 55, 64 };
// act
var result = await service.GetAsync(idsWell, CancellationToken.None);
// assert
var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
Assert.NotNull(compositeWellOperation);
Assert.Equal(5036, compositeWellOperation.IdCategory);
Assert.Equal(3, compositeWellOperation.DurationHours);
Assert.Equal(1372, compositeWellOperation.DepthStart);
}
/// <summary>
/// На вход подаются 3 операции с одинаковыми секциями (id = 31), но разными категориями (ids = 5012, 5083) и ключами скважин (ids = 55, 64)
/// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
/// Операция композитной скважины должна содержать:
/// данные той операции, которая содержит минимальный duration_hours
/// категорию с ключом 5013
/// Операции по скважине с ключом 55 должны объединиться в одну,
/// при этом их длительность складывается, а depth_start берется минимальный из двух
/// </summary>
/// <returns></returns>
[Fact]
public async Task GetAsync_return_data4()
{
// arrange
wellOperationRepository.GetAsync(Arg.Any<WellsOperationRequest>(), Arg.Any<CancellationToken>())
.Returns(wellOperations4);
var idsWell = new List<int>() { 55, 64 };
// act
var result = await service.GetAsync(idsWell, CancellationToken.None);
// assert
var currentWellOperations = result.SelectMany(o => o.Values.Where(o => o.IdWell != 0));
var categories = currentWellOperations.Select(o => o.IdCategory).Distinct();
Assert.NotNull(categories);
Assert.Single(categories);
Assert.Equal(5013, categories.First());
var currentOperationByWell55 = currentWellOperations.Where(o => o.IdWell == 55).FirstOrDefault();
Assert.NotNull(currentOperationByWell55);
Assert.Equal(15, currentOperationByWell55.DurationHours);
Assert.Equal(500, currentOperationByWell55.DepthStart);
var categoryName = currentWellOperations.Select(o => o.OperationCategoryName).First();
Assert.Equal("Подъем КНБК", categoryName);
var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
Assert.NotNull(compositeWellOperation);
Assert.Equal(5013, compositeWellOperation.IdCategory);
Assert.Equal(5, compositeWellOperation.DurationHours);
Assert.Equal(600, compositeWellOperation.DepthStart);
}
/// <summary>
/// На вход подаются список разных операций с разными ключами скважин (ids = 55, 64)
/// Метод возвращает список из 4-х операций в разрезе 3-х скважин: 2 текущие скважины и одна композитная
/// Операция композитной скважины должна содержать глубину забоя = 1372
/// </summary>
/// <returns></returns>
[Fact]
public async Task GetAsync_return_data5()
{
// arrange
var wellOperations = new List<WellOperationDataDto>();
wellOperations.AddRange(wellOperations1);
wellOperations.AddRange(wellOperations2);
wellOperations.AddRange(wellOperations3);
wellOperations.AddRange(wellOperations4);
wellOperationRepository.GetAsync(Arg.Any<WellsOperationRequest>(), Arg.Any<CancellationToken>())
.Returns(wellOperations);
var idsWell = new List<int>() { 55, 64 };
// act
var result = await service.GetAsync(idsWell, CancellationToken.None);
// assert
Assert.Equal(4, result.Count());
var lastOperation = result.Last();
var lastOperationComposite = lastOperation[0];
Assert.Equal(1372, lastOperationComposite.DepthStart);
}
}
}

View File

@ -30,11 +30,11 @@ public abstract class ProcessMapPlanBaseController<TDto> : ControllerBase
{
private readonly IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository;
private readonly IWellService wellService;
private readonly ParserExcelService<TDto> parserService;
private readonly ParserExcelService<TDto, WellRelatedParserRequest> parserService;
protected ProcessMapPlanBaseController(IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository,
IWellService wellService,
ParserExcelService<TDto> parserService)
ParserExcelService<TDto, WellRelatedParserRequest> parserService)
{
this.repository = repository;
this.wellService = wellService;
@ -213,7 +213,7 @@ public abstract class ProcessMapPlanBaseController<TDto> : ControllerBase
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<ParserResultDto<TDto>>> Parse(int idWell,
[Required] IFormFile file,
[Required] IFormFile file,
CancellationToken token)
{
await AssertUserHasAccessToWell(idWell, token);
@ -222,9 +222,8 @@ public abstract class ProcessMapPlanBaseController<TDto> : ControllerBase
try
{
var dto = parserService.Parse(stream, IParserOptionsRequest.Empty());
foreach (var item in dto.Item)
item.Item.IdWell = idWell;
var options = new WellRelatedParserRequest(idWell);
var dto = parserService.Parse(stream, options);
return Ok(dto);
}

View File

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests.ParserOptions;
using AsbCloudInfrastructure.Services.Parser;
using AsbCloudInfrastructure.Services.Trajectory.Parser;
namespace AsbCloudWebApi.Controllers.Trajectory
{
@ -25,11 +26,11 @@ namespace AsbCloudWebApi.Controllers.Trajectory
public abstract class TrajectoryEditableController<TDto> : TrajectoryController<TDto>
where TDto : TrajectoryGeoDto
{
private readonly ParserExcelService<TDto> parserService;
private readonly TrajectoryParser<TDto> parserService;
private readonly ITrajectoryEditableRepository<TDto> trajectoryRepository;
protected TrajectoryEditableController(IWellService wellService,
ParserExcelService<TDto> parserService,
TrajectoryParser<TDto> parserService,
TrajectoryExportService<TDto> trajectoryExportService,
ITrajectoryEditableRepository<TDto> trajectoryRepository)
: base(wellService, trajectoryExportService, trajectoryRepository)
@ -78,7 +79,9 @@ namespace AsbCloudWebApi.Controllers.Trajectory
try
{
var dto = parserService.Parse(stream, IParserOptionsRequest.Empty());
var options = new WellRelatedParserRequest(idWell);
var dto = parserService.Parse(stream, options);
return Ok(dto);
}
catch (FileFormatException ex)

View File

@ -0,0 +1,51 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Controllers
{
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class WellCompositeOperationController : ControllerBase
{
private readonly IWellCompositeOperationService wellCompositeOperationService;
private readonly IWellService wellService;
public WellCompositeOperationController(IWellCompositeOperationService wellCompositeOperationService, IWellService wellService)
{
this.wellCompositeOperationService = wellCompositeOperationService;
this.wellService = wellService;
}
[HttpGet]
[ProducesResponseType(typeof(IList<IDictionary<int, WellOperationDataDto>>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAsync([FromQuery] IEnumerable<int> idsWells, CancellationToken token)
{
foreach (var idWell in idsWells)
if (!await UserHasAccessToWellAsync(idWell, token))
return Forbid();
var result = await wellCompositeOperationService.GetAsync(idsWells, token)
.ConfigureAwait(false);
return Ok(result);
}
protected async Task<bool> UserHasAccessToWellAsync(int idWell, CancellationToken token)
{
var idCompany = User.GetCompanyId();
if (idCompany is not null &&
await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, idWell, token)
.ConfigureAwait(false))
return true;
return false;
}
}
}