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 entities = await BuildQuery(request) .AsNoTracking() .ToArrayAsync(token); var dtos = entities.Select(Convert); return dtos; } public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) { var skip = request.Skip ?? 0; var take = request.Take ?? 32; var query = BuildQuery(request); var entites = await query.Skip(skip) .Take(take) .AsNoTracking() .ToArrayAsync(token); var paginationContainer = new PaginationContainer { Skip = skip, Take = take, Count = await query.CountAsync(token), Items = entites.Select(Convert) }; 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 currentWellOperations = GetQuery() .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.IdWell)); var query = GetQuery() .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.IdWell)) .Select(o => new WellOperation { Id = o.Id, IdPlan = o.IdPlan, IdType = o.IdType, IdWell = o.IdWell, LastUpdateDate = o.LastUpdateDate, IdWellSectionType = o.IdWellSectionType, IdCategory = o.IdCategory, OperationCategory = o.OperationCategory, WellSectionType = o.WellSectionType, DateStart = o.DateStart, DepthStart = o.DepthStart, DepthEnd = o.DepthEnd, DurationHours = o.DurationHours, CategoryInfo = o.CategoryInfo, Comment = o.Comment, IdUser = o.IdUser, NptHours = currentWellOperations .Where(e => e.IdType == 1 && e.IdWell == o.IdWell) .Where(e => WellOperationCategory.NonProductiveTimeSubIds.Contains(e.IdCategory)) .Select(e => e.DurationHours) .Sum(), Day = (o.DateStart - currentWellOperations .Where(subOp => subOp.IdType == o.IdType && subOp.IdWell == o.IdWell) .Where(subOp => subOp.DateStart <= o.DateStart) .Min(subOp => subOp.DateStart)) .TotalDays }); 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(), }); 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; }); var sections = cache.Where(s => idsWells.Contains(s.IdWell)); return sections; } public DatesRangeDto? GetDatesRange(int idWell, int idType) { var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); if (!query.Any()) return null; var minDate = query.Min(o => o.DateStart); var maxDate = query.Max(o => o.DateStart); return new DatesRangeDto { From = minDate.ToOffset(minDate.Offset), To = maxDate.ToOffset(minDate.Offset) }; } 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 minDate = await query.MinAsync(o => o.DateStart, cancellationToken); var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); return new DatesRangeDto { From = minDate.ToOffset(minDate.Offset), To = maxDate.ToOffset(minDate.Offset) }; } protected override WellOperation Convert(WellOperationDto src) { var entity = src.Adapt(); entity.LastUpdateDate = src.LastUpdateDate?.UtcDateTime; 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; } }