using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Vml;
using DocumentFormat.OpenXml.Wordprocessing;
using Irony.Parsing;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository;
///
/// репозиторий операций по скважине
///
public class WellOperationRepository : IWellOperationRepository
{
private const string KeyCacheSections = "OperationsBySectionSummarties";
private const int Gap = 90;
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, IWellOperationCategoryRepository wellOperationCategoryRepository)
{
this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService;
this.wellOperationCategoryRepository = wellOperationCategoryRepository;
}
///
public IEnumerable GetCategories(bool includeParents)
{
return wellOperationCategoryRepository.Get(includeParents);
}
///
public IEnumerable GetSectionTypes() =>
memoryCache
.GetOrCreateBasic(db.Set())
.OrderBy(s => s.Order)
.Select(s => s.Adapt());
public async Task GetOperationsPlanAsync(int idWell, DateTime? currentDate, CancellationToken token)
{
var timezone = wellService.GetTimezone(idWell);
var request = new WellOperationRequest()
{
IdWell = idWell,
OperationType = WellOperation.IdOperationTypePlan,
};
var entities = await BuildQuery(request)
.AsNoTracking()
.ToArrayAsync(token)
.ConfigureAwait(false);
var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token);
var result = new WellOperationPlanDto()
{
WellOperationsPlan = entities,
DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation
};
return result;
}
private async Task GetDateLastAssosiatedPlanOperationAsync(
int idWell,
DateTime? lessThenDate,
double timeZoneHours,
CancellationToken token)
{
if (lessThenDate is null)
return null;
var currentDateOffset = lessThenDate.Value.ToUtcDateTimeOffset(timeZoneHours);
var timeZoneOffset = TimeSpan.FromHours(timeZoneHours);
var lastFactOperation = await db.WellOperations
.Where(o => o.IdWell == idWell)
.Where(o => o.IdType == WellOperation.IdOperationTypeFact)
.Where(o => o.IdPlan != null)
.Where(o => o.DateStart < currentDateOffset)
.Include(x => x.OperationPlan)
.OrderByDescending(x => x.DateStart)
.FirstOrDefaultAsync(token)
.ConfigureAwait(false);
if (lastFactOperation is not null)
return DateTime.SpecifyKind(lastFactOperation.OperationPlan!.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified);
return null;
}
///
public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token)
{
var cache = await memoryCache.GetOrCreateAsync(KeyCacheSections, async (entry) =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
var query = db.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 async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken)
{
var timezone = wellService.GetTimezone(idWell);
var query = db.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.ToRemoteDateTime(timezone.Hours),
To = maxDate.ToRemoteDateTime(timezone.Hours)
};
}
///
public DateTimeOffset? FirstOperationDate(int idWell)
{
var sections = GetSectionsAsync(new[] { idWell }, CancellationToken.None).Result;
var first = sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypeFact)
?? sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypePlan);
return first?.DateStart;
}
///
public async Task> GetAsync(
WellOperationRequest request,
CancellationToken token)
{
var query = BuildQuery(request)
.AsNoTracking();
var dtos = await query.ToArrayAsync(token);
return dtos;
}
///
public async Task> GetPageAsync(
WellOperationRequest request,
CancellationToken token)
{
var query = BuildQuery(request)
.AsNoTracking();
var result = new PaginationContainer
{
Skip = request.Skip ?? 0,
Take = request.Take ?? 32,
Count = await query.CountAsync(token).ConfigureAwait(false),
};
query = query
.Skip(result.Skip)
.Take(result.Take);
result.Items = await query.ToArrayAsync(token);
return result;
}
///
public async Task GetOrDefaultAsync(int id,
CancellationToken token)
{
var entity = await db.WellOperations
.Include(s => s.WellSectionType)
.Include(s => s.OperationCategory)
.FirstOrDefaultAsync(e => e.Id == id, token)
.ConfigureAwait(false);
if (entity is null)
return null;
var timezone = wellService.GetTimezone(entity.IdWell);
var dto = entity.Adapt();
dto.WellSectionTypeName = entity.WellSectionType.Caption;
dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours);
dto.CategoryName = entity.OperationCategory.Name;
return dto;
}
///
public async Task> GetGroupOperationsStatAsync(
WellOperationRequest request,
CancellationToken token)
{
// TODO: Rename controller method
request.OperationType = WellOperation.IdOperationTypeFact;
var query = BuildQuery(request);
var entities = await query
.Select(o => new
{
o.IdCategory,
DurationMinutes = o.DurationHours * 60,
DurationDepth = o.DepthEnd - o.DepthStart
})
.ToListAsync(token);
var parentRelationDictionary = GetCategories(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> ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token)
{
var firstOperation = wellOperationDtos
.FirstOrDefault();
if (firstOperation is null)
return Enumerable.Empty();
var request = new WellOperationRequest()
{
IdWell = firstOperation.IdWell,
OperationType = firstOperation.IdType,
};
var entities = await BuildQuery(request)
.AsNoTracking()
.ToArrayAsync(token);
var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart);
var results = Validate(wellOperationsUnion);
return results;
}
public IEnumerable Validate(IEnumerable wellOperationDtos)
{
var enumerator = wellOperationDtos.OrderBy(o => o.DateStart)
.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
var previous = enumerator.Current;
while(enumerator.MoveNext())
{
var current = enumerator.Current;
var previousDateStart = previous.DateStart.ToUniversalTime();
var currentDateStart = current.DateStart.ToUniversalTime();
var previousDateEnd = previous.DateStart.AddHours(previous.DurationHours).ToUniversalTime();
if (previousDateStart.AddDays(Gap) < currentDateStart)
{
yield return new ValidationResult(
"Разница дат между операциями не должна превышать 90 дней",
new[] { nameof(wellOperationDtos) });
}
previous = current;
}
}
///
public async Task InsertRangeAsync(
IEnumerable wellOperationDtos,
CancellationToken token)
{
var firstOperation = wellOperationDtos
.FirstOrDefault();
if (firstOperation is null)
return 0;
var idWell = firstOperation.IdWell;
var timezone = wellService.GetTimezone(idWell);
foreach (var dto in wellOperationDtos)
{
var entity = dto.Adapt();
entity.Id = default;
entity.DateStart = dto.DateStart.DateTime.ToUtcDateTimeOffset(timezone.Hours);
entity.IdWell = idWell;
entity.LastUpdateDate = DateTimeOffset.UtcNow;
db.WellOperations.Add(entity);
}
var result = await db.SaveChangesAsync(token);
if (result > 0)
memoryCache.Remove(KeyCacheSections);
return result;
}
///
public async Task UpdateAsync(
WellOperationDto dto, CancellationToken token)
{
var timezone = wellService.GetTimezone(dto.IdWell);
var entity = dto.Adapt();
entity.DateStart = dto.DateStart.DateTime.ToUtcDateTimeOffset(timezone.Hours);
entity.LastUpdateDate = DateTimeOffset.UtcNow;
db.WellOperations.Update(entity);
var result = await db.SaveChangesAsync(token);
if (result > 0)
memoryCache.Remove(KeyCacheSections);
return result;
}
///
public async Task DeleteAsync(IEnumerable ids,
CancellationToken token)
{
var query = db.WellOperations.Where(e => ids.Contains(e.Id));
db.WellOperations.RemoveRange(query);
var result = await db.SaveChangesAsync(token);
if (result > 0)
memoryCache.Remove(KeyCacheSections);
return result;
}
///
/// В результате попрежнему требуется конвертировать дату
///
///
///
private IQueryable BuildQuery(WellOperationRequest request)
{
var timezone = wellService.GetTimezone(request.IdWell);
var timeZoneOffset = TimeSpan.FromHours(timezone.Hours);
var query = db.WellOperations
.Include(s => s.WellSectionType)
.Include(s => s.OperationCategory)
.Where(o => o.IdWell == request.IdWell);
if (request.OperationType.HasValue)
query = query.Where(e => e.IdType == request.OperationType.Value);
if (request.SectionTypeIds?.Any() == true)
query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType));
if (request.OperationCategoryIds?.Any() == 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 geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timezone.Hours);
query = query.Where(e => e.DateStart >= geDateOffset);
}
if (request.LtDate.HasValue)
{
var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timezone.Hours);
query = query.Where(e => e.DateStart < ltDateOffset);
}
var currentWellOperations = db.WellOperations
.Where(subOp => subOp.IdWell == request.IdWell);
var wellOperationsWithCategoryNPT = currentWellOperations
.Where(subOp => subOp.IdType == 1)
.Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory));
var dtos = query.Select(o => new WellOperationDto
{
Id = o.Id,
IdPlan = o.IdPlan,
IdType = o.IdType,
IdWell = o.IdWell,
IdWellSectionType = o.IdWellSectionType,
IdCategory = o.IdCategory,
IdParentCategory = o.OperationCategory.IdParent,
CategoryName = o.OperationCategory.Name,
WellSectionTypeName = o.WellSectionType.Caption,
DateStart = DateTime.SpecifyKind(o.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified),
DepthStart = o.DepthStart,
DepthEnd = o.DepthEnd,
DurationHours = o.DurationHours,
CategoryInfo = o.CategoryInfo,
Comment = o.Comment,
NptHours = wellOperationsWithCategoryNPT
.Where(subOp => subOp.DateStart <= o.DateStart)
.Select(subOp => subOp.DurationHours)
.Sum(),
Day = (o.DateStart - currentWellOperations
.Where(subOp => subOp.IdType == o.IdType)
.Where(subOp => subOp.DateStart <= o.DateStart)
.Min(subOp => subOp.DateStart))
.TotalDays,
IdUser = o.IdUser,
LastUpdateDate = DateTime.SpecifyKind(o.LastUpdateDate.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified)
});
if (request.SortFields?.Any() == true)
{
dtos = dtos.SortBy(request.SortFields);
}
else
{
dtos = dtos
.OrderBy(e => e.DateStart)
.ThenBy(e => e.DepthEnd)
.ThenBy(e => e.Id);
}
return dtos;
}
public async Task RemoveDuplicates(Action onProgressCallback, CancellationToken token)
{
IQueryable dbset = db.Set();
var query = dbset
.GroupBy(o => new { o.IdWell, o.IdType })
.Select(g => new { g.Key.IdWell, g.Key.IdType });
var groups = await query
.ToArrayAsync(token);
var count = groups.Count();
var i = 0;
var totalRemoved = 0;
var total = 0;
foreach (var group in groups)
{
var result = await RemoveDuplicatesInGroup(group.IdWell, group.IdType, token);
totalRemoved += result.removed;
total += result.total;
var percent = i++ / count;
var message = $"RemoveDuplicates [{i} of {count}] wellId: {group.IdWell}, opType: {group.IdType}, affected: {result.removed} of {result.total}";
onProgressCallback?.Invoke(message, percent);
Trace.TraceInformation(message);
}
var messageDone = $"RemoveDuplicates done [{i} of {count}] totalAffected: {totalRemoved} of {total}";
Trace.TraceInformation(messageDone);
onProgressCallback?.Invoke(messageDone, 1);
return totalRemoved;
}
private async Task<(int removed, int total)> RemoveDuplicatesInGroup(int idWell, int idType, CancellationToken token)
{
var dbset = db.Set();
var entities = await dbset
.Where(o => o.IdWell == idWell && o.IdType == idType)
.OrderBy(o => o.DateStart)
.ToListAsync(token);
using var entitiesEnumerator = entities.GetEnumerator();
if (!entitiesEnumerator.MoveNext())
return (0, 0);
var preEntity = entitiesEnumerator.Current;
while (entitiesEnumerator.MoveNext())
{
var entity = entitiesEnumerator.Current;
if (preEntity.IsSame(entity))
dbset.Remove(entity);
else
preEntity = entity;
}
var removed = await db.SaveChangesAsync(token);
return (removed, entities.Count);
}
public async Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, Action onProgressCallback, CancellationToken token)
{
var leDateUtc = leDate.ToUniversalTime();
IQueryable query = db.Set();
if (geDate.HasValue)
{
var geDateUtc = geDate.Value.ToUniversalTime();
query = query.Where(e => e.DateStart >= geDateUtc);
}
var groups = await query
.GroupBy(o => new { o.IdWell, o.IdType })
.Select(g => new{
MaxDate = g.Max(o => o.DateStart),
g.Key.IdWell,
g.Key.IdType,
})
.Where(g => g.MaxDate <= leDateUtc)
.ToArrayAsync(token);
var count = groups.Count();
var i = 0;
(int takeover, int trimmed,int total) totalResult = (0, 0, 0);
foreach (var group in groups)
{
var result = await TrimOverlapping(group.IdWell, group.IdType, token);
totalResult.takeover += result.takeover;
totalResult.trimmed += result.trimmed;
totalResult.total += result.total;
var percent = i++ / count;
var message = $"TrimOverlapping [{i} of {count}] wellId: {group.IdWell}, opType: {group.IdType}, takeover:{result.takeover}, trimmed:{result.trimmed}, of {result.total}";
onProgressCallback?.Invoke(message, percent);
Trace.TraceInformation(message);
}
var messageDone = $"TrimOverlapping done [{i} of {count}] total takeover:{totalResult.takeover}, total trimmed:{totalResult.trimmed} of {totalResult.total}";
Trace.TraceInformation(messageDone);
onProgressCallback?.Invoke(messageDone, 1);
return totalResult.takeover + totalResult.trimmed;
}
private async Task<(int takeover, int trimmed, int total)> TrimOverlapping(int idWell, int idType, CancellationToken token)
{
var dbset = db.Set();
var query = dbset
.Where(o => o.IdWell == idWell)
.Where(o => o.IdType == idType)
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
var entities = await query
.ToListAsync(token);
using var entitiesEnumerator = entities.GetEnumerator();
if (!entitiesEnumerator.MoveNext())
return (0, 0, 0);
int takeover = 0;
int trimmed = 0;
var preEntity = entitiesEnumerator.Current;
while (entitiesEnumerator.MoveNext())
{
var entity = entitiesEnumerator.Current;
var preDepth = preEntity.DepthEnd;
if (preEntity.DepthEnd >= entity.DepthEnd)
{
dbset.Remove(entity);
takeover++;
continue;
}
if (preEntity.DepthEnd > entity.DepthStart)
{
entity.DepthStart = preEntity.DepthEnd;
trimmed++;
}
var preDate = preEntity.DateStart.AddHours(preEntity.DurationHours);
if (preDate >= entity.DateStart.AddHours(entity.DurationHours))
{
dbset.Remove(entity);
takeover++;
continue;
}
if (preDate > entity.DateStart)
{
var entityDateEnd = entity.DateStart.AddHours(entity.DurationHours);
entity.DateStart = preDate;
entity.DurationHours = (entityDateEnd - entity.DateStart).TotalHours;
trimmed++;
}
preEntity = entity;
}
var affected = await db.SaveChangesAsync(token);
return (takeover, trimmed, entities.Count);
}
}