Новая реализация экпорта/импорта ГГД

1. Изменено апи
2. Добавлен парсер для ГГД
3. Добавлен ссервис экспорта ГГД
This commit is contained in:
Степанов Дмитрий 2024-03-22 10:42:48 +03:00
parent 39a1c874c0
commit 16645d053a
19 changed files with 729 additions and 876 deletions

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudApp.Data;
@ -9,4 +10,8 @@ namespace AsbCloudApp.Data;
public class ParserResultDto<TDto> : ValidationResultDto<IEnumerable<ValidationResultDto<TDto>>>
where TDto : class, IId
{
/// <summary>
/// Объекты полученные из файла
/// </summary>
public override IEnumerable<ValidationResultDto<TDto>> Item { get; set; } = Enumerable.Empty<ValidationResultDto<TDto>>();
}

View File

@ -18,7 +18,7 @@ public class ValidationResultDto<T>
/// <summary>
/// Объект валидации
/// </summary>
public T Item { get; set; } = null!;
public virtual T Item { get; set; } = null!;
/// <summary>
/// Предупреждения

View File

@ -0,0 +1,20 @@
namespace AsbCloudApp.Requests.ExportOptions;
/// <summary>
/// Параметры экспорта ГГД
/// </summary>
public class WellOperationExportRequest : WellRelatedExportRequest
{
/// <inheritdoc />
public WellOperationExportRequest(int idWell,
int idType)
: base(idWell)
{
IdType = idType;
}
/// <summary>
/// Тип операций
/// </summary>
public int IdType { get; }
}

View File

@ -0,0 +1,29 @@
using AsbCloudApp.Data;
namespace AsbCloudApp.Requests.ParserOptions;
/// <summary>
/// Параметры парсинга ГГД
/// </summary>
public class WellOperationParserRequest : WellRelatedParserRequest
{
/// <inheritdoc />
public WellOperationParserRequest(int idWell,
int idType,
SimpleTimezoneDto wellTimezone)
: base(idWell)
{
IdType = idType;
WellTimezone = wellTimezone;
}
/// <summary>
/// Тип операции
/// </summary>
public int IdType { get; }
/// <summary>
/// Часовой пояс в котором находится скважина
/// </summary>
public SimpleTimezoneDto WellTimezone { get; }
}

View File

@ -45,11 +45,9 @@
<EmbeddedResource Include="Services\Trajectory\Templates\TrajectoryFactManualTemplate.xlsx" />
<EmbeddedResource Include="Services\Trajectory\Templates\TrajectoryPlanTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationImport\Files\WellOperationImportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\Operations.txt" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\Sections.txt" />
<EmbeddedResource Include="Services\WellOperationImport\Files\Dictionaries\OperationAttributes.txt" />
<EmbeddedResource Include="Services\ProcessMaps\Report\ProcessMapReportTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperations\Templates\WellOperationFactTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperations\Templates\WellOperationPlanTemplate.xlsx" />
</ItemGroup>
<ItemGroup>
@ -77,4 +75,10 @@
<HintPath>CommonLibs\AsbWitsInfo.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Remove="Services\ProcessMapPlan\Parser\ProcessMapPlanExcelReamParser.cs" />
<Compile Remove="Services\ProcessMapPlan\Parser\ProcessMapPlanExcelParser.cs" />
<Compile Remove="Services\ProcessMapPlan\Parser\ProcessMapPlanExcelDrillingParser.cs" />
</ItemGroup>
</Project>

View File

@ -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<ITelemetryUserService, TelemetryUserService>();
services.AddTransient<ITimezoneService, TimezoneService>();
services.AddScoped<IWellService, WellService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IProcessMapReportDrillingExportService, ProcessMapReportDataSaubStatExportService>();
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
services.AddTransient<IDailyReportService, DailyReportService>();
@ -270,13 +267,6 @@ namespace AsbCloudInfrastructure
services.AddTransient<IWellboreService, WellboreService>();
services.AddTransient<IWellOperationExportService, WellOperationExportService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationImportTemplateService, WellOperationImportTemplateService>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>, WellOperationGazpromKhantosExcelParser>();
services.AddTransient<DetectedOperationExportService>();
services.AddTransient<IDailyReportService, DailyReportService>();
@ -301,6 +291,9 @@ namespace AsbCloudInfrastructure
services.AddTransient<ProcessMapPlanDrillingExportService>();
services.AddTransient<ProcessMapPlanReamExportService>();
services.AddTransient<WellOperationParserFactory>();
services.AddTransient<WellOperationExportServiceFactory>();
return services;
}
}

View File

@ -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<string, Cell> Cells => new Dictionary<string, Cell>()
{
{ 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)) }
};
}

View File

@ -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<int>
{
private readonly IDictionary<int, Func<IExportService>> exportServices;
public WellOperationExportServiceFactory(IServiceProvider serviceProvider)
{
var wellOperationRepository = serviceProvider.GetRequiredService<IWellOperationRepository>();
var wellService = serviceProvider.GetRequiredService<IWellService>();
exportServices = new Dictionary<int, Func<IExportService>>
{
{
WellOperation.IdOperationTypeFact,
() => new WellOperationExport<WellOperationFactTemplate>(wellOperationRepository, wellService)
},
{
WellOperation.IdOperationTypePlan,
() => new WellOperationExport<WellOperationPlanTemplate>(wellOperationRepository, wellService)
}
};
}
public IExportService<TOptions> CreateExportService<TOptions>(int id)
where TOptions : IExportOptionsRequest
{
var parser = exportServices[id].Invoke();
return parser as IExportService<TOptions>
?? throw new ArgumentNullException(nameof(id), "Не удалось экспортировать файл");
}
}

View File

@ -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<int, WellOperationDto>
{
private readonly IDictionary<int, Func<IParserService>> parsers;
public WellOperationParserFactory(IServiceProvider serviceProvider)
{
var wellOperationRepository = serviceProvider.GetRequiredService<IWellOperationRepository>();
var categoryRepository = serviceProvider.GetRequiredService<IWellOperationCategoryRepository>();
parsers = new Dictionary<int, Func<IParserService>>
{
{
WellOperation.IdOperationTypeFact,
() => new WellOperationParser<WellOperationFactTemplate>(wellOperationRepository, categoryRepository)
},
{
WellOperation.IdOperationTypePlan,
() => new WellOperationParser<WellOperationPlanTemplate>(wellOperationRepository, categoryRepository)
}
};
}
public IParserService<WellOperationDto, TOptions> CreateParser<TOptions>(int id)
where TOptions : IParserOptionsRequest
{
var parser = parsers[id].Invoke();
return parser as IParserService<WellOperationDto, TOptions>
?? throw new ArgumentNullException(nameof(id), "Не удалось распознать файл");
}
}

View File

@ -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<TTemplate> : ExcelExportService<WellOperationDto, WellOperationExportRequest, TTemplate>
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<string> 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<IEnumerable<WellOperationDto>> GetDtosAsync(WellOperationExportRequest options, CancellationToken token)
{
var request = new WellOperationRequest
{
IdsWell = new[] { options.IdWell },
OperationType = options.IdType
};
return wellOperationRepository.GetAsync(request, token);
}
}

View File

@ -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<TTemplateParameters> : ExcelWellRelatedParser<WellOperationDto, WellOperationParserRequest, TTemplateParameters>
where TTemplateParameters : class, ITemplateParameters, new()
{
private readonly IEnumerable<WellSectionTypeDto> sectionTypes;
private readonly IEnumerable<WellOperationCategoryDto> categories;
public WellOperationParser(IWellOperationRepository wellOperationRepository,
IWellOperationCategoryRepository categoryRepository)
{
categories = categoryRepository.Get(false);
sectionTypes = wellOperationRepository.GetSectionTypes();
}
public override ParserResultDto<WellOperationDto> 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<string, object?> 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;
}
}

View File

@ -23,6 +23,8 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Controllers\WellOperations\Files\PlanWellOperations.xlsx" />
<EmbeddedResource Include="Controllers\WellOperations\Files\FactWellOperations.xlsx" />
<EmbeddedResource Include="WellOperationsPlan.xlsx" />
</ItemGroup>

View File

@ -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<IApiResponse<int>> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable<WellOperationDto> dtos);
[Post(BaseRoute + "/{deleteBeforeInsert}")]
Task<IApiResponse<int>> InsertRangeAsync(int idWell,
bool deleteBeforeInsert,
[Body] IEnumerable<WellOperationDto> dtos);
[Put(BaseRoute + "/{idOperation}")]
Task<IApiResponse<int>> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token);
[Put(BaseRoute)]
Task<IApiResponse<int>> UpdateRangeAsync(int idWell, [Body] IEnumerable<WellOperationDto> dtos);
[Get(BaseRoute + "/plan")]
Task<IApiResponse<PaginationContainer<WellOperationDto>>> GetPageOperationsPlanAsync(int idWell,
[Query] WellOperationRequestBase request,
CancellationToken token);
[Get(BaseRoute)]
Task<IApiResponse<PaginationContainer<WellOperationDto>>> GetPageOperationsPlanAsync(int idWell, [Query] WellOperationRequest request);
[Multipart]
[Post(BaseRoute + "/import/plan/default")]
Task<IApiResponse<IEnumerable<WellOperationDto>>> ImportPlanDefaultExcelFileAsync(int idWell,
[AliasAs("files")] IEnumerable<StreamPart> streams,
CancellationToken token);
[Post(BaseRoute + "/parse/{idType}")]
Task<IApiResponse<ParserResultDto<WellOperationDto>>> ParseAsync(int idWell,
int idType,
[AliasAs("file")] StreamPart file);
[Get(BaseRoute + "/export")]
Task<IApiResponse<PhysicalFileResult>> ExportAsync(int idWell, int idType);
}

View File

@ -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<WellOperationDto>() };
//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<WellOperationDto>() };
//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);
}
/// <summary>
/// Успешное обновление операции
/// Успешное обновление операций
/// </summary>
/// <returns></returns>
[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<WellOperationDto>();
var dtos = new[] { entity.Adapt<WellOperationDto>() };
//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<WellOperationDto>();
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,
};
}

View File

@ -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<WellOperationImportDefaultOptionsDto> 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<IWellService>();
wellOperationRepository = Substitute.For<IWellOperationRepository>();
wellOperationCategoryRepository = Substitute.For<IWellOperationCategoryRepository>();
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<WellOperationRequest>(), Arg.Any<CancellationToken>())
.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<string> GetPropsWithDefaultValue<T>(T dto, params string[] excludeProps)
{
IEnumerable<PropertyInfo> 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",
}
};
}
}
}

View File

@ -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,12 +9,15 @@ 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;
/// <summary>
/// Буровые операции (вводимые вручную)
@ -30,35 +27,102 @@ namespace AsbCloudWebApi.Controllers
[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<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser;
private readonly IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser;
private readonly IUserRepository userRepository;
public WellOperationController(IWellOperationRepository operationRepository,
IWellService wellService,
IWellOperationImportTemplateService wellOperationImportTemplateService,
IWellOperationExportService wellOperationExportService,
IWellOperationImportService wellOperationImportService,
IWellOperationCategoryRepository wellOperationCategoryRepository,
IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser,
IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser,
IUserRepository userRepository)
private readonly IDictionary<int, string> templateNames = new Dictionary<int, string>
{
this.operationRepository = operationRepository;
this.wellService = wellService;
this.wellOperationImportTemplateService = wellOperationImportTemplateService;
this.wellOperationExportService = wellOperationExportService;
this.wellOperationImportService = wellOperationImportService;
{ 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.wellOperationDefaultExcelParser = wellOperationDefaultExcelParser;
this.wellOperationGazpromKhantosExcelParser = wellOperationGazpromKhantosExcelParser;
this.wellService = wellService;
this.userRepository = userRepository;
this.wellOperationParserFactory = wellOperationParserFactory;
this.wellOperationExportServiceFactory = wellOperationExportServiceFactory;
}
/// <summary>
/// Добавляет новые операции на скважине
/// </summary>
/// <param name="idWell">Id скважины</param>
/// <param name="dtos">Добавляемые операции</param>
/// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param>
/// <param name="cancellationToken"></param>
/// <returns>Количество добавленных в БД записей</returns>
[HttpPost("{deleteBeforeInsert:bool}")]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> InsertRangeAsync(
[Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")]
int idWell,
bool deleteBeforeInsert,
[FromBody] IEnumerable<WellOperationDto> 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);
}
/// <summary>
/// Обновляет выбранную операцию на скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="dtos"></param>
/// <param name="token">Токен отмены задачи</param>
/// <returns>Количество обновленных в БД строк</returns>
[HttpPut]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public async Task<IActionResult> UpdateRangeAsync(int idWell,
[FromBody] IEnumerable<WellOperationDto> 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);
}
/// <summary>
@ -67,101 +131,10 @@ namespace AsbCloudWebApi.Controllers
/// <returns></returns>
[HttpGet("sectionTypes")]
[Permission]
[ProducesResponseType(typeof(IEnumerable<WellSectionTypeDto>), (int)System.Net.HttpStatusCode.OK)]
[ProducesResponseType(typeof(IEnumerable<WellSectionTypeDto>), StatusCodes.Status200OK)]
public IActionResult GetSectionTypes()
{
var result = operationRepository.GetSectionTypes();
return Ok(result);
}
/// <summary>
/// Возвращает список имен типов операций на скважине
/// </summary>
/// <param name="includeParents">флаг, нужно ли включать родителей в список</param>
/// <returns></returns>
[HttpGet("categories")]
[Permission]
[ProducesResponseType(typeof(IEnumerable<WellOperationCategoryDto>), (int)System.Net.HttpStatusCode.OK)]
public IActionResult GetCategories(bool includeParents = true)
{
var result = wellOperationCategoryRepository.Get(includeParents);
return Ok(result);
}
/// <summary>
/// Возвращает список плановых операций для сопоставления
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="currentDate">дата для нахождения последней сопоставленной плановой операции</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("operationsPlan")]
[ProducesResponseType(typeof(WellOperationPlanDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Отфильтрованный список фактических операций на скважине.
/// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns>Список операций на скважине</returns>
[HttpGet("fact")]
[Permission]
[ProducesResponseType(typeof(IEnumerable<WellOperationDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Отфильтрованный список плановых операций на скважине.
/// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns>Список операций на скважине в контейнере для постраничного просмотра</returns>
[HttpGet("plan")]
[Permission]
[ProducesResponseType(typeof(PaginationContainer<WellOperationDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
var result = wellOperationRepository.GetSectionTypes();
return Ok(result);
}
@ -177,149 +150,79 @@ namespace AsbCloudWebApi.Controllers
[ProducesResponseType(typeof(IEnumerable<WellGroupOpertionDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetGroupOperationsAsync(
[FromRoute] int idWell,
[FromQuery] WellOperationRequestBase request,
[FromQuery] WellOperationRequest 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);
request.IdsWell = new[] { idWell };
var result = await wellOperationRepository.GetGroupOperationsStatAsync(request, token);
return Ok(result);
}
/// <summary>
/// Возвращает нужную операцию на скважине
/// Возвращает список имен типов операций на скважине
/// </summary>
/// <param name="includeParents">флаг, нужно ли включать родителей в список</param>
/// <returns></returns>
[HttpGet("categories")]
[Permission]
[ProducesResponseType(typeof(IEnumerable<WellOperationCategoryDto>), StatusCodes.Status200OK)]
public IActionResult GetCategories(bool includeParents = true)
{
var result = wellOperationCategoryRepository.Get(includeParents);
return Ok(result);
}
/// <summary>
/// Постраничный список операций на скважине.
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="idOperation">id нужной операции</param>
/// <param name="token">Токен отмены задачи</param>
/// <returns>Нужную операцию на скважине</returns>
[HttpGet("{idOperation}")]
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns>Список операций на скважине</returns>
[HttpGet]
[Permission]
[ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetOrDefaultAsync(int idWell, int idOperation,
[ProducesResponseType(typeof(PaginationContainer<WellOperationDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetPageOperationsAsync(
[FromRoute] int idWell,
[FromQuery] WellOperationRequest request,
CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
return Forbid();
var result = await operationRepository.GetOrDefaultAsync(idOperation, token).ConfigureAwait(false);
return Ok(result);
}
/// <summary>
/// Добавляет новую операцию на скважину
/// </summary>
/// <param name="idWell">Id скважины</param>
/// <param name="idType">Тип добавляемой операции</param>
/// <param name="wellOperation">Добавляемая операция</param>
/// <param name="cancellationToken"></param>
/// <returns>Количество добавленных в БД записей</returns>
[HttpPost("{idType:int}")]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Добавляет новые операции на скважине
/// </summary>
/// <param name="idWell">Id скважины</param>
/// <param name="wellOperations">Добавляемые операции</param>
/// <param name="idType">Тип добавляемых операций</param>
/// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param>
/// <param name="cancellationToken"></param>
/// <returns>Количество добавленных в БД записей</returns>
[HttpPost("{idType:int}/{deleteBeforeInsert:bool}")]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> InsertRangeAsync(
[Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell,
[Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType,
bool deleteBeforeInsert,
[FromBody] IEnumerable<WellOperationDto> 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);
}
/// <summary>
/// Обновляет выбранную операцию на скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="idOperation">id выбранной операции</param>
/// <param name="value">Новые данные для выбранной операции</param>
/// <param name="token">Токен отмены задачи</param>
/// <returns>Количество обновленных в БД строк</returns>
[HttpPut("{idOperation}")]
[Permission]
[ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> UpdateAsync(int idWell, int idOperation,
[FromBody] WellOperationDto value, CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token))
return Forbid();
if (!await CanUserEditWellOperationsAsync(idWell, token))
request.IdsWell = new[] { idWell };
var result = await wellOperationRepository.GetPageAsync(request, token);
return Ok(result);
}
/// <summary>
/// Создает excel файл с "сетевым графиком"
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="scheduleReportService"></param>
/// <param name="token"></param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet("scheduleReport")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK)]
public async Task<IActionResult> ScheduleReportAsync([FromRoute] int idWell,
[FromServices] IScheduleReportService scheduleReportService,
CancellationToken token)
{
var idCompany = User.GetCompanyId();
if (idCompany is null)
return Forbid();
value.IdWell = idWell;
value.Id = idOperation;
value.IdUser = User.GetUserId();
if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token))
return Forbid();
var result = await operationRepository.UpdateAsync(value, token)
.ConfigureAwait(false);
return Ok(result);
var stream = await scheduleReportService.MakeReportAsync(idWell, token);
var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx";
return File(stream, "application/octet-stream", fileName);
}
/// <summary>
@ -331,7 +234,7 @@ namespace AsbCloudWebApi.Controllers
/// <returns>Количество удаленных из БД строк</returns>
[HttpDelete("{idOperation}")]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public async Task<IActionResult> DeleteAsync(int idWell, int idOperation, CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token))
@ -340,256 +243,90 @@ namespace AsbCloudWebApi.Controllers
if (!await CanUserEditWellOperationsAsync(idWell, token))
return Forbid();
var result = await operationRepository.DeleteAsync(new int[] { idOperation }, token)
.ConfigureAwait(false);
var result = await wellOperationRepository.DeleteRangeAsync(new[] { idOperation }, token);
return Ok(result);
}
/// <summary>
/// Импорт фактических операций из excel (xlsx) файла. Стандартный заполненный шаблон
/// Формирование excel файла с операциями на скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param>
/// <param name="cancellationToken"></param>
/// <param name="idWell"></param>
/// <param name="idType"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("import/fact/default/{deleteBeforeInsert:bool}")]
[ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[Permission]
public Task<IActionResult> 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);
}
/// <summary>
/// Импорт плановых операций из excel (xlsx) файла. Стандартный заполненный шаблон
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost("import/plan/default")]
[ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[Permission]
public Task<IActionResult> 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);
}
/// <summary>
/// Импорт операций из excel (xlsx) файла. ГПНХ (Хантос)
/// </summary>
/// <param name="idWell">Id скважины</param>
/// <param name="options">Параметры парсинга</param>
/// <param name="files">Коллекция из одного файла xlsx</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost("import/plan/gazpromKhantos")]
[ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[Permission]
public Task<IActionResult> 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);
}
/// <summary>
/// Создает excel файл с операциями по скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="token">Токен отмены задачи </param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet("export")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> ExportAsync([FromRoute] int idWell, CancellationToken token)
[ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")]
public async Task<IActionResult> ExportAsync(int idWell,
int idType,
CancellationToken token)
{
int? idCompany = User.GetCompanyId();
var options = new WellOperationExportRequest(idWell, idType);
var exportService = wellOperationExportServiceFactory.CreateExportService<WellOperationExportRequest>(idType);
if (idCompany is null)
return Forbid();
var (fileName, file) = await exportService.ExportAsync(options, token);
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);
return File(file, "application/octet-stream", fileName);
}
/// <summary>
/// Создает excel файл с "сетевым графиком"
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="scheduleReportService"></param>
/// <param name="token"> Токен отмены задачи</param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet("scheduleReport")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Удаляет полые дубликаты операций
/// Импорт ГГД из excel (xlsx) файла
/// </summary>
/// <param name="idWell"></param>
/// <param name="idType"></param>
/// <param name="file"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("/api/well/wellOperations/RemoveDuplicates")]
[HttpPost("parse/{idType}")]
[Permission]
[Obsolete]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> RemoveDuplicates(CancellationToken token)
[ProducesResponseType(typeof(ParserResultDto<WellOperationDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> ParseAsync(int idWell,
int idType,
[Required] IFormFile file,
CancellationToken token)
{
var result = await operationRepository.RemoveDuplicates((_, _) => { }, token);
return Ok(result);
}
if (!await CanUserAccessToWellAsync(idWell, token))
return Forbid();
/// <summary>
/// Удаляет полностью пересекающиеся операции или "подрезает" более поздние их по глубине и дате.
/// </summary>
/// <param name="geDate"></param>
/// <param name="leDate"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("/api/well/wellOperations/TrimOverlapping")]
[Permission]
[Obsolete]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, CancellationToken token)
{
var result = await operationRepository.TrimOverlapping(geDate, leDate, (_, _) => { }, token);
return Ok(result);
}
if (!await CanUserEditWellOperationsAsync(idWell, token))
return Forbid();
/// <summary>
/// Возвращает шаблон файла импорта
/// </summary>
/// <returns>Запрашиваемый файл</returns>
[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<IActionResult> ImportExcelFileAsync<TOptions>(int idWell, [FromForm] IFormFileCollection files,
TOptions options,
Func<Stream, TOptions, SheetDto> 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();
var stream = file.GetExcelFile();
try
{
var sheet = parseMethod(stream, options);
var timezone = wellService.GetTimezone(idWell);
var options = new WellOperationParserRequest(idWell, idType, timezone);
var parser = wellOperationParserFactory.CreateParser<WellOperationParserRequest>(idType);
var result = parser.Parse(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);
return Ok(result);
}
catch (FileFormatException ex)
{
return this.ValidationBadRequest(nameof(files), ex.Message);
return this.ValidationBadRequest(nameof(file), ex.Message);
}
}
/// <summary>
/// Получение шаблона для заполнения ГГД
/// </summary>
/// <returns></returns>
[HttpGet("template")]
[AllowAnonymous]
[ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetTemplate(int idType)
{
var parser = wellOperationParserFactory.CreateParser<WellOperationParserRequest>(idType);
var stream = parser.GetTemplateFile();
return File(stream, "application/octet-stream", templateNames[idType]);
}
private async Task<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token)
{
int? idCompany = User.GetCompanyId();
var idCompany = User.GetCompanyId();
return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
idWell, token).ConfigureAwait(false);
}
@ -609,4 +346,3 @@ namespace AsbCloudWebApi.Controllers
return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell");
}
}
}