diff --git a/AsbCloudApp/Repositories/IDepositRepository.cs b/AsbCloudApp/Repositories/IDepositRepository.cs index 87f732fb..8f2e421a 100644 --- a/AsbCloudApp/Repositories/IDepositRepository.cs +++ b/AsbCloudApp/Repositories/IDepositRepository.cs @@ -19,15 +19,6 @@ namespace AsbCloudApp.Repositories Task> GetAsync(int idCompany, CancellationToken token); - /// - /// Список месторождений/кустов/скважин у которых заполненны параметры бурения - /// - /// - /// - /// - Task> GetAllWithDrillParamsAsync(int idCompany, - CancellationToken token = default); - /// /// Список кустов месторождения доступных компании /// diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index 0409969e..12246578 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -1,9 +1,9 @@ using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Requests; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using AsbCloudApp.Data.WellOperation; -using AsbCloudApp.Requests; namespace AsbCloudApp.Repositories { @@ -17,8 +17,8 @@ namespace AsbCloudApp.Repositories /// /// IEnumerable GetSectionTypes(); - - /// + + /// /// Получить страницу списка операций /// /// @@ -26,7 +26,7 @@ namespace AsbCloudApp.Repositories /// Task> GetAsync(WellOperationRequest request, CancellationToken token); - /// + /// /// Получить страницу списка операций /// /// @@ -34,7 +34,7 @@ namespace AsbCloudApp.Repositories /// Task> GetPageAsync(WellOperationRequest request, CancellationToken token); - /// + /// /// Получить статистику операции по скважине с группировкой по категориям /// /// @@ -42,19 +42,19 @@ namespace AsbCloudApp.Repositories /// Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token); - /// - /// Добавить несколько операций - /// - /// - /// - /// - /// - Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token); + /// + /// Добавить несколько операций + /// + /// + /// + /// + /// + Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token); /// /// Обновить существующую операцию /// - /// + /// /// /// Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token); @@ -75,13 +75,20 @@ namespace AsbCloudApp.Repositories /// Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token); - /// - /// Получить диапазон дат выполнения операций - /// - /// - /// - /// - /// - Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken); - } + /// + /// Получить диапазон дат выполнения операций + /// + /// + /// + /// + /// + Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken); + + /// + /// Возвращает первую и последнюю фактическую операцию + /// + /// + /// + (WellOperationDto First, WellOperationDto Last)? GetFirstAndLastFact(int idWell); + } } \ No newline at end of file diff --git a/AsbCloudApp/Requests/WellOperationRequest.cs b/AsbCloudApp/Requests/WellOperationRequest.cs index 356ef52d..fa866378 100644 --- a/AsbCloudApp/Requests/WellOperationRequest.cs +++ b/AsbCloudApp/Requests/WellOperationRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace AsbCloudApp.Requests; @@ -8,44 +9,72 @@ namespace AsbCloudApp.Requests; /// public class WellOperationRequestBase : RequestBase { - /// - /// Больше или равно дате начала операции - /// - public DateTimeOffset? GeDate { get; set; } + /// + /// Больше или равно дате начала операции + /// + public DateTimeOffset? GeDate { get; set; } - /// - /// Меньше или равно дате окончания операции - /// - public DateTimeOffset? LeDate { get; set; } + /// + /// Меньше или равно дате окончания операции + /// + public DateTimeOffset? LeDate { get; set; } - /// - /// Больше или равно глубины скважины на начало операции. - /// - public double? GeDepth { get; set; } + /// + /// Больше или равно глубины скважины на начало операции. + /// + public double? GeDepth { get; set; } - /// - /// Меньше или равно глубины скважины на конец операции. - /// - public double? LeDepth { get; set; } + /// + /// Меньше или равно глубины скважины на конец операции. + /// + public double? LeDepth { get; set; } - /// - /// Идентификаторы категорий операции - /// - public IEnumerable? OperationCategoryIds { get; set; } + /// + /// Идентификаторы категорий операции + /// + public IEnumerable? OperationCategoryIds { get; set; } - /// - /// Тип операций - /// - /// 0 - плановая операция - /// 1 - фактическая операция - /// - /// - public int? OperationType { get; set; } + /// + /// Тип операций + /// + /// 0 - плановая операция + /// 1 - фактическая операция + /// + /// + public int? OperationType { get; set; } - /// - /// Идентификаторы конструкций секции - /// - public IEnumerable? SectionTypeIds { get; set; } + /// + /// Идентификаторы конструкций секции + /// + public IEnumerable? SectionTypeIds { get; set; } + + /// + /// + /// + public WellOperationRequestBase() + { + + } + + /// + /// + /// + /// + public WellOperationRequestBase(WellOperationRequestBase request) + { + GeDepth = request.GeDepth; + LeDepth = request.LeDepth; + GeDate = request.GeDate; + LeDate = request.LeDate; + + OperationCategoryIds = request.OperationCategoryIds; + OperationType = request.OperationType; + SectionTypeIds = request.SectionTypeIds; + + Skip = request.Skip; + Take = request.Take; + SortFields = request.SortFields; + } } /// @@ -53,32 +82,23 @@ public class WellOperationRequestBase : RequestBase /// public class WellOperationRequest : WellOperationRequestBase { - /// - public WellOperationRequest(IEnumerable idsWell) - { - IdsWell = idsWell; - } + /// + public WellOperationRequest(IEnumerable idsWell) + { + IdsWell = idsWell; + } - /// - public WellOperationRequest(WellOperationRequestBase request, IEnumerable idsWell) - : this(idsWell) - { - GeDepth = request.GeDepth; - LeDepth = request.LeDepth; - GeDate = request.GeDate; - LeDate = request.LeDate; + /// + public WellOperationRequest(WellOperationRequestBase request, IEnumerable idsWell) + : base(request) + { + IdsWell = idsWell; + } - OperationCategoryIds = request.OperationCategoryIds; - OperationType = request.OperationType; - SectionTypeIds = request.SectionTypeIds; - - Skip = request.Skip; - Take = request.Take; - SortFields = request.SortFields; - } - - /// - /// Идентификаторы скважин - /// - public IEnumerable? IdsWell { get; } + /// + /// Идентификаторы скважин + /// + [Required] + [Length(1, 100)] + public IEnumerable IdsWell { get; } } \ No newline at end of file diff --git a/AsbCloudDb/EFExtensions.cs b/AsbCloudDb/EFExtensions.cs index 9da4c626..8829462a 100644 --- a/AsbCloudDb/EFExtensions.cs +++ b/AsbCloudDb/EFExtensions.cs @@ -223,11 +223,12 @@ namespace AsbCloudDb private static string FormatValue(object? v) => v switch { + null => "NULL", string vStr => $"'{EscapeCurlyBraces(vStr)}'", DateTime vDate => $"'{FormatDateValue(vDate)}'", DateTimeOffset vDate => $"'{FormatDateValue(vDate.UtcDateTime)}'", IFormattable vFormattable => FormatFormattableValue(vFormattable), - _ => System.Text.Json.JsonSerializer.Serialize(v), + _ => $"'{EscapeCurlyBraces(JsonSerializer.Serialize(v))}'", }; private static string EscapeCurlyBraces(string vStr) diff --git a/AsbCloudDb/Model/DefaultData/WellOperationCategories.xlsx b/AsbCloudDb/Model/DefaultData/WellOperationCategories.xlsx index 1f41a051..7d257567 100644 Binary files a/AsbCloudDb/Model/DefaultData/WellOperationCategories.xlsx and b/AsbCloudDb/Model/DefaultData/WellOperationCategories.xlsx differ diff --git a/AsbCloudInfrastructure/Repository/DepositRepository.cs b/AsbCloudInfrastructure/Repository/DepositRepository.cs index 804d130e..344ad0f7 100644 --- a/AsbCloudInfrastructure/Repository/DepositRepository.cs +++ b/AsbCloudInfrastructure/Repository/DepositRepository.cs @@ -10,118 +10,100 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Repository +namespace AsbCloudInfrastructure.Repository; + +public class DepositRepository : IDepositRepository { + private readonly IAsbCloudDbContext db; + private readonly ITelemetryService telemetryService; - public class DepositRepository : IDepositRepository + public DepositRepository(IAsbCloudDbContext db, ITelemetryService telemetryService) { - private readonly IAsbCloudDbContext db; - private readonly IWellService wellService; - - public DepositRepository(IAsbCloudDbContext db, IWellService wellService) - { - this.db = db; - this.wellService = wellService; - } - - /// - public async Task> GetAsync(int idCompany, - CancellationToken token = default) - { - var wellEntities = await (from well in db.Wells - .Include(w => w.RelationCompaniesWells) - .Include(w => w.WellType) - .Include(w => w.Cluster) - .ThenInclude(c => c.Deposit) - where well.RelationCompaniesWells.Any(r => r.IdCompany == idCompany) - select well).ToListAsync(token) - .ConfigureAwait(false); - - var gDepositEntities = GroupWells(wellEntities); - - var dtos = CreateDepositDto(gDepositEntities); - - return dtos; - } - - /// - public async Task> GetAllWithDrillParamsAsync(int idCompany, - CancellationToken token = default) - { - var wellEntities = await (from well in db.Wells - .Include(w => w.RelationCompaniesWells) - .Include(w => w.WellType) - .Include(w => w.Cluster) - .ThenInclude(c => c.Deposit) - where well.RelationCompaniesWells.Any(r => r.IdCompany == idCompany) - select well).ToListAsync(token) - .ConfigureAwait(false); - - var gDepositEntities = GroupWells(wellEntities); - - var dtos = CreateDepositDto(gDepositEntities); - - return dtos; - } - - /// - public async Task> GetClustersAsync(int idCompany, - int depositId, CancellationToken token = default) - { - var entities = await GetWellsForCompany(idCompany) - .Select(e => e.Cluster) - .Where(e => e.IdDeposit == depositId) - .Distinct() - .AsNoTracking() - .ToListAsync(token) - .ConfigureAwait(false); - - var dtos = entities.Adapt>(); - - return dtos; - } - - private static IEnumerable>> GroupWells(IEnumerable wellEntities) - => wellEntities - .GroupBy(w => w.Cluster) - .GroupBy(c => c.Key.Deposit); - - private IQueryable GetWellsForCompany(int idCompany) - => db.Wells - .Include(w => w.RelationCompaniesWells) - .ThenInclude(r => r.Company) - .Include(w => w.Cluster) - .ThenInclude(c => c.Deposit) - .Where(w => w.RelationCompaniesWells.Any(c => c.IdCompany == idCompany)); - - private IEnumerable CreateDepositDto(IEnumerable>> gDepositEntities) - { - var dtos = gDepositEntities.Select(gDeposit => new DepositDto - { - Id = gDeposit.Key.Id, - Caption = gDeposit.Key.Caption, - Latitude = gDeposit.Key.Latitude, - Longitude = gDeposit.Key.Longitude, - Clusters = gDeposit.Select(gCluster => new ClusterDto - { - Id = gCluster.Key.Id, - Caption = gCluster.Key.Caption, - Latitude = gCluster.Key.Latitude, - Longitude = gCluster.Key.Longitude, - Wells = gCluster.Select(well => - { - var dto = well.Adapt(); - dto.WellType = well.WellType.Caption; - dto.LastTelemetryDate = wellService.GetLastTelemetryDate(well.Id) - .ToOffset(TimeSpan.FromHours(well.Timezone.Hours)); - dto.Cluster = gCluster.Key.Caption; - dto.Deposit = gDeposit.Key.Caption; - return dto; - }), - }), - }); - return dtos; - } + this.db = db; + this.telemetryService = telemetryService; } + /// + public async Task> GetAsync(int idCompany, + CancellationToken token = default) + { + var wellsQuery = db.Set() + .Include(w => w.RelationCompaniesWells) + .Include(w => w.WellType) + .Include(w => w.Cluster) + .ThenInclude(c => c.Deposit) + .Where(well => well.RelationCompaniesWells.Any(r => r.IdCompany == idCompany)); + + var wellEntities = await wellsQuery.ToArrayAsync(token); + + var gDepositEntities = GroupWells(wellEntities); + + var dtos = CreateDepositDto(gDepositEntities) + .ToArray(); + + return dtos; + } + + /// + public async Task> GetClustersAsync(int idCompany, + int depositId, CancellationToken token = default) + { + var entities = await GetWellsForCompany(idCompany) + .Select(e => e.Cluster) + .Where(e => e.IdDeposit == depositId) + .Distinct() + .AsNoTracking() + .ToListAsync(token) + .ConfigureAwait(false); + + var dtos = entities.Adapt>(); + + return dtos; + } + + private static IEnumerable>> GroupWells(IEnumerable wellEntities) + => wellEntities + .GroupBy(w => w.Cluster) + .GroupBy(c => c.Key.Deposit); + + private IQueryable GetWellsForCompany(int idCompany) + => db.Set() + .Include(w => w.RelationCompaniesWells) + .ThenInclude(r => r.Company) + .Include(w => w.Cluster) + .ThenInclude(c => c.Deposit) + .Where(w => w.RelationCompaniesWells.Any(c => c.IdCompany == idCompany)); + + private IEnumerable CreateDepositDto(IEnumerable>> gDepositEntities) + { + var dtos = gDepositEntities.Select(gDeposit => new DepositDto + { + Id = gDeposit.Key.Id, + Caption = gDeposit.Key.Caption, + Latitude = gDeposit.Key.Latitude, + Longitude = gDeposit.Key.Longitude, + Clusters = gDeposit.Select(gCluster => new ClusterDto + { + Id = gCluster.Key.Id, + Caption = gCluster.Key.Caption, + Latitude = gCluster.Key.Latitude, + Longitude = gCluster.Key.Longitude, + Wells = gCluster.Select(well => + { + var dto = well.Adapt(); + dto.WellType = well.WellType.Caption; + + dto.LastTelemetryDate = DateTimeOffset.MinValue; + + if (well.IdTelemetry != null) + dto.LastTelemetryDate = telemetryService.GetDatesRange(well.IdTelemetry.Value).To; + + dto.Cluster = gCluster.Key.Caption; + dto.Deposit = gDeposit.Key.Caption; + return dto; + }), + }), + }); + return dtos; + } } diff --git a/AsbCloudInfrastructure/Repository/DrillTestRepository.cs b/AsbCloudInfrastructure/Repository/DrillTestRepository.cs index 961dd67b..96ba0ece 100644 --- a/AsbCloudInfrastructure/Repository/DrillTestRepository.cs +++ b/AsbCloudInfrastructure/Repository/DrillTestRepository.cs @@ -2,6 +2,7 @@ using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; +using AsbCloudDb; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; @@ -68,11 +69,12 @@ namespace AsbCloudInfrastructure.Repository var entities = dtos.Select(dto => { var entity = dto.Adapt(); + entity.TimeStampStart = dto.TimeStampStart.ToUniversalTime(); entity.IdTelemetry = idTelemetry; return entity; }); - db.DrillTests.AddRange(entities); - var result = await db.SaveChangesAsync(token); + + var result = await db.Database.ExecInsertOrUpdateAsync(db.Set(), entities, token); return result; } diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 3b67eb54..1ab3de01 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudApp.Data; +using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; @@ -14,350 +9,436 @@ 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, - IWellOperationRepository + IWellOperationRepository { - private readonly IMemoryCache memoryCache; - private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - private readonly IWellService wellService; + private const string cacheKeyWellOperations = "FirstAndLastFactWellsOperations"; + private readonly IMemoryCache memoryCache; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellService wellService; + private Lazy> LazyWellCategories { get; } + private Lazy> LazyWellSectionTypes { get; } - 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 WellOperationRepository(IAsbCloudDbContext context, + IMemoryCache memoryCache, + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellService wellService) + : base(context, dbSet => dbSet) + { + this.memoryCache = memoryCache; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellService = wellService; - public IEnumerable GetSectionTypes() => - memoryCache - .GetOrCreateBasic(dbContext.WellSectionTypes) - .OrderBy(s => s.Order) - .Select(s => s.Adapt()); + LazyWellCategories = new(() => wellOperationCategoryRepository.Get(true, false).ToDictionary(c => c.Id)); + LazyWellSectionTypes = new(() => GetSectionTypes().ToDictionary(c => c.Id)); + } - public async Task> GetAsync(WellOperationRequest request, CancellationToken token) - { - var query = BuildQuery(request); + public IEnumerable GetSectionTypes() => + memoryCache + .GetOrCreateBasic(dbContext.WellSectionTypes) + .OrderBy(s => s.Order) + .Select(s => s.Adapt()); - if (request.Skip.HasValue) - query = query.Skip(request.Skip.Value); + public async Task> GetAsync(WellOperationRequest request, CancellationToken token) + { + var (items, _) = await GetWithDaysAndNpvAsync(request, token); + return items; + } - if (request.Take.HasValue) - query = query.Take(request.Take.Value); + public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) + { + request.Skip = request.Skip ?? 0; + request.Take = request.Take ?? 32; - var entities = await query.AsNoTracking() - .ToArrayAsync(token); + var (items, count) = await GetWithDaysAndNpvAsync(request, token); - return await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token); - } + var paginationContainer = new PaginationContainer + { + Skip = request.Skip!.Value, + Take = request.Take!.Value, + Count = count, + Items = items + }; - public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) - { - var skip = request.Skip ?? 0; - var take = request.Take ?? 32; + return paginationContainer; + } - var query = BuildQuery(request); + 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 entities = await query.Skip(skip) - .Take(take) - .AsNoTracking() - .ToArrayAsync(token); + var parentRelationDictionary = wellOperationCategoryRepository.Get(true) + .ToDictionary(c => c.Id, c => new + { + c.Name, + c.IdParent + }); - var paginationContainer = new PaginationContainer - { - Skip = skip, - Take = take, - Count = await query.CountAsync(token), - Items = await ConvertWithDrillingDaysAndNpvHoursAsync(entities, token) - }; + 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 + }); - return paginationContainer; - } + 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; + }); + } - 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); + return dtos; + } - var parentRelationDictionary = wellOperationCategoryRepository.Get(true) - .ToDictionary(c => c.Id, c => new - { - c.Name, - c.IdParent - }); + public async Task InsertRangeAsync(IEnumerable dtos, + bool deleteBeforeInsert, + CancellationToken token) + { + EnsureValidWellOperations(dtos); - 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 - }); + var result = 0; - 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; - }); - } + if (!deleteBeforeInsert) + { + result = await InsertRangeAsync(dtos, token); - return dtos; - } + if (result > 0) + memoryCache.Remove(cacheKeyWellOperations); - public async Task InsertRangeAsync(IEnumerable dtos, - bool deleteBeforeInsert, - CancellationToken token) - { - EnsureValidWellOperations(dtos); + return result; + } + - if (!deleteBeforeInsert) - return await InsertRangeAsync(dtos, token); + var idType = dtos.First().IdType; + var idWell = dtos.First().IdWell; - 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); - var existingOperationIds = await dbContext.WellOperations - .Where(e => e.IdWell == idWell && e.IdType == idType) - .Select(e => e.Id) - .ToArrayAsync(token); + await DeleteRangeAsync(existingOperationIds, token); - await DeleteRangeAsync(existingOperationIds, token); + result = await InsertRangeAsync(dtos, token); - return await InsertRangeAsync(dtos, token); - } + if (result > 0) + memoryCache.Remove(cacheKeyWellOperations); - public override Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) - { - EnsureValidWellOperations(dtos); + return result; - return base.UpdateRangeAsync(dtos, token); - } + } - private static void EnsureValidWellOperations(IEnumerable dtos) - { - if (dtos.GroupBy(d => d.IdType).Count() > 1) - throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); + public override async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + { + EnsureValidWellOperations(dtos); - if (dtos.GroupBy(d => d.IdType).Count() > 1) - throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); - } + var result = await base.UpdateRangeAsync(dtos, token); - private IQueryable BuildQuery(WellOperationRequest request) - { - var query = GetQuery() - .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.IdWell)) - .OrderBy(e => e.DateStart) - .AsQueryable(); + if (result > 0) + memoryCache.Remove(cacheKeyWellOperations); - if (request.OperationType.HasValue) - query = query.Where(e => e.IdType == request.OperationType.Value); + return result; - 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)); + private static void EnsureValidWellOperations(IEnumerable dtos) + { + if (dtos.GroupBy(d => d.IdType).Count() > 1) + throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); - if (request.GeDepth.HasValue) - query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); + if (dtos.GroupBy(d => d.IdType).Count() > 1) + throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); + } - if (request.LeDepth.HasValue) - query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); + private async Task> GetByIdsWells(IEnumerable idsWells, CancellationToken token) + { + var query = GetQuery() + .Where(e => idsWells.Contains(e.IdWell)) + .OrderBy(e => e.DateStart); + var entities = await query.ToArrayAsync(token); + return entities; + } - if (request.GeDate.HasValue) - { - var geDateUtc = request.GeDate.Value.UtcDateTime; - query = query.Where(e => e.DateStart >= geDateUtc); - } + private async Task<(IEnumerable items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token) + { + var entities = await GetByIdsWells(request.IdsWell, token); + var groupedByWellAndType = entities + .GroupBy(e => new { e.IdWell, e.IdType }); - if (request.LeDate.HasValue) - { - var leDateUtc = request.LeDate.Value.UtcDateTime; - query = query.Where(e => e.DateStart <= leDateUtc); - } + var result = new List(); + var count = 0; + foreach (var wellOperationsWithType in groupedByWellAndType) + { + var firstWellOperation = wellOperationsWithType + .OrderBy(e => e.DateStart) + .FirstOrDefault()!; - if (request.SortFields?.Any() is true) - query = query.SortBy(request.SortFields); + var operationsWithNpt = wellOperationsWithType + .Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory)); - return query; - } + IEnumerable filteredWellOperations = FilterByRequest(wellOperationsWithType.AsQueryable(), request); - public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) - { - const string keyCacheSections = "OperationsBySectionSummarties"; + count += filteredWellOperations.Count(); - var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + if (request.Skip != null) + filteredWellOperations = filteredWellOperations.Skip((int)request.Skip); + if (request.Take != null) + filteredWellOperations = filteredWellOperations.Take((int)request.Take); - 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, + var dtos = filteredWellOperations + .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; + }); - First = group - .OrderBy(operation => operation.DateStart) - .Select(operation => new - { - operation.DateStart, - operation.DepthStart, - }) - .First(), + result.AddRange(dtos); + } - 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, + return (result, count); + } - Caption = item.Caption, + private static IQueryable FilterByRequest(IQueryable entities, WellOperationRequest request) + { + if (request.OperationType.HasValue) + entities = entities.Where(e => e.IdType == request.OperationType.Value); + if (request.SectionTypeIds?.Any() is true) + entities = entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + if (request.OperationCategoryIds?.Any() is true) + entities = entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + if (request.GeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd >= request.GeDepth.Value); + if (request.LeDepth.HasValue) + entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); - DateStart = item.First.DateStart, - DepthStart = item.First.DepthStart, + if (request.GeDate.HasValue) + { + var geDateUtc = request.GeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart >= geDateUtc); + } - DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), - DepthEnd = item.Last.DepthEnd, - }) - .ToArray() - .AsEnumerable(); + if (request.LeDate.HasValue) + { + var leDateUtc = request.LeDate.Value.UtcDateTime; + entities = entities.Where(e => e.DateStart <= leDateUtc); + } + if (request.SortFields?.Any() is true) + entities = entities.AsQueryable().SortBy(request.SortFields); + else + entities = entities.AsQueryable().OrderBy(e => e.DateStart); - entry.Value = sections; - return sections; - }); + return entities; + } - return cache; - } + private IQueryable BuildQuery(WellOperationRequest request) + { + var query = GetQuery() + .Where(e => request.IdsWell.Contains(e.IdWell)) + .OrderBy(e => e.DateStart) + .AsQueryable(); + query = FilterByRequest(query, request); - public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) - { - var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); + return query; + } - if (!await query.AnyAsync(cancellationToken)) - return null; + public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) + { + const string keyCacheSections = "OperationsBySectionSummarties"; - var timeZoneOffset = wellService.GetTimezone(idWell).Offset; - - var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); - var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); + var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - return new DatesRangeDto - { - From = minDate.ToOffset(timeZoneOffset), - To = maxDate.ToOffset(timeZoneOffset) - }; - } + 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, - private async Task> ConvertWithDrillingDaysAndNpvHoursAsync(IEnumerable entities, - CancellationToken token) - { - var idsWell = entities.Select(e => e.IdWell).Distinct(); + First = group + .OrderBy(operation => operation.DateStart) + .Select(operation => new + { + operation.DateStart, + operation.DepthStart, + }) + .First(), - var currentWellOperations = GetQuery() - .Where(entity => idsWell.Contains(entity.IdWell)); + 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, - var dateFirstDrillingOperationByIdWell = await currentWellOperations - .Where(entity => entity.IdType == WellOperation.IdOperationTypeFact) - .GroupBy(entity => entity.IdWell) - .ToDictionaryAsync(g => g.Key, g => g.Min(o => o.DateStart), token); + Caption = item.Caption, - var operationsWithNptByIdWell = await currentWellOperations.Where(entity => - entity.IdType == WellOperation.IdOperationTypeFact && - WellOperationCategory.NonProductiveTimeSubIds.Contains(entity.IdCategory)) - .GroupBy(entity => entity.IdWell) - .ToDictionaryAsync(g => g.Key, g => g.Select(o => o), token); + DateStart = item.First.DateStart, + DepthStart = item.First.DepthStart, - var dtos = entities.Select(entity => - { - var dto = Convert(entity); + DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), + DepthEnd = item.Last.DepthEnd, + }) + .ToArray() + .AsEnumerable(); - if (dateFirstDrillingOperationByIdWell.TryGetValue(entity.IdWell, out var dateFirstDrillingOperation)) - dto.Day = (entity.DateStart - dateFirstDrillingOperation).TotalDays; + entry.Value = sections; + return sections; + }); - if (operationsWithNptByIdWell.TryGetValue(entity.IdWell, out var wellOperationsWithNtp)) - dto.NptHours = wellOperationsWithNtp - .Where(o => o.DateStart <= entity.DateStart) - .Sum(e => e.DurationHours); + return cache!; + } - return dto; - }); + public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) + { + var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); - return dtos; - } + if (!await query.AnyAsync(cancellationToken)) + return null; - protected override WellOperation Convert(WellOperationDto src) - { - var entity = src.Adapt(); - entity.DateStart = src.DateStart.UtcDateTime; - return entity; - } + var timeZoneOffset = wellService.GetTimezone(idWell).Offset; - 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; - } + 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) + }; + } + + public (WellOperationDto First, WellOperationDto Last)? GetFirstAndLastFact(int idWell) + { + var cachedDictionary = memoryCache.GetOrCreate(cacheKeyWellOperations, (entry) => + { + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); + var query = dbContext.Set() + .Where(o => o.IdType == WellOperation.IdOperationTypeFact) + .GroupBy(o => o.IdWell) + .Select(group => new + { + IdWell = group.Key, + FirstFact = group.OrderBy(o => o.DateStart).First(), + LastFact = group.OrderBy(o => o.DateStart).Last(), + }); + + var entities = query.ToArray(); + + var dictionary = entities.ToDictionary(s => s.IdWell, s => (Convert(s.FirstFact), Convert(s.LastFact))); + entry.Value = dictionary; + + return dictionary; + + })!; + + var firstAndLast = cachedDictionary.GetValueOrDefault(idWell); + return firstAndLast; + + } + + public override async Task DeleteAsync(int id, CancellationToken token) + { + var result = await base.DeleteAsync(id, token); + if (result > 0) + memoryCache.Remove(cacheKeyWellOperations); + + return result; + } + + public override async Task DeleteRangeAsync(IEnumerable ids, CancellationToken token) + { + var result = await base.DeleteRangeAsync(ids, token); + if (result > 0) + memoryCache.Remove(cacheKeyWellOperations); + + return result; + } + + protected override WellOperation Convert(WellOperationDto src) + { + var entity = src.Adapt(); + 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); + + 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; + } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs index 17d92d8d..ea6cc79e 100644 --- a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs @@ -142,14 +142,13 @@ namespace AsbCloudInfrastructure.Services.SAUB return Task.CompletedTask; var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid); - var timezone = telemetryService.GetTimezone(telemetry.Id); - + foreach (var dto in dtos) { var entity = dto.Adapt(); entity.Id = 0; entity.IdTelemetry = telemetry.Id; - entity.DateTime = dto.Date.ToOffset(TimeSpan.FromHours(timezone.Hours)); + entity.DateTime = dto.Date.ToUniversalTime(); db.TelemetryMessages.Add(entity); } diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs index 53ed062b..0e07ee09 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs @@ -102,7 +102,7 @@ namespace AsbCloudInfrastructure.Services.SAUB if (dateBegin == default) { var dateRange = telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id); - dateBeginUtc = (dateRange?.To ?? DateTimeOffset.UtcNow) + dateBeginUtc = (dateRange?.To.ToUniversalTime() ?? DateTimeOffset.UtcNow) .AddSeconds(-intervalSec); } else diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index 0dcfe080..9d99c700 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -152,8 +152,8 @@ namespace AsbCloudInfrastructure.Services.SAUB if (!cacheItem.LastData.Any()) return null; - var from = cacheItem.FirstByDate.DateTime; - var to = cacheItem.LastData[^1].DateTime; + var from = DateTime.SpecifyKind(cacheItem.FirstByDate.DateTime, DateTimeKind.Unspecified); + var to = DateTime.SpecifyKind(cacheItem.LastData[^1].DateTime, DateTimeKind.Unspecified); return new DatesRangeDto { diff --git a/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs index cc944835..735a80a0 100644 --- a/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs +++ b/AsbCloudInfrastructure/Services/WellCompositeOperationService.cs @@ -144,7 +144,63 @@ public class WellCompositeOperationService : IWellCompositeOperationService { (6, 5095) }, { (6, 5012) }, { (6, 5040) }, - { (6, 5092) } + { (6, 5092) }, + + { (5, 5001) }, + { (5, 5015) }, + { (5, 5046) }, + { (5, 5037) }, + { (5, 5097) }, + { (5, 5057) }, + { (5, 5113) }, + { (5, 5036) }, + { (5, 5008) }, + { (5, 5003) }, + { (5, 5036) }, + { (5, 5013) }, + { (5, 5000) }, + { (5, 5029) }, + { (5, 5022) }, + { (5, 5017) }, + { (5, 5019) }, + { (5, 5042) }, + { (5, 5046) }, + + { (2, 5096) }, + { (2, 5008) }, + { (2, 5002) }, + { (2, 5003) }, + + { (3, 5096) }, + { (3, 5008) }, + { (3, 5002) }, + { (3, 5003) }, + { (3, 5085) }, + { (3, 5014) }, + + { (31, 5002) }, + { (31, 5003) }, + { (31, 5014) }, + { (31, 5012) }, + { (31, 5083) }, + + { (4, 5002) }, + { (4, 5003) }, + { (4, 5085) }, + { (4, 5087) }, + { (4, 5014) }, + { (4, 5053) }, + { (4, 5084) }, + { (4, 5086) }, + + { (6, 5002) }, + { (6, 5003) }, + { (6, 5085) }, + { (6, 5036) }, + { (6, 5035) }, + { (6, 5021) }, + { (6, 5086) }, + }; public WellCompositeOperationService( @@ -221,8 +277,8 @@ public class WellCompositeOperationService : IWellCompositeOperationService compositeOperation.DepthStart = compositeDepth; compositeDepth = compositeOperation.DepthStart; - compositeDay += compositeOperation.DurationHours; compositeOperation.Day = compositeDay / 24; + compositeDay += compositeOperation.DurationHours; compositeOperations.Add(compositeOperation); } diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 23054a2d..9c407490 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -37,12 +37,16 @@ public class WellInfoService var telemetryDataSaubCache = services.GetRequiredService>(); var messageHub = services.GetRequiredService>(); - var wells = await wellService.GetAllAsync(token); + var entries = await wellService.GetAllAsync(token); + var wells = entries.ToList(); var activeWells = wells.Where(well => well.IdState == 1); var wellsIds = activeWells.Select(w => w.Id); - var processMapPlanWellDrillingRequests = wellsIds.Select(id => new ProcessMapPlanBaseRequestWithWell(id)); + var processMapPlanWellDrillingRequests = wellsIds.Select(id => new ProcessMapPlanBaseRequestWithWell(id) + { + Moment = DateTimeOffset.UtcNow.AddDays(1) + }); var processMapPlanWellDrillings = new List(); foreach (var processMapPlanWellDrillingRequest in processMapPlanWellDrillingRequests) { @@ -96,14 +100,21 @@ public class WellInfoService int? idSection = wellLastFactSection?.Id; ProcessMapPlanDrillingDto? processMapPlanWellDrilling = null; - if (idSection.HasValue) + if(idSection.HasValue && currentDepth.HasValue) { - processMapPlanWellDrilling = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection); - } - else if (currentDepth.HasValue) + processMapPlanWellDrilling = wellProcessMaps + .Where(p => p.IdWellSectionType == idSection) + .Where(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value) + .FirstOrDefault(); + } + else if(currentDepth.HasValue) { processMapPlanWellDrilling = wellProcessMaps.FirstOrDefault(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value); } + else if (idSection.HasValue) + { + processMapPlanWellDrilling = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection); + } double? planTotalDepth = null; planTotalDepth = wellDepthByProcessMap.FirstOrDefault(p => p.Id == well.Id)?.DepthEnd; diff --git a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx index 928ffeb6..12fc2a44 100644 Binary files a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx and b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationFactTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx index ffacf2e5..ca2d01c7 100644 Binary files a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx and b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs index 430bf92f..66e52fd8 100644 --- a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs +++ b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs @@ -42,7 +42,7 @@ public class WellOperationExport : ExcelExportService well.Id == idWell); if (wellInfo is null) return well.Adapt(); - + wellInfo.IdState = well.IdState; return wellInfo; } @@ -153,7 +153,7 @@ namespace AsbCloudInfrastructure.Services { if (IsTelemetryAssignedToDifferentWell(dto)) throw new ArgumentInvalidException(nameof(dto), "Телеметрия уже была привязана к другой скважине."); - + if (dto.Id != 0 && (await GetCacheAsync(token)).Any(w => w.Id == dto.Id)) throw new ArgumentInvalidException(nameof(dto), $"Нельзя повторно добавить скважину с id: {dto.Id}"); @@ -177,12 +177,12 @@ namespace AsbCloudInfrastructure.Services throw new NotImplementedException(); } - public override async Task UpdateAsync(WellDto dto, + public override async Task UpdateAsync(WellDto dto, CancellationToken token) { if (IsTelemetryAssignedToDifferentWell(dto)) throw new ArgumentInvalidException(nameof(dto), "Телеметрия уже была привязана к другой скважине."); - + var oldRelations = (await GetCacheRelationCompanyWellAsync(token)) .Where(r => r.IdWell == dto.Id).ToArray(); @@ -192,16 +192,16 @@ namespace AsbCloudInfrastructure.Services dbContext.RelationCompaniesWells .RemoveRange(dbContext.RelationCompaniesWells .Where(r => r.IdWell == dto.Id)); - + DropCacheRelationCompanyWell(); var newRelations = dto.Companies .Select(c => new RelationCompanyWell { - IdWell = dto.Id, + IdWell = dto.Id, IdCompany = c.Id }); - + dbContext.RelationCompaniesWells.AddRange(newRelations); } @@ -215,7 +215,7 @@ namespace AsbCloudInfrastructure.Services public async Task GetWellCaptionByIdAsync(int idWell, CancellationToken token) { - var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); + var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); return entity!.Caption; } @@ -272,10 +272,9 @@ namespace AsbCloudInfrastructure.Services if (entity.Timezone is null) dto.Timezone = GetTimezone(entity.Id); - - dto.StartDate = dbContext.WellOperations.Where(e => e.IdType == WellOperation.IdOperationTypeFact) - .AsNoTracking() - .MinOrDefault(e => e.DateStart)?.ToRemoteDateTime(dto.Timezone.Hours); + + dto.StartDate = wellOperationRepository + .GetFirstAndLastFact(entity.Id)?.First?.DateStart; dto.WellType = entity.WellType.Caption; dto.Cluster = entity.Cluster.Caption; dto.Deposit = entity.Cluster.Deposit.Caption; diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IDrillTestControllerClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IDrillTestControllerClient.cs new file mode 100644 index 00000000..ba622557 --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Clients/IDrillTestControllerClient.cs @@ -0,0 +1,29 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.DrillTestReport; +using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Requests; +using Microsoft.AspNetCore.Mvc; +using Refit; + +namespace AsbCloudWebApi.IntegrationTests.Clients; + +public interface IDrillTestControllerClient +{ + [Post("/api/telemetry/{uid}/DrillTest")] + Task PostDataAsync( + string uid, + IEnumerable dtos, + CancellationToken token); + + [Get("/api/well/{idWell}/DrillTest")] + Task> GenerateReportAsync( + int idWell, + int id, + CancellationToken cancellationToken); + + [HttpGet("/api/well/{idWell}/DrillTest/all")] + Task>> GetListAsync( + int idWell, + FileReportRequest request, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs new file mode 100644 index 00000000..94f4293e --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs @@ -0,0 +1,23 @@ +using AsbCloudApp.Data.SAUB; +using Refit; + +namespace AsbCloudWebApi.IntegrationTests.Clients; +public interface ITelemetryControllerClient +{ + private const string BaseRoute = "/api/telemetry"; + + [Get($"{BaseRoute}/Active")] + Task GetTelemetriesInfoByLastData(CancellationToken token); + + [Post($"{BaseRoute}/{{uid}}/info")] + Task PostInfoAsync(string uid, [Body] TelemetryInfoDto info, CancellationToken token); + + [Post($"{BaseRoute}/{{uid}}/message")] + Task PostMessagesAsync(string uid, [Body] IEnumerable dtos, CancellationToken token); + + [Post($"{BaseRoute}/{{uid}}/event")] + Task PostEventsAsync(string uid, [Body] IEnumerable dtos, CancellationToken token); + + [Post($"{BaseRoute}/{{uid}}/user")] + Task PostUsersAsync(string uid, [Body] IEnumerable dtos, CancellationToken token); +} diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellClient.cs new file mode 100644 index 00000000..1e6961a5 --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellClient.cs @@ -0,0 +1,12 @@ +using AsbCloudApp.Data; +using Refit; + +namespace AsbCloudWebApi.IntegrationTests.Clients; + +public interface IWellClient +{ + private const string BaseRoute = "/api/well"; + + [Get(BaseRoute)] + Task>> GetWellsAsync(); +} diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/DrillControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/DrillControllerTest.cs index 638630ca..ed15f095 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/DrillControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/DrillControllerTest.cs @@ -1,9 +1,6 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Requests; using AsbCloudDb.Model; using AsbCloudWebApi.IntegrationTests.Clients; using AsbCloudWebApi.IntegrationTests.Data; -using System; using Xunit; namespace AsbCloudWebApi.IntegrationTests.Controllers; diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/DrillTestControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/DrillTestControllerTest.cs new file mode 100644 index 00000000..5b1da11b --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Controllers/DrillTestControllerTest.cs @@ -0,0 +1,77 @@ +using AsbCloudApp.Data.SAUB; +using AsbCloudDb.Model; +using AsbCloudWebApi.IntegrationTests.Clients; +using Xunit; + +namespace AsbCloudWebApi.IntegrationTests.Controllers; + +public class DrillTestControllerTest : BaseIntegrationTest +{ + private readonly IDrillTestControllerClient client; + static readonly string uid = DateTime.UtcNow.ToString("yyyyMMdd_HHmmssfff"); + private static readonly SimpleTimezone timezone = new() { TimezoneId = "a", Hours = 5 }; + private static readonly Telemetry telemetry = new Telemetry() { Id = 1, RemoteUid = uid, TimeZone = timezone, Info = new() }; + private readonly IEnumerable drillTests = [new DrillTestBaseDto { + DepthStart = 12, + Id = 1, + Params = [ new DrillTestParamsDto() { + DepthDrillStep = 1, + DepthSpeed = 2, + Speed = 3, + Step = 4, + TimeDrillStep = 5, + Workload = 6, + }, new DrillTestParamsDto() { + DepthDrillStep = 7, + DepthSpeed = 8, + Speed = 9, + Step = 10, + TimeDrillStep = 11, + Workload = 12, + }], + TimeStampStart = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(5)) + }]; + + public DrillTestControllerTest(WebAppFactoryFixture factory) + : base(factory) + { + client = factory.GetAuthorizedHttpClient(string.Empty); + } + + [Fact] + public async Task PostDataAsync() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + var response = await client.PostDataAsync(uid, drillTests, CancellationToken.None); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var count = dbContext.Set().Count(); + Assert.Equal(1, count); + } + + [Fact] + public async Task PostDataAsync_twice_should_be_ok() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + _ = await client.PostDataAsync(uid, drillTests, CancellationToken.None); + var response = await client.PostDataAsync(uid, drillTests, CancellationToken.None); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var count = dbContext.Set().Count(); + Assert.Equal(1, count); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs new file mode 100644 index 00000000..8622f11b --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs @@ -0,0 +1,233 @@ +using AsbCloudApp.Data.SAUB; +using AsbCloudDb.Model; +using AsbCloudWebApi.IntegrationTests.Clients; +using Xunit; + +namespace AsbCloudWebApi.IntegrationTests.Controllers; +public class TelemetryControllerTest : BaseIntegrationTest +{ + private ITelemetryControllerClient client; + static readonly string uid = DateTime.UtcNow.ToString("yyyyMMdd_HHmmssfff"); + + private static readonly SimpleTimezone timezone = new() {TimezoneId = "a", Hours = 5 }; + private static readonly Telemetry telemetry = new Telemetry() {Id = 1, RemoteUid = uid, TimeZone = timezone, Info = new() }; + private readonly IEnumerable events = [new() { Id = 1, EventType = 1, IdCategory = 1, IdSound = 1, Message = "there is no spoon {tag1}", Tag = "tag1" }]; + private readonly IEnumerable users = [new TelemetryUserDto() { Id = 1, Level = 0, Name = "Neo", Patronymic = "Kianovich", Surname = "Theone" }]; + private readonly IEnumerable messages = [new TelemetryMessageDto() { Id = 100, IdEvent = 1, WellDepth = 5, Date = DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(5)), Arg0 = "3.14", IdTelemetryUser = 1 }]; + private readonly IEnumerable telemetryDataSaubEntities = [new TelemetryDataSaub() + { + IdTelemetry = telemetry.Id, + DateTime = DateTimeOffset.UtcNow, + AxialLoad = 2, + WellDepth = 5, + BitDepth = 5, + BlockPosition = 5, + BlockSpeed = 5, + }]; + private readonly TelemetryInfoDto telemetryInfoDto = new() + { + TimeZoneId = timezone.TimezoneId, + TimeZoneOffsetTotalHours = timezone.Hours, + Cluster = "cluster1", + }; + + public TelemetryControllerTest(WebAppFactoryFixture factory) + : base(factory) + { + client = factory.GetAuthorizedHttpClient(string.Empty); + } + + [Fact] + public async Task GetTelemetriesInfoByLastData() + { + // Arrange + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.Set().AddRange(telemetryDataSaubEntities); + dbContext.SaveChanges(); + + // Act + var response = await client.GetTelemetriesInfoByLastData(CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task PostInfoAsync() + { + // arrange + dbContext.CleanupDbSet(); + + // act + var response = await client.PostInfoAsync(uid, telemetryInfoDto, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + } + + [Fact] + public async Task PostInfoAsync_twice_should_be_ok() + { + // arrange + dbContext.CleanupDbSet(); + + // act + _ = await client.PostInfoAsync(uid, telemetryInfoDto, CancellationToken.None); + var response = await client.PostInfoAsync(uid, telemetryInfoDto, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + } + + [Fact] + public async Task PostUsersAsync() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + var response = await client.PostUsersAsync(uid, users, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryUserCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryUserCount); + } + + [Fact] + public async Task PostUsers_twice_should_be_ok() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + _ = await client.PostUsersAsync(uid, users, CancellationToken.None); + var response = await client.PostUsersAsync(uid, users, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryUserCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryUserCount); + } + + [Fact] + public async Task PostEventsAsync() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + var response = await client.PostEventsAsync(uid, events, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryEventCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryEventCount); + } + + [Fact] + public async Task PostEventsAsync_twice_should_be_ok() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + _ = await client.PostEventsAsync(uid, events, CancellationToken.None); + var response = await client.PostEventsAsync(uid, events, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryEventCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryEventCount); + } + + [Fact] + public async Task PostMessagesAsync() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + _ = await client.PostEventsAsync(uid, events, CancellationToken.None); + _ = await client.PostUsersAsync(uid, users, CancellationToken.None); + var response = await client.PostMessagesAsync(uid, messages, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryEventCount = dbContext.Set().Count(); + var telemetryUserCount = dbContext.Set().Count(); + var telemetryMessageCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryEventCount); + Assert.Equal(1, telemetryUserCount); + Assert.Equal(1, telemetryMessageCount); + } + + [Fact] + public async Task PostMessagesAsync_twice_should_be_ok() + { + // arrange + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + dbContext.Set().Add(telemetry); + dbContext.SaveChanges(); + + // act + _ = await client.PostEventsAsync(uid, events, CancellationToken.None); + _ = await client.PostUsersAsync(uid, users, CancellationToken.None); + _ = await client.PostMessagesAsync(uid, messages, CancellationToken.None); + var response = await client.PostMessagesAsync(uid, messages, CancellationToken.None); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + var telemetriesCount = dbContext.Set().Count(); + var telemetryEventCount = dbContext.Set().Count(); + var telemetryUserCount = dbContext.Set().Count(); + var telemetryMessageCount = dbContext.Set().Count(); + + Assert.Equal(1, telemetriesCount); + Assert.Equal(1, telemetryEventCount); + Assert.Equal(1, telemetryUserCount); + Assert.Equal(2, telemetryMessageCount); + } +} diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellControllerTest.cs new file mode 100644 index 00000000..fe3fea64 --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellControllerTest.cs @@ -0,0 +1,67 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; +using AsbCloudDb.Model; +using AsbCloudInfrastructure; +using AsbCloudWebApi.IntegrationTests.Clients; +using Mapster; +using Microsoft.EntityFrameworkCore; +using System.Net; +using Xunit; + +namespace AsbCloudWebApi.IntegrationTests.Controllers; + +public class WellControllerTest : BaseIntegrationTest +{ + + private static readonly WellOperationDto wellOperationDto = new() + { + DateStart = DateTimeOffset.UtcNow, + Day = 1, + DepthEnd = 1000, + DepthStart = 500, + DurationHours = 5, + Id = 1, + IdCategory = 5095, + IdPlan = null, + IdType = 1, + IdUser = 1, + IdWell = 1, + IdWellSectionType = 1, + NptHours = 5 + }; + + private readonly IWellClient wellClient; + private readonly IWellOperationClient wellOperationClient; + + public WellControllerTest(WebAppFactoryFixture factory) + : base(factory) + { + wellClient = factory.GetAuthorizedHttpClient(string.Empty); + wellOperationClient = factory.GetAuthorizedHttpClient(string.Empty); + } + + [Fact] + public async Task CheckDateStartForWell_returns_success() + { + //act + var wellOperationDto1 = wellOperationDto.Adapt(); + wellOperationDto1.DateStart = DateTimeOffset.UtcNow; + wellOperationDto1.Id = 2; + + var wellOperations = new List() { wellOperationDto, wellOperationDto1 }; + var insertedRedult = await wellOperationClient.InsertRangeAsync(1, false, wellOperations); + + var wellResponse = await wellClient.GetWellsAsync(); + + //assert + Assert.Equal(HttpStatusCode.OK, wellResponse.StatusCode); + Assert.NotNull(wellResponse.Content); + + var expectedCount = await dbContext.Wells.CountAsync(); + Assert.Equal(expectedCount, wellResponse.Content.Count()); + + var actualFirstStartDate = wellResponse.Content.ElementAt(0).StartDate!.Value.ToUniversalTime(); + var expectedFirstStartDate = wellOperations.MinByOrDefault(o => o.DateStart)!.DateStart.ToUniversalTime(); + Assert.Equal(expectedFirstStartDate.ToString(), actualFirstStartDate.ToString()); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx index 45ee8a0b..a7af0814 100644 Binary files a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx and b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx differ diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx index 3083297e..d54dd669 100644 Binary files a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx and b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx differ diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs index 9d05aea6..117ddae1 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs @@ -27,7 +27,7 @@ public class WellOperationControllerTest : BaseIntegrationTest } /// - /// Успешное добавление операций (без предварительной очистки данных) + /// ( ) /// /// [Fact] @@ -46,7 +46,7 @@ public class WellOperationControllerTest : BaseIntegrationTest } /// - /// Успешное добавление операций (с предварительной очисткой данных) + /// ( ) /// /// [Fact] @@ -65,7 +65,7 @@ public class WellOperationControllerTest : BaseIntegrationTest } /// - /// Успешное обновление операций + /// /// /// [Fact] @@ -87,7 +87,7 @@ public class WellOperationControllerTest : BaseIntegrationTest } /// - /// Получение плановых операций + /// /// /// [Fact] @@ -144,7 +144,7 @@ public class WellOperationControllerTest : BaseIntegrationTest IdWellSectionType = 2, IdCategory = WellOperationCategory.IdSlide, IdPlan = null, - CategoryInfo = "Доп.инфо", + CategoryInfo = ".", IdType = idType, DepthStart = 10.0, DepthEnd = 20.0, @@ -201,7 +201,7 @@ public class WellOperationControllerTest : BaseIntegrationTest var stream = responseTemplate.Content; using var workbook = new XLWorkbook(stream); - var sheet = workbook.GetWorksheet("Справочники"); + var sheet = workbook.GetWorksheet(""); var count = sheet.RowsUsed().Count() - 1; @@ -230,21 +230,75 @@ public class WellOperationControllerTest : BaseIntegrationTest Assert.True(notExistedInDb.Count() == 0); } - private static WellOperation CreateWellOperation(int idWell, int idType = WellOperation.IdOperationTypePlan) => - new() - { - IdWell = idWell, - IdWellSectionType = 2, - IdCategory = WellOperationCategory.IdSlide, - IdPlan = null, - CategoryInfo = "Доп.инфо", - LastUpdateDate = new DateTimeOffset(new DateTime(2023, 1, 10)).ToUniversalTime(), - IdType = idType, - DepthStart = 10.0, - DepthEnd = 20.0, - DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Timezone.Hours)).ToUniversalTime(), - DurationHours = 1.0, - Comment = "1", - IdUser = 1, + [Theory] + [InlineData(WellOperation.IdOperationTypePlan)] + [InlineData(WellOperation.IdOperationTypeFact)] + public async Task GetPageOperationsAsyncWithDaysAndNpv_returns_success(int idType) + { + //arrange + const int pageSize = 10; + const int pageIndex = 0; + + var well = await dbContext.Wells.FirstAsync(); + var entity1 = CreateWellOperation(well.Id); + + var entity2 = entity1.Adapt(); + entity2.DateStart = entity2.DateStart.AddDays(1); + entity2.IdCategory = WellOperationCategory.IdEquipmentDrillingRepair; + entity2.DurationHours = 2; + + var entity3 = entity2.Adapt(); + entity3.DateStart = entity3.DateStart.AddDays(1); + entity3.IdCategory = WellOperationCategory.IdEquipmentDrillingRepair; + entity3.DurationHours = 3; + + dbContext.WellOperations.Add(entity1); + dbContext.WellOperations.Add(entity2); + dbContext.WellOperations.Add(entity3); + + await dbContext.SaveChangesAsync(); + + var request = new WellOperationRequestBase + { + OperationType = WellOperation.IdOperationTypePlan, + Skip = pageIndex, + Take = pageSize, + SortFields = [nameof(WellOperation.DateStart)] }; + + //act + var response = await client.GetPageOperationsAsync(well.Id, request); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + var items = response.Content.Items.ToArray(); + + Assert.Equal(0, items[0].Day); + Assert.Equal(1, items[1].Day); + Assert.Equal(2, items[2].Day); + + Assert.Equal(0, items[0].NptHours); + Assert.Equal(2, items[1].NptHours); + Assert.Equal(5, items[2].NptHours); + } + + private static WellOperation CreateWellOperation(int idWell, int idType = WellOperation.IdOperationTypePlan) => + new() + { + IdWell = idWell, + IdWellSectionType = 2, + IdCategory = WellOperationCategory.IdSlide, + IdPlan = null, + CategoryInfo = ".", + LastUpdateDate = new DateTimeOffset(new DateTime(2023, 1, 10)).ToUniversalTime(), + IdType = idType, + DepthStart = 10.0, + DepthEnd = 20.0, + DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Timezone.Hours)).ToUniversalTime(), + DurationHours = 1.0, + Comment = "1", + IdUser = 1, + }; } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Background/WorkTest.cs b/AsbCloudWebApi.Tests/Background/WorkTest.cs index 97a0c6c2..75628370 100644 --- a/AsbCloudWebApi.Tests/Background/WorkTest.cs +++ b/AsbCloudWebApi.Tests/Background/WorkTest.cs @@ -21,7 +21,7 @@ public class WorkTest ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock); } - [Fact] + [Fact, MethodImpl(MethodImplOptions.NoOptimization)] public async Task Start_ShouldReturn_Success() { //arrange @@ -50,7 +50,7 @@ public class WorkTest Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime); } - [Fact] + [Fact, MethodImpl(MethodImplOptions.NoOptimization)] public async Task ExecutionWork_Invokes_Callback() { //arrange diff --git a/AsbCloudWebApi/Controllers/DepositController.cs b/AsbCloudWebApi/Controllers/DepositController.cs index 9b8d0c91..4e9a287f 100644 --- a/AsbCloudWebApi/Controllers/DepositController.cs +++ b/AsbCloudWebApi/Controllers/DepositController.cs @@ -43,26 +43,6 @@ namespace AsbCloudWebApi.Controllers return Ok(result); } - /// - /// Получает список доступных пользователю месторождений (только скважины с параметрами бурения) - /// - /// Токен отмены задачи - /// - [HttpGet("drillParamsWells")] - [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public async Task GetDepositsDrillParamsAsync(CancellationToken token) - { - int? idCompany = User.GetCompanyId(); - - if (idCompany is null) - return Forbid(); - - var result = await depositService.GetAllWithDrillParamsAsync((int)idCompany, - token).ConfigureAwait(false); - return Ok(result); - } - /// /// Получает список доступных пользователю кустов месторождения /// diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 245b1f17..5c6383a2 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -16,6 +16,7 @@ using AsbCloudApp.Requests.ExportOptions; using AsbCloudApp.Requests.ParserOptions; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.WellOperations.Factories; +using System.Linq; namespace AsbCloudWebApi.Controllers; @@ -250,6 +251,35 @@ public class WellOperationController : ControllerBase return Ok(result); } + /// + /// Удаляет выбранные операции по скважине + /// + /// id скважины + /// ids выбранных операций + /// Токен отмены задачи + /// Количество удаленных из БД строк + [HttpDelete] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task DeleteRangeAsync([FromRoute] int idWell, IEnumerable ids, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, token)) + return Forbid(); + + if (!ids.Any()) + return this.ValidationBadRequest(nameof(ids), "Пустой список операций"); + + var result = await wellOperationRepository.DeleteRangeAsync(ids, token); + + if(result == ICrudRepository.ErrorIdNotFound) + return this.ValidationBadRequest(nameof(ids), "Минимум одна из операций не найдена в базе"); + + return Ok(result); + } + /// /// Формирование excel файла с операциями на скважине /// diff --git a/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs b/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs index 3c444b4a..53191eaf 100644 --- a/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs +++ b/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs @@ -44,6 +44,9 @@ namespace AsbCloudWebApi.Middlewares } catch (Exception ex) // TODO: find explicit exception. Use Trace. Add body size to message. { + if (context.Response.HasStarted) + throw; + context.Response.Clear(); context.Response.StatusCode = 500;