DD.WellWorkover.Cloud/AsbCloudInfrastructure/Repository/WellOperationRepository.cs

374 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository;
public class WellOperationRepository : CrudRepositoryBase<WellOperationDto, WellOperation>,
IWellOperationRepository
{
private readonly IMemoryCache memoryCache;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IWellService wellService;
private Lazy<IDictionary<int, WellOperationCategoryDto>> LazyWellCategories { get; }
private Lazy<IDictionary<int, WellSectionTypeDto>> LazyWellSectionTypes { get; }
public WellOperationRepository(IAsbCloudDbContext context,
IMemoryCache memoryCache,
IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellService wellService)
: base(context, dbSet => dbSet)
{
this.memoryCache = memoryCache;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
this.wellService = wellService;
LazyWellCategories = new(() => wellOperationCategoryRepository.Get(true, false).ToDictionary(c => c.Id));
LazyWellSectionTypes = new(() => GetSectionTypes().ToDictionary(c => c.Id));
}
public IEnumerable<WellSectionTypeDto> GetSectionTypes() =>
memoryCache
.GetOrCreateBasic(dbContext.WellSectionTypes)
.OrderBy(s => s.Order)
.Select(s => s.Adapt<WellSectionTypeDto>());
public async Task<IEnumerable<WellOperationDto>> GetAsync(WellOperationRequest request, CancellationToken token)
{
var (items, _) = await GetWithDaysAndNpvAsync(request, token);
return items;
}
public async Task<PaginationContainer<WellOperationDto>> GetPageAsync(WellOperationRequest request, CancellationToken token)
{
var skip = request.Skip ?? 0;
var take = request.Take ?? 32;
var (items, count) = await GetWithDaysAndNpvAsync(request, token);
var paginationContainer = new PaginationContainer<WellOperationDto>
{
Skip = skip,
Take = take,
Count = count,
Items = items
};
return paginationContainer;
}
public async Task<IEnumerable<WellGroupOpertionDto>> 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 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<int> InsertRangeAsync(IEnumerable<WellOperationDto> 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<int> UpdateRangeAsync(IEnumerable<WellOperationDto> dtos, CancellationToken token)
{
EnsureValidWellOperations(dtos);
return base.UpdateRangeAsync(dtos, token);
}
private static void EnsureValidWellOperations(IEnumerable<WellOperationDto> 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 async Task<IEnumerable<WellOperation>> GetByIdsWells(IEnumerable<int> 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<WellOperationDto> items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token)
{
var skip = request.Skip ?? 0;
var take = request.Take ?? 32;
var entities = await GetByIdsWells(request.IdsWell, token);
var groupedByWellAndType = entities
.GroupBy(e => new { e.IdWell, e.IdType });
var result = new List<WellOperationDto>();
var count = 0;
foreach (var wellOperationsWithType in groupedByWellAndType)
{
var firstWellOperation = wellOperationsWithType
.OrderBy(e => e.DateStart)
.FirstOrDefault()!;
var operationsWithNpt = wellOperationsWithType
.Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory));
IEnumerable<WellOperation> filteredWellOperations = FilterByRequest(wellOperationsWithType.AsQueryable(), request);
var filteredWellOperationsPart = filteredWellOperations
.Skip(skip)
.Take(take);
var dtos = filteredWellOperationsPart
.Select(entity =>
{
var dto = Convert(entity);
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);
count += filteredWellOperations.Count();
}
return (result, count);
}
private T FilterByRequest<T>(T entities, WellOperationRequest request)
where T : IQueryable<WellOperation>, IEnumerable<WellOperation>
{
if (request.OperationType.HasValue)
entities = (T)entities.Where(e => e.IdType == request.OperationType.Value);
if (request.SectionTypeIds?.Any() is true)
entities = (T)entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType));
if (request.OperationCategoryIds?.Any() is true)
entities = (T)entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory));
if (request.GeDepth.HasValue)
entities = (T)entities.Where(e => e.DepthEnd >= request.GeDepth.Value);
if (request.LeDepth.HasValue)
entities = (T)entities.Where(e => e.DepthEnd <= request.LeDepth.Value);
if (request.GeDate.HasValue)
{
var geDateUtc = request.GeDate.Value.UtcDateTime;
entities = (T)entities.Where(e => e.DateStart >= geDateUtc);
}
if (request.LeDate.HasValue)
{
var leDateUtc = request.LeDate.Value.UtcDateTime;
entities = (T)entities.Where(e => e.DateStart <= leDateUtc);
}
if (request.SortFields?.Any() is true)
entities = (T)entities.AsQueryable().SortBy(request.SortFields);
return entities;
}
private async Task<IQueryable<WellOperation>> BuildQuery(WellOperationRequest request, CancellationToken token)
{
var query = GetQuery()
.Where(e => request.IdsWell.Contains(e.IdWell))
.OrderBy(e => e.DateStart)
.AsQueryable();
query = FilterByRequest(query, request);
return query;
}
public async Task<IEnumerable<SectionByOperationsDto>> GetSectionsAsync(IEnumerable<int> idsWells, CancellationToken token)
{
const string keyCacheSections = "OperationsBySectionSummarties";
var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
var query = dbContext.Set<WellOperation>()
.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<DatesRangeDto?> 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)
};
}
protected override WellOperation Convert(WellOperationDto src)
{
var entity = src.Adapt<WellOperation>();
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<WellOperationDto>();
dto.DateStart = src.DateStart.ToOffset(timeZoneOffset);
dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timeZoneOffset);
dto.OperationCategoryName = LazyWellCategories.Value.TryGetValue(src.IdCategory, out WellOperationCategoryDto? category) ? category.Name : string.Empty;
dto.WellSectionTypeCaption = LazyWellSectionTypes.Value.TryGetValue(src.IdWellSectionType, out WellSectionTypeDto? sectionType) ? sectionType.Caption : string.Empty;
return dto;
}
}