using AsbCloudApp.Data;
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.ComponentModel.DataAnnotations;
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;
public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
{
this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService;
}
///
public IEnumerable GetCategories(bool includeParents)
{
var categories = memoryCache
.GetOrCreateBasic(db.Set());
if (!includeParents)
{
var parentIds = categories
.Select(o => o.IdParent)
.Distinct();
categories = categories
.Where(o => !parentIds.Contains(o.Id));
}
var result = categories
.OrderBy(o => o.Name)
.Adapt>();
return result;
}
///
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 result = await query.ToArrayAsync(token);
return result;
}
///
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) });
}
if (previousDateEnd > currentDateStart)
{
yield return new ValidationResult(
"Предыдущая операция не завершена",
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.ToUniversalTime();
entity.IdWell = idWell;
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.ToUniversalTime();
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 result = 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 = o.LastUpdateDate.ToOffset(TimeSpan.FromHours(timezone.Hours))
});
if (request.SortFields?.Any() == true)
{
result = result.SortBy(request.SortFields);
}
else
{
result = result
.OrderBy(e => e.DateStart)
.ThenBy(e => e.DepthEnd)
.ThenBy(e => e.Id);
};
return result;
}
}