diff --git a/AsbCloudApp/Data/WellOperation/WellOperationDto.cs b/AsbCloudApp/Data/WellOperation/WellOperationDto.cs index b940a9ae..13f070a3 100644 --- a/AsbCloudApp/Data/WellOperation/WellOperationDto.cs +++ b/AsbCloudApp/Data/WellOperation/WellOperationDto.cs @@ -1,119 +1,22 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace AsbCloudApp.Data.WellOperation; /// -/// Операция по скважине +/// Операция по скважине c Day и NPV /// -public class WellOperationDto : ItemInfoDto, - IId, - IWellRelated, - IValidatableObject +public class WellOperationDto : WellOperationBaseDto { - /// - [Required] - public int Id { get; set; } + /// + /// Кол-во дней от даты начала первой плановой (а если её нет, то фактической) операции + /// + [Required] + public double Day { get; set; } - /// - [Required] - public int IdWell { get; set; } + /// + /// Кол-во часов НПВ от даты начала первой плановой (а если её нет, то фактической) операции + /// + [Required] + public double NptHours { get; set; } - /// - /// Id секции скважины - /// - public int IdWellSectionType { get; set; } - - /// - /// 0 = план или 1 = факт или прогноз = 2 - /// - [Required] - public int IdType { get; set; } - - /// - /// id категории операции - /// - public int IdCategory { get; set; } - - /// - /// Глубина на начало операции, м - /// - public double DepthStart { get; set; } - - /// - /// Глубина после завершения операции, м - /// - [Required] - [Range(0, 50_000)] - public double DepthEnd { get; set; } - - /// - /// Дата начала операции - /// - [Required] - public DateTimeOffset DateStart { get; set; } - - /// - /// Продолжительность, часы - /// - public double DurationHours { get; set; } - - /// - /// Наименование секции - /// - public string? WellSectionTypeCaption { get; set; } - - /// - /// Наименование категории - /// - public string? OperationCategoryName { get; set; } - - /// - /// id плановой операции для сопоставления - /// - public int? IdPlan { get; set; } - - /// - /// Ключ родителя у категории - /// - public int? IdParentCategory { get; set; } - - /// - /// дополнительная информация по операции - /// - [StringLength(8192)] - public string? CategoryInfo { get; set; } - - /// - /// Кол-во дней от даты начала первой плановой (а если её нет, то фактической) операции - /// - [Required] - public double Day { get; set; } - - /// - /// Кол-во часов НПВ от даты начала первой плановой (а если её нет, то фактической) операции - /// - [Required] - public double NptHours { get; set; } - - /// - /// Полезный комментарий - /// - [StringLength(4096, ErrorMessage = "Комментарий не может быть длиннее 4096 символов")] - public string? Comment { get; set; } - - /// - /// Валидация даты - /// - /// - /// - public IEnumerable Validate(ValidationContext validationContext) - { - var gtDate = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero); - if (DateStart <= gtDate) - yield return new ValidationResult( - $"{nameof(DateStart)}: DateStart не может быть меньше {gtDate}", - new[] { nameof(DateStart) }); - } } \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index 612d58d9..c6b3c724 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -18,46 +18,6 @@ namespace AsbCloudApp.Repositories /// IEnumerable GetSectionTypes(); - /// - /// Получить страницу списка операций - /// - /// - /// - /// - Task> GetAsync(WellOperationRequest request, CancellationToken token); - - /// - /// Получить страницу списка операций - /// - /// - /// - /// - Task> GetPageAsync(WellOperationRequest request, CancellationToken token); - - /// - /// Получить страницу с операцией - /// - /// - /// - /// - /// - /// - /// - /// - Task?> GetPageAsync(int idWell, - int id, - int operationType, - int? take, - IEnumerable? sortFields, - CancellationToken token); - - /// - /// Получить статистику операции по скважине с группировкой по категориям - /// - /// - /// - /// - Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token); /// /// Добавить несколько операций @@ -66,7 +26,7 @@ namespace AsbCloudApp.Repositories /// /// /// - Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token); + Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token); /// /// Обновить существующую операцию @@ -74,7 +34,7 @@ namespace AsbCloudApp.Repositories /// /// /// - Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token); + Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token); /// /// Удалить операции по id @@ -106,6 +66,14 @@ namespace AsbCloudApp.Repositories /// /// /// - (WellOperationDto First, WellOperationDto Last)? GetFirstAndLastFact(int idWell); + (WellOperationBaseDto First, WellOperationBaseDto Last)? GetFirstAndLastFact(int idWell); + + /// + /// Получить список операций по запросу + /// + /// + /// + /// + Task> GetAll(WellOperationRequest request, CancellationToken token); } } \ No newline at end of file diff --git a/AsbCloudApp/Services/IWellOperationService.cs b/AsbCloudApp/Services/IWellOperationService.cs new file mode 100644 index 00000000..fc755237 --- /dev/null +++ b/AsbCloudApp/Services/IWellOperationService.cs @@ -0,0 +1,58 @@ +using AsbCloudApp.Data; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Requests; + +namespace AsbCloudApp.Services +{ + /// + /// Сервис по представлению данных по операциям + /// + public interface IWellOperationService + { + /// + /// Получить страницу списка операций + /// + /// + /// + /// + Task> GetAsync(WellOperationRequest request, CancellationToken token); + + /// + /// Получить страницу списка операций + /// + /// + /// + /// + Task> GetPageAsync(WellOperationRequest request, CancellationToken token); + + /// + /// Получить страницу с операцией + /// + /// + /// + /// + /// + /// + /// + /// + Task?> GetPageAsync(int idWell, + int id, + int operationType, + int? take, + IEnumerable? sortFields, + CancellationToken token); + + /// + /// Получить статистику операции по скважине с группировкой по категориям + /// + /// + /// + /// + Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token); + } + + +} diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 5ce1ada0..b3943a6c 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -40,6 +40,7 @@ using AsbCloudInfrastructure.Services.Trajectory.Export; using AsbCloudInfrastructure.Services.Trajectory.Parser; using AsbCloudInfrastructure.Services.WellOperations.Factories; using AsbCloudInfrastructure.Services.WellOperationService; +using AsbCloudInfrastructure.Services.WellOperationService.WellOperationService; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -317,6 +318,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient< IChangeLogRepository, diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 843956cb..1e0d1add 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -17,7 +17,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; -public class WellOperationRepository : CrudRepositoryBase, +public class WellOperationRepository : CrudRepositoryBase, IWellOperationRepository { private const string cacheKeyWellOperations = "FirstAndLastFactWellsOperations"; @@ -40,139 +40,14 @@ public class WellOperationRepository : CrudRepositoryBase wellOperationCategoryRepository.Get(true, false).ToDictionary(c => c.Id)); LazyWellSectionTypes = new(() => GetSectionTypes().ToDictionary(c => c.Id)); } - + public IEnumerable GetSectionTypes() => memoryCache .GetOrCreateBasic(dbContext.WellSectionTypes) .OrderBy(s => s.Order) .Select(s => s.Adapt()); - public async Task> GetAsync(WellOperationRequest request, CancellationToken token) - { - var (items, _) = await GetWithDaysAndNpvAsync(request, token); - return items; - } - - public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) - { - request.Skip = request.Skip ?? 0; - request.Take = request.Take ?? 32; - - var (items, count) = await GetWithDaysAndNpvAsync(request, token); - - var paginationContainer = new PaginationContainer - { - Skip = request.Skip!.Value, - Take = request.Take!.Value, - Count = count, - Items = items - }; - - return paginationContainer; - } - - public async Task?> GetPageAsync(int idWell, - int id, - int operationType, - int? take, - IEnumerable? sortFields, - CancellationToken token) - { - var request = new WellOperationRequest(new[] { idWell }) - { - OperationType = operationType, - SortFields = sortFields, - }; - - var (wellOperations, count) = await GetWithDaysAndNpvAsync(request, token); - - var skip = 0; - take ??= 32; - - while (skip < count) - { - var page = wellOperations.Skip(skip) - .Take(take.Value); - - if (page.Any(x => x.Id == id)) - { - var paginationContainer = new PaginationContainer - { - Skip = skip, - Take = take.Value, - Items = page, - Count = count - }; - - return paginationContainer; - } - - skip += take.Value; - } - - return null; - } - - public async Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token) - { - var query = BuildQuery(request); - var entities = await query - .Select(o => new - { - o.IdCategory, - DurationMinutes = o.DurationHours * 60, - DurationDepth = o.DepthEnd - o.DepthStart - }) - .ToArrayAsync(token); - - var parentRelationDictionary = wellOperationCategoryRepository.Get(true) - .ToDictionary(c => c.Id, c => new - { - c.Name, - c.IdParent - }); - - var dtos = entities - .GroupBy(o => o.IdCategory) - .Select(g => new WellGroupOpertionDto - { - IdCategory = g.Key, - Category = parentRelationDictionary[g.Key].Name, - Count = g.Count(), - MinutesAverage = g.Average(o => o.DurationMinutes), - MinutesMin = g.Min(o => o.DurationMinutes), - MinutesMax = g.Max(o => o.DurationMinutes), - TotalMinutes = g.Sum(o => o.DurationMinutes), - DeltaDepth = g.Sum(o => o.DurationDepth), - IdParent = parentRelationDictionary[g.Key].IdParent - }); - - while (dtos.All(x => x.IdParent != null)) - { - dtos = dtos - .GroupBy(o => o.IdParent!) - .Select(g => - { - var idCategory = g.Key ?? int.MinValue; - var category = parentRelationDictionary.GetValueOrDefault(idCategory); - var newDto = new WellGroupOpertionDto - { - IdCategory = idCategory, - Category = category?.Name ?? "unknown", - Count = g.Sum(o => o.Count), - DeltaDepth = g.Sum(o => o.DeltaDepth), - TotalMinutes = g.Sum(o => o.TotalMinutes), - Items = g.ToList(), - IdParent = category?.IdParent, - }; - return newDto; - }); - } - - return dtos; - } - - public async Task InsertRangeAsync(IEnumerable dtos, + public async Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token) { @@ -189,7 +64,7 @@ public class WellOperationRepository : CrudRepositoryBase UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + public override async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) { EnsureValidWellOperations(dtos); @@ -223,7 +98,7 @@ public class WellOperationRepository : CrudRepositoryBase dtos) + private static void EnsureValidWellOperations(IEnumerable dtos) { if (dtos.GroupBy(d => d.IdType).Count() > 1) throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); @@ -232,101 +107,6 @@ public class WellOperationRepository : CrudRepositoryBase> GetByIdsWells(IEnumerable idsWells, CancellationToken token) - { - var query = GetQuery() - .Where(e => idsWells.Contains(e.IdWell)) - .OrderBy(e => e.DateStart); - var entities = await query.ToArrayAsync(token); - return entities; - } - - private async Task<(IEnumerable items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token) - { - var entities = await GetByIdsWells(request.IdsWell, token); - var groupedByWellAndType = entities - .GroupBy(e => new { e.IdWell, e.IdType }); - - var result = new List(); - var count = 0; - foreach (var wellOperationsWithType in groupedByWellAndType) - { - var firstWellOperation = wellOperationsWithType.MinBy(e => e.DateStart); - - var operationsWithNpt = wellOperationsWithType - .Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory)); - - IEnumerable filteredWellOperations = FilterByRequest(wellOperationsWithType.AsQueryable(), request); - - count += filteredWellOperations.Count(); - - if (request.Skip != null) - filteredWellOperations = filteredWellOperations.Skip((int)request.Skip); - if (request.Take != null) - filteredWellOperations = filteredWellOperations.Take((int)request.Take); - - var timezoneOffset = wellService.GetTimezone(wellOperationsWithType.Key.IdWell).Offset; - - var dtos = filteredWellOperations - .Select(entity => - { - var dto = Convert(entity, timezoneOffset); - dto.Day = (entity.DateStart - firstWellOperation.DateStart).TotalDays; - dto.NptHours = operationsWithNpt - .Where(o => o.DateStart <= entity.DateStart) - .Sum(e => e.DurationHours); - return dto; - }); - - result.AddRange(dtos); - } - - return (result, count); - } - - private static IQueryable FilterByRequest(IQueryable entities, WellOperationRequest request) - { - if (request.OperationType.HasValue) - entities = entities.Where(e => e.IdType == request.OperationType.Value); - if (request.SectionTypeIds?.Any() is true) - entities = entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); - if (request.OperationCategoryIds?.Any() is true) - entities = entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); - if (request.GeDepth.HasValue) - entities = entities.Where(e => e.DepthEnd >= request.GeDepth.Value); - if (request.LeDepth.HasValue) - entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); - - if (request.GeDate.HasValue) - { - var geDateUtc = request.GeDate.Value.UtcDateTime; - entities = entities.Where(e => e.DateStart >= geDateUtc); - } - - if (request.LeDate.HasValue) - { - var leDateUtc = request.LeDate.Value.UtcDateTime; - entities = entities.Where(e => e.DateStart <= leDateUtc); - } - if (request.SortFields?.Any() is true) - entities = entities.AsQueryable().SortBy(request.SortFields); - else - entities = entities.AsQueryable().OrderBy(e => e.DateStart); - - return entities; - } - - private IQueryable BuildQuery(WellOperationRequest request) - { - var query = GetQuery() - .Where(e => request.IdsWell.Contains(e.IdWell)) - .OrderBy(e => e.DateStart) - .AsQueryable(); - query = FilterByRequest(query, request); - - return query; - } - public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) { const string keyCacheSections = "OperationsBySectionSummarties"; @@ -415,7 +195,7 @@ public class WellOperationRepository : CrudRepositoryBase { @@ -462,16 +242,16 @@ public class WellOperationRepository : CrudRepositoryBase(); entity.DateStart = src.DateStart.UtcDateTime; return entity; } - private WellOperationDto Convert(WellOperation src, TimeSpan timezoneOffset) + private WellOperationBaseDto Convert(WellOperation src, TimeSpan timezoneOffset) { - var dto = src.Adapt(); + var dto = src.Adapt(); dto.DateStart = src.DateStart.ToOffset(timezoneOffset); dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timezoneOffset); @@ -479,4 +259,58 @@ public class WellOperationRepository : CrudRepositoryBase> GetAll(WellOperationRequest request, CancellationToken token) + { + var timezoneOffsetDictionary = new Dictionary(); + foreach (var idWell in request.IdsWell) + { + var offset = wellService.GetTimezone(idWell).Offset; + timezoneOffsetDictionary.Add(idWell, offset); + } + + var query = GetQuery() + .Where(e => request.IdsWell.Contains(e.IdWell)) + .OrderBy(e => e.DateStart) + .AsQueryable(); + query = FilterByRequest(query, request); + + var entities = await query.ToArrayAsync(token); + + var dtos = entities.Select(o => Convert(o, timezoneOffsetDictionary[o.IdWell])); + return dtos; + } + + + public static IQueryable FilterByRequest(IQueryable entities, WellOperationRequest request) + { + if (request.OperationType.HasValue) + entities = entities.Where(e => e.IdType == request.OperationType.Value); + if (request.SectionTypeIds?.Any() is true) + entities = entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + if (request.OperationCategoryIds?.Any() is true) + entities = entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + if (request.GeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd >= request.GeDepth.Value); + if (request.LeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); + + if (request.GeDate.HasValue) + { + var geDateUtc = request.GeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart >= geDateUtc); + } + + if (request.LeDate.HasValue) + { + var leDateUtc = request.LeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart <= leDateUtc); + } + if (request.SortFields?.Any() is true) + entities = entities.AsQueryable().SortBy(request.SortFields); + else + entities = entities.AsQueryable().OrderBy(e => e.DateStart); + + return entities; + } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs index 08ac2420..4e94a571 100644 --- a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs +++ b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs @@ -32,27 +32,30 @@ public class DailyReportService : IDailyReportService private readonly ISubsystemService subsystemService; private readonly IProcessMapReportDrillingService processMapReportDrillingService; private readonly IDetectedOperationService detectedOperationService; + private readonly IWellOperationService wellOperationService; - public DailyReportService(IWellService wellService, + public DailyReportService(IWellService wellService, ITrajectoryNnbRepository trajectoryFactNnbRepository, - IDailyReportRepository dailyReportRepository, - IScheduleRepository scheduleRepository, - IWellOperationRepository wellOperationRepository, - ISubsystemService subsystemService, + IDailyReportRepository dailyReportRepository, + IScheduleRepository scheduleRepository, + IWellOperationRepository wellOperationRepository, + ISubsystemService subsystemService, IProcessMapReportDrillingService processMapReportDrillingService, - IDetectedOperationService detectedOperationService) - { - this.wellService = wellService; - this.trajectoryFactNnbRepository = trajectoryFactNnbRepository; - this.dailyReportRepository = dailyReportRepository; - this.scheduleRepository = scheduleRepository; - this.wellOperationRepository = wellOperationRepository; - this.subsystemService = subsystemService; - this.processMapReportDrillingService = processMapReportDrillingService; - this.detectedOperationService = detectedOperationService; - } + IDetectedOperationService detectedOperationService, + IWellOperationService wellOperationService) + { + this.wellService = wellService; + this.trajectoryFactNnbRepository = trajectoryFactNnbRepository; + this.dailyReportRepository = dailyReportRepository; + this.scheduleRepository = scheduleRepository; + this.wellOperationRepository = wellOperationRepository; + this.subsystemService = subsystemService; + this.processMapReportDrillingService = processMapReportDrillingService; + this.detectedOperationService = detectedOperationService; + this.wellOperationService = wellOperationService; + } - public async Task UpdateOrInsertAsync(int idWell, DateOnly dateDailyReport, int idUser, TBlock editableBlock, + public async Task UpdateOrInsertAsync(int idWell, DateOnly dateDailyReport, int idUser, TBlock editableBlock, CancellationToken cancellationToken) where TBlock : ItemInfoDto { @@ -118,7 +121,7 @@ public class DailyReportService : IDailyReportService LeDate = leDate }; - var factWellOperations = (await wellOperationRepository.GetAsync(factOperationRequest, cancellationToken)) + var factWellOperations = (await wellOperationService.GetAsync(factOperationRequest, cancellationToken)) .OrderBy(o => o.DateStart) .ThenBy(o => o.DepthStart); @@ -191,7 +194,7 @@ public class DailyReportService : IDailyReportService LeDate = leDateFactWellOperation }; - var factWellOperations = await wellOperationRepository.GetAsync(factWellOperationRequest, cancellationToken); + var factWellOperations = await wellOperationService.GetAsync(factWellOperationRequest, cancellationToken); if (request.SortFields?.Contains("DateStart desc") == true) { diff --git a/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDrillingService.cs b/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDrillingService.cs index e0d1287d..cae5bdce 100644 --- a/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDrillingService.cs +++ b/AsbCloudInfrastructure/Services/ProcessMaps/Report/ProcessMapReportDrillingService.cs @@ -26,13 +26,15 @@ public class ProcessMapReportDrillingService : IProcessMapReportDrillingService private readonly IDataSaubStatRepository dataSaubStatRepository; private readonly IWellOperationRepository wellOperationRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellOperationService wellOperationService; public ProcessMapReportDrillingService(IWellService wellService, IChangeLogRepository processMapPlanRotorRepository, IChangeLogRepository processMapPlanSlideRepository, IDataSaubStatRepository dataSaubStatRepository, IWellOperationRepository wellOperationRepository, - IWellOperationCategoryRepository wellOperationCategoryRepository + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellOperationService wellOperationService ) { this.wellService = wellService; @@ -41,6 +43,7 @@ public class ProcessMapReportDrillingService : IProcessMapReportDrillingService this.dataSaubStatRepository = dataSaubStatRepository; this.wellOperationRepository = wellOperationRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellOperationService = wellOperationService; } public async Task> GetAsync(int idWell, DataSaubStatRequest request, CancellationToken token) @@ -72,7 +75,7 @@ public class ProcessMapReportDrillingService : IProcessMapReportDrillingService GeDepth = geDepth, LeDepth = leDepth }; - var wellOperations = await wellOperationRepository + var wellOperations = await wellOperationService .GetAsync(requestWellOperationFact, token); var orderedWellOperations = wellOperations diff --git a/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs index 34665720..209161f7 100644 --- a/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs +++ b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs @@ -17,8 +17,8 @@ public class WellCompositeOperationService : IWellCompositeOperationService { private readonly ICrudRepository wellSectionTypeRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - private readonly IWellOperationRepository wellOperationRepository; private readonly IWellService wellService; + private readonly IWellOperationService wellOperationService; /// /// Тип секции "Транспортный стол" @@ -206,12 +206,10 @@ public class WellCompositeOperationService : IWellCompositeOperationService public WellCompositeOperationService( ICrudRepository wellSectionTypeRepository, IWellOperationCategoryRepository wellOperationCategoryRepository, - IWellOperationRepository wellOperationRepository, IWellService wellService) { this.wellSectionTypeRepository = wellSectionTypeRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository; - this.wellOperationRepository = wellOperationRepository; this.wellService = wellService; } @@ -235,7 +233,7 @@ public class WellCompositeOperationService : IWellCompositeOperationService SectionTypeIds = idsWellSectionTypes, OperationType = WellOperation.IdOperationTypeFact }; - var operations = await wellOperationRepository.GetAsync(wellOperationRequest, token); + var operations = await wellOperationService.GetAsync(wellOperationRequest, token); var operationsForComposite = operations.Select(o => CreateCompositeOperation(o, sectionsDict, categoriesDict)); diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs new file mode 100644 index 00000000..c1ff4eb3 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs @@ -0,0 +1,232 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Services; +using AsbCloudDb; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.WellOperationService.WellOperationService; +public class WellOperationService : IWellOperationService +{ + private readonly IWellService wellService; + private readonly IWellOperationRepository wellOperationRepository; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + + public WellOperationService( + IWellService wellService, + IWellOperationRepository wellOperationRepository, + IWellOperationCategoryRepository wellOperationCategoryRepository) + { + this.wellService = wellService; + this.wellOperationRepository = wellOperationRepository; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + } + + public async Task> GetAsync(WellOperationRequest request, CancellationToken token) + { + var (items, _) = await GetWithDaysAndNpvAsync(request, token); + return items; + } + + public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) + { + request.Skip = request.Skip ?? 0; + request.Take = request.Take ?? 32; + + var (items, count) = await GetWithDaysAndNpvAsync(request, token); + + var paginationContainer = new PaginationContainer + { + Skip = request.Skip!.Value, + Take = request.Take!.Value, + Count = count, + Items = items + }; + + return paginationContainer; + } + + public async Task?> GetPageAsync(int idWell, + int id, + int operationType, + int? take, + IEnumerable? sortFields, + CancellationToken token) + { + var request = new WellOperationRequest(new[] { idWell }) + { + OperationType = operationType, + SortFields = sortFields, + }; + + var (wellOperations, count) = await GetWithDaysAndNpvAsync(request, token); + + var skip = 0; + take ??= 32; + + while (skip < count) + { + var page = wellOperations.Skip(skip) + .Take(take.Value); + + if (page.Any(x => x.Id == id)) + { + var paginationContainer = new PaginationContainer + { + Skip = skip, + Take = take.Value, + Items = page, + Count = count + }; + + return paginationContainer; + } + + skip += take.Value; + } + + return null; + } + + public async Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token) + { + var wellOperationsBaseDto = await wellOperationRepository.GetAll(request, token); + var wellOperationsData = wellOperationsBaseDto + .Select(o => new + { + o.IdCategory, + DurationMinutes = o.DurationHours * 60, + DurationDepth = o.DepthEnd - o.DepthStart + }); + + var parentRelationDictionary = wellOperationCategoryRepository.Get(true) + .ToDictionary(c => c.Id, c => new + { + c.Name, + c.IdParent + }); + + var dtos = wellOperationsData + .GroupBy(o => o.IdCategory) + .Select(g => new WellGroupOpertionDto + { + IdCategory = g.Key, + Category = parentRelationDictionary[g.Key].Name, + Count = g.Count(), + MinutesAverage = g.Average(o => o.DurationMinutes), + MinutesMin = g.Min(o => o.DurationMinutes), + MinutesMax = g.Max(o => o.DurationMinutes), + TotalMinutes = g.Sum(o => o.DurationMinutes), + DeltaDepth = g.Sum(o => o.DurationDepth), + IdParent = parentRelationDictionary[g.Key].IdParent + }); + + while (dtos.All(x => x.IdParent != null)) + { + dtos = dtos + .GroupBy(o => o.IdParent!) + .Select(g => + { + var idCategory = g.Key ?? int.MinValue; + var category = parentRelationDictionary.GetValueOrDefault(idCategory); + var newDto = new WellGroupOpertionDto + { + IdCategory = idCategory, + Category = category?.Name ?? "unknown", + Count = g.Sum(o => o.Count), + DeltaDepth = g.Sum(o => o.DeltaDepth), + TotalMinutes = g.Sum(o => o.TotalMinutes), + Items = g.ToList(), + IdParent = category?.IdParent, + }; + return newDto; + }); + } + + return dtos; + } + + private async Task<(IEnumerable items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token) + { + var requestByWellIds = new WellOperationRequest(request.IdsWell); + var wellOperationsBaseDtos = await wellOperationRepository.GetAll(requestByWellIds, token); + var groupedByWellAndTypeDtos = wellOperationsBaseDtos + .GroupBy(e => new { e.IdWell, e.IdType }); + + var result = new List(); + var count = 0; + foreach (var wellOperationsWithTypeDto in groupedByWellAndTypeDtos) + { + var firstWellOperation = wellOperationsWithTypeDto.MinBy(e => e.DateStart)!; + + var operationsWithNpt = wellOperationsWithTypeDto + .Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory)); + + var filteredWellOperations = FilterByRequest(wellOperationsWithTypeDto, request); + + count += filteredWellOperations.Count(); + + if (request.Skip != null) + filteredWellOperations = filteredWellOperations.Skip((int)request.Skip); + if (request.Take != null) + filteredWellOperations = filteredWellOperations.Take((int)request.Take); + + var timezoneOffset = wellService.GetTimezone(wellOperationsWithTypeDto.Key.IdWell).Offset; + + var dtos = filteredWellOperations + .Select(dto => + { + var newDto = dto.Adapt(); + newDto.Day = (dto.DateStart - firstWellOperation.DateStart).TotalDays; + newDto.NptHours = operationsWithNpt + .Where(o => o.DateStart <= dto.DateStart) + .Sum(e => e.DurationHours); + return newDto; + }); + + result.AddRange(dtos); + } + + return (result, count); + } + + public static IEnumerable FilterByRequest(IEnumerable dtos, WellOperationRequest request) + { + if (request.OperationType.HasValue) + dtos = dtos.Where(e => e.IdType == request.OperationType.Value); + if (request.SectionTypeIds?.Any() is true) + dtos = dtos.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + if (request.OperationCategoryIds?.Any() is true) + dtos = dtos.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + if (request.GeDepth.HasValue) + dtos = dtos.Where(e => e.DepthEnd >= request.GeDepth.Value); + if (request.LeDepth.HasValue) + dtos = dtos.Where(e => e.DepthEnd <= request.LeDepth.Value); + + if (request.GeDate.HasValue) + { + var geDateUtc = request.GeDate.Value.UtcDateTime; + dtos = dtos.Where(e => e.DateStart >= geDateUtc); + } + + if (request.LeDate.HasValue) + { + var leDateUtc = request.LeDate.Value.UtcDateTime; + dtos = dtos.Where(e => e.DateStart <= leDateUtc); + } + if (request.SortFields?.Any() is true) + dtos = dtos.AsQueryable().SortBy(request.SortFields); + else + dtos = dtos.AsQueryable().OrderBy(e => e.DateStart); + + return dtos; + } +} diff --git a/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs index 40768e86..6872aa96 100644 --- a/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs +++ b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs @@ -16,18 +16,18 @@ public class WellOperationExportServiceFactory : IExportServiceFactory public WellOperationExportServiceFactory(IServiceProvider serviceProvider) { - var wellOperationRepository = serviceProvider.GetRequiredService(); + var wellOperationService = serviceProvider.GetRequiredService(); var wellService = serviceProvider.GetRequiredService(); exportServices = new Dictionary> { { WellOperation.IdOperationTypeFact, - () => new WellOperationExport(wellOperationRepository, wellService) + () => new WellOperationExport(wellOperationService, wellService) }, { WellOperation.IdOperationTypePlan, - () => new WellOperationExport(wellOperationRepository, wellService) + () => new WellOperationExport(wellOperationService, wellService) } }; } diff --git a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs index cdb58cc6..f17ed63e 100644 --- a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs +++ b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs @@ -1,50 +1,50 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using AsbCloudApp.Data.WellOperation; -using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Requests.ExportOptions; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.ExcelServices; using AsbCloudInfrastructure.Services.ExcelServices.Templates; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.WellOperations; public class WellOperationExport : ExcelExportService where TTemplate : class, ITemplateParameters, new() { - private readonly IWellService wellService; - private readonly IWellOperationRepository wellOperationRepository; + private readonly IWellService wellService; + private readonly IWellOperationService wellOperationService; - public WellOperationExport(IWellOperationRepository wellOperationRepository, - IWellService wellService) - { - this.wellOperationRepository = wellOperationRepository; - this.wellService = wellService; - } + public WellOperationExport( + IWellOperationService wellOperationService, + IWellService wellService) + { + this.wellService = wellService; + this.wellOperationService = wellOperationService; + } - protected override async Task BuildFileNameAsync(WellOperationExportRequest options, CancellationToken token) - { - var caption = await wellService.GetWellCaptionByIdAsync(options.IdWell, token); + protected override async Task BuildFileNameAsync(WellOperationExportRequest options, CancellationToken token) + { + var caption = await wellService.GetWellCaptionByIdAsync(options.IdWell, token); - return options.IdType switch - { - WellOperation.IdOperationTypeFact => $"{caption}_Фактические_операции.xlsx", - WellOperation.IdOperationTypePlan => $"{caption}_Плановые_операции.xlsx", - _ => throw new ArgumentOutOfRangeException(nameof(options.IdType)) - }; - } + return options.IdType switch + { + WellOperation.IdOperationTypeFact => $"{caption}_Фактические_операции.xlsx", + WellOperation.IdOperationTypePlan => $"{caption}_Плановые_операции.xlsx", + _ => throw new ArgumentOutOfRangeException(nameof(options.IdType)) + }; + } - protected override Task> GetDtosAsync(WellOperationExportRequest options, CancellationToken token) - { - var request = new WellOperationRequest(new[] { options.IdWell }) - { - OperationType = options.IdType, - }; - - return wellOperationRepository.GetAsync(request, token); - } + protected override Task> GetDtosAsync(WellOperationExportRequest options, CancellationToken token) + { + var request = new WellOperationRequest(new[] { options.IdWell }) + { + OperationType = options.IdType, + }; + + return wellOperationService.GetAsync(request, token); + } } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs index 90c00957..0bbcb431 100644 --- a/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs +++ b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs @@ -207,10 +207,11 @@ public class DailyReportServiceTest private readonly ITrajectoryNnbRepository trajectoryFactNnbRepositoryMock = Substitute.For(); private readonly IDailyReportRepository dailyReportRepositoryMock = Substitute.For(); private readonly IScheduleRepository scheduleRepositoryMock = Substitute.For(); - private readonly IWellOperationRepository wellOperationRepositoryMock = Substitute.For(); private readonly ISubsystemService subsystemServiceMock = Substitute.For(); private readonly IProcessMapReportDrillingService processMapReportWellDrillingServiceMock = Substitute.For(); private readonly IDetectedOperationService detectedOperationServiceMock = Substitute.For(); + private readonly IWellOperationService wellOperationServiceMock = Substitute.For(); + private readonly IWellOperationRepository wellOperationRepositoryMock = Substitute.For(); private readonly DailyReportService dailyReportService; @@ -252,7 +253,8 @@ public class DailyReportServiceTest wellOperationRepositoryMock, subsystemServiceMock, processMapReportWellDrillingServiceMock, - detectedOperationServiceMock); + detectedOperationServiceMock, + wellOperationServiceMock); dailyReportRepositoryMock.InsertAsync(Arg.Any(), Arg.Any()) .ReturnsForAnyArgs(idDailyReport); @@ -269,7 +271,7 @@ public class DailyReportServiceTest trajectoryFactNnbRepositoryMock.GetByRequestAsync(Arg.Any(), Arg.Any()) .ReturnsForAnyArgs(new[] { fakeLastFactTrajectory }); - wellOperationRepositoryMock.GetAsync(Arg.Any(), Arg.Any()) + wellOperationServiceMock.GetAsync(Arg.Any(), Arg.Any()) .ReturnsForAnyArgs(new[] { fakeFirstFactWellOperation, fakeLastFactWellOperation }); wellOperationRepositoryMock.GetDatesRangeAsync(Arg.Any(), Arg.Any(), Arg.Any()) diff --git a/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs index b5ac6d1f..b8bf0791 100644 --- a/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs +++ b/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs @@ -22,8 +22,8 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation = Substitute.For>(); private IWellOperationCategoryRepository wellOperationCategoryRepository = Substitute.For(); - private IWellOperationRepository wellOperationRepository - = Substitute.For(); + private IWellOperationService wellOperationService + = Substitute.For(); private IWellService wellService = Substitute.For(); private List idsWells; @@ -175,7 +175,6 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation service = new WellCompositeOperationService( wellSectionTypeRepository, wellOperationCategoryRepository, - wellOperationRepository, wellService); } @@ -189,7 +188,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation public async Task GetAsync_return_composite_and_others_with_category_5013() { // arrange - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) + wellOperationService.GetAsync(Arg.Any(), Arg.Any()) .Returns(wellOperations1); // act @@ -216,7 +215,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation public async Task GetAsync_return_composite_with_minimum_depth_start() { // arrange - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) + wellOperationService.GetAsync(Arg.Any(), Arg.Any()) .Returns(wellOperations2); // act @@ -240,7 +239,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation public async Task GetAsync_return_data3() { // arrange - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) + wellOperationService.GetAsync(Arg.Any(), Arg.Any()) .Returns(wellOperations3); // act @@ -266,7 +265,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation public async Task GetAsync_return_data4() { // arrange - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) + wellOperationService.GetAsync(Arg.Any(), Arg.Any()) .Returns(wellOperations4); // act @@ -296,7 +295,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation wellOperations.AddRange(wellOperations3); wellOperations.AddRange(wellOperations4); - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) + wellOperationService.GetAsync(Arg.Any(), Arg.Any()) .Returns(wellOperations); // act diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 90b7404f..6bb4fe28 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -38,6 +38,7 @@ public class WellOperationController : ControllerBase private readonly IWellOperationRepository wellOperationRepository; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellService wellService; + private readonly IWellOperationService wellOperationService; private readonly WellOperationParserFactory wellOperationParserFactory; private readonly WellOperationExportServiceFactory wellOperationExportServiceFactory; @@ -47,7 +48,8 @@ public class WellOperationController : ControllerBase IWellService wellService, IUserRepository userRepository, WellOperationParserFactory wellOperationParserFactory, - WellOperationExportServiceFactory wellOperationExportServiceFactory) + WellOperationExportServiceFactory wellOperationExportServiceFactory, + IWellOperationService wellOperationService) { this.wellOperationRepository = wellOperationRepository; this.wellOperationCategoryRepository = wellOperationCategoryRepository; @@ -55,6 +57,7 @@ public class WellOperationController : ControllerBase this.userRepository = userRepository; this.wellOperationParserFactory = wellOperationParserFactory; this.wellOperationExportServiceFactory = wellOperationExportServiceFactory; + this.wellOperationService = wellOperationService; } /// @@ -159,7 +162,7 @@ public class WellOperationController : ControllerBase var requestToservice = new WellOperationRequest(request, new[] { idWell }); - var result = await wellOperationRepository.GetGroupOperationsStatAsync(requestToservice, token); + var result = await wellOperationService.GetGroupOperationsStatAsync(requestToservice, token); return Ok(result); } @@ -197,7 +200,7 @@ public class WellOperationController : ControllerBase var requestToService = new WellOperationRequest(request, new[] { idWell }); - var result = await wellOperationRepository.GetPageAsync(requestToService, token); + var result = await wellOperationService.GetPageAsync(requestToService, token); return Ok(result); } @@ -224,7 +227,7 @@ public class WellOperationController : ControllerBase if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); - var paginationContainer = await wellOperationRepository.GetPageAsync(idWell, id, operationType, take, sortFields, token); + var paginationContainer = await wellOperationService.GetPageAsync(idWell, id, operationType, take, sortFields, token); if (paginationContainer == null) return NoContent();