From 6738a3059219eed29436b92f5cd3eb07e02f18bd Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Wed, 24 Jan 2024 09:21:07 +0500 Subject: [PATCH 01/11] =?UTF-8?q?=D0=92=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20wel?= =?UTF-8?q?lOperation=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D0=B2=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B2=D0=BA=D0=BE=D0=B9,=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC,=20=D0=B8=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D1=80=D1=82=D0=BE=D0=BC...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/IWellOperationRepository.cs | 17 ++++- .../Repository/WellOperationRepository.cs | 73 ++++++++++++++++--- .../Controllers/WellOperationController.cs | 66 ++++++++++++----- 3 files changed, 125 insertions(+), 31 deletions(-) diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index d1db155e..d6302726 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -114,5 +114,20 @@ namespace AsbCloudApp.Repositories /// /// Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken); - } + + /// + /// Валидация данных + /// + /// + /// + bool Validate(IEnumerable wellOperations); + + /// + /// Валидация данных (проверка с базой) + /// + /// + /// + /// + Task ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index fb0a8c12..ba566bca 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -50,7 +50,7 @@ public class WellOperationRepository : IWellOperationRepository } var result = categories - .OrderBy(o => o.Name) + .OrderBy(o => o.Name) .Adapt>(); return result; @@ -89,14 +89,14 @@ public class WellOperationRepository : IWellOperationRepository } private async Task GetDateLastAssosiatedPlanOperationAsync( - int idWell, - DateTime? lessThenDate, - double timeZoneHours, + int idWell, + DateTime? lessThenDate, + double timeZoneHours, CancellationToken token) { - if (lessThenDate is null) + if (lessThenDate is null) return null; - + var currentDateOffset = lessThenDate.Value.ToUtcDateTimeOffset(timeZoneHours); var timeZoneOffset = TimeSpan.FromHours(timeZoneHours); @@ -187,7 +187,7 @@ public class WellOperationRepository : IWellOperationRepository public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) { var timezone = wellService.GetTimezone(idWell); - + var query = db.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); if (!await query.AnyAsync(cancellationToken)) @@ -195,7 +195,7 @@ public class WellOperationRepository : IWellOperationRepository var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); - + return new DatesRangeDto { From = minDate.ToRemoteDateTime(timezone.Hours), @@ -306,12 +306,13 @@ public class WellOperationRepository : IWellOperationRepository DeltaDepth = g.Sum(o => o.DurationDepth), IdParent = parentRelationDictionary[g.Key].IdParent }); - + while (dtos.All(x => x.IdParent != null)) { dtos = dtos .GroupBy(o => o.IdParent!) - .Select(g => { + .Select(g => + { var idCategory = g.Key ?? int.MinValue; var category = parentRelationDictionary.GetValueOrDefault(idCategory); var newDto = new WellGroupOpertionDto @@ -330,6 +331,58 @@ public class WellOperationRepository : IWellOperationRepository return dtos; } + public async Task ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token) + { + var firstOperation = wellOperationDtos + .FirstOrDefault(); + if (firstOperation is null) + return false; + + var request = new WellOperationRequest() + { + IdWell = firstOperation.IdWell, + OperationType = firstOperation.IdType, + }; + + var operationWithMaxDateStart = await BuildQuery(request) + .OrderByDescending(o => o.DateStart) + .FirstOrDefaultAsync(token); + + if (operationWithMaxDateStart is null) + return Validate(wellOperationDtos); + + var maxOperationDateStart = operationWithMaxDateStart.DateStart; + foreach (var dto in wellOperationDtos) + { + var currentOperationDateStart = dto.DateStart.ToUniversalTime(); + if (maxOperationDateStart!.AddMonths(3) < currentOperationDateStart) + return false; + + maxOperationDateStart = currentOperationDateStart; + } + return true; + } + + public bool Validate(IEnumerable wellOperationDtos) + { + var firstOperation = wellOperationDtos + .FirstOrDefault(); + if (firstOperation is null) + return false; + + var maxOperationDateStart = firstOperation.DateStart.ToUniversalTime(); + + foreach (var dto in wellOperationDtos) + { + var currentOperationDateStart = dto.DateStart.ToUniversalTime(); + if (maxOperationDateStart.AddMonths(3) < currentOperationDateStart) + return false; + + maxOperationDateStart = currentOperationDateStart; + } + return true; + } + /// public async Task InsertRangeAsync( IEnumerable wellOperationDtos, diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index ae0c8b92..fa479f12 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -1,7 +1,13 @@ using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperationImport; +using AsbCloudApp.Data.WellOperationImport.Options; +using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; +using AsbCloudApp.Services.WellOperationImport; +using AsbCloudDb.Model; +using AsbCloudInfrastructure; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -12,12 +18,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using AsbCloudApp.Data.WellOperationImport; -using AsbCloudApp.Services.WellOperationImport; -using AsbCloudApp.Data.WellOperationImport.Options; -using AsbCloudApp.Exceptions; -using AsbCloudDb.Model; -using AsbCloudInfrastructure; namespace AsbCloudWebApi.Controllers { @@ -39,8 +39,8 @@ namespace AsbCloudWebApi.Controllers private readonly IWellOperationExcelParser wellOperationGazpromKhantosExcelParser; private readonly IUserRepository userRepository; - public WellOperationController(IWellOperationRepository operationRepository, - IWellService wellService, + public WellOperationController(IWellOperationRepository operationRepository, + IWellService wellService, IWellOperationImportTemplateService wellOperationImportTemplateService, IWellOperationExportService wellOperationExportService, IWellOperationImportService wellOperationImportService, @@ -231,12 +231,15 @@ namespace AsbCloudWebApi.Controllers if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) return Forbid(); - + wellOperation.IdWell = idWell; wellOperation.LastUpdateDate = DateTimeOffset.UtcNow; wellOperation.IdUser = User.GetUserId(); wellOperation.IdType = idType; + if (!await operationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken)) + return this.ValidationBadRequest(nameof(wellOperation), "The date difference between the operations is more than 3 months"); + var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); return Ok(result); @@ -278,7 +281,7 @@ namespace AsbCloudWebApi.Controllers await operationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken); } - + foreach (var wellOperation in wellOperations) { wellOperation.IdWell = idWell; @@ -287,11 +290,31 @@ namespace AsbCloudWebApi.Controllers wellOperation.IdType = idType; } + + if (!await Validate(wellOperations, deleteBeforeInsert, cancellationToken)) + return this.ValidationBadRequest(nameof(wellOperations), "The date difference between the operations is more than 3 months"); + var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); return Ok(result); } + + /// + /// Валидация данных перед вставкой / обновлением / импортом + /// + /// + /// + /// + /// + private async Task Validate(IEnumerable wellOperations, bool deleteBeforeInsert, CancellationToken cancellationToken) + { + if (deleteBeforeInsert) + return operationRepository.Validate(wellOperations); + else + return await operationRepository.ValidateWithDbAsync(wellOperations, cancellationToken); + } + /// /// Обновляет выбранную операцию на скважине /// @@ -308,7 +331,7 @@ namespace AsbCloudWebApi.Controllers { if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); - + if (!await CanUserEditWellOperationsAsync(idWell, token)) return Forbid(); @@ -317,6 +340,9 @@ namespace AsbCloudWebApi.Controllers value.LastUpdateDate = DateTimeOffset.UtcNow; value.IdUser = User.GetUserId(); + if (!await operationRepository.ValidateWithDbAsync(new[] { value }, token)) + return this.ValidationBadRequest(nameof(value), "The date difference between the operations is more than 3 months"); + var result = await operationRepository.UpdateAsync(value, token) .ConfigureAwait(false); return Ok(result); @@ -336,7 +362,7 @@ namespace AsbCloudWebApi.Controllers { if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); - + if (!await CanUserEditWellOperationsAsync(idWell, token)) return Forbid(); @@ -373,7 +399,7 @@ namespace AsbCloudWebApi.Controllers deleteBeforeInsert, cancellationToken); } - + /// /// Импорт плановых операций из excel (xlsx) файла. Стандартный заполненный шаблон /// @@ -393,7 +419,7 @@ namespace AsbCloudWebApi.Controllers { IdType = WellOperation.IdOperationTypePlan }; - + return ImportExcelFileAsync(idWell, files, options, (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), null, @@ -522,7 +548,7 @@ namespace AsbCloudWebApi.Controllers return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); using Stream stream = file.OpenReadStream(); - + try { var sheet = parseMethod(stream, options); @@ -541,8 +567,8 @@ namespace AsbCloudWebApi.Controllers //TODO: очень быстрый костыль if (deleteBeforeInsert is not null && options.IdType == WellOperation.IdOperationTypeFact) { - return await InsertRangeAsync(idWell, options.IdType, - deleteBeforeInsert.Value, + return await InsertRangeAsync(idWell, options.IdType, + deleteBeforeInsert.Value, wellOperations, cancellationToken); } @@ -554,21 +580,21 @@ namespace AsbCloudWebApi.Controllers return this.ValidationBadRequest(nameof(files), ex.Message); } } - + private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token) { int? idCompany = User.GetCompanyId(); return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, idWell, token).ConfigureAwait(false); } - + private async Task CanUserEditWellOperationsAsync(int idWell, CancellationToken token) { var idUser = User.GetUserId(); if (!idUser.HasValue) return false; - + var well = await wellService.GetOrDefaultAsync(idWell, token); if (well is null) From f9504aea21bce37af2c7a4bc5619e2db77ce6206 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Wed, 24 Jan 2024 11:18:58 +0500 Subject: [PATCH 02/11] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20ValidateW?= =?UTF-8?q?ithDbAsync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/WellOperationRepository.cs | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index ba566bca..4f77a485 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -22,6 +22,8 @@ namespace AsbCloudInfrastructure.Repository; public class WellOperationRepository : IWellOperationRepository { private const string KeyCacheSections = "OperationsBySectionSummarties"; + private const int Gap = 90; + private readonly IAsbCloudDbContext db; private readonly IMemoryCache memoryCache; private readonly IWellService wellService; @@ -344,21 +346,31 @@ public class WellOperationRepository : IWellOperationRepository OperationType = firstOperation.IdType, }; - var operationWithMaxDateStart = await BuildQuery(request) - .OrderByDescending(o => o.DateStart) - .FirstOrDefaultAsync(token); + var entities = await BuildQuery(request) + .AsNoTracking() + .ToArrayAsync(token) + .ConfigureAwait(false); - if (operationWithMaxDateStart is null) + if (!entities.Any()) return Validate(wellOperationDtos); - var maxOperationDateStart = operationWithMaxDateStart.DateStart; - foreach (var dto in wellOperationDtos) - { - var currentOperationDateStart = dto.DateStart.ToUniversalTime(); - if (maxOperationDateStart!.AddMonths(3) < currentOperationDateStart) - return false; + var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart).ToArray(); - maxOperationDateStart = currentOperationDateStart; + for(var i = 1; i < wellOperationsUnion.Count(); i++) + { + var prevOperation = wellOperationsUnion[i - 1]; + var currentOperation = wellOperationsUnion[i]; + + var prevOperationDateStart = prevOperation.DateStart.ToUniversalTime(); + var currentOperationDateStart = currentOperation.DateStart.ToUniversalTime(); + + var prevOperationDateEnd = prevOperation.DateStart.AddHours(prevOperation.DurationHours).ToUniversalTime(); + var currrentOperationDateEnd = currentOperation.DateStart.AddHours(currentOperation.DurationHours).ToUniversalTime(); + + if (currentOperation.Id == 0 && ((prevOperationDateStart.AddDays(Gap) < currentOperationDateStart) || (prevOperationDateEnd >= currrentOperationDateEnd))) + { + return false; + } } return true; } @@ -375,7 +387,7 @@ public class WellOperationRepository : IWellOperationRepository foreach (var dto in wellOperationDtos) { var currentOperationDateStart = dto.DateStart.ToUniversalTime(); - if (maxOperationDateStart.AddMonths(3) < currentOperationDateStart) + if (maxOperationDateStart.AddDays(Gap) < currentOperationDateStart) return false; maxOperationDateStart = currentOperationDateStart; From 011a479a4b1ad278d99de9d9f4942b74e900b817 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Thu, 25 Jan 2024 10:35:16 +0500 Subject: [PATCH 03/11] =?UTF-8?q?=D0=92=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=B2=D1=81=D1=82=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20/=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20+=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8B=20(=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/IWellOperationRepository.cs | 5 +- .../Repository/WellOperationRepository.cs | 67 +++++----- .../Clients/IWellOperationClient.cs | 14 +++ .../WellOperationControllerTest.cs | 115 ++++++++++++++++++ .../Controllers/WellOperationController.cs | 13 +- 5 files changed, 173 insertions(+), 41 deletions(-) create mode 100644 AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs create mode 100644 AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index d6302726..43a7a568 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -2,6 +2,7 @@ using AsbCloudApp.Requests; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; @@ -120,7 +121,7 @@ namespace AsbCloudApp.Repositories /// /// /// - bool Validate(IEnumerable wellOperations); + IEnumerable Validate(IEnumerable wellOperations); /// /// Валидация данных (проверка с базой) @@ -128,6 +129,6 @@ namespace AsbCloudApp.Repositories /// /// /// - Task ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); + IEnumerable ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 4f77a485..8a389e74 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -333,12 +334,12 @@ public class WellOperationRepository : IWellOperationRepository return dtos; } - public async Task ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token) + public IEnumerable ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token) { var firstOperation = wellOperationDtos .FirstOrDefault(); if (firstOperation is null) - return false; + return Enumerable.Empty(); var request = new WellOperationRequest() { @@ -346,53 +347,51 @@ public class WellOperationRepository : IWellOperationRepository OperationType = firstOperation.IdType, }; - var entities = await BuildQuery(request) + var entities = BuildQuery(request) .AsNoTracking() - .ToArrayAsync(token) - .ConfigureAwait(false); + .ToArray(); - if (!entities.Any()) - return Validate(wellOperationDtos); + var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart); - var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart).ToArray(); + return Validate(wellOperationsUnion); + } - for(var i = 1; i < wellOperationsUnion.Count(); i++) + public IEnumerable Validate(IEnumerable wellOperationDtos) + { + var firstOperation = wellOperationDtos + .FirstOrDefault(); + if (firstOperation is null) + return Enumerable.Empty(); + + var validationResults = new List(); + + var wellOperations = wellOperationDtos.ToArray(); + for (var i = wellOperations.Count() - 1; i >= 1; i--) { - var prevOperation = wellOperationsUnion[i - 1]; - var currentOperation = wellOperationsUnion[i]; + var prevOperation = wellOperations[i - 1]; + var currentOperation = wellOperations[i]; var prevOperationDateStart = prevOperation.DateStart.ToUniversalTime(); var currentOperationDateStart = currentOperation.DateStart.ToUniversalTime(); var prevOperationDateEnd = prevOperation.DateStart.AddHours(prevOperation.DurationHours).ToUniversalTime(); - var currrentOperationDateEnd = currentOperation.DateStart.AddHours(currentOperation.DurationHours).ToUniversalTime(); - if (currentOperation.Id == 0 && ((prevOperationDateStart.AddDays(Gap) < currentOperationDateStart) || (prevOperationDateEnd >= currrentOperationDateEnd))) + if (prevOperationDateStart.AddDays(Gap) < currentOperationDateStart) { - return false; + validationResults.Add(new ValidationResult( + $"Разница дат между операциями не должна превышать 90 дней", + new[] { nameof(wellOperations) })); + } + + if (prevOperationDateEnd > currentOperationDateStart) + { + validationResults.Add(new ValidationResult( + $"Предыдущая операция не завершена", + new[] { nameof(wellOperations) })); } } - return true; - } - public bool Validate(IEnumerable wellOperationDtos) - { - var firstOperation = wellOperationDtos - .FirstOrDefault(); - if (firstOperation is null) - return false; - - var maxOperationDateStart = firstOperation.DateStart.ToUniversalTime(); - - foreach (var dto in wellOperationDtos) - { - var currentOperationDateStart = dto.DateStart.ToUniversalTime(); - if (maxOperationDateStart.AddDays(Gap) < currentOperationDateStart) - return false; - - maxOperationDateStart = currentOperationDateStart; - } - return true; + return validationResults; } /// diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs new file mode 100644 index 00000000..6192d306 --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -0,0 +1,14 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Requests; +using Refit; + +namespace AsbCloudWebApi.IntegrationTests.Clients; + +public interface IWellOperationClient +{ + private const string BaseRoute = "/api/wellOperation"; + + [Post(BaseRoute)] + Task> InsertRange(int idWell, [Body] IEnumerable dtos); +} \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs new file mode 100644 index 00000000..17130b5e --- /dev/null +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs @@ -0,0 +1,115 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Requests; +using AsbCloudDb.Model; +using AsbCloudDb.Model.ProcessMaps; +using AsbCloudWebApi.IntegrationTests.Clients; +using System.Net; +using Xunit; + +namespace AsbCloudWebApi.IntegrationTests.Controllers; + + +public class WellOperationControllerTest : BaseIntegrationTest +{ + private readonly int idWell = 4; + + private readonly WellOperationDto[] dtos = new WellOperationDto[] + { + new WellOperationDto() + { + + }, + new WellOperationDto() + { + + } + }; + + private IWellOperationClient wellOperationClient; + + public WellOperationControllerTest(WebAppFactoryFixture factory) + : base(factory) + { + wellOperationClient = factory.GetAuthorizedHttpClient(); + var rep = factory.Get + } + + /// + /// Успешное добавление операции с предварительной очисткой + /// + /// + [Fact] + public async Task InsertRangeWithDeleteBefore_returns_success() + { + ////arrange + //dbContext.WellOperations.Add(wellOperation); + //dbContext.SaveChanges(); + + //var request = new OperationStatRequest + //{ + // DateStartUTC = schedule.DrillStart.DateTime, + // DateEndUTC = schedule.DrillEnd.DateTime, + // DurationMinutesMin = 0, + // DurationMinutesMax = 5 + //}; + + //var dtoExpected = new SlipsStatDto + //{ + // DrillerName = $"{Data.Defaults.Drillers[0].Surname} {Data.Defaults.Drillers[0].Name} {Data.Defaults.Drillers[0].Patronymic}", + // WellCount = 1, + // SectionCaption = "Пилотный ствол", + // SlipsCount = 1, + // SlipsTimeInMinutes = (detectedOperation.DateEnd - detectedOperation.DateStart).TotalMinutes, + // SectionDepth = factWellOperation.DepthEnd - factWellOperation.DepthStart, + //}; + + ////act + //var response = await slipsTimeClient.GetAll(request); + + ////assert + //Assert.NotNull(response.Content); + //Assert.Single(response.Content); + + //var dtoActual = response.Content.First(); + //MatchHelper.Match(dtoExpected, dtoActual); + } + + /// + /// Успешное добавление операции без очистки + /// + /// + [Fact] + public async Task InsertRange_returns_success() { + //arrange + var dbset = dbContext.Set(); + dbset.RemoveRange(dbset); + dbContext.SaveChanges(); + + operationRepository.Validate(dtos); + + //act + var response = await wellOperationClient.InsertRange(idWell, dtos); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + /// + /// Неуспешное добавление операции с предварительной очисткой + /// + /// + [Fact] + public async Task InsertRangeWithDeleteBefore_returns_error() + { + } + + /// + /// Неуспешное добавление операции без очистки + /// + /// + [Fact] + public async Task InsertRange_returns_error() + { + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index fa479f12..5c0039cc 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -237,7 +237,8 @@ namespace AsbCloudWebApi.Controllers wellOperation.IdUser = User.GetUserId(); wellOperation.IdType = idType; - if (!await operationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken)) + var validationResult = operationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken); + if(validationResult.Any()) return this.ValidationBadRequest(nameof(wellOperation), "The date difference between the operations is more than 3 months"); var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); @@ -291,7 +292,8 @@ namespace AsbCloudWebApi.Controllers } - if (!await Validate(wellOperations, deleteBeforeInsert, cancellationToken)) + var validationResult = Validate(wellOperations, deleteBeforeInsert, cancellationToken); + if (validationResult.Any()) return this.ValidationBadRequest(nameof(wellOperations), "The date difference between the operations is more than 3 months"); var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); @@ -307,12 +309,12 @@ namespace AsbCloudWebApi.Controllers /// /// /// - private async Task Validate(IEnumerable wellOperations, bool deleteBeforeInsert, CancellationToken cancellationToken) + private IEnumerable Validate(IEnumerable wellOperations, bool deleteBeforeInsert, CancellationToken cancellationToken) { if (deleteBeforeInsert) return operationRepository.Validate(wellOperations); else - return await operationRepository.ValidateWithDbAsync(wellOperations, cancellationToken); + return operationRepository.ValidateWithDbAsync(wellOperations, cancellationToken); } /// @@ -340,7 +342,8 @@ namespace AsbCloudWebApi.Controllers value.LastUpdateDate = DateTimeOffset.UtcNow; value.IdUser = User.GetUserId(); - if (!await operationRepository.ValidateWithDbAsync(new[] { value }, token)) + var validationResult = operationRepository.ValidateWithDbAsync(new[] { value }, token); + if (validationResult.Any()) return this.ValidationBadRequest(nameof(value), "The date difference between the operations is more than 3 months"); var result = await operationRepository.UpdateAsync(value, token) From ff208a6aa8a168d29f3341efd2a9aaf9bd4529f3 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Thu, 25 Jan 2024 15:56:34 +0500 Subject: [PATCH 04/11] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=20(InsertRangeAsync)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BaseIntegrationTest.cs | 2 +- .../Clients/IWellOperationClient.cs | 9 +- .../WellOperationControllerTest.cs | 169 +++++++++++------- .../Data/Defaults.cs | 23 ++- .../WebAppFactoryFixture.cs | 3 +- 5 files changed, 133 insertions(+), 73 deletions(-) diff --git a/AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs b/AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs index d18c54bf..add2e07b 100644 --- a/AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs +++ b/AsbCloudWebApi.IntegrationTests/BaseIntegrationTest.cs @@ -6,7 +6,7 @@ namespace AsbCloudWebApi.IntegrationTests; public abstract class BaseIntegrationTest : IClassFixture { - private readonly IServiceScope scope; + protected readonly IServiceScope scope; protected readonly IAsbCloudDbContext dbContext; diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs index 6192d306..43497d6c 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -1,14 +1,13 @@ using AsbCloudApp.Data; -using AsbCloudApp.Data.ProcessMapPlan; -using AsbCloudApp.Requests; using Refit; namespace AsbCloudWebApi.IntegrationTests.Clients; public interface IWellOperationClient { - private const string BaseRoute = "/api/wellOperation"; + private const string BaseRoute = "/api/well/{idWell}/wellOperations"; + + [Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")] + Task> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable dtos); - [Post(BaseRoute)] - Task> InsertRange(int idWell, [Body] IEnumerable dtos); } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs index 17130b5e..2651243f 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs @@ -1,8 +1,4 @@ using AsbCloudApp.Data; -using AsbCloudApp.Data.ProcessMapPlan; -using AsbCloudApp.Requests; -using AsbCloudDb.Model; -using AsbCloudDb.Model.ProcessMaps; using AsbCloudWebApi.IntegrationTests.Clients; using System.Net; using Xunit; @@ -12,104 +8,147 @@ namespace AsbCloudWebApi.IntegrationTests.Controllers; public class WellOperationControllerTest : BaseIntegrationTest { - private readonly int idWell = 4; + private static int idWell = 1; private readonly WellOperationDto[] dtos = new WellOperationDto[] - { - new WellOperationDto() - { + { + new WellOperationDto() + { + Id = 2, + IdWell = idWell, + IdType = 1, + DateStart = DateTimeOffset.Now, + CategoryInfo = "1", + CategoryName = "1", + Comment = "1", + Day = 1, + DepthEnd = 20, + DepthStart = 10, + DurationHours = 1, + IdCategory = 5000, + IdParentCategory = null, + IdPlan = null, + IdUser = 1, + IdWellSectionType = 1, + LastUpdateDate = DateTimeOffset.Now, + NptHours = 1, + WellSectionTypeName = null, + UserName = null + } + }; - }, + private readonly WellOperationDto[] dtosWithError = new WellOperationDto[] + { new WellOperationDto() { - + Id = 3, + IdWell = idWell, + IdType = 1, + DateStart = DateTimeOffset.Now, + CategoryInfo = "1", + CategoryName = "1", + Comment = "1", + Day = 1, + DepthEnd = 20, + DepthStart = 10, + DurationHours = 1, + IdCategory = 5000, + IdParentCategory = null, + IdPlan = null, + IdUser = 1, + IdWellSectionType = 1, + LastUpdateDate = DateTimeOffset.Now, + NptHours = 1, + WellSectionTypeName = null, + UserName = null + }, + new WellOperationDto() + { + Id = 4, + IdWell = idWell, + IdType = 1, + DateStart = DateTimeOffset.Now.AddDays(1000), + CategoryInfo = "1", + CategoryName = "1", + Comment = "1", + Day = 1, + DepthEnd = 20, + DepthStart = 10, + DurationHours = 1, + IdCategory = 5000, + IdParentCategory = null, + IdPlan = null, + IdUser = 1, + IdWellSectionType = 1, + LastUpdateDate = DateTimeOffset.Now, + NptHours = 1, + WellSectionTypeName = null, + UserName = null } }; private IWellOperationClient wellOperationClient; public WellOperationControllerTest(WebAppFactoryFixture factory) - : base(factory) - { + : base(factory) + { wellOperationClient = factory.GetAuthorizedHttpClient(); - var rep = factory.Get } /// - /// Успешное добавление операции с предварительной очисткой + /// Успешное добавление операций (с предварительной очисткой данных) /// /// [Fact] - public async Task InsertRangeWithDeleteBefore_returns_success() - { - ////arrange - //dbContext.WellOperations.Add(wellOperation); - //dbContext.SaveChanges(); + public async Task InsertRange_returns_success() + { + //act + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtos); - //var request = new OperationStatRequest - //{ - // DateStartUTC = schedule.DrillStart.DateTime, - // DateEndUTC = schedule.DrillEnd.DateTime, - // DurationMinutesMin = 0, - // DurationMinutesMax = 5 - //}; + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } - //var dtoExpected = new SlipsStatDto - //{ - // DrillerName = $"{Data.Defaults.Drillers[0].Surname} {Data.Defaults.Drillers[0].Name} {Data.Defaults.Drillers[0].Patronymic}", - // WellCount = 1, - // SectionCaption = "Пилотный ствол", - // SlipsCount = 1, - // SlipsTimeInMinutes = (detectedOperation.DateEnd - detectedOperation.DateStart).TotalMinutes, - // SectionDepth = factWellOperation.DepthEnd - factWellOperation.DepthStart, - //}; - - ////act - //var response = await slipsTimeClient.GetAll(request); - - ////assert - //Assert.NotNull(response.Content); - //Assert.Single(response.Content); - - //var dtoActual = response.Content.First(); - //MatchHelper.Match(dtoExpected, dtoActual); - } /// - /// Успешное добавление операции без очистки + /// Неуспешное добавление операций (с предварительной очисткой данных) /// /// [Fact] - public async Task InsertRange_returns_success() { - //arrange - var dbset = dbContext.Set(); - dbset.RemoveRange(dbset); - dbContext.SaveChanges(); - - operationRepository.Validate(dtos); - + public async Task InsertRange_returns_error() + { //act - var response = await wellOperationClient.InsertRange(idWell, dtos); + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtosWithError); + + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + /// + /// Успешное добавление операций (с предварительной очисткой данных) + /// + /// + [Fact] + public async Task InsertRangeWithDeleteBefore_returns_success() + { + //act + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); } /// - /// Неуспешное добавление операции с предварительной очисткой + /// Неуспешное добавление операций (с предварительной очисткой данных) /// /// [Fact] public async Task InsertRangeWithDeleteBefore_returns_error() { - } + //act + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtosWithError); - /// - /// Неуспешное добавление операции без очистки - /// - /// - [Fact] - public async Task InsertRange_returns_error() - { + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs b/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs index ddb7dbe7..fc36eb3f 100644 --- a/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs +++ b/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs @@ -14,7 +14,28 @@ namespace AsbCloudWebApi.IntegrationTests.Data Surname = "test" } }; - + + public static WellOperation[] WellOperations = new WellOperation[] + { + new() + { + Id = 2, + IdWell = 1, + IdType = 1, + DateStart = DateTimeOffset.UtcNow.AddDays(-1), + CategoryInfo = "1", + Comment = "1", + DepthEnd = 20, + DepthStart = 10, + DurationHours = 1, + IdCategory = 5000, + IdPlan = null, + IdUser = 1, + IdWellSectionType = 1, + LastUpdateDate = DateTimeOffset.UtcNow + } + }; + public static Deposit[] Deposits = new Deposit[] { new() { diff --git a/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs b/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs index a0111303..af7303b6 100644 --- a/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs +++ b/AsbCloudWebApi.IntegrationTests/WebAppFactoryFixture.cs @@ -60,7 +60,8 @@ public class WebAppFactoryFixture : WebApplicationFactory, dbContext.AddRange(Data.Defaults.RelationsCompanyWell); dbContext.AddRange(Data.Defaults.Telemetries); dbContext.AddRange(Data.Defaults.Drillers); - await dbContext.SaveChangesAsync(); + dbContext.AddRange(Data.Defaults.WellOperations); + await dbContext.SaveChangesAsync(); } public new async Task DisposeAsync() From d4935b0e7b59a4530686bc8f271a1bf35591b486 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Thu, 25 Jan 2024 16:22:40 +0500 Subject: [PATCH 05/11] =?UTF-8?q?=D0=92=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20yield?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/WellOperationRepository.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 8a389e74..2e4e956a 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -339,7 +339,7 @@ public class WellOperationRepository : IWellOperationRepository var firstOperation = wellOperationDtos .FirstOrDefault(); if (firstOperation is null) - return Enumerable.Empty(); + yield break; var request = new WellOperationRequest() { @@ -353,7 +353,9 @@ public class WellOperationRepository : IWellOperationRepository var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart); - return Validate(wellOperationsUnion); + var results = Validate(wellOperationsUnion); + foreach ( var result in results) + yield return result; } public IEnumerable Validate(IEnumerable wellOperationDtos) @@ -361,12 +363,10 @@ public class WellOperationRepository : IWellOperationRepository var firstOperation = wellOperationDtos .FirstOrDefault(); if (firstOperation is null) - return Enumerable.Empty(); + yield break; - var validationResults = new List(); - - var wellOperations = wellOperationDtos.ToArray(); - for (var i = wellOperations.Count() - 1; i >= 1; i--) + var wellOperations = wellOperationDtos.OrderBy(o => o.DateStart).ToArray(); + for (var i = 1; i < wellOperations.Length; i++) { var prevOperation = wellOperations[i - 1]; var currentOperation = wellOperations[i]; @@ -378,20 +378,18 @@ public class WellOperationRepository : IWellOperationRepository if (prevOperationDateStart.AddDays(Gap) < currentOperationDateStart) { - validationResults.Add(new ValidationResult( + yield return new ValidationResult( $"Разница дат между операциями не должна превышать 90 дней", - new[] { nameof(wellOperations) })); + new[] { nameof(wellOperations) }); } if (prevOperationDateEnd > currentOperationDateStart) { - validationResults.Add(new ValidationResult( + yield return new ValidationResult( $"Предыдущая операция не завершена", - new[] { nameof(wellOperations) })); + new[] { nameof(wellOperations) }); } } - - return validationResults; } /// From 672f78fca99461b65b5f25de51a5efe0644e8d52 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 25 Jan 2024 16:47:04 +0500 Subject: [PATCH 06/11] WellOperationRepository refactor ValidateWithDbAsync and Validate --- .../Repositories/IWellOperationRepository.cs | 2 +- .../Repository/WellOperationRepository.cs | 48 ++++++++++--------- .../Controllers/WellOperationController.cs | 12 ++--- AsbCloudWebApi/Extentions.cs | 24 ++++++++++ 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index 43a7a568..8cb313ac 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -129,6 +129,6 @@ namespace AsbCloudApp.Repositories /// /// /// - IEnumerable ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); + Task> ValidateWithDbAsync(IEnumerable wellOperations, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 2e4e956a..208e94ac 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -334,12 +334,13 @@ public class WellOperationRepository : IWellOperationRepository return dtos; } - public IEnumerable ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token) + public async Task> ValidateWithDbAsync(IEnumerable wellOperationDtos, CancellationToken token) { var firstOperation = wellOperationDtos .FirstOrDefault(); + if (firstOperation is null) - yield break; + return Enumerable.Empty(); var request = new WellOperationRequest() { @@ -347,48 +348,49 @@ public class WellOperationRepository : IWellOperationRepository OperationType = firstOperation.IdType, }; - var entities = BuildQuery(request) + var entities = await BuildQuery(request) .AsNoTracking() - .ToArray(); + .ToArrayAsync(token); var wellOperationsUnion = entities.Union(wellOperationDtos).OrderBy(o => o.DateStart); var results = Validate(wellOperationsUnion); - foreach ( var result in results) - yield return result; + return results; } public IEnumerable Validate(IEnumerable wellOperationDtos) { - var firstOperation = wellOperationDtos - .FirstOrDefault(); - if (firstOperation is null) + var enumerator = wellOperationDtos.OrderBy(o => o.DateStart) + .GetEnumerator(); + + if (!enumerator.MoveNext()) yield break; - var wellOperations = wellOperationDtos.OrderBy(o => o.DateStart).ToArray(); - for (var i = 1; i < wellOperations.Length; i++) + var previous = enumerator.Current; + + while(enumerator.MoveNext()) { - var prevOperation = wellOperations[i - 1]; - var currentOperation = wellOperations[i]; + var current = enumerator.Current; + var previousDateStart = previous.DateStart.ToUniversalTime(); + var currentDateStart = current.DateStart.ToUniversalTime(); - var prevOperationDateStart = prevOperation.DateStart.ToUniversalTime(); - var currentOperationDateStart = currentOperation.DateStart.ToUniversalTime(); + var previousDateEnd = previous.DateStart.AddHours(previous.DurationHours).ToUniversalTime(); - var prevOperationDateEnd = prevOperation.DateStart.AddHours(prevOperation.DurationHours).ToUniversalTime(); - - if (prevOperationDateStart.AddDays(Gap) < currentOperationDateStart) + if (previousDateStart.AddDays(Gap) < currentDateStart) { yield return new ValidationResult( - $"Разница дат между операциями не должна превышать 90 дней", - new[] { nameof(wellOperations) }); + "Разница дат между операциями не должна превышать 90 дней", + new[] { nameof(wellOperationDtos) }); } - if (prevOperationDateEnd > currentOperationDateStart) + if (previousDateEnd > currentDateStart) { yield return new ValidationResult( - $"Предыдущая операция не завершена", - new[] { nameof(wellOperations) }); + "Предыдущая операция не завершена", + new[] { nameof(wellOperationDtos) }); } + + previous = current; } } diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 5c0039cc..79c7d900 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -237,9 +237,9 @@ namespace AsbCloudWebApi.Controllers wellOperation.IdUser = User.GetUserId(); wellOperation.IdType = idType; - var validationResult = operationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken); + var validationResult = awaitoperationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken); if(validationResult.Any()) - return this.ValidationBadRequest(nameof(wellOperation), "The date difference between the operations is more than 3 months"); + return this.ValidationBadRequest(validationResult); var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); @@ -294,7 +294,7 @@ namespace AsbCloudWebApi.Controllers var validationResult = Validate(wellOperations, deleteBeforeInsert, cancellationToken); if (validationResult.Any()) - return this.ValidationBadRequest(nameof(wellOperations), "The date difference between the operations is more than 3 months"); + return this.ValidationBadRequest(validationResult); var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); @@ -314,7 +314,7 @@ namespace AsbCloudWebApi.Controllers if (deleteBeforeInsert) return operationRepository.Validate(wellOperations); else - return operationRepository.ValidateWithDbAsync(wellOperations, cancellationToken); + return await operationRepository.ValidateWithDbAsync(wellOperations, cancellationToken); } /// @@ -342,9 +342,9 @@ namespace AsbCloudWebApi.Controllers value.LastUpdateDate = DateTimeOffset.UtcNow; value.IdUser = User.GetUserId(); - var validationResult = operationRepository.ValidateWithDbAsync(new[] { value }, token); + var validationResult = await operationRepository.ValidateWithDbAsync(new[] { value }, token); if (validationResult.Any()) - return this.ValidationBadRequest(nameof(value), "The date difference between the operations is more than 3 months"); + return this.ValidationBadRequest(validationResult); var result = await operationRepository.UpdateAsync(value, token) .ConfigureAwait(false); diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs index ba87612f..c064d2de 100644 --- a/AsbCloudWebApi/Extentions.cs +++ b/AsbCloudWebApi/Extentions.cs @@ -3,6 +3,8 @@ using AsbCloudWebApi.Converters; using System; using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Security.Claims; namespace Microsoft.AspNetCore.Mvc @@ -53,6 +55,28 @@ namespace Microsoft.AspNetCore.Mvc return controller.BadRequest(problem); } + /// + /// + /// Returns BadRequest with ValidationProblemDetails as body + /// + /// + /// Используйте этот метод только если валидацию нельзя сделать через + /// атрибуты валидации или IValidatableObject модели. + /// + /// + /// + /// + /// + /// + public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) + { + var errors = validationResults + .SelectMany(e => e.MemberNames.Select(name=> new { name, e.ErrorMessage })) + .ToDictionary(e => e.name, e => new[] { e.ErrorMessage ?? string.Empty }); + var problem = new ValidationProblemDetails(errors); + return controller.BadRequest(problem); + } + public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options) { TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); From 4bd0a835fce99f3f46071d72597fe91f47f14d40 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Thu, 25 Jan 2024 16:52:27 +0500 Subject: [PATCH 07/11] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudWebApi/Controllers/WellOperationController.cs | 8 ++++---- AsbCloudWebApi/Extentions.cs | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 79c7d900..74d4e16f 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -237,8 +237,8 @@ namespace AsbCloudWebApi.Controllers wellOperation.IdUser = User.GetUserId(); wellOperation.IdType = idType; - var validationResult = awaitoperationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken); - if(validationResult.Any()) + var validationResult = await operationRepository.ValidateWithDbAsync(new[] { wellOperation }, cancellationToken); + if (validationResult.Any()) return this.ValidationBadRequest(validationResult); var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); @@ -292,7 +292,7 @@ namespace AsbCloudWebApi.Controllers } - var validationResult = Validate(wellOperations, deleteBeforeInsert, cancellationToken); + var validationResult = await Validate(wellOperations, deleteBeforeInsert, cancellationToken); if (validationResult.Any()) return this.ValidationBadRequest(validationResult); @@ -309,7 +309,7 @@ namespace AsbCloudWebApi.Controllers /// /// /// - private IEnumerable Validate(IEnumerable wellOperations, bool deleteBeforeInsert, CancellationToken cancellationToken) + private async Task> Validate(IEnumerable wellOperations, bool deleteBeforeInsert, CancellationToken cancellationToken) { if (deleteBeforeInsert) return operationRepository.Validate(wellOperations); diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs index c064d2de..5659292c 100644 --- a/AsbCloudWebApi/Extentions.cs +++ b/AsbCloudWebApi/Extentions.cs @@ -65,8 +65,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// - /// - /// + /// /// public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) { From f6e638f8dbaceaa6098fad6de9925c6468bc94af Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Thu, 25 Jan 2024 17:13:56 +0500 Subject: [PATCH 08/11] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D1=8B:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20wellOperation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/IWellOperationClient.cs | 4 +++ .../WellOperationControllerTest.cs | 30 +++++++++++++++++++ .../Data/Defaults.cs | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs index 43497d6c..2db55d36 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using Microsoft.AspNetCore.Mvc; using Refit; namespace AsbCloudWebApi.IntegrationTests.Clients; @@ -10,4 +11,7 @@ public interface IWellOperationClient [Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")] Task> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable dtos); + [Put(BaseRoute + "/{idOperation}")] + Task> UpdateAsync(int idWell, int idOperation, [FromBody] WellOperationDto value, CancellationToken token); + } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs index 2651243f..809ccf1d 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs @@ -151,4 +151,34 @@ public class WellOperationControllerTest : BaseIntegrationTest //assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } + + /// + /// Успешное обновление операции + /// + /// + [Fact] + public async Task UpdateAsync_returns_success() + { + //act + var dto = dtos.FirstOrDefault()!; + var response = await wellOperationClient.UpdateAsync(idWell, 1, dto, CancellationToken.None); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + /// + /// Неуспешное обновление операции + /// + /// + [Fact] + public async Task UpdateAsync_returns_error() + { + //act + var dto = dtosWithError.LastOrDefault()!; + var response = await wellOperationClient.UpdateAsync(idWell, 1, dto, CancellationToken.None); + + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs b/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs index fc36eb3f..45219449 100644 --- a/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs +++ b/AsbCloudWebApi.IntegrationTests/Data/Defaults.cs @@ -19,7 +19,7 @@ namespace AsbCloudWebApi.IntegrationTests.Data { new() { - Id = 2, + Id = 1, IdWell = 1, IdType = 1, DateStart = DateTimeOffset.UtcNow.AddDays(-1), From 3337112be254023bf570564e8e6c107d2f9f47a0 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Fri, 26 Jan 2024 09:49:00 +0500 Subject: [PATCH 09/11] =?UTF-8?q?=D0=90=D0=B2=D1=82=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D1=8B=20(=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/WellOperationControllerTest.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs index 809ccf1d..3ac32e40 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudDb.Model; using AsbCloudWebApi.IntegrationTests.Clients; using System.Net; using Xunit; @@ -96,14 +97,15 @@ public class WellOperationControllerTest : BaseIntegrationTest } /// - /// Успешное добавление операций (с предварительной очисткой данных) + /// Успешное добавление операций (без предварительной очистки данных) /// /// [Fact] public async Task InsertRange_returns_success() { + dbContext.CleanupDbSet(); //act - var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtos); + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -111,14 +113,14 @@ public class WellOperationControllerTest : BaseIntegrationTest /// - /// Неуспешное добавление операций (с предварительной очисткой данных) + /// Неуспешное добавление операций (без предварительной очистки данных) /// /// [Fact] public async Task InsertRange_returns_error() { //act - var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtosWithError); + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtosWithError); //assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); @@ -132,7 +134,7 @@ public class WellOperationControllerTest : BaseIntegrationTest public async Task InsertRangeWithDeleteBefore_returns_success() { //act - var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtos); + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -146,7 +148,7 @@ public class WellOperationControllerTest : BaseIntegrationTest public async Task InsertRangeWithDeleteBefore_returns_error() { //act - var response = await wellOperationClient.InsertRangeAsync(idWell, 1, false, dtosWithError); + var response = await wellOperationClient.InsertRangeAsync(idWell, 1, true, dtosWithError); //assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); From 6d17500b2746b33e7ff1545193f76dbd53eb8bf0 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 26 Jan 2024 17:23:21 +0500 Subject: [PATCH 10/11] Add ChangeLogServiceAbstract --- .../Repositories/IChangeLogRepository.cs | 64 +++++++++++++---- .../IProcessMapPlanBaseRepository.cs | 52 -------------- .../Repositories/IProcessMapPlanRepository.cs | 2 + .../Services/ChangeLogServiceAbstract.cs | 69 +++++++++++++++++++ .../ProcessMapPlanBaseController.cs | 4 +- 5 files changed, 124 insertions(+), 67 deletions(-) delete mode 100644 AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs create mode 100644 AsbCloudApp/Services/ChangeLogServiceAbstract.cs diff --git a/AsbCloudApp/Repositories/IChangeLogRepository.cs b/AsbCloudApp/Repositories/IChangeLogRepository.cs index 9a5ab8c9..97db1dd9 100644 --- a/AsbCloudApp/Repositories/IChangeLogRepository.cs +++ b/AsbCloudApp/Repositories/IChangeLogRepository.cs @@ -1,15 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; +using AsbCloudApp.Requests; namespace AsbCloudApp.Repositories; /// /// Репозиторий для записей с историей /// -public interface IChangeLogRepository - where T : ChangeLogAbstract +public interface IChangeLogRepository + where TDto : ChangeLogAbstract + where TRequest : ChangeLogBaseRequest { /// /// Добавление записей @@ -18,7 +21,7 @@ public interface IChangeLogRepository /// /// /// - Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token); + Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token); /// /// Редактирование записей @@ -27,15 +30,50 @@ public interface IChangeLogRepository /// /// /// - Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token); + Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token); + + /// + /// Добавление записей с удалением старых (для импорта) + /// + /// + /// + /// + /// + Task Clear(int idUser, TRequest request, CancellationToken token); + + /// + /// Удаление записей + /// + /// + /// + /// + /// + Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token); + + /// + /// Получение дат изменений записей + /// + /// + /// + /// + Task> GetDatesChange(TRequest request, CancellationToken token); + + /// + /// Получение журнала изменений + /// + /// + /// + /// + /// + Task> GetChangeLog(TRequest request, DateOnly? date, CancellationToken token); + + /// + /// Получение записей по параметрам + /// + /// + /// + /// + Task> Get(TRequest request, CancellationToken token); - /// - /// Удаление записей - /// - /// - /// - /// - /// - Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token); } diff --git a/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs b/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs deleted file mode 100644 index 7524002a..00000000 --- a/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudApp.Data.ProcessMapPlan; -using AsbCloudApp.Requests; - -namespace AsbCloudApp.Repositories; - -/// -/// Общий интерфейс для РТК план с учетом истории изменений -/// -/// -public interface IProcessMapPlanBaseRepository: IChangeLogRepository - where T: ProcessMapPlanBaseDto -{ - /// - /// Добавление записей с удалением старых (для импорта) - /// - /// - /// - /// - /// - /// - Task ClearAndInsertRange(int idUser, int idWell, IEnumerable dtos, CancellationToken token); - - /// - /// Получение дат изменений записей - /// - /// - /// - /// - Task> GetDatesChange(int idWell, CancellationToken token); - - /// - /// Получение журнала изменений - /// - /// - /// - /// - /// - Task> GetChangeLog(int idWell, DateOnly? date, CancellationToken token); - - /// - /// Получение записей по параметрам - /// - /// - /// - /// - /// - Task> Get(int idWell, ProcessMapPlanBaseRequest request, CancellationToken token); -} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs b/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs index 1828b3ed..7e41cb58 100644 --- a/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs +++ b/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs @@ -4,12 +4,14 @@ using System.Threading; using AsbCloudApp.Data.ProcessMaps; using AsbCloudApp.Requests; using AsbCloudApp.Services; +using System; namespace AsbCloudApp.Repositories; /// /// РТК план /// +[Obsolete] public interface IProcessMapPlanRepository : IRepositoryWellRelated where TDto : ProcessMapPlanBaseDto { diff --git a/AsbCloudApp/Services/ChangeLogServiceAbstract.cs b/AsbCloudApp/Services/ChangeLogServiceAbstract.cs new file mode 100644 index 00000000..31202939 --- /dev/null +++ b/AsbCloudApp/Services/ChangeLogServiceAbstract.cs @@ -0,0 +1,69 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using System.Linq; +using System.ComponentModel.DataAnnotations; +using AsbCloudApp.Exceptions; + +namespace AsbCloudApp.Services +{ + /// + /// + /// + /// + /// + public abstract class ChangeLogServiceAbstract + where TDto : ChangeLogAbstract + where TRequest : ChangeLogBaseRequest + { + /// + /// Репозиторий + /// + public IChangeLogRepository repository { get; } + + /// + /// ctor + /// + /// + public ChangeLogServiceAbstract(IChangeLogRepository repository) + { + this.repository = repository; + } + + /// + /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0 + /// + /// + /// + /// + /// + public virtual async Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token) + { + var validationResults = Validate(dtos); + if (validationResults.Any()) + { + var errors = validationResults.SelectMany(r => r.MemberNames.Select(member => $"{member}: {r.ErrorMessage}")); + throw new ArgumentInvalidException(nameof(dtos), $"not valid: {string.Join("\n", errors)}"); + } + + var itemsToInsert = dtos.Where(e => e.Id == 0); + var itemsToUpdate = dtos.Where(e => e.Id != 0); + + var result = await repository.InsertRange(idUser, itemsToInsert, token); + result += await repository.UpdateRange(idUser, itemsToInsert, token); + + return result; + } + + /// + /// Валидация входных данных + /// + /// + /// + protected virtual IEnumerable Validate(IEnumerable dtos) + => Enumerable.Empty(); + } +} diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs index 1237e628..8d954a60 100644 --- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs +++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs @@ -23,10 +23,10 @@ namespace AsbCloudWebApi.Controllers.ProcessMapPlan; public abstract class ProcessMapPlanBaseController : ControllerBase where TDto : ProcessMapPlanBaseDto { - private readonly IProcessMapPlanBaseRepository repository; + private readonly ProcessMapPlanBaseService repository; private readonly IWellService wellService; - public ProcessMapPlanBaseController(IProcessMapPlanBaseRepository repository, IWellService wellService) + public ProcessMapPlanBaseController(ProcessMapPlanBaseService repository, IWellService wellService) { this.repository = repository; this.wellService = wellService; From acb4e25f128c97d7d6225b6571d9b177aead6944 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 29 Jan 2024 12:25:58 +0500 Subject: [PATCH 11/11] =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20ProcessMapPlanBaseRepository.?= =?UTF-8?q?=20=D0=9E=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=B0=D1=8F=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=20ChangeLogRepositoryAbstract.=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B=20=D1=81=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=BC=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/IChangeLogRepository.cs | 21 +- .../Requests/ProcessMapPlanBaseRequest.cs | 33 +++ .../Services/ChangeLogServiceAbstract.cs | 69 ----- AsbCloudDb/Model/ChangeLogAbstract.cs | 2 +- AsbCloudInfrastructure/DependencyInjection.cs | 9 +- .../Repository/ChangeLogRepositoryAbstract.cs | 279 ++++++++++++++++++ .../ProcessMapPlanBaseRepository.cs | 244 +-------------- .../Clients/IProcessMapPlanDrillingClient.cs | 5 +- .../ProcessMapPlanDrillingControllerTest.cs | 100 +++++-- .../ProcessMapPlanBaseController.cs | 49 ++- .../ProcessMapPlanDrillingController.cs | 5 +- 11 files changed, 471 insertions(+), 345 deletions(-) delete mode 100644 AsbCloudApp/Services/ChangeLogServiceAbstract.cs create mode 100644 AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs diff --git a/AsbCloudApp/Repositories/IChangeLogRepository.cs b/AsbCloudApp/Repositories/IChangeLogRepository.cs index 97db1dd9..44a6faed 100644 --- a/AsbCloudApp/Repositories/IChangeLogRepository.cs +++ b/AsbCloudApp/Repositories/IChangeLogRepository.cs @@ -32,6 +32,15 @@ public interface IChangeLogRepository /// Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token); + /// + /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0 + /// + /// + /// + /// + /// + Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token); + /// /// Добавление записей с удалением старых (для импорта) /// @@ -41,6 +50,16 @@ public interface IChangeLogRepository /// Task Clear(int idUser, TRequest request, CancellationToken token); + /// + /// Очистить и добавить новые + /// + /// + /// + /// + /// + /// + Task ClearAndInsertRange(int idUser, TRequest request, IEnumerable dtos, CancellationToken token); + /// /// Удаление записей /// @@ -62,7 +81,7 @@ public interface IChangeLogRepository /// Получение журнала изменений /// /// - /// + /// Фильтр по дате. Если null - вернет все /// /// Task> GetChangeLog(TRequest request, DateOnly? date, CancellationToken token); diff --git a/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs b/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs index 74fc0ec4..006c26e5 100644 --- a/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs +++ b/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs @@ -18,4 +18,37 @@ public class ProcessMapPlanBaseRequest: ChangeLogBaseRequest /// Вернуть данные, которые поменялись с указанной даты /// public DateTimeOffset? UpdateFrom { get; set; } +} + +/// +/// Запрос для получения РТК план по скважине +/// +public class ProcessMapPlanBaseRequestWithWell: ProcessMapPlanBaseRequest +{ + /// + /// Запрос для получения РТК план по скважине + /// + /// + public ProcessMapPlanBaseRequestWithWell(int idWell) + { + IdWell = idWell; + } + + /// + /// Запрос для получения РТК план по скважине + /// + /// + /// + public ProcessMapPlanBaseRequestWithWell(ProcessMapPlanBaseRequest request, int idWell) + { + IdWell=idWell; + IdWellSectionType=request.IdWellSectionType; + UpdateFrom = request.UpdateFrom; + Moment = request.Moment; + } + + /// + /// Id скважины + /// + public int IdWell { get; set; } } \ No newline at end of file diff --git a/AsbCloudApp/Services/ChangeLogServiceAbstract.cs b/AsbCloudApp/Services/ChangeLogServiceAbstract.cs deleted file mode 100644 index 31202939..00000000 --- a/AsbCloudApp/Services/ChangeLogServiceAbstract.cs +++ /dev/null @@ -1,69 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Repositories; -using AsbCloudApp.Requests; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Threading; -using System.Linq; -using System.ComponentModel.DataAnnotations; -using AsbCloudApp.Exceptions; - -namespace AsbCloudApp.Services -{ - /// - /// - /// - /// - /// - public abstract class ChangeLogServiceAbstract - where TDto : ChangeLogAbstract - where TRequest : ChangeLogBaseRequest - { - /// - /// Репозиторий - /// - public IChangeLogRepository repository { get; } - - /// - /// ctor - /// - /// - public ChangeLogServiceAbstract(IChangeLogRepository repository) - { - this.repository = repository; - } - - /// - /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0 - /// - /// - /// - /// - /// - public virtual async Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token) - { - var validationResults = Validate(dtos); - if (validationResults.Any()) - { - var errors = validationResults.SelectMany(r => r.MemberNames.Select(member => $"{member}: {r.ErrorMessage}")); - throw new ArgumentInvalidException(nameof(dtos), $"not valid: {string.Join("\n", errors)}"); - } - - var itemsToInsert = dtos.Where(e => e.Id == 0); - var itemsToUpdate = dtos.Where(e => e.Id != 0); - - var result = await repository.InsertRange(idUser, itemsToInsert, token); - result += await repository.UpdateRange(idUser, itemsToInsert, token); - - return result; - } - - /// - /// Валидация входных данных - /// - /// - /// - protected virtual IEnumerable Validate(IEnumerable dtos) - => Enumerable.Empty(); - } -} diff --git a/AsbCloudDb/Model/ChangeLogAbstract.cs b/AsbCloudDb/Model/ChangeLogAbstract.cs index f4065ea6..d9bc74e0 100644 --- a/AsbCloudDb/Model/ChangeLogAbstract.cs +++ b/AsbCloudDb/Model/ChangeLogAbstract.cs @@ -28,7 +28,7 @@ public abstract class ChangeLogAbstract /// /// Очищено при импорте /// - public const int IdClearedOnImport = 3; + public const int IdCleared = 3; /// /// Ид записи diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 0e88a972..913fdfe4 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -45,6 +45,7 @@ using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance; using AsbCloudDb.Model.WellSections; using AsbCloudInfrastructure.Services.ProcessMaps; using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Requests; namespace AsbCloudInfrastructure { @@ -195,10 +196,10 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -222,7 +223,9 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); - services.AddTransient, ProcessMapPlanBaseRepository>(); + services.AddTransient< + IChangeLogRepository, + ProcessMapPlanBaseRepository>(); services.AddTransient(); diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs new file mode 100644 index 00000000..84506a21 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs @@ -0,0 +1,279 @@ +using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository; + +public abstract class ChangeLogRepositoryAbstract : IChangeLogRepository + where TDto : AsbCloudApp.Data.ChangeLogAbstract + where TEntity : ChangeLogAbstract + where TRequest : ChangeLogBaseRequest +{ + protected readonly IAsbCloudDbContext context; + + public ChangeLogRepositoryAbstract(IAsbCloudDbContext context) + { + this.context = context; + } + + public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) + { + var result = 0; + if (dtos.Any()) + { + var entities = dtos.Select(Convert); + var creation = DateTimeOffset.UtcNow; + var dbSet = context.Set(); + foreach (var entity in entities) + { + entity.Id = default; + entity.IdAuthor = idUser; + entity.Creation = creation; + entity.IdState = ChangeLogAbstract.IdStateActual; + entity.IdEditor = null; + entity.IdPrevious = null; + entity.Obsolete = null; + dbSet.Add(entity); + } + + result += await SaveChangesWithExceptionHandling(token); + } + return result; + } + + public async Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token) + { + if (dtos.Any(d => d.Id == 0)) + throw new ArgumentInvalidException(nameof(dtos), "Отредактированные значения должны иметь id не 0"); + + if (!dtos.Any()) + return 0; + + var updateTime = DateTimeOffset.UtcNow; + + var ids = dtos.Select(d => d.Id); + + using var transaction = context.Database.BeginTransaction(); + var result = 0; + var dbSet = context + .Set(); + + var entitiesToDelete = await dbSet + .Where(e => e.Obsolete == null) + .Where(e => ids.Contains(e.Id)) + .ToArrayAsync(token); + + var entitiesNotFound = dtos.Where(d => !entitiesToDelete.Any(e => e.Id == d.Id)); + if (entitiesNotFound.Any()) + { + var notFoundIds = entitiesNotFound.Select(e => e.Id); + var stringnotFoundIds = string.Join(", ", notFoundIds); + throw new ArgumentInvalidException(nameof(dtos), $"записи с id:[{stringnotFoundIds}] не найдены, или не актуальны."); + } + + foreach (var entity in entitiesToDelete) + { + entity.IdState = ChangeLogAbstract.IdStateReplaced; + entity.Obsolete = updateTime; + entity.IdEditor = idUser; + } + result += await context.SaveChangesAsync(token); + + var entitiesNew = dtos.Select(Convert); + foreach (var entity in entitiesNew) + { + entity.IdPrevious = entity.Id; + entity.Id = default; + entity.Creation = updateTime; + entity.IdAuthor = idUser; + entity.Obsolete = null; + entity.IdEditor = null; + entity.IdState = ChangeLogAbstract.IdStateActual; + dbSet.Add(entity); + } + + result += await SaveChangesWithExceptionHandling(token); + await transaction.CommitAsync(token); + return result; + } + + public async Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token) + { + var itemsToInsert = dtos.Where(e => e.Id == 0); + var itemsToUpdate = dtos.Where(e => e.Id != 0); + + var result = 0; + if (itemsToInsert.Any()) + result += await InsertRange(idUser, itemsToInsert, token); + + if (itemsToUpdate.Any()) + result += await UpdateRange(idUser, itemsToUpdate, token); + + return result; + } + + public async Task Clear(int idUser, TRequest request, CancellationToken token) + { + var updateTime = DateTimeOffset.UtcNow; + var query = BuildQuery(request); + query = query.Where(e => e.Obsolete == null); + + var entitiesToDelete = await query.ToArrayAsync(token); + + foreach (var entity in entitiesToDelete) + { + entity.IdState = ChangeLogAbstract.IdCleared; + entity.Obsolete = updateTime; + entity.IdEditor = idUser; + } + + var result = await SaveChangesWithExceptionHandling(token); + return result; + } + + public async Task ClearAndInsertRange(int idUser, TRequest request, IEnumerable dtos, CancellationToken token) + { + var result = 0; + var transaction = await context.Database.BeginTransactionAsync(token); + result += await Clear(idUser, request, token); + result += await InsertRange(idUser, dtos, token); + await transaction.CommitAsync(token); + return result; + } + + public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token) + { + var updateTime = DateTimeOffset.UtcNow; + var query = context.Set() + .Where(e => ids.Contains(e.Id)) + .Where(e => e.Obsolete == null); + + var entitiesToDelete = await query.ToArrayAsync(token); + + foreach (var entity in entitiesToDelete) + { + entity.IdState = ChangeLogAbstract.IdStateDeleted; + entity.Obsolete = updateTime; + entity.IdEditor = idUser; + } + + var result = await SaveChangesWithExceptionHandling(token); + return result; + } + + public async Task> GetDatesChange(TRequest request, CancellationToken token) + { + var query = BuildQuery(request); + + var datesCreateQuery = query + .Select(e => e.Creation) + .Distinct(); + + var datesCreate = await datesCreateQuery.ToArrayAsync(token); + + var datesUpdateQuery = query + .Where(e => e.Obsolete != null) + .Select(e => e.Obsolete!.Value) + .Distinct(); + + var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); + + TimeSpan offset = GetTimezoneOffset(request); + + var dates = Enumerable.Concat(datesCreate, datesUpdate); + dates = dates.Select(date => date.ToOffset(offset)); + var datesOnly = dates + .Select(d => new DateOnly(d.Year, d.Month, d.Day)) + .Distinct(); + + return datesOnly; + } + + public async Task> GetChangeLog(TRequest request, DateOnly? date, CancellationToken token) + { + var query = BuildQuery(request); + TimeSpan offset = GetTimezoneOffset(request); + + if (date.HasValue) + { + var min = new DateTimeOffset(date.Value.Year, date.Value.Month, date.Value.Day, 0, 0, 0, offset).ToUniversalTime(); + var max = min.AddDays(1); + + var createdQuery = query.Where(e => e.Creation >= min && e.Creation <= max); + var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); + + query = createdQuery.Union(editedQuery); + } + + var entities = await query.ToListAsync(token); + var dtos = entities.Select(e => Convert(e, offset)); + + return dtos; + } + + public async Task> Get(TRequest request, CancellationToken token) + { + var query = BuildQuery(request); + var entities = await query.ToArrayAsync(token); + + TimeSpan offset = GetTimezoneOffset(request); + var dtos = entities.Select(e => Convert(e, offset)); + return dtos; + } + + protected abstract TimeSpan GetTimezoneOffset(TRequest request); + + protected abstract IQueryable BuildQuery(TRequest request); + + protected virtual TEntity Convert(TDto dto) + { + var entity = dto.Adapt(); + entity.Creation = entity.Creation.ToUniversalTime(); + + if(entity.Obsolete.HasValue) + entity.Obsolete = entity.Obsolete.Value.ToUniversalTime(); + + return entity; + } + + protected virtual TDto Convert(TEntity entity, TimeSpan offset) + { + var dto = entity.Adapt(); + dto.Creation = entity.Creation.ToOffset(offset); + + if (entity.Obsolete.HasValue) + dto.Obsolete = entity.Obsolete.Value.ToOffset(offset); + + return dto; + } + + private async Task SaveChangesWithExceptionHandling(CancellationToken token) + { + try + { + var result = await context.SaveChangesAsync(token); + return result; + } + catch (DbUpdateException ex) + { + if (ex.InnerException is PostgresException pgException) + TryConvertPostgresExceptionToValidateException(pgException); + throw; + } + } + + private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException) + { + if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation) + throw new ArgumentInvalidException("dtos", pgException.Message + "\r\n" + pgException.Detail); + } +} diff --git a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs index 11b4d0fd..0133b527 100644 --- a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs +++ b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs @@ -1,115 +1,36 @@ using AsbCloudApp.Data.ProcessMapPlan; -using AsbCloudApp.Exceptions; -using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudDb.Model.ProcessMapPlan; -using AsbCloudDb.Model.WellSections; -using Mapster; using Microsoft.EntityFrameworkCore; -using Npgsql; using System; -using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; -public class ProcessMapPlanBaseRepository : IProcessMapPlanBaseRepository +public class ProcessMapPlanBaseRepository : ChangeLogRepositoryAbstract where TDto : ProcessMapPlanBaseDto where TEntity : ProcessMapPlanBase { - private readonly IAsbCloudDbContext context; private readonly IWellService wellService; - public ProcessMapPlanBaseRepository(IAsbCloudDbContext context, IWellService wellService) + public ProcessMapPlanBaseRepository(IAsbCloudDbContext context, IWellService wellService) + : base(context) { - this.context = context; this.wellService = wellService; } - public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) + protected override IQueryable BuildQuery(ProcessMapPlanBaseRequestWithWell request) { - var result = 0; - if (dtos.Any()) - { - var entities = dtos.Select(Convert); - var creation = DateTimeOffset.UtcNow; - var dbSet = context.Set(); - foreach (var entity in entities) { - entity.Id = default; - entity.IdAuthor = idUser; - entity.Creation = creation; - entity.IdState = ChangeLogAbstract.IdStateActual; - entity.IdEditor = null; - entity.Editor = null; - entity.IdPrevious = null; - entity.Obsolete = null; - dbSet.Add(entity); - } - - result += await SaveChangesWithExceptionHandling(token); - } - return result; - } - - public async Task ClearAndInsertRange(int idUser, int idWell, IEnumerable dtos, CancellationToken token) - { - if (dtos.Any(d => d.IdWell != idWell)) - throw new ArgumentInvalidException(nameof(dtos), $"Все записи должны относиться к скважине idWell = {idWell}"); - - using var transaction = context.Database.BeginTransaction(); - var result = 0; - - var dbSet = context.Set(); - var entitiesToMarkDeleted = dbSet - .Where(e => e.IdWell == idWell) - .Where(e => e.Obsolete == null); - var obsolete = DateTimeOffset.UtcNow; - foreach (var entity in entitiesToMarkDeleted) - { - entity.IdState = ChangeLogAbstract.IdClearedOnImport; - entity.Obsolete = obsolete; - entity.IdEditor = idUser; - } - result += await SaveChangesWithExceptionHandling(token); - result += await InsertRange(idUser, dtos, token); - await transaction.CommitAsync(token); - - return result; - } - - public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token) - { - var dbSet = context.Set(); - var entitiesToMarkDeleted = dbSet - .Where(e => ids.Contains(e.Id)) - .Where(e => e.Obsolete == null); - var obsolete = DateTimeOffset.UtcNow; - foreach (var entity in entitiesToMarkDeleted) - { - entity.IdState = ChangeLogAbstract.IdStateDeleted; - entity.Obsolete = obsolete; - entity.IdEditor = idUser; - } - var result = await SaveChangesWithExceptionHandling(token); - return result; - } - - public async Task> Get(int idWell, ProcessMapPlanBaseRequest request, CancellationToken token) - { - var timezone = wellService.GetTimezone(idWell); - var offset = TimeSpan.FromHours(timezone.Hours); - var query = context .Set() .Include(e => e.Author) .Include(e => e.Editor) - .Where(e => e.IdWell == idWell); + .Include(e => e.Well) + .Where(e => e.IdWell == request.IdWell); - if(request.IdWellSectionType.HasValue) + if (request.IdWellSectionType.HasValue) query = query.Where(e => e.IdWellSectionType == request.IdWellSectionType); if (request.UpdateFrom.HasValue) @@ -126,156 +47,13 @@ public class ProcessMapPlanBaseRepository : IProcessMapPlanBaseRe .Where(e => e.Obsolete == null || e.Obsolete >= moment); } - var entities = await query.ToArrayAsync(token); - var dtos = entities.Select(e => Convert(e, offset)); - return dtos; + return query; } - public async Task> GetChangeLog(int idWell, DateOnly? date, CancellationToken token) + protected override TimeSpan GetTimezoneOffset(ProcessMapPlanBaseRequestWithWell request) { - var query = context - .Set() - .Include(e => e.Author) - .Include(e => e.Editor) - .Where(e => e.IdWell == idWell); - - var timezone = wellService.GetTimezone(idWell); + var timezone = wellService.GetTimezone(request.IdWell); var offset = TimeSpan.FromHours(timezone.Hours); - - if (date.HasValue) - { - var min = new DateTimeOffset(date.Value.Year, date.Value.Month, date.Value.Day, 0, 0, 0, offset).ToUniversalTime(); - var max = min.AddDays(1); - - var createdQuery = query.Where(e => e.Creation >= min && e.Creation <= max); - var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); - - query = createdQuery.Union(editedQuery); - } - - var entities = await query.ToListAsync(token); - var dtos = entities.Select(e => Convert(e, offset)); - - return dtos; - } - - public async Task> GetDatesChange(int idWell, CancellationToken token) - { - var wellEntitiesQuery = context - .Set() - .Where(e => e.IdWell == idWell); - - var datesCreateQuery = wellEntitiesQuery - .Select(e => e.Creation) - .Distinct(); - - var datesCreate = await datesCreateQuery.ToArrayAsync(token); - - var datesUpdateQuery = wellEntitiesQuery - .Where(e => e.Obsolete != null) - .Select(e => e.Obsolete!.Value) - .Distinct(); - - var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); - - var timezone = wellService.GetTimezone(idWell); - var offset = TimeSpan.FromHours(timezone.Hours); - - var dates = Enumerable.Concat( datesCreate, datesUpdate); - dates = dates.Select(date => date.ToOffset(offset)); - var datesOnly = dates - .Select(d => new DateOnly(d.Year, d.Month, d.Day)) - .Distinct(); - - return datesOnly; - } - - public async Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token) - { - if (dtos.Any(d => d.Id == 0)) - throw new ArgumentInvalidException(nameof(dtos), "Отредактированные значения должны иметь id больше 0"); - - if (!dtos.Any()) - return 0; - - using var transaction = context.Database.BeginTransaction(); - var result = 0; - - var ids = dtos.Select(d => d.Id); - var dbSet = context.Set(); - - var entitiesToDelete = dbSet - .Where(e => ids.Contains(e.Id)); - - var updateTime = DateTimeOffset.UtcNow; - foreach (var entity in entitiesToDelete) - { - if(entity.Obsolete is not null) - throw new ArgumentInvalidException(nameof(dtos), "Недопустимо редактировать устаревшие записи"); - entity.IdState = ChangeLogAbstract.IdStateReplaced; - entity.Obsolete = updateTime; - entity.IdEditor = idUser; - } - result += await context.SaveChangesAsync(token); - - var entitiesNew = dtos.Select(Convert); - foreach (var entity in entitiesNew) - { - entity.IdPrevious = entity.Id; - entity.Id = default; - entity.Creation = updateTime; - entity.IdAuthor = idUser; - entity.Obsolete = null; - entity.IdEditor = null; - entity.IdState = ChangeLogAbstract.IdStateActual; - dbSet.Add(entity); - } - - result += await SaveChangesWithExceptionHandling(token); - await transaction.CommitAsync(token); - return result; - } - - protected virtual TEntity Convert(TDto dto) - { - var entity = dto.Adapt(); - entity.Creation = entity.Creation.ToUniversalTime(); - - if(entity.Obsolete.HasValue) - entity.Obsolete = entity.Obsolete.Value.ToUniversalTime(); - - return entity; - } - - protected virtual TDto Convert(TEntity entity, TimeSpan offset) - { - var dto = entity.Adapt(); - dto.Creation = entity.Creation.ToOffset(offset); - - if (entity.Obsolete.HasValue) - dto.Obsolete = entity.Obsolete.Value.ToOffset(offset); - - return dto; - } - - private async Task SaveChangesWithExceptionHandling(CancellationToken token) - { - try - { - var result = await context.SaveChangesAsync(token); - return result; - } - catch (DbUpdateException ex) - { - if (ex.InnerException is PostgresException pgException) - TryConvertPostgresExceptionToValidateException(pgException); - throw; - } - } - - private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException) - { - if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation) - throw new ArgumentInvalidException("dtos", pgException.Message + "\r\n" + pgException.Detail); + return offset; } } diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs index 45dfd59a..7c8c65e3 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs @@ -18,6 +18,9 @@ public interface IProcessMapPlanDrillingClient [Delete(BaseRoute)] Task> DeleteRange(int idWell, [Body] IEnumerable ids); + [Delete($"{BaseRoute}/clear")] + Task> Clear(int idWell); + [Get(BaseRoute)] Task>> Get(int idWell, ProcessMapPlanBaseRequest request); @@ -28,5 +31,5 @@ public interface IProcessMapPlanDrillingClient Task>> GetDatesChange(int idWell); [Put(BaseRoute)] - Task> UpdateRangeAsync(int idWell, IEnumerable dtos); + Task> UpdateOrInsertRange(int idWell, IEnumerable dtos); } diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs index 7bdf3135..c0ba7093 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs @@ -179,7 +179,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest Assert.Equal(2, count); var oldEntity = dbset.First(p => p.Id == entry.Entity.Id); - Assert.Equal(ProcessMapPlanBase.IdClearedOnImport, oldEntity.IdState); + Assert.Equal(ProcessMapPlanBase.IdCleared, oldEntity.IdState); Assert.Equal(1, oldEntity.IdEditor); Assert.NotNull(oldEntity.Obsolete); Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime); @@ -194,44 +194,61 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest } [Fact] - public async Task UpdateRange_returns_success() + public async Task UpdateOrInsertRange_returns_success() { // arrange var startTime = DateTimeOffset.UtcNow; - + var dbset = dbContext.Set(); - + var entry = dbset.Add(entity); dbContext.SaveChanges(); entry.State = EntityState.Detached; - var dtoCopy = dto.Adapt(); - dtoCopy.Id = entry.Entity.Id; - dtoCopy.Comment = "nebuchadnezzar"; - dtoCopy.DeltaPressureLimitMax ++; - dtoCopy.DeltaPressurePlan ++; - dtoCopy.FlowPlan ++; - dtoCopy.FlowLimitMax ++; - dtoCopy.RopPlan ++; - dtoCopy.AxialLoadPlan ++; - dtoCopy.AxialLoadLimitMax ++; - dtoCopy.DepthStart ++; - dtoCopy.DepthEnd ++; - dtoCopy.TopDriveSpeedPlan ++; - dtoCopy.TopDriveSpeedLimitMax ++; - dtoCopy.TopDriveTorquePlan ++; - dtoCopy.TopDriveTorqueLimitMax ++; + var dtoUpdate = dto.Adapt(); + dtoUpdate.Id = entry.Entity.Id; + dtoUpdate.Comment = "nebuchadnezzar"; + dtoUpdate.DeltaPressureLimitMax++; + dtoUpdate.DeltaPressurePlan++; + dtoUpdate.FlowPlan++; + dtoUpdate.FlowLimitMax++; + dtoUpdate.RopPlan++; + dtoUpdate.AxialLoadPlan++; + dtoUpdate.AxialLoadLimitMax++; + dtoUpdate.DepthStart++; + dtoUpdate.DepthEnd++; + dtoUpdate.TopDriveSpeedPlan++; + dtoUpdate.TopDriveSpeedLimitMax++; + dtoUpdate.TopDriveTorquePlan++; + dtoUpdate.TopDriveTorqueLimitMax++; + + var dtoInsert = dtoUpdate.Adapt(); + dtoInsert.Id = 0; + dtoInsert.Comment = "nebuchad"; + dtoInsert.DeltaPressureLimitMax++; + dtoInsert.DeltaPressurePlan++; + dtoInsert.FlowPlan++; + dtoInsert.FlowLimitMax++; + dtoInsert.RopPlan++; + dtoInsert.AxialLoadPlan++; + dtoInsert.AxialLoadLimitMax++; + dtoInsert.DepthStart++; + dtoInsert.DepthEnd++; + dtoInsert.TopDriveSpeedPlan++; + dtoInsert.TopDriveSpeedLimitMax++; + dtoInsert.TopDriveTorquePlan++; + dtoInsert.TopDriveTorqueLimitMax++; // act - var result = await client.UpdateRangeAsync(entity.IdWell, new ProcessMapPlanDrillingDto[] { dtoCopy }); + var result = await client.UpdateOrInsertRange(entity.IdWell, new ProcessMapPlanDrillingDto[] { dtoUpdate, dtoInsert }); // assert var doneTime = DateTimeOffset.UtcNow; Assert.Equal(HttpStatusCode.OK, result.StatusCode); - Assert.Equal(2, result.Content); + Assert.Equal(3, result.Content); var count = dbset.Count(); - Assert.Equal(2, count); + Assert.Equal(3, count); var oldEntity = dbset.First(p => p.Id == entry.Entity.Id); Assert.Equal(ProcessMapPlanBase.IdStateReplaced, oldEntity.IdState); @@ -239,7 +256,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest Assert.NotNull(oldEntity.Obsolete); Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime); - var newEntity = dbset.First(p => p.Id != entry.Entity.Id); + var newEntity = dbset.First(p => p.Comment == dtoUpdate.Comment); Assert.Equal(ProcessMapPlanBase.IdStateActual, newEntity.IdState); Assert.Equal(1, newEntity.IdAuthor); Assert.Null(newEntity.IdEditor); @@ -247,7 +264,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest Assert.Equal(oldEntity.Id, newEntity.IdPrevious); Assert.InRange(newEntity.Creation, startTime, doneTime); - var expected = dtoCopy.Adapt(); + var expected = dtoUpdate.Adapt(); var excludeProps = new[] { nameof(ProcessMapPlanDrilling.Id), nameof(ProcessMapPlanDrilling.Author), @@ -289,6 +306,39 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest Assert.InRange(actual.Obsolete.Value, startTime, doneTime); } + + [Fact] + public async Task Clear_returns_success() + { + //arrange + var dbset = dbContext.Set(); + + var entry = dbset.Add(entity); + dbContext.SaveChanges(); + entry.State = EntityState.Detached; + + var startTime = DateTimeOffset.UtcNow; + + //act + var response = await client.Clear(dto.IdWell); + + //assert + var doneTime = DateTimeOffset.UtcNow; + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(1, response.Content); + + var actual = dbContext + .Set() + .FirstOrDefault(p => p.Id == entry.Entity.Id); + + Assert.NotNull(actual); + Assert.Equal(ProcessMapPlanBase.IdCleared, actual.IdState); + Assert.Equal(1, actual.IdEditor); + Assert.NotNull(actual.Obsolete); + Assert.InRange(actual.Obsolete.Value, startTime, doneTime); + } + + [Fact] public async Task GetDatesChange_returns_success() { diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs index 8d954a60..c3fd0c49 100644 --- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs +++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs @@ -23,10 +23,10 @@ namespace AsbCloudWebApi.Controllers.ProcessMapPlan; public abstract class ProcessMapPlanBaseController : ControllerBase where TDto : ProcessMapPlanBaseDto { - private readonly ProcessMapPlanBaseService repository; + private readonly IChangeLogRepository repository; private readonly IWellService wellService; - public ProcessMapPlanBaseController(ProcessMapPlanBaseService repository, IWellService wellService) + public ProcessMapPlanBaseController(IChangeLogRepository repository, IWellService wellService) { this.repository = repository; this.wellService = wellService; @@ -68,7 +68,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase return this.ValidationBadRequest(nameof(dtos), "all dtos should contain same idWell"); var idUser = await AssertUserHasAccessToWell(idWell, token); - var result = await repository.ClearAndInsertRange(idUser, idWell, dtos, token); + + var request = new ProcessMapPlanBaseRequestWithWell(idWell); + var result = await repository.ClearAndInsertRange(idUser, request, dtos, token); return Ok(result); } @@ -85,10 +87,29 @@ public abstract class ProcessMapPlanBaseController : ControllerBase public async Task DeleteRange([FromRoute]int idWell, IEnumerable ids, CancellationToken token) { var idUser = await AssertUserHasAccessToWell(idWell, token); + var result = await repository.DeleteRange(idUser, ids, token); return Ok(result); } + /// + /// Очистка + /// + /// + /// + /// + [HttpDelete("clear")] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task Clear([FromRoute] int idWell, CancellationToken token) + { + var idUser = await AssertUserHasAccessToWell(idWell, token); + + var request = new ProcessMapPlanBaseRequestWithWell(idWell); + var result = await repository.Clear(idUser, request, token); + return Ok(result); + } + /// /// Получение /// @@ -102,7 +123,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase public async Task>> Get([FromRoute] int idWell, [FromQuery]ProcessMapPlanBaseRequest request, CancellationToken token) { await AssertUserHasAccessToWell(idWell, token); - var result = await repository.Get(idWell, request, token); + + var serviceRequest = new ProcessMapPlanBaseRequestWithWell(request, idWell); + var result = await repository.Get(serviceRequest, token); return Ok(result); } @@ -119,7 +142,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase public async Task>> GetChangeLog([FromRoute] int idWell, [FromQuery] DateOnly? date, CancellationToken token) { await AssertUserHasAccessToWell(idWell, token); - var result = await repository.GetChangeLog(idWell, date, token); + + var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell); + var result = await repository.GetChangeLog(serviceRequest, date, token); return Ok(result); } @@ -135,24 +160,26 @@ public abstract class ProcessMapPlanBaseController : ControllerBase public async Task>> GetDatesChange([FromRoute] int idWell, CancellationToken token) { await AssertUserHasAccessToWell(idWell, token); - var result = await repository.GetDatesChange(idWell, token); + + var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell); + var result = await repository.GetDatesChange(serviceRequest, token); return Ok(result); } /// - /// Редактирование + /// Редактирование или добавление [для пакетного редактирования] /// /// /// /// /// - [HttpPut] + [HttpPut()] [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task UpdateRange([FromRoute] int idWell, IEnumerable dtos, CancellationToken token) + public async Task UpdateOrInsertRange([FromRoute] int idWell, IEnumerable dtos, CancellationToken token) { var first = dtos.FirstOrDefault(); - if(first is null) + if (first is null) return NoContent(); if (idWell == 0 || dtos.Any(d => d.IdWell != idWell)) @@ -160,7 +187,7 @@ public abstract class ProcessMapPlanBaseController : ControllerBase var idUser = await AssertUserHasAccessToWell(idWell, token); - var result = await repository.UpdateRange(idUser, dtos, token); + var result = await repository.UpdateOrInsertRange(idUser, dtos, token); return Ok(result); } diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs index 907f4ec5..b0a20f1e 100644 --- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs +++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs @@ -1,12 +1,15 @@ using AsbCloudApp.Data.ProcessMapPlan; using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; using AsbCloudApp.Services; namespace AsbCloudWebApi.Controllers.ProcessMapPlan; public class ProcessMapPlanDrillingController : ProcessMapPlanBaseController { - public ProcessMapPlanDrillingController(IProcessMapPlanBaseRepository repository, IWellService wellService) + public ProcessMapPlanDrillingController( + IChangeLogRepository repository, + IWellService wellService) : base(repository, wellService) { }