diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs index 9ab9189e..262b193f 100644 --- a/AsbCloudApp/Services/IDetectedOperationService.cs +++ b/AsbCloudApp/Services/IDetectedOperationService.cs @@ -72,6 +72,7 @@ namespace AsbCloudApp.Services /// /// /// + [Obsolete] Task> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token); /// diff --git a/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs index 944dabd5..80bc3a99 100644 --- a/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs @@ -133,18 +133,22 @@ namespace AsbCloudInfrastructure.Repository if (ids.Length != dtos.Count()) throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id"); - var dbSet = dbContext.Set(); - - var existingEntitiesCount = await dbSet + var existingEntitiesCount = await dbContext.Set() .Where(o => ids.Contains(o.Id)) .CountAsync(token); if (ids.Length != existingEntitiesCount) throw new ArgumentInvalidException(nameof(dtos), "Все записи должны существовать в БД"); + + var entities = dtos.Select(Convert); - var entities = dbContext.Set().Where(e => ids.Contains(e.Id)); - dbContext.Set().UpdateRange(entities); - return await dbContext.SaveChangesAsync(token); + var entries = entities.Select(entity => dbContext.Set().Update(entity)).ToList(); + + var affected = await dbContext.SaveChangesAsync(token); + + entries.ForEach(entry => entry.State = EntityState.Detached); + + return affected; } /// diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index 167dc4de..0cac62b2 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -143,6 +143,7 @@ public class DetectedOperationService : IDetectedOperationService } } + [Obsolete] public async Task> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token) { var well = await wellService.GetOrDefaultAsync(request.IdWell, token); diff --git a/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs b/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs index e68eeef3..d91c893d 100644 --- a/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs @@ -96,7 +96,7 @@ namespace AsbCloudInfrastructure.Services.SAUB return 0; entity.IdState = setpointsRequestDto.IdState; - var affected = await setpointsRepository.UpdateAsync(entity, token); + var affected = await setpointsRepository.UpdateRangeAsync(new[] { entity }, token); return affected; } diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs index 795d944b..a10ff824 100644 --- a/AsbCloudInfrastructure/Services/WellService.cs +++ b/AsbCloudInfrastructure/Services/WellService.cs @@ -274,6 +274,7 @@ namespace AsbCloudInfrastructure.Services 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.WellType = entity.WellType.Caption; dto.Cluster = entity.Cluster.Caption; diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs index b9e63518..9a9a5b7e 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -29,4 +29,10 @@ public interface IWellOperationClient [Get(BaseRoute + "/export")] Task> ExportAsync(int idWell, int idType); + + [Get(BaseRoute + "/template")] + Task> GetTemplate(int idWell, int idType); + + [Get(BaseRoute + "/categories")] + Task>> GetCategories(int idWell, bool includeParents, bool includeHidden); } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs index 13ed2fbc..4f44333e 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs @@ -1,193 +1,252 @@ -using System.Net; -using System.Reflection; using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Requests; using AsbCloudDb.Model; +using AsbCloudInfrastructure; using AsbCloudWebApi.IntegrationTests.Clients; using AsbCloudWebApi.IntegrationTests.Data; +using ClosedXML.Excel; using Mapster; using Microsoft.EntityFrameworkCore; using Refit; +using System.Net; +using System.Reflection; using Xunit; namespace AsbCloudWebApi.IntegrationTests.Controllers.WellOperations; public class WellOperationControllerTest : BaseIntegrationTest { - private IWellOperationClient client; + private IWellOperationClient client; - public WellOperationControllerTest(WebAppFactoryFixture factory) - : base(factory) - { - client = factory.GetAuthorizedHttpClient(string.Empty); + public WellOperationControllerTest(WebAppFactoryFixture factory) + : base(factory) + { + client = factory.GetAuthorizedHttpClient(string.Empty); - dbContext.CleanupDbSet(); - } + dbContext.CleanupDbSet(); + } - /// - /// Успешное добавление операций (без предварительной очистки данных) - /// - /// - [Fact] - public async Task InsertRange_returns_success() - { - //arrange - var well = await dbContext.Wells.FirstAsync(); - var entity = CreateWellOperation(well.Id); - var dtos = new[] { entity.Adapt() }; + /// + /// Успешное добавление операций (без предварительной очистки данных) + /// + /// + [Fact] + public async Task InsertRange_returns_success() + { + //arrange + var well = await dbContext.Wells.FirstAsync(); + var entity = CreateWellOperation(well.Id); + var dtos = new[] { entity.Adapt() }; - //act - var response = await client.InsertRangeAsync(well.Id, false, dtos); + //act + var response = await client.InsertRangeAsync(well.Id, false, dtos); - //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } - /// - /// Успешное добавление операций (с предварительной очисткой данных) - /// - /// - [Fact] - public async Task InsertRangeWithDeleteBefore_returns_success() - { - //arrange - var well = await dbContext.Wells.FirstAsync(); - var entity = CreateWellOperation(well.Id); - var dtos = new[] { entity.Adapt() }; + /// + /// Успешное добавление операций (с предварительной очисткой данных) + /// + /// + [Fact] + public async Task InsertRangeWithDeleteBefore_returns_success() + { + //arrange + var well = await dbContext.Wells.FirstAsync(); + var entity = CreateWellOperation(well.Id); + var dtos = new[] { entity.Adapt() }; - //act - var response = await client.InsertRangeAsync(well.Id, true, dtos); + //act + var response = await client.InsertRangeAsync(well.Id, true, dtos); - //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } - /// - /// Успешное обновление операций - /// - /// - [Fact] - public async Task UpdateRangeAsync_returns_success() - { - //arrange - var well = await dbContext.Wells.FirstAsync(); - var entity = CreateWellOperation(well.Id); - dbContext.WellOperations.Add(entity); - await dbContext.SaveChangesAsync(); + /// + /// Успешное обновление операций + /// + /// + [Fact] + public async Task UpdateRangeAsync_returns_success() + { + //arrange + var well = await dbContext.Wells.FirstAsync(); + var entity = CreateWellOperation(well.Id); + dbContext.WellOperations.Add(entity); + await dbContext.SaveChangesAsync(); - var dtos = new[] { entity.Adapt() }; + var dtos = new[] { entity.Adapt() }; - //act - var response = await client.UpdateRangeAsync(well.Id, dtos); + //act + var response = await client.UpdateRangeAsync(well.Id, dtos); - //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } - /// - /// Получение плановых операций - /// - /// - [Fact] - public async Task GetPageOperationsAsync_returns_first_page() - { - //arrange - const int pageSize = 10; - const int pageIndex = 0; - - var well = await dbContext.Wells.FirstAsync(); - var entity = CreateWellOperation(well.Id); - dbContext.WellOperations.Add(entity); - await dbContext.SaveChangesAsync(); + /// + /// Получение плановых операций + /// + /// + [Fact] + public async Task GetPageOperationsAsync_returns_first_page() + { + //arrange + const int pageSize = 10; + const int pageIndex = 0; - var dto = entity.Adapt(); - var timezoneOffset = TimeSpan.FromHours(well.Timezone.Hours); - dto.DateStart = dto.DateStart.ToOffset(timezoneOffset); - dto.LastUpdateDate = dto.LastUpdateDate?.ToOffset(timezoneOffset); + var well = await dbContext.Wells.FirstAsync(); + var entity = CreateWellOperation(well.Id); + dbContext.WellOperations.Add(entity); + await dbContext.SaveChangesAsync(); - var request = new WellOperationRequestBase - { - OperationType = WellOperation.IdOperationTypePlan, - Skip = pageIndex, - Take = pageSize, - }; + var dto = entity.Adapt(); + var timezoneOffset = TimeSpan.FromHours(well.Timezone.Hours); + dto.DateStart = dto.DateStart.ToOffset(timezoneOffset); + dto.LastUpdateDate = dto.LastUpdateDate?.ToOffset(timezoneOffset); - //act - var response = await client.GetPageOperationsAsync(well.Id, request); + var request = new WellOperationRequestBase + { + OperationType = WellOperation.IdOperationTypePlan, + Skip = pageIndex, + Take = pageSize, + }; - //assert - Assert.Equal(response.StatusCode, HttpStatusCode.OK); - Assert.NotNull(response.Content); + //act + var response = await client.GetPageOperationsAsync(well.Id, request); - var totalExpected = response.Content.Count - pageSize * pageIndex; - Assert.Equal(totalExpected, response.Content.Items.Count()); + //assert + Assert.Equal(response.StatusCode, HttpStatusCode.OK); + Assert.NotNull(response.Content); - Assert.Single(response.Content.Items); - var actualDto = response.Content.Items.First(); + var totalExpected = response.Content.Count - pageSize * pageIndex; + Assert.Equal(totalExpected, response.Content.Items.Count()); - MatchHelper.Match(dto, actualDto); - } + Assert.Single(response.Content.Items); + var actualDto = response.Content.Items.First(); - [Theory] - [InlineData(WellOperation.IdOperationTypePlan, "PlanWellOperations.xlsx")] - [InlineData(WellOperation.IdOperationTypeFact, "FactWellOperations.xlsx")] - public async Task ParseAsync_returns_success(int idType, string fileName) - { - //arrange - var well = await dbContext.Wells.FirstAsync(); + MatchHelper.Match(dto, actualDto); + } - var expectedDto = new WellOperationDto - { - IdWell = well.Id, - IdWellSectionType = 2, - IdCategory = WellOperationCategory.IdSlide, - IdPlan = null, - CategoryInfo = "Доп.инфо", - IdType = idType, - DepthStart = 10.0, - DepthEnd = 20.0, - DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(well.Timezone.Hours)), - DurationHours = 1.0, - Comment = "123", - }; + [Theory] + [InlineData(WellOperation.IdOperationTypePlan, "PlanWellOperations.xlsx")] + [InlineData(WellOperation.IdOperationTypeFact, "FactWellOperations.xlsx")] + public async Task ParseAsync_returns_success(int idType, string fileName) + { + //arrange + var well = await dbContext.Wells.FirstAsync(); - var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName); + var expectedDto = new WellOperationDto + { + IdWell = well.Id, + IdWellSectionType = 2, + IdCategory = WellOperationCategory.IdSlide, + IdPlan = null, + CategoryInfo = "Доп.инфо", + IdType = idType, + DepthStart = 10.0, + DepthEnd = 20.0, + DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(well.Timezone.Hours)), + DurationHours = 1.0, + Comment = "123", + }; - var streamPart = new StreamPart(stream, fileName, "application/octet-stream"); + var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName); - //act - var response = await client.ParseAsync(well.Id, idType, streamPart); + var streamPart = new StreamPart(stream, fileName, "application/octet-stream"); - //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.NotNull(response.Content); + //act + var response = await client.ParseAsync(well.Id, idType, streamPart); - var actualDto = response.Content.Item.First().Item; + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); - MatchHelper.Match(expectedDto, actualDto); - } + var actualDto = response.Content.Item.First().Item; - [Theory] - [InlineData(WellOperation.IdOperationTypePlan)] - [InlineData(WellOperation.IdOperationTypeFact)] - public async Task ExportAsync_returns_success(int idType) - { - //arrange - var well = await dbContext.Wells.FirstAsync(); + MatchHelper.Match(expectedDto, actualDto); + } - var entity = CreateWellOperation(well.Id, idType); - dbContext.WellOperations.Add(entity); - await dbContext.SaveChangesAsync(); + [Theory] + [InlineData(WellOperation.IdOperationTypePlan)] + [InlineData(WellOperation.IdOperationTypeFact)] + public async Task ExportAsync_returns_success(int idType) + { + //arrange + var well = await dbContext.Wells.FirstAsync(); - //act - var response = await client.ExportAsync(well.Id, idType); + var entity = CreateWellOperation(well.Id, idType); + dbContext.WellOperations.Add(entity); + await dbContext.SaveChangesAsync(); - //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("application/octet-stream", response.ContentHeaders?.ContentType?.MediaType); - Assert.True(response.ContentHeaders?.ContentLength > 0); - } + //act + var response = await client.ExportAsync(well.Id, idType); + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/octet-stream", response.ContentHeaders?.ContentType?.MediaType); + Assert.True(response.ContentHeaders?.ContentLength > 0); + } + + [Theory] + [InlineData(WellOperation.IdOperationTypePlan)] + [InlineData(WellOperation.IdOperationTypeFact)] + public async Task MatchCategoriesBetweenTemplateAndDb_returns_success(int idType) + { + //arrange + var well = await dbContext.Wells.FirstAsync(); + var responseTemplate = await client.GetTemplate(well.Id, idType); + var stream = responseTemplate.Content; + + using var workbook = new XLWorkbook(stream); + var sheet = workbook.GetWorksheet("Справочники"); + + var count = sheet.RowsUsed().Count() - 1; + + var categoryCaptionsInTemplate = new List(); + for (var i = 0; i < count; i++) + { + var xlRow = sheet.Row(i + 2); + var rowNumber = xlRow.RowNumber(); + + var xlCell = xlRow.Cell(1); + var cellValue = xlCell.GetText().Trim(); + + categoryCaptionsInTemplate.Add(cellValue); + } + + var responseCategories = await client.GetCategories(well.Id, false, false); + var categories = responseCategories.Content; + var categoryCaptionsInDb = categories!.Select(c => c.Name.Trim()); + + var notExistedInTemplate = categoryCaptionsInDb.Except(categoryCaptionsInTemplate); + var notExistedInDb = categoryCaptionsInTemplate.Except(categoryCaptionsInDb); + + //assert + Assert.True(categoryCaptionsInDb.Count() == categoryCaptionsInTemplate.Count()); + Assert.True(notExistedInTemplate.Count() == 0); + 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)] @@ -249,7 +308,7 @@ public class WellOperationControllerTest : BaseIntegrationTest IdWellSectionType = 2, IdCategory = WellOperationCategory.IdSlide, IdPlan = null, - CategoryInfo = "Доп.инфо", + CategoryInfo = ".", LastUpdateDate = new DateTimeOffset(new DateTime(2023, 1, 10)).ToUniversalTime(), IdType = idType, DepthStart = 10.0, diff --git a/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs index 271e9c7e..c6f5f5cf 100644 --- a/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs +++ b/AsbCloudWebApi.Tests/Services/WellCompositeOperation/WellCompositeOperationServiceTest.cs @@ -36,6 +36,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation new(){Id = 5036, Name = "Промывка"}, new(){Id = 5012, Name = "Подъем инструмента"}, new(){Id = 5083, Name = "Проработка принудительная"}, + new(){Id = 5113, Name = "Бурение"}, }; private readonly static IEnumerable sectionTypes = new List() @@ -224,7 +225,7 @@ namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation // assert var compositeWellOperations = result.WellOperationsComposite; Assert.Single(compositeWellOperations); - Assert.Equal(5003, compositeWellOperations.FirstOrDefault()!.IdCategory); + Assert.Equal(5113, compositeWellOperations.FirstOrDefault()!.IdCategory); Assert.Equal(1.5, compositeWellOperations.FirstOrDefault()!.DurationHours); Assert.Equal(10, compositeWellOperations.FirstOrDefault()!.DepthStart); } diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs index 11d7451d..fe5f289e 100644 --- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs @@ -1,4 +1,5 @@ -using AsbCloudApp.Data.DetectedOperation; +using System; +using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Requests; using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; @@ -110,7 +111,7 @@ namespace AsbCloudWebApi.Controllers.SAUB } /// - /// Получить фильтрованный список операций по телеметрии САУБ + /// Получить список автоопределенных операций для редактирования /// /// /// @@ -118,7 +119,7 @@ namespace AsbCloudWebApi.Controllers.SAUB /// [HttpGet] [ProducesResponseType(typeof(PaginationContainer), StatusCodes.Status200OK)] - public async Task GetAsync(int idWell, [FromQuery] DetectedOperationRequest request, + public async Task GetPageAsync(int idWell, [FromQuery] DetectedOperationRequest request, CancellationToken token) { await AssertUserHasAccessToWellAsync(idWell, token); @@ -133,24 +134,23 @@ namespace AsbCloudWebApi.Controllers.SAUB var result = await detectedOperationRepository.GetPageAsync(requestToService, token); return Ok(result); } - + /// - /// Получить статистику по фильтрованному списку операций по телеметрии САУБ + /// Получить статистику по автоопределенным операциям /// /// /// /// /// [HttpGet("stat")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task GetStatAsync(int idWell, [FromQuery] DetectedOperationRequest request, - CancellationToken token) + [ProducesResponseType(typeof(DetectedOperationListDto), StatusCodes.Status200OK)] + public async Task GetAsync(int idWell, [FromQuery] DetectedOperationRequest request, CancellationToken token) { await AssertUserHasAccessToWellAsync(idWell, token); - + var requestToService = new DetectedOperationByWellRequest(idWell, request); - var result = await detectedOperationService.GetOperationsStatAsync(requestToService, token); + var result = await detectedOperationService.GetAsync(requestToService, token); return Ok(result); }