diff --git a/AsbCloudApp/Services/IWellCompositeOperationService.cs b/AsbCloudApp/Services/IWellCompositeOperationService.cs
new file mode 100644
index 00000000..94ba1f93
--- /dev/null
+++ b/AsbCloudApp/Services/IWellCompositeOperationService.cs
@@ -0,0 +1,21 @@
+using AsbCloudApp.Data;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudApp.Services
+{
+ ///
+ /// Интерфейс для вычисления композитной скважины
+ ///
+ public interface IWellCompositeOperationService
+ {
+ ///
+ /// Получение данных для построения композитной скважины
+ ///
+ ///
+ ///
+ ///
+ Task>> GetAsync(IEnumerable idsWells, CancellationToken token);
+ }
+}
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index 2111356b..6250112f 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -198,6 +198,7 @@ namespace AsbCloudInfrastructure
services.AddTransient, CrudCacheRepositoryBase>();
services.AddTransient();
+ services.AddTransient();
// admin crud services:
services.AddTransient, CrudCacheRepositoryBase>(s =>
diff --git a/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs
new file mode 100644
index 00000000..562b82c6
--- /dev/null
+++ b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs
@@ -0,0 +1,165 @@
+using AsbCloudApp.Data;
+using AsbCloudApp.Services;
+using AsbCloudDb.Model;
+using Mapster;
+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 IAsbCloudDbContext db;
+
+ ///
+ /// Тип секции "Транспортный стол"
+ ///
+ private const int wellSectionTransportTable = 5;
+
+ ///
+ /// Тип секции "Эксплуатационная колонна"
+ ///
+ private const int wellSectionProductionString = 4;
+
+
+ ///
+ /// набор настроек для замены одной категории секции на другую
+ ///
+ 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 Dictionary> WellSectionTypesWithCategories = new Dictionary>()
+ {
+ { 2, new List{ 5001, 5003, 5013, 5000, 5022, 5017, 5023, 4007, 5090 } },
+ { 3, new List{ 5001, 5015, 5037, 5057, 5003, 5036, 5084, 5013, 5000, 5022, 5017, 4007, 5090, 5045, 5042, 5046 } },
+ { 31, new List{ 5001, 5015, 5037, 5057, 5003, 5036, 5013, 5022, 5017, 5023, 4007, 5045, 5042, 5046 } },
+ { 4, new List{ 5001, 5015, 5046, 5037, 5097, 5057, 5003, 5036, 5008, 5003, 5036, 5013, 5000, 5029, 5022, 5017, 5019, 5042, 5046 } },
+ { 6, new List{ 5001, 5015, 5034, 5037, 5097, 5057, 5003 } },
+ };
+
+
+
+ public WellCompositeOperationService(IAsbCloudDbContext db)
+ {
+ this.db = db;
+ }
+
+ public async Task>> GetAsync(IEnumerable idsWells, CancellationToken token)
+ {
+ var sections = await db.WellSectionTypes
+ .Select(t => t.Adapt())
+ .ToArrayAsync(token);
+ var sectionsDict = sections.ToDictionary(s => s.Id, s => s.Caption);
+
+ var categories = await db.WellOperationCategories
+ .Select(c => c.Adapt())
+ .ToArrayAsync(token);
+ var categoriesDict = categories.ToDictionary(s => s.Id, s => s.Name);
+
+ var idsWellSectionTypes = WellSectionTypesWithCategories.Select(t => t.Key);
+
+ var groupedWellOperations = db.WellOperations
+ .Where(o => idsWells.Contains(o.IdWell))
+ .Where(o => o.IdType == WellOperation.IdOperationTypeFact)
+ .Where(o => idsWellSectionTypes.Contains(o.IdWellSectionType))
+ .AsEnumerable()
+ .GroupBy(o => o.IdWellSectionType, (key, group) => group.Where(x => WellSectionTypesWithCategories[key].Contains(x.IdCategory)))
+ .SelectMany(o => o)
+ .Select(x => Convert(x, sectionsDict, categoriesDict))
+ .GroupBy(o => (o.IdWellSectionType, o.IdCategory, o.IdWell), (gr, o) => o.Count() == 1 ? o.FirstOrDefault()! : new WellOperationDataDto()
+ {
+ IdWellSectionType = o.FirstOrDefault()!.IdWellSectionType,
+ IdCategory = o.FirstOrDefault()!.IdCategory,
+ DepthStart = o.Max(x => x.DepthStart),
+ DurationHours = o.Sum(x => x.DurationHours),
+ IdWell = o.FirstOrDefault()!.IdWell,
+ WellSectionTypeCaption = o.FirstOrDefault()!.WellSectionTypeCaption,
+ OperationCategoryName = o.FirstOrDefault()!.OperationCategoryName
+ });
+
+ var wellOperationsWithComposite = new List>();
+ var operationsGroupBySectionAndCategory = groupedWellOperations.GroupBy(o => (o.IdWellSectionType, o.IdCategory), (key, group) => group.ToList());
+ foreach (var operationGroupBySectionAndCategory in operationsGroupBySectionAndCategory)
+ {
+ var dictElem = operationGroupBySectionAndCategory.ToDictionary(o => o.IdWell);
+
+ var currentOperation = operationGroupBySectionAndCategory.FirstOrDefault()!;
+ var composite = new WellOperationDataDto()
+ {
+ IdWell = 0,
+ IdCategory = currentOperation.IdCategory,
+ IdWellSectionType = currentOperation.IdWellSectionType,
+ DurationHours = currentOperation.DurationHours,
+ DepthStart = currentOperation.DepthStart,
+ OperationCategoryName = currentOperation.OperationCategoryName,
+ WellSectionTypeCaption = currentOperation.WellSectionTypeCaption,
+ };
+ dictElem.Add(0, composite);
+
+ if (operationGroupBySectionAndCategory.Count() == 1)
+ {
+ wellOperationsWithComposite.Add(dictElem);
+ continue;
+ }
+
+ var maxDurationHours = operationGroupBySectionAndCategory.Max(o => o.DurationHours);
+ var minDurationHours = operationGroupBySectionAndCategory.Min(o => o.DurationHours);
+ if (maxDurationHours == minDurationHours)
+ {
+ composite.DepthStart = operationGroupBySectionAndCategory.Max(o => o.DepthStart);
+ }
+ else
+ {
+ composite.DepthStart = operationGroupBySectionAndCategory.Min(o => o.DepthStart);
+ }
+ composite.DurationHours = minDurationHours;
+ dictElem[0] = composite;
+ wellOperationsWithComposite.Add(dictElem);
+ }
+ return wellOperationsWithComposite;
+ }
+
+ private static WellOperationDataDto Convert(WellOperation entity, Dictionary sectionsDict, Dictionary categoriesDict)
+ {
+ var dto = new WellOperationDataDto();
+ dto.IdWellSectionType = entity.IdWellSectionType == wellSectionTransportTable
+ ? wellSectionProductionString
+ : entity.IdWellSectionType;
+ dto.IdCategory = (SettingsForSectionCategoryChange.TryGetValue(((entity.IdWellSectionType == wellSectionTransportTable ? wellSectionProductionString : entity.IdWellSectionType), entity.IdCategory), out int newIdCategory))
+ ? newIdCategory
+ : entity.IdCategory;
+ dto.DepthStart = entity.DepthStart;
+ dto.DurationHours = entity.DurationHours;
+ dto.IdWell = entity.IdWell;
+ dto.OperationCategoryName = categoriesDict.TryGetValue(dto.IdCategory, out string? CategoryName)
+ ? CategoryName
+ : string.Empty;
+ dto.WellSectionTypeCaption = sectionsDict.TryGetValue(dto.IdWellSectionType, out string? WellSectionName)
+ ? WellSectionName
+ : string.Empty;
+
+ return dto;
+ }
+ }
+}
diff --git a/AsbCloudWebApi/Controllers/WellCompositeOperationController.cs b/AsbCloudWebApi/Controllers/WellCompositeOperationController.cs
new file mode 100644
index 00000000..e2e4ee4b
--- /dev/null
+++ b/AsbCloudWebApi/Controllers/WellCompositeOperationController.cs
@@ -0,0 +1,35 @@
+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;
+
+ public WellCompositeOperationController(IWellCompositeOperationService wellCompositeOperationService, IWellService wellService)
+ {
+ this.wellCompositeOperationService = wellCompositeOperationService;
+ }
+
+ [HttpGet]
+ [ProducesResponseType(typeof(List>), (int)System.Net.HttpStatusCode.OK)]
+ public async Task GetAsync([FromQuery] IEnumerable idsWells, CancellationToken token)
+ {
+ var result = await wellCompositeOperationService.GetAsync(idsWells, token)
+ .ConfigureAwait(false);
+
+ return Ok(result);
+ }
+ }
+
+}