using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; namespace AsbCloudInfrastructure.Repository; public class WellOperationRepository : CrudRepositoryBase, IWellOperationRepository { 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 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); if (request.Skip.HasValue) query = query.Skip(request.Skip.Value); if (request.Take.HasValue) query = query.Take(request.Take.Value); var entities = await query.AsNoTracking() .ToArrayAsync(token); return await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token); } public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) { var skip = request.Skip ?? 0; var take = request.Take ?? 32; var query = BuildQuery(request); var entities = await query.Skip(skip) .Take(take) .AsNoTracking() .ToArrayAsync(token); var paginationContainer = new PaginationContainer { Skip = skip, Take = take, Count = await query.CountAsync(token), Items = await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token) }; return paginationContainer; } 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, bool deleteBeforeInsert, CancellationToken token) { EnsureValidWellOperations(dtos); if (!deleteBeforeInsert) return await InsertRangeAsync(dtos, token); var idType = dtos.First().IdType; var idWell = dtos.First().IdWell; var existingOperationIds = await dbContext.WellOperations .Where(e => e.IdWell == idWell && e.IdType == idType) .Select(e => e.Id) .ToArrayAsync(token); await DeleteRangeAsync(existingOperationIds, token); return await InsertRangeAsync(dtos, token); } public override Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) { EnsureValidWellOperations(dtos); return base.UpdateRangeAsync(dtos, token); } private static void EnsureValidWellOperations(IEnumerable dtos) { if (dtos.GroupBy(d => d.IdType).Count() > 1) throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); if (dtos.GroupBy(d => d.IdType).Count() > 1) throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); } private IQueryable BuildQuery(WellOperationRequest request) { var query = GetQuery() .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.IdWell)) .OrderBy(e => e.DateStart) .AsQueryable(); if (request.OperationType.HasValue) query = query.Where(e => e.IdType == request.OperationType.Value); if (request.SectionTypeIds?.Any() is true) query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); if (request.OperationCategoryIds?.Any() is true) query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); if (request.GeDepth.HasValue) query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); if (request.LeDepth.HasValue) query = query.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.LeDate.HasValue) { var leDateUtc = request.LeDate.Value.UtcDateTime; query = query.Where(e => e.DateStart <= leDateUtc); } if (request.SortFields?.Any() is true) query = query.SortBy(request.SortFields); return query; } public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) { const string keyCacheSections = "OperationsBySectionSummarties"; var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); 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, First = group .OrderBy(operation => operation.DateStart) .Select(operation => new { operation.DateStart, operation.DepthStart, }) .First(), 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, Caption = item.Caption, DateStart = item.First.DateStart, DepthStart = item.First.DepthStart, DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), DepthEnd = item.Last.DepthEnd, }) .ToArray() .AsEnumerable(); entry.Value = sections; return sections; }); return cache; } public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) { var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); if (!await query.AnyAsync(cancellationToken)) return null; var timeZoneOffset = wellService.GetTimezone(idWell).Offset; var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); return new DatesRangeDto { From = minDate.ToOffset(timeZoneOffset), To = maxDate.ToOffset(timeZoneOffset) }; } private async Task> ConvertWithDrillingDaysAndNpvHoursAsync(IEnumerable entities, CancellationToken token) { var idsWell = entities.Select(e => e.IdWell).Distinct(); var currentWellOperations = GetQuery() .Where(entity => idsWell.Contains(entity.IdWell)); 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); 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); var dtos = entities.Select(entity => { var dto = Convert(entity); if (dateFirstDrillingOperationByIdWell.TryGetValue(entity.IdWell, out var dateFirstDrillingOperation)) dto.Day = (entity.DateStart - dateFirstDrillingOperation).TotalDays; if (operationsWithNptByIdWell.TryGetValue(entity.IdWell, out var wellOperationsWithNtp)) dto.NptHours = wellOperationsWithNtp .Where(o => o.DateStart <= entity.DateStart) .Sum(e => e.DurationHours); return dto; }); return dtos; } 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; } }