diff --git a/AsbCloudApp/Data/WellboreDto.cs b/AsbCloudApp/Data/WellboreDto.cs new file mode 100644 index 00000000..e64a188f --- /dev/null +++ b/AsbCloudApp/Data/WellboreDto.cs @@ -0,0 +1,64 @@ +using System; + +namespace AsbCloudApp.Data; + +/// +/// Ствол скважины +/// +public class WellboreDto +{ + /// + /// Идентификатор + /// + public int Id { get; set; } + + /// + /// Название + /// + public string Name { get; set; } = null!; + + /// + /// Идентификатор скважины + /// + public int IdWell { get; set; } + + /// + /// Состояние скважины + /// + public int IdWellState { get; set; } + + /// + /// Идентификатор телеметрии + /// + public int? IdWellTelemetry { get; set; } + + /// + /// Временная зона скважины + /// + public SimpleTimezoneDto? WellTimezone { get; set; } + + /// + /// Название скважины + /// + public string WellName { get; set; } = null!; + + /// + /// Начальная глубина ствола + /// + public double DepthStart { get; set; } + + /// + /// Конечная глубина скважины + /// + public double DepthEnd { get; set; } + + /// + /// Дата начала первой операции + /// + public DateTimeOffset DateStart { get; set; } + + /// + /// Дата завершения последней операции + /// + public DateTimeOffset DateEnd { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Requests/WellboreRequest.cs b/AsbCloudApp/Requests/WellboreRequest.cs new file mode 100644 index 00000000..32b4cbe2 --- /dev/null +++ b/AsbCloudApp/Requests/WellboreRequest.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace AsbCloudApp.Requests; + +/// +/// Параметры запроса для ствола скважины +/// +public class WellboreRequest : RequestBase +{ + /// + /// Пары идентификаторов скважины и секции + /// + public IEnumerable<(int idWell, int? idSection)> Ids { get; set; } = null!; +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IWellboreService.cs b/AsbCloudApp/Services/IWellboreService.cs new file mode 100644 index 00000000..f42485ae --- /dev/null +++ b/AsbCloudApp/Services/IWellboreService.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Requests; + +namespace AsbCloudApp.Services; + +/// +/// Сервис для ствола скважины +/// +public interface IWellboreService +{ + /// + /// Получение ствола скважины + /// + /// + /// + /// + /// + Task GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken); + + /// + /// Получение стволов скважин + /// + /// + /// + /// + Task> GetWellboresAsync(WellboreRequest request, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 601d05e7..bb894d63 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -222,6 +222,8 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); + services.AddTransient(); + return services; } diff --git a/AsbCloudInfrastructure/Services/WellboreService.cs b/AsbCloudInfrastructure/Services/WellboreService.cs new file mode 100644 index 00000000..727878dc --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellboreService.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb.Model; + +namespace AsbCloudInfrastructure.Services; + +public class WellboreService : IWellboreService +{ + private readonly IWellService wellService; + private readonly IWellOperationRepository wellOperationRepository; + + public WellboreService(IWellService wellService, IWellOperationRepository wellOperationRepository) + { + this.wellService = wellService; + this.wellOperationRepository = wellOperationRepository; + } + + public async Task GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken) + { + var request = new WellboreRequest + { + Ids = new (int, int?)[] { (idWell, idSection) }, + Take = 1, + }; + var data = await GetWellboresAsync(request, cancellationToken); + return data.FirstOrDefault(); + } + + public async Task> GetWellboresAsync(WellboreRequest request, + CancellationToken cancellationToken) + { + var wellbores = new List(request.Ids.Count()); + var skip = request.Skip ?? 0; + var take = request.Take ?? 10; + + var sections = wellOperationRepository.GetSectionTypes() + .ToDictionary(w => w.Id, w => w); + + var ids = request.Ids.GroupBy(i => i.idWell); + + foreach (var id in ids) + { + var well = await wellService.GetOrDefaultAsync(id.Key, cancellationToken); + + if (well is null) + continue; + + var wellOperations = await GetFactOperationsAsync(well.Id, id.Select(i => i.idSection), cancellationToken); + var groupedOperations = wellOperations.GroupBy(o => o.IdWellSectionType); + var wellWellbores = groupedOperations.Select(group => new WellboreDto { + Id = group.Key, + IdWell = well.Id, + IdWellState = well.IdState, + IdWellTelemetry = well.IdTelemetry, + Name = sections[group.Key].Caption, + WellName = well.Caption, + WellTimezone = well.Timezone, + + DateStart = group.Min(operation => operation.DateStart), + DateEnd = group.Max(operation => operation.DateStart.AddHours(operation.DurationHours)), + DepthStart = group.Min(operation => operation.DepthStart), + DepthEnd = group.Max(operation => operation.DepthEnd), + }); + wellbores.AddRange(wellWellbores); + } + + return wellbores + .OrderBy(w =>w.IdWell).ThenBy(w=>w.Id) + .Skip(skip).Take(take); + } + + private async Task> GetFactOperationsAsync(int idWell, IEnumerable idsSections, + CancellationToken cancellationToken) + { + var request = new WellOperationRequest + { + IdWell = idWell, + OperationType = WellOperation.IdOperationTypeFact, + SortFields = new[] { "DateStart asc" }, + }; + + request.SectionTypeIds = idsSections.All(i => i.HasValue) + ? idsSections.Select(i => i!.Value) + : null; + + return (await wellOperationRepository.GetAsync(request, cancellationToken)) + .OrderBy(o => o.DateStart); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/WellboreController.cs b/AsbCloudWebApi/Controllers/WellboreController.cs new file mode 100644 index 00000000..7c8bdaf3 --- /dev/null +++ b/AsbCloudWebApi/Controllers/WellboreController.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace AsbCloudWebApi.Controllers; + +/// +/// Ствол скважины +/// +[Authorize] +[ApiController] +[Route("api/well/[controller]")] +public class WellboreController : ControllerBase +{ + private readonly IWellboreService wellboreService; + + public WellboreController(IWellboreService wellboreService) + { + this.wellboreService = wellboreService; + } + + /// + /// Получение ствола скважины + /// + /// Id скважины + /// Id типа секции скважины + /// + /// + + [HttpGet("{idWell:int}/{idSection:int}")] + [ProducesResponseType(typeof(WellboreDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task GetAsync(int idWell, int idSection, CancellationToken cancellationToken) + { + var wellbore = await wellboreService.GetWellboreAsync(idWell, idSection, cancellationToken); + + if (wellbore is null) + return NoContent(); + + return Ok(wellbore); + } + + /// + /// Получение списка стволов скважин + /// + /// Пары идентификаторов скважины и секции + /// Опциональный параметр. Количество пропускаемых записей + /// Опциональный параметр. Количество получаемых записей + /// + /// + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllAsync([FromQuery] IEnumerable ids, + int? skip, + int? take, + CancellationToken cancellationToken) + { + var request = new WellboreRequest + { + Ids = ids.Select(id => ParseId(id)), + Skip = skip, + Take = take + }; + + return Ok(await wellboreService.GetWellboresAsync(request, cancellationToken)); + } + + private static (int, int?) ParseId(string id) + { + var idPair = id.Split(','); + if (!int.TryParse(idPair[0], out var idWell)) + throw new ArgumentInvalidException($"Не удалось получить Id скважины \"{idPair[0]}\"", nameof(id)); + + if (idPair.Length > 1) + { + if (int.TryParse(idPair[1], out int idWellSectionType)) + return (idWell, idWellSectionType); + else + throw new ArgumentInvalidException($"Не удалось получить Id ствола \"{idPair[1]}\"", nameof(id)); + } + return (idWell, null); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Rest/wellbore.http b/AsbCloudWebApi/Rest/wellbore.http new file mode 100644 index 00000000..9a7918ac --- /dev/null +++ b/AsbCloudWebApi/Rest/wellbore.http @@ -0,0 +1,15 @@ +@baseUrl = http://127.0.0.1:5000 +@contentType = application/json +@auth = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjI1NDgxNjIsImV4cCI6MTY5NDEwNTc2MiwiaXNzIjoiYSIsImF1ZCI6ImEifQ.OEAlNzxi7Jat6pzDBTAjTbChskc-tdJthJexyWwwUKE + +@uid = 20210101_000000000 +@idCluster = 1 +@idWell = 1 + +# https://marketplace.visualstudio.com/items?itemName=humao.rest-client + +### +GET {{baseUrl}}/api/well/wellbore?ids=1,2 +Content-Type: {{contentType}} +accept: */* +Authorization: {{auth}}