diff --git a/AsbCloudApp/Requests/WellOperationRequest.cs b/AsbCloudApp/Requests/WellOperationRequest.cs index 356ef52d..08b7fad8 100644 --- a/AsbCloudApp/Requests/WellOperationRequest.cs +++ b/AsbCloudApp/Requests/WellOperationRequest.cs @@ -80,5 +80,5 @@ public class WellOperationRequest : WellOperationRequestBase /// /// Идентификаторы скважин /// - public IEnumerable? IdsWell { get; } + public IEnumerable IdsWell { get; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 3b67eb54..bc3cee39 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudApp.Data; +using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; @@ -14,350 +9,402 @@ using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; public class WellOperationRepository : CrudRepositoryBase, - IWellOperationRepository + IWellOperationRepository { - private readonly IMemoryCache memoryCache; - private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - private readonly IWellService wellService; + private readonly IMemoryCache memoryCache; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellService wellService; - public WellOperationRepository(IAsbCloudDbContext context, - IMemoryCache memoryCache, - IWellOperationCategoryRepository wellOperationCategoryRepository, - IWellService wellService) - : base(context, dbSet => dbSet.Include(e => e.WellSectionType) - .Include(e => e.OperationCategory)) - { - this.memoryCache = memoryCache; - this.wellOperationCategoryRepository = wellOperationCategoryRepository; - this.wellService = wellService; - } + public WellOperationRepository(IAsbCloudDbContext context, + IMemoryCache memoryCache, + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellService wellService) + : base(context, dbSet => dbSet.Include(e => e.WellSectionType) + .Include(e => e.OperationCategory)) + { + this.memoryCache = memoryCache; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellService = wellService; + } - public IEnumerable GetSectionTypes() => - memoryCache - .GetOrCreateBasic(dbContext.WellSectionTypes) - .OrderBy(s => s.Order) - .Select(s => s.Adapt()); + public IEnumerable GetSectionTypes() => + memoryCache + .GetOrCreateBasic(dbContext.WellSectionTypes) + .OrderBy(s => s.Order) + .Select(s => s.Adapt()); - public async Task> GetAsync(WellOperationRequest request, CancellationToken token) - { - var query = BuildQuery(request); + public async Task> GetAsync(WellOperationRequest request, CancellationToken token) + { + var (items, _) = await GetPrivateAsync(request, token); + return items; + } - if (request.Skip.HasValue) - query = query.Skip(request.Skip.Value); + public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) + { + var skip = request.Skip ?? 0; + var take = request.Take ?? 32; - if (request.Take.HasValue) - query = query.Take(request.Take.Value); + var (items, count) = await GetPrivateAsync(request, token); - var entities = await query.AsNoTracking() - .ToArrayAsync(token); + var paginationContainer = new PaginationContainer + { + Skip = skip, + Take = take, + Count = count, + Items = items + }; - return await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token); - } + return paginationContainer; + } - public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) - { - var skip = request.Skip ?? 0; - var take = request.Take ?? 32; + public async Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token) + { + var query = BuildQuery(request, token); + var entities = (await query) + .Select(o => new + { + o.IdCategory, + DurationMinutes = o.DurationHours * 60, + DurationDepth = o.DepthEnd - o.DepthStart + }) + .ToArray(); - var query = BuildQuery(request); + var parentRelationDictionary = wellOperationCategoryRepository.Get(true) + .ToDictionary(c => c.Id, c => new + { + c.Name, + c.IdParent + }); - var entities = await query.Skip(skip) - .Take(take) - .AsNoTracking() - .ToArrayAsync(token); + 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 + }); - var paginationContainer = new PaginationContainer - { - Skip = skip, - Take = take, - Count = await query.CountAsync(token), - Items = await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token) - }; + 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 paginationContainer; - } + return dtos; + } - 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); + public async Task InsertRangeAsync(IEnumerable dtos, + bool deleteBeforeInsert, + CancellationToken token) + { + EnsureValidWellOperations(dtos); - var parentRelationDictionary = wellOperationCategoryRepository.Get(true) - .ToDictionary(c => c.Id, c => new - { - c.Name, - c.IdParent - }); + if (!deleteBeforeInsert) + return await InsertRangeAsync(dtos, token); - 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 - }); + var idType = dtos.First().IdType; + var idWell = dtos.First().IdWell; - 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; - }); - } + var existingOperationIds = await dbContext.WellOperations + .Where(e => e.IdWell == idWell && e.IdType == idType) + .Select(e => e.Id) + .ToArrayAsync(token); - return dtos; - } + await DeleteRangeAsync(existingOperationIds, token); - public async Task InsertRangeAsync(IEnumerable dtos, - bool deleteBeforeInsert, - CancellationToken token) - { - EnsureValidWellOperations(dtos); + return await InsertRangeAsync(dtos, token); + } - if (!deleteBeforeInsert) - return await InsertRangeAsync(dtos, token); + public override Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + { + EnsureValidWellOperations(dtos); - var idType = dtos.First().IdType; - var idWell = dtos.First().IdWell; + return base.UpdateRangeAsync(dtos, token); + } - var existingOperationIds = await dbContext.WellOperations - .Where(e => e.IdWell == idWell && e.IdType == idType) - .Select(e => e.Id) - .ToArrayAsync(token); + private static void EnsureValidWellOperations(IEnumerable dtos) + { + if (dtos.GroupBy(d => d.IdType).Count() > 1) + throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); - await DeleteRangeAsync(existingOperationIds, token); + if (dtos.GroupBy(d => d.IdType).Count() > 1) + throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); + } - return await InsertRangeAsync(dtos, token); - } + private async Task> 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; + } - public override Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) - { - EnsureValidWellOperations(dtos); + private async Task<(IEnumerable items, int count)> GetPrivateAsync(WellOperationRequest request, CancellationToken token) + { + var skip = request.Skip ?? 0; + var take = request.Take ?? 32; - return base.UpdateRangeAsync(dtos, token); - } + /* + каунт = сумма всех фильтеред1 + for{ + все записи по скважине и типу план/факт = wellOperationswithType + из wellOperationswithType выбираем первую операцию + из wellOperationswithType все НПВ + к wellOperationswithType применяем оставшиеся фильтры из buildquery = фильтеред1 + к фильтеред1 применить скип тэйк = фильтеред2 + фильтеред2 конвертировать в дто и рассчитать дэй и время нпв + ... + } + */ - private static void EnsureValidWellOperations(IEnumerable dtos) - { - if (dtos.GroupBy(d => d.IdType).Count() > 1) - throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); + var entities = await GetByIdsWells(request.IdsWell, token); + var entitiesByWellAndType = entities + .GroupBy(e => new { e.IdWell, e.IdType }) + .Select(grp => grp.ToArray()); - if (dtos.GroupBy(d => d.IdType).Count() > 1) - throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); - } + var result = new List(); + foreach (var wellOperationswithType in entitiesByWellAndType) + { + var firstWellOperation = wellOperationswithType + .OrderBy(e => e.DateStart) + .FirstOrDefault(); - private IQueryable BuildQuery(WellOperationRequest request) - { - var query = GetQuery() - .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.IdWell)) - .OrderBy(e => e.DateStart) - .AsQueryable(); + //НПВ + var operationsWithNpt = wellOperationswithType + .Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory)); - if (request.OperationType.HasValue) - query = query.Where(e => e.IdType == request.OperationType.Value); + var filteredWellOperations = FilterOperations(wellOperationswithType, request); - if (request.SectionTypeIds?.Any() is true) - query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + var filteredWellOperationsPart = filteredWellOperations + .Skip(skip) + .Take(take); - if (request.OperationCategoryIds?.Any() is true) - query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + var dtos = filteredWellOperationsPart + .Select(o => ConvertWithDrillingDaysAndNpvHours(o, firstWellOperation, operationsWithNpt, token)); + result.AddRange(dtos); + } - if (request.GeDepth.HasValue) - query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); + return (result, entities.Count()); + } - if (request.LeDepth.HasValue) - query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); + private IEnumerable FilterOperations(IEnumerable 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; - query = query.Where(e => e.DateStart >= geDateUtc); - } + 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; - query = query.Where(e => e.DateStart <= leDateUtc); - } + 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); - if (request.SortFields?.Any() is true) - query = query.SortBy(request.SortFields); + return entities; + } - return query; - } + private async Task> BuildQuery(WellOperationRequest request, CancellationToken token) + { + var entities = await GetByIdsWells(request.IdsWell, token); - public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) - { - const string keyCacheSections = "OperationsBySectionSummarties"; + if (request.OperationType.HasValue) + entities = entities.Where(e => e.IdType == request.OperationType.Value); - var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + if (request.SectionTypeIds?.Any() is true) + entities = entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); - var query = dbContext.Set() - .GroupBy(operation => new - { - operation.IdWell, - operation.IdType, - operation.IdWellSectionType, - operation.WellSectionType.Caption, - }) - .Select(group => new - { - group.Key.IdWell, - group.Key.IdType, - group.Key.IdWellSectionType, - group.Key.Caption, + if (request.OperationCategoryIds?.Any() is true) + entities = entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); - First = group - .OrderBy(operation => operation.DateStart) - .Select(operation => new - { - operation.DateStart, - operation.DepthStart, - }) - .First(), + if (request.GeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd >= request.GeDepth.Value); - Last = group - .OrderByDescending(operation => operation.DateStart) - .Select(operation => new - { - operation.DateStart, - operation.DurationHours, - operation.DepthEnd, - }) - .First(), - }) - .Where(s => idsWells.Contains(s.IdWell)); - var dbData = await query.ToArrayAsync(token); - var sections = dbData.Select( - item => new SectionByOperationsDto - { - IdWell = item.IdWell, - IdType = item.IdType, - IdWellSectionType = item.IdWellSectionType, + if (request.LeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); - Caption = item.Caption, + if (request.GeDate.HasValue) + { + var geDateUtc = request.GeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart >= geDateUtc); + } - DateStart = item.First.DateStart, - DepthStart = item.First.DepthStart, + if (request.LeDate.HasValue) + { + var leDateUtc = request.LeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart <= leDateUtc); + } - DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), - DepthEnd = item.Last.DepthEnd, - }) - .ToArray() - .AsEnumerable(); + if (request.SortFields?.Any() is true) + entities = entities.AsQueryable().SortBy(request.SortFields); - entry.Value = sections; - return sections; - }); + return entities; + } - return cache; - } + public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) + { + const string keyCacheSections = "OperationsBySectionSummarties"; - public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) - { - var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); + var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - if (!await query.AnyAsync(cancellationToken)) - return null; + var query = dbContext.Set() + .GroupBy(operation => new + { + operation.IdWell, + operation.IdType, + operation.IdWellSectionType, + operation.WellSectionType.Caption, + }) + .Select(group => new + { + group.Key.IdWell, + group.Key.IdType, + group.Key.IdWellSectionType, + group.Key.Caption, - var timeZoneOffset = wellService.GetTimezone(idWell).Offset; - - var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); - var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); + First = group + .OrderBy(operation => operation.DateStart) + .Select(operation => new + { + operation.DateStart, + operation.DepthStart, + }) + .First(), - return new DatesRangeDto - { - From = minDate.ToOffset(timeZoneOffset), - To = maxDate.ToOffset(timeZoneOffset) - }; - } + Last = group + .OrderByDescending(operation => operation.DateStart) + .Select(operation => new + { + operation.DateStart, + operation.DurationHours, + operation.DepthEnd, + }) + .First(), + }) + .Where(s => idsWells.Contains(s.IdWell)); + var dbData = await query.ToArrayAsync(token); + var sections = dbData.Select( + item => new SectionByOperationsDto + { + IdWell = item.IdWell, + IdType = item.IdType, + IdWellSectionType = item.IdWellSectionType, - private async Task> ConvertWithDrillingDaysAndNpvHoursAsync(IEnumerable entities, - CancellationToken token) - { - var idsWell = entities.Select(e => e.IdWell).Distinct(); + Caption = item.Caption, - var currentWellOperations = GetQuery() - .Where(entity => idsWell.Contains(entity.IdWell)); + DateStart = item.First.DateStart, + DepthStart = item.First.DepthStart, - var dateFirstDrillingOperationByIdWell = await currentWellOperations - .Where(entity => entity.IdType == WellOperation.IdOperationTypeFact) - .GroupBy(entity => entity.IdWell) - .ToDictionaryAsync(g => g.Key, g => g.Min(o => o.DateStart), token); + DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), + DepthEnd = item.Last.DepthEnd, + }) + .ToArray() + .AsEnumerable(); - var operationsWithNptByIdWell = await currentWellOperations.Where(entity => - entity.IdType == WellOperation.IdOperationTypeFact && - WellOperationCategory.NonProductiveTimeSubIds.Contains(entity.IdCategory)) - .GroupBy(entity => entity.IdWell) - .ToDictionaryAsync(g => g.Key, g => g.Select(o => o), token); + entry.Value = sections; + return sections; + }); - var dtos = entities.Select(entity => - { - var dto = Convert(entity); + return cache; + } - if (dateFirstDrillingOperationByIdWell.TryGetValue(entity.IdWell, out var dateFirstDrillingOperation)) - dto.Day = (entity.DateStart - dateFirstDrillingOperation).TotalDays; + public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) + { + var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); - if (operationsWithNptByIdWell.TryGetValue(entity.IdWell, out var wellOperationsWithNtp)) - dto.NptHours = wellOperationsWithNtp - .Where(o => o.DateStart <= entity.DateStart) - .Sum(e => e.DurationHours); + if (!await query.AnyAsync(cancellationToken)) + return null; - return dto; - }); + var timeZoneOffset = wellService.GetTimezone(idWell).Offset; - return dtos; - } + var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); + var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); - protected override WellOperation Convert(WellOperationDto src) - { - var entity = src.Adapt(); - entity.DateStart = src.DateStart.UtcDateTime; - return entity; - } + return new DatesRangeDto + { + From = minDate.ToOffset(timeZoneOffset), + To = maxDate.ToOffset(timeZoneOffset) + }; + } - protected override WellOperationDto Convert(WellOperation src) - { - //TODO: пока такое получение TimeZone скважины, нужно исправить на Lazy - //Хоть мы и тянем данные из кэша, но от получения TimeZone в этом методе нужно избавиться, пока так - var timeZoneOffset = wellService.GetTimezone(src.IdWell).Offset; - var dto = src.Adapt(); - dto.DateStart = src.DateStart.ToOffset(timeZoneOffset); - dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timeZoneOffset); - return dto; - } + private WellOperationDto ConvertWithDrillingDaysAndNpvHours( + WellOperation entity, + WellOperation firstOperation, + IEnumerable wellOperationsWithNtp, + CancellationToken token) + { + var dto = Convert(entity); + dto.Day = (entity.DateStart - firstOperation.DateStart).TotalDays; + dto.NptHours = wellOperationsWithNtp + .Where(o => o.DateStart <= entity.DateStart) + .Sum(e => e.DurationHours); + + return dto; + } + + protected override WellOperation Convert(WellOperationDto src) + { + var entity = src.Adapt(); + entity.DateStart = src.DateStart.UtcDateTime; + return entity; + } + + protected override WellOperationDto Convert(WellOperation src) + { + //TODO: пока такое получение TimeZone скважины, нужно исправить на Lazy + //Хоть мы и тянем данные из кэша, но от получения TimeZone в этом методе нужно избавиться, пока так + var timeZoneOffset = wellService.GetTimezone(src.IdWell).Offset; + var dto = src.Adapt(); + dto.DateStart = src.DateStart.ToOffset(timeZoneOffset); + dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timeZoneOffset); + return dto; + } } \ No newline at end of file