diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index ff3bfb8e..33fbf1f5 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -132,5 +132,23 @@ namespace AsbCloudApp.Repositories /// /// Task> ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); + + /// + /// Удаление полных дубликатов операций по всем скважинам + /// + /// + /// + /// + Task RemoveDuplicates(Action onProgressCallback, CancellationToken token); + + /// + /// Усечение пересекающейся последующей операции по дате и глубине забоя + /// + /// Фильтр по дате. Если хоть одна операция попадет в в фильтр, то будет обработана вся скважина, а не только эта операция + /// Фильтр по дате. Если хоть одна операция попадет в в фильтр, то будет обработана вся скважина, а не только эта операция + /// + /// + /// + Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, Action onProgressCallback, CancellationToken token); } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 398945a6..2af1127e 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -5,6 +5,8 @@ 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; @@ -12,6 +14,7 @@ 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; @@ -533,59 +536,160 @@ public class WellOperationRepository : IWellOperationRepository return dtos; } - public async Task RemoveDuplicates(CancellationToken token) + public async Task RemoveDuplicates(Action onProgressCallback, CancellationToken token) { - var dbset = db.Set(); - var groups = await dbset + 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); - foreach (var group in groups) - await RemoveDuplicatesInGroup(group.Key.IdWell, group.Key.IdType, 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 RemoveDuplicatesInGroup(int idWell, int idType, CancellationToken token) + private async Task<(int removed, int total)> RemoveDuplicatesInGroup(int idWell, int idType, CancellationToken token) { var dbset = db.Set(); - var operationsEnumerator = dbset + var entities = await dbset .Where(o => o.IdWell == idWell && o.IdType == idType) .OrderBy(o => o.DateStart) - .GetEnumerator(); + .ToListAsync(token); - if (!operationsEnumerator.MoveNext()) - return; + using var entitiesEnumerator = entities.GetEnumerator(); - var preOperation = operationsEnumerator.Current; - while (operationsEnumerator.MoveNext()) + if (!entitiesEnumerator.MoveNext()) + return (0, 0); + + var preEntity = entitiesEnumerator.Current; + while (entitiesEnumerator.MoveNext()) { - var operation = operationsEnumerator.Current; - if (preOperation.IsSame(operation)) - dbset.Remove(operation); + var entity = entitiesEnumerator.Current; + if (preEntity.IsSame(entity)) + dbset.Remove(entity); else - preOperation = operation; + preEntity = entity; } - await db.SaveChangesAsync(token); + var removed = await db.SaveChangesAsync(token); + return (removed, entities.Count); } - public async Task TrimOverlapping(DateTimeOffset leDate, CancellationToken token) + public async Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, Action onProgressCallback, CancellationToken token) { var leDateUtc = leDate.ToUniversalTime(); - var dbset = db.Set(); - var groups = await dbset + 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) - await TrimOverlapping(group.IdWell, group.IdType, token); + { + 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 Task TrimOverlapping(int idWell, int idType, CancellationToken token) + private async Task<(int takeover, int trimmed, int total)> TrimOverlapping(int idWell, int idType, CancellationToken token) { - throw new NotImplementedException(); + 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); } } diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 27827a62..b9b99dc8 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -505,6 +505,38 @@ namespace AsbCloudWebApi.Controllers return File(stream, "application/octet-stream", fileName); } + /// + /// Удаляет полые дубликаты операций + /// + /// + /// + [HttpPost("/api/well/wellOperations/RemoveDuplicates")] + [Permission] + [Obsolete] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task RemoveDuplicates(CancellationToken token) + { + var result = await operationRepository.RemoveDuplicates((_, _) => { }, token); + return Ok(result); + } + + /// + /// Удаляет полностью пересекающиеся операции или "подрезает" более поздние их по глубине и дате. + /// + /// + /// + /// + /// + [HttpPost("/api/well/wellOperations/TrimOverlapping")] + [Permission] + [Obsolete] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, CancellationToken token) + { + var result = await operationRepository.TrimOverlapping(geDate, leDate, (_, _) => { }, token); + return Ok(result); + } + /// /// Возвращает шаблон файла импорта ///