diff --git a/AsbCloudApp/Data/ParserResultDto.cs b/AsbCloudApp/Data/ParserResultDto.cs index 5b6eca8d..ffb24d50 100644 --- a/AsbCloudApp/Data/ParserResultDto.cs +++ b/AsbCloudApp/Data/ParserResultDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace AsbCloudApp.Data; @@ -9,4 +10,8 @@ namespace AsbCloudApp.Data; public class ParserResultDto : ValidationResultDto>> where TDto : class, IId { + /// + /// Объекты полученные из файла + /// + public override IEnumerable> Item { get; set; } = Enumerable.Empty>(); } \ No newline at end of file diff --git a/AsbCloudApp/Data/ValidationResultDto.cs b/AsbCloudApp/Data/ValidationResultDto.cs index 0116fe0a..1fe0f918 100644 --- a/AsbCloudApp/Data/ValidationResultDto.cs +++ b/AsbCloudApp/Data/ValidationResultDto.cs @@ -18,7 +18,7 @@ public class ValidationResultDto /// /// Объект валидации /// - public T Item { get; set; } = null!; + public virtual T Item { get; set; } = null!; /// /// Предупреждения diff --git a/AsbCloudApp/Requests/ExportOptions/WellOperationExportRequest.cs b/AsbCloudApp/Requests/ExportOptions/WellOperationExportRequest.cs new file mode 100644 index 00000000..facbba72 --- /dev/null +++ b/AsbCloudApp/Requests/ExportOptions/WellOperationExportRequest.cs @@ -0,0 +1,20 @@ +namespace AsbCloudApp.Requests.ExportOptions; + +/// +/// Параметры экспорта ГГД +/// +public class WellOperationExportRequest : WellRelatedExportRequest +{ + /// + public WellOperationExportRequest(int idWell, + int idType) + : base(idWell) + { + IdType = idType; + } + + /// + /// Тип операций + /// + public int IdType { get; } +} \ No newline at end of file diff --git a/AsbCloudApp/Requests/ParserOptions/WellOperationParserRequest.cs b/AsbCloudApp/Requests/ParserOptions/WellOperationParserRequest.cs new file mode 100644 index 00000000..efb9aad2 --- /dev/null +++ b/AsbCloudApp/Requests/ParserOptions/WellOperationParserRequest.cs @@ -0,0 +1,29 @@ +using AsbCloudApp.Data; + +namespace AsbCloudApp.Requests.ParserOptions; + +/// +/// Параметры парсинга ГГД +/// +public class WellOperationParserRequest : WellRelatedParserRequest +{ + /// + public WellOperationParserRequest(int idWell, + int idType, + SimpleTimezoneDto wellTimezone) + : base(idWell) + { + IdType = idType; + WellTimezone = wellTimezone; + } + + /// + /// Тип операции + /// + public int IdType { get; } + + /// + /// Часовой пояс в котором находится скважина + /// + public SimpleTimezoneDto WellTimezone { get; } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 0d56b6bd..371127c4 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -45,11 +45,9 @@ - - - - + + @@ -77,4 +75,10 @@ CommonLibs\AsbWitsInfo.dll + + + + + + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 5ee77197..bcde8237 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -14,7 +14,6 @@ using AsbCloudApp.Services.DailyReport; using AsbCloudApp.Services.Notifications; using AsbCloudApp.Services.ProcessMaps; using AsbCloudApp.Services.ProcessMaps.WellDrilling; -using AsbCloudApp.Services.WellOperationImport; using AsbCloudDb.Model; using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance; using AsbCloudDb.Model.Manuals; @@ -36,8 +35,6 @@ using AsbCloudInfrastructure.Services.Subsystems; using AsbCloudInfrastructure.Services.Trajectory; using AsbCloudInfrastructure.Services.Trajectory.Export; using AsbCloudInfrastructure.Services.Trajectory.Parser; -using AsbCloudInfrastructure.Services.WellOperationImport; -using AsbCloudInfrastructure.Services.WellOperationImport.FileParser; using AsbCloudInfrastructure.Services.WellOperationService; using Mapster; using Microsoft.EntityFrameworkCore; @@ -46,6 +43,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using AsbCloudInfrastructure.Services.ProcessMapPlan.Export; +using AsbCloudInfrastructure.Services.WellOperations.Factories; namespace AsbCloudInfrastructure { @@ -160,7 +158,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddScoped(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -270,13 +267,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddTransient, WellOperationDefaultExcelParser>(); - services.AddTransient, WellOperationGazpromKhantosExcelParser>(); - services.AddTransient(); services.AddTransient(); @@ -300,6 +290,9 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/AsbCloudInfrastructure/Services/ExcelServices/Templates/WellOperations/WellOperationPlanTemplate.cs b/AsbCloudInfrastructure/Services/ExcelServices/Templates/WellOperations/WellOperationPlanTemplate.cs new file mode 100644 index 00000000..85e88db7 --- /dev/null +++ b/AsbCloudInfrastructure/Services/ExcelServices/Templates/WellOperations/WellOperationPlanTemplate.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Data.WellOperation; + +namespace AsbCloudInfrastructure.Services.ExcelServices.Templates.WellOperations; + +public class WellOperationPlanTemplate : ITemplateParameters +{ + public string SheetName => "План"; + + public int HeaderRowsCount => 1; + + public string FileName => "WellOperationPlanTemplate.xlsx"; + + public IDictionary Cells => new Dictionary() + { + { nameof(WellOperationDto.WellSectionTypeCaption), new Cell(1, typeof(string)) }, + { nameof(WellOperationDto.OperationCategoryName), new Cell(2, typeof(string)) }, + { nameof(WellOperationDto.CategoryInfo), new Cell(3, typeof(string)) }, + { nameof(WellOperationDto.DepthStart), new Cell(4, typeof(double)) }, + { nameof(WellOperationDto.DepthEnd), new Cell(5, typeof(double)) }, + { nameof(WellOperationDto.DateStart), new Cell(6, typeof(DateTime)) }, + { nameof(WellOperationDto.DurationHours), new Cell(7, typeof(double)) }, + { nameof(WellOperationDto.Comment), new Cell(8, typeof(string)) } + }; +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs new file mode 100644 index 00000000..1dc1d2a7 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationExportServiceFactory.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests.ExportOptions; +using AsbCloudApp.Services; +using AsbCloudApp.Services.Export; +using AsbCloudDb.Model; +using AsbCloudInfrastructure.Services.ExcelServices.Templates.WellOperations; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services.WellOperations.Factories; + +public class WellOperationExportServiceFactory : IExportServiceFactory +{ + private readonly IDictionary> exportServices; + + public WellOperationExportServiceFactory(IServiceProvider serviceProvider) + { + var wellOperationRepository = serviceProvider.GetRequiredService(); + var wellService = serviceProvider.GetRequiredService(); + + exportServices = new Dictionary> + { + { + WellOperation.IdOperationTypeFact, + () => new WellOperationExport(wellOperationRepository, wellService) + }, + { + WellOperation.IdOperationTypePlan, + () => new WellOperationExport(wellOperationRepository, wellService) + } + }; + } + + public IExportService CreateExportService(int id) + where TOptions : IExportOptionsRequest + { + var parser = exportServices[id].Invoke(); + + return parser as IExportService + ?? throw new ArgumentNullException(nameof(id), "Не удалось экспортировать файл"); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationParserFactory.cs b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationParserFactory.cs new file mode 100644 index 00000000..4107a7dd --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperations/Factories/WellOperationParserFactory.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudApp.Services.Parsers; +using AsbCloudDb.Model; +using AsbCloudInfrastructure.Services.ExcelServices.Templates.WellOperations; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services.WellOperations.Factories; + +public class WellOperationParserFactory : IParserFactory +{ + private readonly IDictionary> parsers; + + public WellOperationParserFactory(IServiceProvider serviceProvider) + { + var wellOperationRepository = serviceProvider.GetRequiredService(); + var categoryRepository = serviceProvider.GetRequiredService(); + + parsers = new Dictionary> + { + { + WellOperation.IdOperationTypeFact, + () => new WellOperationParser(wellOperationRepository, categoryRepository) + }, + { + WellOperation.IdOperationTypePlan, + () => new WellOperationParser(wellOperationRepository, categoryRepository) + } + }; + } + + public IParserService CreateParser(int id) + where TOptions : IParserOptionsRequest + { + var parser = parsers[id].Invoke(); + + return parser as IParserService + ?? throw new ArgumentNullException(nameof(id), "Не удалось распознать файл"); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx new file mode 100644 index 00000000..c5d53089 Binary files /dev/null and b/AsbCloudInfrastructure/Services/WellOperations/Templates/WellOperationPlanTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs new file mode 100644 index 00000000..31a49d2a --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests; +using AsbCloudApp.Requests.ExportOptions; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using AsbCloudInfrastructure.Services.ExcelServices; +using AsbCloudInfrastructure.Services.ExcelServices.Templates; + +namespace AsbCloudInfrastructure.Services.WellOperations; + +public class WellOperationExport : ExcelExportService + where TTemplate : class, ITemplateParameters, new() +{ + private readonly IWellService wellService; + private readonly IWellOperationRepository wellOperationRepository; + + public WellOperationExport(IWellOperationRepository wellOperationRepository, + IWellService wellService) + { + this.wellOperationRepository = wellOperationRepository; + this.wellService = wellService; + } + + protected override async Task BuildFileNameAsync(WellOperationExportRequest options, CancellationToken token) + { + var caption = await wellService.GetWellCaptionByIdAsync(options.IdWell, token); + + return options.IdType switch + { + WellOperation.IdOperationTypeFact => $"{caption}_Фактические_операции.xlsx", + WellOperation.IdOperationTypePlan => $"{caption}_Плановые_операции.xlsx", + _ => throw new ArgumentOutOfRangeException(nameof(options.IdType)) + }; + } + + protected override Task> GetDtosAsync(WellOperationExportRequest options, CancellationToken token) + { + var request = new WellOperationRequest + { + IdsWell = new[] { options.IdWell }, + OperationType = options.IdType + }; + + return wellOperationRepository.GetAsync(request, token); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellOperations/WellOperationParser.cs b/AsbCloudInfrastructure/Services/WellOperations/WellOperationParser.cs new file mode 100644 index 00000000..65bda0a2 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WellOperations/WellOperationParser.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Repositories; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudInfrastructure.Services.ExcelServices; +using AsbCloudInfrastructure.Services.ExcelServices.Templates; + +namespace AsbCloudInfrastructure.Services.WellOperations; + +public class WellOperationParser : ExcelWellRelatedParser + where TTemplateParameters : class, ITemplateParameters, new() +{ + private readonly IEnumerable sectionTypes; + private readonly IEnumerable categories; + + public WellOperationParser(IWellOperationRepository wellOperationRepository, + IWellOperationCategoryRepository categoryRepository) + { + categories = categoryRepository.Get(false); + sectionTypes = wellOperationRepository.GetSectionTypes(); + } + + public override ParserResultDto Parse(Stream file, WellOperationParserRequest options) + { + var result = base.Parse(file, options); + + foreach (var dto in result.Item) + { + dto.Item.IdWell = options.IdWell; + dto.Item.IdType = options.IdType; + dto.Item.DateStart = new DateTimeOffset(dto.Item.DateStart.DateTime, options.WellTimezone.Offset); + } + + return result; + } + + protected override WellOperationDto BuildDto(IDictionary row, int rowNumber) + { + var dto = base.BuildDto(row, rowNumber); + + var sectionType = sectionTypes.FirstOrDefault(s => + string.Equals(s.Caption.Trim(), dto.WellSectionTypeCaption?.Trim(), StringComparison.CurrentCultureIgnoreCase)); + + if (sectionType is null) + { + var message = string.Format(XLExtentions.ProblemDetailsTemplate, + TemplateParameters.SheetName, + rowNumber, + TemplateParameters.Cells[nameof(WellOperationDto.WellSectionTypeCaption)], + "Указана некорректная секция"); + throw new FileFormatException(message); + } + + var category = categories.FirstOrDefault(c => + string.Equals(c.Name.Trim(), dto.OperationCategoryName?.Trim(), StringComparison.CurrentCultureIgnoreCase)); + + if (category is null) + { + var message = string.Format(XLExtentions.ProblemDetailsTemplate, + TemplateParameters.SheetName, + rowNumber, + TemplateParameters.Cells[nameof(WellOperationDto.OperationCategoryName)], + "Указана некорректная операция"); + throw new FileFormatException(message); + } + + dto.IdWellSectionType = sectionType.Id; + dto.IdCategory = category.Id; + dto.IdParentCategory = category.IdParent; + + return dto; + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj index b97bed80..b23176e3 100644 --- a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj +++ b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj @@ -23,6 +23,8 @@ + + diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs index 8e2db98f..e7943144 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -1,5 +1,7 @@ using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Requests; +using Microsoft.AspNetCore.Mvc; using Refit; namespace AsbCloudWebApi.IntegrationTests.Clients; @@ -8,20 +10,23 @@ public interface IWellOperationClient { 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 + "/{deleteBeforeInsert}")] + Task> InsertRangeAsync(int idWell, + bool deleteBeforeInsert, + [Body] IEnumerable dtos); - [Put(BaseRoute + "/{idOperation}")] - Task> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token); + [Put(BaseRoute)] + Task> UpdateRangeAsync(int idWell, [Body] IEnumerable dtos); - [Get(BaseRoute + "/plan")] - Task>> GetPageOperationsPlanAsync(int idWell, - [Query] WellOperationRequestBase request, - CancellationToken token); + [Get(BaseRoute)] + Task>> GetPageOperationsPlanAsync(int idWell, [Query] WellOperationRequest request); [Multipart] - [Post(BaseRoute + "/import/plan/default")] - Task>> ImportPlanDefaultExcelFileAsync(int idWell, - [AliasAs("files")] IEnumerable streams, - CancellationToken token); + [Post(BaseRoute + "/parse/{idType}")] + Task>> ParseAsync(int idWell, + int idType, + [AliasAs("file")] StreamPart file); + + [Get(BaseRoute + "/export")] + Task> ExportAsync(int idWell, int idType); } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx new file mode 100644 index 00000000..45ee8a0b Binary files /dev/null and b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/FactWellOperations.xlsx differ diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx new file mode 100644 index 00000000..3083297e Binary files /dev/null and b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/Files/PlanWellOperations.xlsx differ diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs similarity index 55% rename from AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs rename to AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs index 830f2e14..5520121c 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperations/WellOperationControllerTest.cs @@ -1,16 +1,16 @@ -using AsbCloudApp.Data; -using AsbCloudDb.Model; -using AsbCloudWebApi.IntegrationTests.Clients; using System.Net; using System.Reflection; +using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Requests; +using AsbCloudDb.Model; +using AsbCloudWebApi.IntegrationTests.Clients; using AsbCloudWebApi.IntegrationTests.Data; using Mapster; using Microsoft.EntityFrameworkCore; using Refit; using Xunit; -namespace AsbCloudWebApi.IntegrationTests.Controllers; +namespace AsbCloudWebApi.IntegrationTests.Controllers.WellOperations; public class WellOperationControllerTest : BaseIntegrationTest { @@ -37,7 +37,7 @@ public class WellOperationControllerTest : BaseIntegrationTest var dtos = new[] { entity.Adapt() }; //act - var response = await client.InsertRangeAsync(well.Id, 1, false, dtos); + var response = await client.InsertRangeAsync(well.Id, false, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -56,18 +56,18 @@ public class WellOperationControllerTest : BaseIntegrationTest var dtos = new[] { entity.Adapt() }; //act - var response = await client.InsertRangeAsync(well.Id, 1, true, dtos); + var response = await client.InsertRangeAsync(well.Id, true, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); } /// - /// Успешное обновление операции + /// Успешное обновление операций /// /// [Fact] - public async Task UpdateAsync_returns_success() + public async Task UpdateRangeAsync_returns_success() { //arrange var well = await dbContext.Wells.FirstAsync(); @@ -75,10 +75,10 @@ public class WellOperationControllerTest : BaseIntegrationTest dbContext.WellOperations.Add(entity); await dbContext.SaveChangesAsync(); - var dto = entity.Adapt(); + var dtos = new[] { entity.Adapt() }; //act - var response = await client.UpdateAsync(well.Id, entity.Id, dto, CancellationToken.None); + var response = await client.UpdateRangeAsync(well.Id, dtos); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -100,66 +100,101 @@ public class WellOperationControllerTest : BaseIntegrationTest var dto = entity.Adapt(); var timezoneOffset = TimeSpan.FromHours(well.Timezone.Hours); dto.DateStart = dto.DateStart.ToOffset(timezoneOffset); + dto.LastUpdateDate = dto.LastUpdateDate?.ToOffset(timezoneOffset); - var request = new WellOperationRequestBase + var request = new WellOperationRequest { OperationType = WellOperation.IdOperationTypePlan }; //act - var response = await client.GetPageOperationsPlanAsync(well.Id, request, CancellationToken.None); + var response = await client.GetPageOperationsPlanAsync(well.Id, request); //assert + Assert.Equal(response.StatusCode, HttpStatusCode.OK); Assert.NotNull(response.Content); Assert.Single(response.Content.Items); var actualDto = response.Content.Items.First(); - var excludeProps = new[] - { - nameof(WellOperationDto.LastUpdateDate) - }; - MatchHelper.Match(dto, actualDto, excludeProps); + MatchHelper.Match(dto, actualDto); } - [Fact] - public async Task ImportPlanDefaultExcelFileAsync_returns_success() + [Theory] + [InlineData(WellOperation.IdOperationTypePlan, "PlanWellOperations.xlsx")] + [InlineData(WellOperation.IdOperationTypeFact, "FactWellOperations.xlsx")] + public async Task ParseAsync_returns_success(int idType, string fileName) { //arrange - var stream = Assembly.GetExecutingAssembly().GetFileCopyStream("WellOperationsPlan.xlsx"); - - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); - memoryStream.Position = 0; - var well = await dbContext.Wells.FirstAsync(); - //act - var streamPart = new StreamPart(memoryStream, "WellOperations.xlsx", "application/octet-stream"); + var expectedDto = new WellOperationDto + { + IdWell = well.Id, + IdWellSectionType = 2, + IdCategory = WellOperationCategory.IdSlide, + IdPlan = null, + CategoryInfo = "Доп.инфо", + IdType = idType, + DepthStart = 10.0, + DepthEnd = 20.0, + DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(well.Timezone.Hours)), + DurationHours = 1.0, + Comment = "123", + }; - var response = await client.ImportPlanDefaultExcelFileAsync(well.Id, new[] { streamPart }, CancellationToken.None); + var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName); + + var streamPart = new StreamPart(stream, fileName, "application/octet-stream"); + + //act + var response = await client.ParseAsync(well.Id, idType, streamPart); //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(response.Content); - Assert.Equal(4, response.Content.Count()); - Assert.True(response.Content.All(w => Math.Abs(w.DateStart.Offset.Hours - Defaults.Timezone.Hours) < 0.1)); + + var actualDto = response.Content.Item.First().Item; + + MatchHelper.Match(expectedDto, actualDto); } - private static WellOperation CreateWellOperation(int idWell) => + [Theory] + [InlineData(WellOperation.IdOperationTypePlan)] + [InlineData(WellOperation.IdOperationTypeFact)] + public async Task ExportAsync_returns_success(int idType) + { + //arrange + var well = await dbContext.Wells.FirstAsync(); + + var entity = CreateWellOperation(well.Id, idType); + dbContext.WellOperations.Add(entity); + await dbContext.SaveChangesAsync(); + + //act + var response = await client.ExportAsync(well.Id, idType); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/octet-stream", response.ContentHeaders?.ContentType?.MediaType); + Assert.True(response.ContentHeaders?.ContentLength > 0); + } + + private static WellOperation CreateWellOperation(int idWell, int idType = WellOperation.IdOperationTypePlan) => new() { IdWell = idWell, IdWellSectionType = 2, - LastUpdateDate = DateTimeOffset.UtcNow, - IdCategory = 5000, + IdCategory = WellOperationCategory.IdSlide, IdPlan = null, - CategoryInfo = "1", - IdType = 0, + CategoryInfo = "Доп.инфо", + LastUpdateDate = new DateTimeOffset(new DateTime(2023, 1, 10)).ToUniversalTime(), + IdType = idType, DepthStart = 10.0, DepthEnd = 20.0, DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Timezone.Hours)).ToUniversalTime(), DurationHours = 1.0, Comment = "1", - IdUser = 1 + IdUser = 1, }; } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs deleted file mode 100644 index ad3d3895..00000000 --- a/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs +++ /dev/null @@ -1,216 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Data.WellOperationImport.Options; -using AsbCloudApp.Repositories; -using AsbCloudApp.Requests; -using AsbCloudApp.Services; -using AsbCloudApp.Services.WellOperationImport; -using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services.WellOperationImport; -using AsbCloudInfrastructure.Services.WellOperationImport.FileParser; -using NSubstitute; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace AsbCloudWebApi.Tests.Services.WellOperationExport -{ - public class WellOperationExportServiceTest - { - private const int idWell = 4; - - private IWellService wellService; - private IWellOperationRepository wellOperationRepository; - private IWellOperationCategoryRepository wellOperationCategoryRepository; - private IWellOperationImportTemplateService wellOperationImportTemplateService; - private WellOperationExportService wellOperationExportService; - private WellOperationImportService wellOperationImportService; - private IWellOperationExcelParser wellOperationDefaultExcelParser; - - private readonly WellSectionTypeDto[] sectionTypes = new WellSectionTypeDto[2] - { - new WellSectionTypeDto() - { - Caption = "1", - Id = 1, - Order = 0 - }, - new WellSectionTypeDto() - { - Caption = "2", - Id = 2, - Order = 1 - } - }; - - private readonly WellOperationCategoryDto[] categories = new WellOperationCategoryDto[2] - { - new WellOperationCategoryDto() - { - Id = 1, - IdParent = 1, - KeyValueName = "1", - KeyValueUnits = "1", - Name = "1" - }, - new WellOperationCategoryDto() - { - Id = 2, - IdParent = 2, - KeyValueName = "2", - KeyValueUnits = "2", - Name = "2" - } - }; - private readonly ITestOutputHelper output; - - public WellOperationExportServiceTest(ITestOutputHelper output) - { - wellService = Substitute.For(); - wellOperationRepository = Substitute.For(); - wellOperationCategoryRepository = Substitute.For(); - wellOperationImportTemplateService = new WellOperationImportTemplateService(); - wellOperationExportService = new WellOperationExportService(wellOperationRepository, wellOperationImportTemplateService, wellOperationCategoryRepository); - - wellOperationImportService = new WellOperationImportService(wellService, wellOperationRepository, wellOperationCategoryRepository); - wellOperationDefaultExcelParser = new WellOperationDefaultExcelParser(); - this.output = output; - - wellService.GetTimezone(idWell).Returns(new SimpleTimezoneDto() - { - Hours = 5 - }); - } - - [Fact] - public async Task Check_Exported_WellOperations_With_Operations_In_Db() - { - var operations = getOperations(); - - var localOperations = operations.ToArray(); - - foreach (var operation in localOperations) - operation.Id = 0; - - wellOperationRepository.GetAsync(Arg.Any(), Arg.Any()) - .ReturnsForAnyArgs(localOperations); - wellOperationRepository.GetSectionTypes().Returns(sectionTypes); - wellOperationCategoryRepository.Get(false).Returns(categories); - - var stream = await wellOperationExportService.ExportAsync(idWell, CancellationToken.None); - - var options = new WellOperationImportDefaultOptionsDto - { - IdType = WellOperation.IdOperationTypePlan - }; - var sheet = wellOperationDefaultExcelParser.Parse(stream, options); - var result = wellOperationImportService.Import(idWell, 1, options.IdType, sheet); - - var expected = JsonSerializer.Serialize(localOperations); - var actual = JsonSerializer.Serialize(result); - - Assert.Equal(expected, actual); - } - - [Fact] - public void TestDataContainsNotDefaultProps() - { - var initOk = true; - var operations = getOperations(); - for (int i = 0; i < operations.Length; i++) - { - var operation = operations[i]; - var propsWithDefault = GetPropsWithDefaultValue(operation, - nameof(WellOperationDto.Id), - nameof(WellOperationDto.IdType), - nameof(WellOperationDto.IdPlan), - nameof(WellOperationDto.IdParentCategory), - nameof(WellOperationDto.Day), - nameof(WellOperationDto.NptHours), - nameof(WellOperationDto.UserName), - nameof(WellOperationDto.LastUpdateDate) - ) - .ToArray(); - - if (propsWithDefault.Any()) - { - initOk = false; - foreach (var propertyName in propsWithDefault) - output.WriteLine($"{nameof(operations)}[{i}].{propertyName} is default"); - } - } - - Assert.True(initOk); - } - - private static IEnumerable GetPropsWithDefaultValue(T dto, params string[] excludeProps) - { - IEnumerable props = typeof(T).GetProperties( - BindingFlags.Public - | BindingFlags.Instance); - - props = props - .Where(prop => prop.CanWrite) - .Where(prop => !excludeProps.Contains(prop.Name)); - - foreach (var prop in props) - { - var value = prop.GetValue(dto); - if (prop.PropertyType.IsDefaultValue(value)) - yield return prop.Name; - } - } - - private WellOperationDto[] getOperations() - { - - var timezone = wellService.GetTimezone(idWell); - - DateTimeOffset GetDate(int days) - { - var date = DateTimeOffset.UtcNow.AddDays(days); - return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, TimeSpan.FromHours(timezone.Hours)); - } - - return new WellOperationDto[2] { - new WellOperationDto() { - Id = 5, - IdWell = idWell, - IdUser = 1, - IdType = 0, - IdWellSectionType = 1, - WellSectionTypeName = "1", - IdCategory = 1, - CategoryName = "1", - CategoryInfo = "CategoryInfo 1", - DepthStart = 10, - DepthEnd = 20, - DateStart = GetDate(days: 0), - DurationHours = 10, - Comment = "Комментарий 1", - }, - new WellOperationDto() { - Id = 6, - IdWell = idWell, - IdUser = 1, - IdType = 0, - IdWellSectionType = 2, - WellSectionTypeName = "2", - IdCategory = 2, - CategoryName = "2", - CategoryInfo = "CategoryInfo 2", - DepthStart = 20, - DepthEnd = 30, - DateStart = GetDate(days: 1), - DurationHours = 20, - Comment = "Комментарий 2", - } - }; - } - } -} diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index d624e89a..e91c9c65 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -1,13 +1,7 @@ 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; @@ -15,598 +9,340 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Requests.ExportOptions; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudDb.Model; +using AsbCloudInfrastructure.Services.WellOperations.Factories; -namespace AsbCloudWebApi.Controllers +namespace AsbCloudWebApi.Controllers; + +/// +/// Буровые операции (вводимые вручную) +/// +[Route("api/well/{idWell}/wellOperations")] +[ApiController] +[Authorize] +public class WellOperationController : ControllerBase { - - /// - /// Буровые операции (вводимые вручную) - /// - [Route("api/well/{idWell}/wellOperations")] - [ApiController] - [Authorize] - public class WellOperationController : ControllerBase - { - private readonly IWellOperationRepository operationRepository; - private readonly IWellService wellService; - private readonly IWellOperationExportService wellOperationExportService; - private readonly IWellOperationImportTemplateService wellOperationImportTemplateService; - private readonly IWellOperationImportService wellOperationImportService; - private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - private readonly IWellOperationExcelParser wellOperationDefaultExcelParser; - private readonly IWellOperationExcelParser wellOperationGazpromKhantosExcelParser; - private readonly IUserRepository userRepository; - - public WellOperationController(IWellOperationRepository operationRepository, - IWellService wellService, - IWellOperationImportTemplateService wellOperationImportTemplateService, - IWellOperationExportService wellOperationExportService, - IWellOperationImportService wellOperationImportService, - IWellOperationCategoryRepository wellOperationCategoryRepository, - IWellOperationExcelParser wellOperationDefaultExcelParser, - IWellOperationExcelParser wellOperationGazpromKhantosExcelParser, - IUserRepository userRepository) - { - this.operationRepository = operationRepository; - this.wellService = wellService; - this.wellOperationImportTemplateService = wellOperationImportTemplateService; - this.wellOperationExportService = wellOperationExportService; - this.wellOperationImportService = wellOperationImportService; - this.wellOperationCategoryRepository = wellOperationCategoryRepository; - this.wellOperationDefaultExcelParser = wellOperationDefaultExcelParser; - this.wellOperationGazpromKhantosExcelParser = wellOperationGazpromKhantosExcelParser; - this.userRepository = userRepository; - } - - /// - /// Возвращает словарь типов секций - /// - /// - [HttpGet("sectionTypes")] - [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public IActionResult GetSectionTypes() - { - var result = operationRepository.GetSectionTypes(); - return Ok(result); - } - - /// - /// Возвращает список имен типов операций на скважине - /// - /// флаг, нужно ли включать родителей в список - /// - [HttpGet("categories")] - [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public IActionResult GetCategories(bool includeParents = true) - { - var result = wellOperationCategoryRepository.Get(includeParents); - return Ok(result); - } - - /// - /// Возвращает список плановых операций для сопоставления - /// - /// id скважины - /// дата для нахождения последней сопоставленной плановой операции - /// - /// - [HttpGet("operationsPlan")] - [ProducesResponseType(typeof(WellOperationPlanDto), (int)System.Net.HttpStatusCode.OK)] - public async Task GetOperationsPlanAsync( - [FromRoute] int idWell, - [FromQuery] DateTime currentDate, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - - var result = await operationRepository - .GetOperationsPlanAsync(idWell, currentDate, token) - .ConfigureAwait(false); - return Ok(result); - } - - /// - /// Отфильтрованный список фактических операций на скважине. - /// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате - /// - /// id скважины - /// - /// - /// Список операций на скважине - [HttpGet("fact")] - [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public async Task GetPageOperationsFactAsync( - [FromRoute] int idWell, - [FromQuery] WellOperationRequestBase request, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - - var requestToService = new WellOperationRequest(request, idWell); - var result = await operationRepository.GetAsync( - requestToService, - token) - .ConfigureAwait(false); - return Ok(result); - } - - /// - /// Отфильтрованный список плановых операций на скважине. - /// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате - /// - /// id скважины - /// - /// - /// Список операций на скважине в контейнере для постраничного просмотра - [HttpGet("plan")] - [Permission] - [ProducesResponseType(typeof(PaginationContainer), (int)System.Net.HttpStatusCode.OK)] - public async Task GetPageOperationsPlanAsync( - [FromRoute] int idWell, - [FromQuery] WellOperationRequestBase request, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - - var requestToService = new WellOperationRequest(request, idWell); - var result = await operationRepository.GetPageAsync( - requestToService, - token) - .ConfigureAwait(false); - return Ok(result); - } - - /// - /// Статистика операций по скважине, группированная по категориям - /// - /// id скважины - /// - /// - /// - [HttpGet("groupStat")] - [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] - public async Task GetGroupOperationsAsync( - [FromRoute] int idWell, - [FromQuery] WellOperationRequestBase request, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - - var requestToService = new WellOperationRequest(request, idWell); - var result = await operationRepository.GetGroupOperationsStatAsync( - requestToService, - token) - .ConfigureAwait(false); - return Ok(result); - } - - /// - /// Возвращает нужную операцию на скважине - /// - /// id скважины - /// id нужной операции - /// Токен отмены задачи - /// Нужную операцию на скважине - [HttpGet("{idOperation}")] - [Permission] - [ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)] - public async Task GetOrDefaultAsync(int idWell, int idOperation, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - - var result = await operationRepository.GetOrDefaultAsync(idOperation, token).ConfigureAwait(false); - return Ok(result); - } - - /// - /// Добавляет новую операцию на скважину - /// - /// Id скважины - /// Тип добавляемой операции - /// Добавляемая операция - /// - /// Количество добавленных в БД записей - [HttpPost("{idType:int}")] - [Permission] - [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task InsertAsync( - [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, - [Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType, - WellOperationDto wellOperation, - CancellationToken cancellationToken) - { - if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) - return Forbid(); - - if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) - return Forbid(); - - wellOperation.IdWell = idWell; - wellOperation.IdUser = User.GetUserId(); - wellOperation.IdType = idType; - - var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); - - return Ok(result); - } - - /// - /// Добавляет новые операции на скважине - /// - /// Id скважины - /// Добавляемые операции - /// Тип добавляемых операций - /// Удалить операции перед сохранением - /// - /// Количество добавленных в БД записей - [HttpPost("{idType:int}/{deleteBeforeInsert:bool}")] - [Permission] - [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - public async Task InsertRangeAsync( - [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, - [Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType, - bool deleteBeforeInsert, - [FromBody] IEnumerable wellOperations, - CancellationToken cancellationToken) - { - if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) - return Forbid(); - - if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) - return Forbid(); - - if (deleteBeforeInsert) - { - var existingOperations = await operationRepository.GetAsync(new WellOperationRequest - { - IdWell = idWell, - OperationType = idType - }, cancellationToken); - - await operationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken); - } - - foreach (var wellOperation in wellOperations) - { - wellOperation.IdWell = idWell; - wellOperation.IdUser = User.GetUserId(); - wellOperation.IdType = idType; - } - - var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); - - return Ok(result); - } - - /// - /// Обновляет выбранную операцию на скважине - /// - /// id скважины - /// id выбранной операции - /// Новые данные для выбранной операции - /// Токен отмены задачи - /// Количество обновленных в БД строк - [HttpPut("{idOperation}")] - [Permission] - [ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)] - public async Task UpdateAsync(int idWell, int idOperation, - [FromBody] WellOperationDto value, CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token)) - return Forbid(); - - if (!await CanUserEditWellOperationsAsync(idWell, token)) - return Forbid(); - - value.IdWell = idWell; - value.Id = idOperation; - value.IdUser = User.GetUserId(); - - var result = await operationRepository.UpdateAsync(value, token) - .ConfigureAwait(false); - return Ok(result); - } - - /// - /// Удаляет выбранную операцию на скважине - /// - /// id скважины - /// id выбранной операции - /// Токен отмены задачи - /// Количество удаленных из БД строк - [HttpDelete("{idOperation}")] - [Permission] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task DeleteAsync(int idWell, int idOperation, CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token)) - return Forbid(); - - if (!await CanUserEditWellOperationsAsync(idWell, token)) - return Forbid(); - - var result = await operationRepository.DeleteAsync(new int[] { idOperation }, token) - .ConfigureAwait(false); - - return Ok(result); - } - - /// - /// Импорт фактических операций из excel (xlsx) файла. Стандартный заполненный шаблон - /// - /// id скважины - /// Коллекция из одного файла xlsx - /// Удалить операции перед сохранением - /// - /// - [HttpPost("import/fact/default/{deleteBeforeInsert:bool}")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - [Permission] - public Task ImportFactDefaultExcelFileAsync(int idWell, - [FromForm] IFormFileCollection files, - bool deleteBeforeInsert, - CancellationToken cancellationToken) - { - var options = new WellOperationImportDefaultOptionsDto - { - IdType = WellOperation.IdOperationTypeFact - }; - - return ImportExcelFileAsync(idWell, files, options, - (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), - deleteBeforeInsert, - cancellationToken); - } - - /// - /// Импорт плановых операций из excel (xlsx) файла. Стандартный заполненный шаблон - /// - /// id скважины - /// Коллекция из одного файла xlsx - /// - /// - [HttpPost("import/plan/default")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - [Permission] - public Task ImportPlanDefaultExcelFileAsync(int idWell, - [FromForm] IFormFileCollection files, - CancellationToken cancellationToken) - { - var options = new WellOperationImportDefaultOptionsDto - { - IdType = WellOperation.IdOperationTypePlan - }; - - return ImportExcelFileAsync(idWell, files, options, - (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), - null, - cancellationToken); - } - - /// - /// Импорт операций из excel (xlsx) файла. ГПНХ (Хантос) - /// - /// Id скважины - /// Параметры парсинга - /// Коллекция из одного файла xlsx - /// - /// - [HttpPost("import/plan/gazpromKhantos")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - [Permission] - public Task ImportPlanGazpromKhantosExcelFileAsync(int idWell, - [FromQuery] WellOperationImportGazpromKhantosOptionsDto options, - [FromForm] IFormFileCollection files, - CancellationToken cancellationToken) - { - options.IdType = WellOperation.IdOperationTypePlan; - - return ImportExcelFileAsync(idWell, files, options, - (stream, _) => wellOperationGazpromKhantosExcelParser.Parse(stream, options), - null, - cancellationToken); - } - - /// - /// Создает excel файл с операциями по скважине - /// - /// id скважины - /// Токен отмены задачи - /// Запрашиваемый файл - [HttpGet("export")] - [Permission] - [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task ExportAsync([FromRoute] int idWell, CancellationToken token) - { - int? idCompany = User.GetCompanyId(); - - if (idCompany is null) - return Forbid(); - - if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, - idWell, token).ConfigureAwait(false)) - return Forbid(); - - var stream = await wellOperationExportService.ExportAsync(idWell, token); - var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_operations.xlsx"; - return File(stream, "application/octet-stream", fileName); - } - - /// - /// Создает excel файл с "сетевым графиком" - /// - /// id скважины - /// - /// Токен отмены задачи - /// Запрашиваемый файл - [HttpGet("scheduleReport")] - [Permission] - [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)] - public async Task ScheduleReportAsync([FromRoute] int idWell, [FromServices] IScheduleReportService scheduleReportService, CancellationToken token) - { - int? idCompany = User.GetCompanyId(); - - if (idCompany is null) - return Forbid(); - - if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, - idWell, token).ConfigureAwait(false)) - return Forbid(); - - var stream = await scheduleReportService.MakeReportAsync(idWell, token); - var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx"; - return File(stream, "application/octet-stream", fileName); - } - - /// - /// Удаляет полые дубликаты операций - /// - /// - /// - [HttpPost("/api/well/wellOperations/RemoveDuplicates")] - [Permission] - [Obsolete] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task RemoveDuplicates(CancellationToken token) - { - var result = await operationRepository.RemoveDuplicates((_, _) => { }, token); - return Ok(result); - } - - /// - /// Удаляет полностью пересекающиеся операции или "подрезает" более поздние их по глубине и дате. - /// - /// - /// - /// - /// - [HttpPost("/api/well/wellOperations/TrimOverlapping")] - [Permission] - [Obsolete] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, CancellationToken token) - { - var result = await operationRepository.TrimOverlapping(geDate, leDate, (_, _) => { }, token); - return Ok(result); - } - - /// - /// Возвращает шаблон файла импорта - /// - /// Запрашиваемый файл - [HttpGet("template")] - [AllowAnonymous] - [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] - public IActionResult GetTemplate() - { - var stream = wellOperationImportTemplateService.GetExcelTemplateStream(); - var fileName = "ЕЦП_шаблон_файла_операций.xlsx"; - return File(stream, "application/octet-stream", fileName); - } - - //TODO: deleteBeforeInsert тоже быстрый костыль - private async Task ImportExcelFileAsync(int idWell, [FromForm] IFormFileCollection files, - TOptions options, - Func parseMethod, - bool? deleteBeforeInsert, - CancellationToken cancellationToken) - where TOptions : IWellOperationImportOptions - { - var idCompany = User.GetCompanyId(); - var idUser = User.GetUserId(); - - if (!idCompany.HasValue || !idUser.HasValue) - throw new ForbidException("Неизвестный пользователь"); - - if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) - throw new ForbidException("Нет доступа к скважине"); - - if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) - throw new ForbidException("Недостаточно прав для редактирования ГГД на завершенной скважине"); - - if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken)) - throw new ForbidException("Скважина недоступна для компании"); - - if (files.Count < 1) - return this.ValidationBadRequest(nameof(files), "Нет файла"); - - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); - - using Stream stream = file.OpenReadStream(); - - try - { - var sheet = parseMethod(stream, options); - - var wellOperations = wellOperationImportService.Import(idWell, idUser.Value, options.IdType, sheet) - .OrderBy(w => w.DateStart); - - var dateStart = wellOperations.MinOrDefault(w => w.DateStart); - - foreach (var wellOperation in wellOperations) - { - if (dateStart.HasValue) - wellOperation.Day = (wellOperation.DateStart - dateStart.Value).TotalDays; - } - - //TODO: очень быстрый костыль - if (deleteBeforeInsert is not null && options.IdType == WellOperation.IdOperationTypeFact) - { - return await InsertRangeAsync(idWell, options.IdType, - deleteBeforeInsert.Value, - wellOperations, - cancellationToken); - } - - return Ok(wellOperations); - } - catch (FileFormatException ex) - { - 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) - return false; - - return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell"); - } - } + private readonly IDictionary templateNames = new Dictionary + { + { WellOperation.IdOperationTypeFact, "ЕЦП_шаблон_файла_фактические_операции.xlsx" }, + { WellOperation.IdOperationTypePlan, "ЕЦП_шаблон_файла_плановые_операции.xlsx" } + }; + + private readonly IUserRepository userRepository; + private readonly IWellOperationRepository wellOperationRepository; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellService wellService; + + private readonly WellOperationParserFactory wellOperationParserFactory; + private readonly WellOperationExportServiceFactory wellOperationExportServiceFactory; + + public WellOperationController(IWellOperationRepository wellOperationRepository, + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellService wellService, + IUserRepository userRepository, + WellOperationParserFactory wellOperationParserFactory, + WellOperationExportServiceFactory wellOperationExportServiceFactory) + { + this.wellOperationRepository = wellOperationRepository; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellService = wellService; + this.userRepository = userRepository; + this.wellOperationParserFactory = wellOperationParserFactory; + this.wellOperationExportServiceFactory = wellOperationExportServiceFactory; + } + + /// + /// Добавляет новые операции на скважине + /// + /// Id скважины + /// Добавляемые операции + /// Удалить операции перед сохранением + /// + /// Количество добавленных в БД записей + [HttpPost("{deleteBeforeInsert:bool}")] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task InsertRangeAsync( + [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] + int idWell, + bool deleteBeforeInsert, + [FromBody] IEnumerable dtos, + CancellationToken cancellationToken) + { + if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdWell = idWell; + dto.LastUpdateDate = null; + dto.IdUser = User.GetUserId(); + } + + var result = await wellOperationRepository.InsertRangeAsync(dtos, deleteBeforeInsert, cancellationToken); + + return Ok(result); + } + + /// + /// Обновляет выбранную операцию на скважине + /// + /// id скважины + /// + /// Токен отмены задачи + /// Количество обновленных в БД строк + [HttpPut] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + public async Task UpdateRangeAsync(int idWell, + [FromBody] IEnumerable dtos, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, token)) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdWell = idWell; + dto.IdUser = User.GetUserId(); + dto.LastUpdateDate = DateTimeOffset.UtcNow; + } + + var result = await wellOperationRepository.UpdateRangeAsync(dtos, token); + + return Ok(result); + } + + /// + /// Возвращает словарь типов секций + /// + /// + [HttpGet("sectionTypes")] + [Permission] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetSectionTypes() + { + var result = wellOperationRepository.GetSectionTypes(); + return Ok(result); + } + + /// + /// Статистика операций по скважине, группированная по категориям + /// + /// id скважины + /// + /// + /// + [HttpGet("groupStat")] + [Permission] + [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public async Task GetGroupOperationsAsync( + [FromRoute] int idWell, + [FromQuery] WellOperationRequest request, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) + return Forbid(); + + request.IdsWell = new[] { idWell }; + var result = await wellOperationRepository.GetGroupOperationsStatAsync(request, token); + return Ok(result); + } + + /// + /// Возвращает список имен типов операций на скважине + /// + /// флаг, нужно ли включать родителей в список + /// + [HttpGet("categories")] + [Permission] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetCategories(bool includeParents = true) + { + var result = wellOperationCategoryRepository.Get(includeParents); + return Ok(result); + } + + /// + /// Постраничный список операций на скважине. + /// + /// id скважины + /// + /// + /// Список операций на скважине + [HttpGet] + [Permission] + [ProducesResponseType(typeof(PaginationContainer), StatusCodes.Status200OK)] + public async Task GetPageOperationsAsync( + [FromRoute] int idWell, + [FromQuery] WellOperationRequest request, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + request.IdsWell = new[] { idWell }; + var result = await wellOperationRepository.GetPageAsync(request, token); + return Ok(result); + } + + /// + /// Создает excel файл с "сетевым графиком" + /// + /// id скважины + /// + /// + /// Запрашиваемый файл + [HttpGet("scheduleReport")] + [Permission] + [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK)] + public async Task ScheduleReportAsync([FromRoute] int idWell, + [FromServices] IScheduleReportService scheduleReportService, + CancellationToken token) + { + var idCompany = User.GetCompanyId(); + + if (idCompany is null) + return Forbid(); + + if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token)) + return Forbid(); + + var stream = await scheduleReportService.MakeReportAsync(idWell, token); + var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx"; + return File(stream, "application/octet-stream", fileName); + } + + /// + /// Удаляет выбранную операцию на скважине + /// + /// id скважины + /// id выбранной операции + /// Токен отмены задачи + /// Количество удаленных из БД строк + [HttpDelete("{idOperation}")] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + public async Task DeleteAsync(int idWell, int idOperation, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, token)) + return Forbid(); + + var result = await wellOperationRepository.DeleteRangeAsync(new[] { idOperation }, token); + + return Ok(result); + } + + /// + /// Формирование excel файла с операциями на скважине + /// + /// + /// + /// + /// + [HttpGet("export")] + [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")] + public async Task ExportAsync(int idWell, + int idType, + CancellationToken token) + { + var options = new WellOperationExportRequest(idWell, idType); + var exportService = wellOperationExportServiceFactory.CreateExportService(idType); + + var (fileName, file) = await exportService.ExportAsync(options, token); + + return File(file, "application/octet-stream", fileName); + } + + /// + /// Импорт ГГД из excel (xlsx) файла + /// + /// + /// + /// + /// + /// + [HttpPost("parse/{idType}")] + [Permission] + [ProducesResponseType(typeof(ParserResultDto), StatusCodes.Status200OK)] + public async Task ParseAsync(int idWell, + int idType, + [Required] IFormFile file, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, token)) + return Forbid(); + + var stream = file.GetExcelFile(); + + try + { + var timezone = wellService.GetTimezone(idWell); + var options = new WellOperationParserRequest(idWell, idType, timezone); + var parser = wellOperationParserFactory.CreateParser(idType); + var result = parser.Parse(stream, options); + + return Ok(result); + } + catch (FileFormatException ex) + { + return this.ValidationBadRequest(nameof(file), ex.Message); + } + } + + /// + /// Получение шаблона для заполнения ГГД + /// + /// + [HttpGet("template")] + [AllowAnonymous] + [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult GetTemplate(int idType) + { + var parser = wellOperationParserFactory.CreateParser(idType); + var stream = parser.GetTemplateFile(); + + return File(stream, "application/octet-stream", templateNames[idType]); + } + + private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token) + { + var 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) + return false; + + return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell"); + } } \ No newline at end of file